Add: windows mvp - transparent bugs not fixed
This commit is contained in:
132
skills/unsafe-checker/rules/ffi-10-thread-safety.md
Normal file
132
skills/unsafe-checker/rules/ffi-10-thread-safety.md
Normal file
@@ -0,0 +1,132 @@
|
||||
---
|
||||
id: ffi-10
|
||||
original_id: P.UNS.FFI.10
|
||||
level: P
|
||||
impact: CRITICAL
|
||||
---
|
||||
|
||||
# Exported Rust Functions Must Be Designed for Thread-Safety
|
||||
|
||||
## Summary
|
||||
|
||||
Functions exported to C with `#[no_mangle] extern "C"` may be called from multiple threads. Ensure they are thread-safe.
|
||||
|
||||
## Rationale
|
||||
|
||||
- C code doesn't know about Rust's thread safety guarantees
|
||||
- C may call your function from any thread
|
||||
- Global state must be synchronized
|
||||
- Race conditions are undefined behavior
|
||||
|
||||
## Bad Example
|
||||
|
||||
```rust
|
||||
// DON'T: Unsynchronized global state
|
||||
static mut COUNTER: i32 = 0;
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn increment() -> i32 {
|
||||
unsafe {
|
||||
COUNTER += 1; // Data race if called from multiple threads!
|
||||
COUNTER
|
||||
}
|
||||
}
|
||||
|
||||
// DON'T: Thread-local assuming single thread
|
||||
thread_local! {
|
||||
static CONFIG: RefCell<Config> = RefCell::new(Config::default());
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn set_config(value: i32) {
|
||||
// Different threads get different configs!
|
||||
// Is that what the C caller expects?
|
||||
CONFIG.with(|c| c.borrow_mut().value = value);
|
||||
}
|
||||
|
||||
// DON'T: Non-Send types in globals
|
||||
static mut HANDLE: Option<Rc<Data>> = None; // Rc is not Send!
|
||||
```
|
||||
|
||||
## Good Example
|
||||
|
||||
```rust
|
||||
use std::sync::atomic::{AtomicI32, Ordering};
|
||||
use std::sync::{Mutex, OnceLock};
|
||||
|
||||
// DO: Use atomics for simple counters
|
||||
static COUNTER: AtomicI32 = AtomicI32::new(0);
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn increment() -> i32 {
|
||||
COUNTER.fetch_add(1, Ordering::SeqCst) + 1
|
||||
}
|
||||
|
||||
// DO: Use Mutex for complex state
|
||||
static CONFIG: OnceLock<Mutex<Config>> = OnceLock::new();
|
||||
|
||||
fn get_config() -> &'static Mutex<Config> {
|
||||
CONFIG.get_or_init(|| Mutex::new(Config::default()))
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn set_config_value(value: i32) -> i32 {
|
||||
match get_config().lock() {
|
||||
Ok(mut config) => {
|
||||
config.value = value;
|
||||
0 // Success
|
||||
}
|
||||
Err(_) => -1 // Lock poisoned
|
||||
}
|
||||
}
|
||||
|
||||
// DO: Document thread safety requirements
|
||||
/// Initializes the library. NOT thread-safe.
|
||||
/// Must be called once from main thread before any other function.
|
||||
#[no_mangle]
|
||||
pub extern "C" fn init() -> i32 {
|
||||
// One-time initialization
|
||||
0
|
||||
}
|
||||
|
||||
/// Processes data. Thread-safe.
|
||||
/// May be called from multiple threads concurrently.
|
||||
#[no_mangle]
|
||||
pub extern "C" fn process(data: *const u8, len: usize) -> i32 {
|
||||
// Uses only local state or synchronized globals
|
||||
0
|
||||
}
|
||||
|
||||
// DO: Make non-thread-safe APIs explicit
|
||||
/// Handle for single-threaded use only.
|
||||
///
|
||||
/// # Thread Safety
|
||||
///
|
||||
/// This handle must only be used from the thread that created it.
|
||||
struct SingleThreadHandle {
|
||||
data: *mut Data,
|
||||
_not_send: std::marker::PhantomData<*const ()>, // !Send
|
||||
}
|
||||
```
|
||||
|
||||
## Synchronization Patterns
|
||||
|
||||
| Pattern | Use Case |
|
||||
|---------|----------|
|
||||
| `AtomicT` | Simple counters, flags |
|
||||
| `Mutex<T>` | Complex shared state |
|
||||
| `RwLock<T>` | Read-heavy shared state |
|
||||
| `OnceLock<T>` | Lazy one-time init |
|
||||
| `thread_local!` | Per-thread state (document!) |
|
||||
|
||||
## Checklist
|
||||
|
||||
- [ ] Does my exported function access global state?
|
||||
- [ ] Is that state properly synchronized?
|
||||
- [ ] Have I documented thread-safety guarantees?
|
||||
- [ ] Are any types !Send/!Sync exposed across FFI?
|
||||
|
||||
## Related Rules
|
||||
|
||||
- `ptr-01`: Don't share raw pointers across threads
|
||||
- `safety-05`: Send/Sync implementation safety
|
||||
Reference in New Issue
Block a user