Files
Sprimo/skills/unsafe-checker/rules/mem-04-reentrant.md
2026-02-12 22:58:33 +08:00

3.1 KiB

id, original_id, level, impact
id original_id level impact
mem-04 P.UNS.MEM.04 P HIGH

Prefer Reentrant Versions of C-API or Syscalls

Summary

When calling C functions or system calls, use reentrant (_r) versions to avoid data races from global state.

Rationale

Many C library functions use static buffers or global state, making them unsafe in multithreaded programs. Reentrant versions use caller-provided buffers instead.

Bad Example

use std::ffi::CStr;

extern "C" {
    fn strtok(s: *mut i8, delim: *const i8) -> *mut i8;
    fn localtime(time: *const i64) -> *mut Tm;
    fn rand() -> i32;
}

// DON'T: Use non-reentrant functions
fn bad_tokenize(s: &mut [i8]) {
    unsafe {
        let delim = b" \0".as_ptr() as *const i8;
        // strtok uses static buffer - not thread-safe!
        let token = strtok(s.as_mut_ptr(), delim);
    }
}

fn bad_time() {
    unsafe {
        let now: i64 = 0;
        // localtime returns pointer to static buffer
        let tm = localtime(&now);  // Data race if called from multiple threads!
    }
}

fn bad_random() -> i32 {
    // rand() uses global state - not thread-safe
    unsafe { rand() }
}

Good Example

extern "C" {
    fn strtok_r(s: *mut i8, delim: *const i8, saveptr: *mut *mut i8) -> *mut i8;
    fn localtime_r(time: *const i64, result: *mut Tm) -> *mut Tm;
    fn rand_r(seed: *mut u32) -> i32;
}

// DO: Use reentrant versions
fn good_tokenize(s: &mut [i8]) {
    unsafe {
        let delim = b" \0".as_ptr() as *const i8;
        let mut saveptr: *mut i8 = std::ptr::null_mut();
        // strtok_r uses caller-provided saveptr
        let token = strtok_r(s.as_mut_ptr(), delim, &mut saveptr);
    }
}

fn good_time() {
    unsafe {
        let now: i64 = 0;
        let mut result: Tm = std::mem::zeroed();
        // localtime_r writes to caller-provided buffer
        localtime_r(&now, &mut result);
    }
}

fn good_random(seed: &mut u32) -> i32 {
    // rand_r uses caller-provided seed
    unsafe { rand_r(seed) }
}

// BETTER: Use Rust standard library
fn best_time() {
    use std::time::SystemTime;
    let now = SystemTime::now();  // Thread-safe!
}

fn best_random() -> u32 {
    use rand::Rng;
    rand::thread_rng().gen()  // Thread-safe!
}

Common Non-Reentrant Functions

Non-Reentrant Reentrant Rust Alternative
strtok strtok_r str::split
localtime localtime_r chrono crate
gmtime gmtime_r chrono crate
ctime ctime_r chrono crate
rand rand_r rand crate
strerror strerror_r std::io::Error
getenv None (inherent race) std::env::var (not atomic)
readdir readdir_r std::fs::read_dir
gethostbyname getaddrinfo std::net::ToSocketAddrs

Checklist

  • Am I calling a C function that might use global state?
  • Is there a _r reentrant version available?
  • Is there a Rust standard library alternative?
  • If neither, do I need synchronization?
  • ffi-10: Exported functions must be thread-safe
  • ptr-01: Don't share raw pointers across threads