Files
Sprimo/skills/unsafe-checker/rules/mem-03-no-auto-drop-foreign.md
2026-02-12 22:58:33 +08:00

3.2 KiB

id, original_id, level, impact
id original_id level impact
mem-03 P.UNS.MEM.03 P CRITICAL

Do Not Let String/Vec Auto-Drop Other Process's Memory

Summary

Never create String, Vec, or Box from memory allocated outside Rust's allocator. They will try to free the memory with the wrong deallocator.

Rationale

String, Vec, and Box assume memory was allocated by Rust's global allocator. When dropped, they call dealloc. If the memory came from C's malloc, a different allocator, or shared memory, this causes undefined behavior.

Bad Example

// DON'T: Create String from C-allocated memory
extern "C" {
    fn c_get_string() -> *mut std::os::raw::c_char;
}

fn bad_string() -> String {
    unsafe {
        let ptr = c_get_string();
        // BAD: String will try to free with Rust allocator
        String::from_raw_parts(ptr as *mut u8, len, cap)
    }
}

// DON'T: Create Vec from foreign memory
fn bad_vec(ptr: *mut u8, len: usize) -> Vec<u8> {
    // BAD: Vec will free this memory incorrectly
    unsafe { Vec::from_raw_parts(ptr, len, len) }
}

// DON'T: Wrap shared memory in Box
fn bad_box(shared_ptr: *mut Data) -> Box<Data> {
    // BAD: Box will try to deallocate shared memory!
    unsafe { Box::from_raw(shared_ptr) }
}

Good Example

use std::ffi::CStr;

extern "C" {
    fn c_get_string() -> *mut std::os::raw::c_char;
    fn c_free_string(s: *mut std::os::raw::c_char);
}

// DO: Copy data into Rust-owned allocation
fn good_string() -> String {
    unsafe {
        let ptr = c_get_string();
        let cstr = CStr::from_ptr(ptr);
        let result = cstr.to_string_lossy().into_owned();
        c_free_string(ptr);  // Free with correct deallocator
        result
    }
}

// DO: Use wrapper that calls correct deallocator
struct CString {
    ptr: *mut std::os::raw::c_char,
}

impl Drop for CString {
    fn drop(&mut self) {
        unsafe { c_free_string(self.ptr); }
    }
}

// DO: Use slice for borrowed view, don't take ownership
fn good_slice(ptr: *const u8, len: usize) -> &'static [u8] {
    // Only borrow, don't own
    unsafe { std::slice::from_raw_parts(ptr, len) }
}

// DO: For shared memory, use raw pointers or custom wrapper
struct SharedBuffer {
    ptr: *mut u8,
    len: usize,
}

impl SharedBuffer {
    fn as_slice(&self) -> &[u8] {
        unsafe { std::slice::from_raw_parts(self.ptr, self.len) }
    }
}

impl Drop for SharedBuffer {
    fn drop(&mut self) {
        // Unmap shared memory, don't deallocate
        // munmap(self.ptr, self.len);
    }
}

Memory Allocation Compatibility

Allocator Can use Rust Vec/String/Box?
Rust global allocator Yes
C malloc No - use wrapper with C free
C++ new No - use wrapper with C++ delete
Custom allocator No - use allocator_api
mmap/shared memory No - use munmap
Stack/static No - never "free"

Checklist

  • Who allocated this memory?
  • Is it from Rust's global allocator?
  • If not, do I have a custom Drop that frees correctly?
  • Am I copying data or taking ownership?
  • mem-02: Don't modify other process's memory
  • ffi-03: Implement Drop for wrapped C pointers
  • ffi-07: Don't implement Drop for types passed to external code