with one click
heimdall-rust-unsafe-ffi
// Review and author guidance for all unsafe blocks and libc FFI in heimdall — SAFETY comments, env::set_var, flock/getuid patterns, Drop guarantee, dep unsafe propagation, and manual Send/Sync.
// Review and author guidance for all unsafe blocks and libc FFI in heimdall — SAFETY comments, env::set_var, flock/getuid patterns, Drop guarantee, dep unsafe propagation, and manual Send/Sync.
[HINT] Download the complete skill directory including SKILL.md and all related files
| name | heimdall-rust-unsafe-ffi |
| description | Review and author guidance for all unsafe blocks and libc FFI in heimdall — SAFETY comments, env::set_var, flock/getuid patterns, Drop guarantee, dep unsafe propagation, and manual Send/Sync. |
Guide review and authoring of unsafe code in heimdall. Apply every rule to every unsafe block in a diff — not only the first one. Heimdall is a single-crate project with edition-2024 Rust.
Known unsafe locations (verify current state before auditing):
| File | Pattern | Notes |
|---|---|---|
src/archive/mod.rs | libc::flock | File locking via libc |
src/scheduler/daemon.rs | libc::getuid | Process UID check |
src/scheduler/launchd.rs | extern "C" FFI | launchd service registration |
src/statusline/mod.rs | 2 unsafe blocks | Terminal control sequences |
src/config.rs | unsafe { env::set_var } | Edition-2024 unsafe in test setup |
Every unsafe {} block MUST be immediately preceded by a // SAFETY: comment (or /// # Safety for unsafe fn) that states:
// SAFETY: `fd` was opened by our own `open(2)` call above and is still valid;
// LOCK_EX | LOCK_NB is a valid flag combination; return value checked below.
let ret = unsafe { libc::flock(fd, libc::LOCK_EX | libc::LOCK_NB) };
if ret != 0 {
return Err(std::io::Error::last_os_error());
}
Missing SAFETY comment on any unsafe {} block is a CRITICAL finding.
env::set_var in edition 2024std::env::set_var is unsafe in Rust edition 2024 because it is not thread-safe: calling it while other threads are reading the environment is UB.
Rules:
env::set_var in single-threaded contexts (e.g., the very beginning of a test before any tokio::spawn, std::thread::spawn, or rayon parallel scope).// SAFETY: single-threaded: called before any thread spawns in this test.std::env::vars() capture at startup and pass config explicitly; never mutate the environment after main has started threads.Current location: src/config.rs test setup — audit that no thread is spawned before the set_var call.
flock and getuid libc patternsFor all libc FFI calls in Rust 2024, use the unsafe extern "C" block syntax:
extern "C" {
// declaration only -- no unsafe here in Rust 2021
}
// In Rust 2024, extern blocks without `unsafe` generate a warning; prefer:
unsafe extern "C" {
fn getuid() -> libc::uid_t;
}
For flock and getuid calls:
flock returns 0 on success, -1 on error; retrieve errno via std::io::Error::last_os_error().File::open in this function").BorrowedFd::borrow_raw when passing a raw fd to ensure Rust knows about the borrow.mem::forget is a safe function. Any unsafe API that relies on a RAII guard running its Drop for soundness is unsound.
Concrete rule: if a File, OwnedFd, or custom guard in heimdall is expected to run cleanup (close fd, release lock), it must NOT be passed to code that might call mem::forget or ManuallyDrop::new on it — those are safe calls.
Current risk areas:
src/archive/mod.rs: flock guard — verify the guard is not mem::forget-ed anywhere on the path between acquisition and release.src/scheduler/daemon.rs: daemon PID file handle — same check.Reference: crabbook/raii_and_memory_safety.md
unsafe in a dep breaks local reasoningA single unsafe in a dependency can invalidate type invariants codebase-wide. You cannot assume that safe code in heimdall is sound if a dep calls str::from_utf8_unchecked on unvalidated data.
Action: run cargo deny check to find deps with known soundness advisories. Also:
cargo tree --depth 3 | head -40
rg 'from_utf8_unchecked\|from_raw_parts' src/ --type rust -n
Any hit without a SAFETY comment is HIGH risk.
Reference: crabbook/unsafe_is_unsafe.md
unsafe impl Sync/SendBefore writing unsafe impl Sync for T or unsafe impl Send for T:
Sync/Send or document why the wrapper maintains safety despite the field not being so.Debug, Clone, Display) — they must not expose inner non-Sync/non-Send state.static_assertions::assert_impl_all! or assert_not_impl_all! to catch regressions.unsafe impl with a // SAFETY: comment.Grep: rg 'unsafe impl Sync|unsafe impl Send' src/ --type rust -n
Reference: crabbook/send_and_sync.md
Heimdall has no self-referential types today. If future tokio patterns (long-lived servers, custom Future combinators, async generators) introduce self-referential state:
Pin<Box<T>> + PhantomPinned.Box::pin(val) is the standard way to heap-pin a value.Pin<&mut T> is required for types that must not move after construction (e.g., C++ types behind FFI).Reference: crabbook/pin.md
#[no_mangle] and #[link_section] symbol collision (edition 2024)Severity: CRITICAL
In Rust 2024, #[no_mangle], #[export_name = "..."], and #[link_section = "..."] must use the #[unsafe(...)] form. The hazard that motivates this: two compilation units exporting the same unmangled symbol causes the linker to silently pick one, calling the wrong function — a soundness bug with no compile-time diagnostic.
Heimdall currently uses edition = "2024". Audit:
rg '#\[no_mangle\]|#\[export_name' src/ --type rust -n
Any hit must use #[unsafe(no_mangle)] or #[unsafe(export_name = "...")] and have a SAFETY comment asserting the symbol name is unique across the linked binary.
Drop::drop during unwinding aborts the processSeverity: CRITICAL
If a panic is already in progress (stack unwinding), and a Drop impl panics, Rust immediately aborts the process — this is a "double panic" and cannot be caught by catch_unwind. Any .unwrap() or .expect() inside drop() is a double-panic bomb that fires exactly when another error is already in flight.
// DANGEROUS: double-panic if called during unwind
impl Drop for LockFile {
fn drop(&mut self) {
std::fs::remove_file(&self.path).unwrap(); // aborts on unwind
}
}
// CORRECT: log-and-discard in drop(); expose explicit close() -> Result
impl Drop for LockFile {
fn drop(&mut self) {
if let Err(e) = std::fs::remove_file(&self.path) {
tracing::error!("failed to remove lock file: {e}");
}
}
}
Check heimdall's src/scheduler/daemon.rs PID-file guard and src/archive/mod.rs flock guard — both perform cleanup in Drop. Ensure neither has .unwrap() on the cleanup path.
Apply to every unsafe block in a diff:
env::set_var: confirmed single-threaded context before any thread spawn?flock/getuid/libc: return value checked, errno retrieved on error?unsafe extern "C" blocks: Rust 2024 unsafe extern syntax used?mem::forget-ed by callers?from_utf8_unchecked or from_raw_parts without a SAFETY comment tracing the invariant?unsafe impl Sync/Send: every field type listed in the SAFETY comment?Drop::drop containing .unwrap() or .expect() on the cleanup path? → move to explicit close()/flush() returning Result.#[no_mangle] without #[unsafe(no_mangle)] form (edition 2024 requirement)?