security: finish native unsafe invariant documentation
ober
b10f97842add2cdbf84bc3945ac03c196304248e
--- a/docs/ffi-audit.md +++ b/docs/ffi-audit.md @@ -41,9 +41,9 @@ Latest summary from `tools/ffi-audit-report.ss`: (c-file-count 5) (no-mangle-exports 196) (unsafe-sites 369) - (unsafe-sites-with-nearby-safety-comment 356) - (unsafe-sites-without-nearby-safety-comment 13) - (unsafe-comment-review-sites 13) + (unsafe-sites-with-nearby-safety-comment 369) + (unsafe-sites-without-nearby-safety-comment 0) + (unsafe-comment-review-sites 0) (export-review-sites 196)) (vendor (jsqlite (path "vendor/jsqlite") (status accepted-risk-cve-gated))) @@ -101,7 +101,7 @@ documents output-slot and ovector invariants, checks capture slot arithmetic, and returns a normal FFI error if the regex handle store is poisoned. `process_ctl.rs` now documents process-name, memory-lock, signal-probe, errno, FreeBSD sysctl/setproctitle, and executable-path output invariants adjacent to -its remaining unsafe sites, and rejects FreeBSD process-title capacity overflow +its audited unsafe sites, and rejects FreeBSD process-title capacity overflow before allocating. `pcap_capture.rs` now routes its C ABI exports through panic containment, documents BPF ioctl, packet-buffer, timestamp, interface-list, and `getifaddrs` invariants, and has @@ -113,8 +113,8 @@ empty paths, oversized lengths, interior NUL path truncation, and null output pointers before crossing into libc. `epoll.rs` now documents epoll/eventfd syscall and caller-output invariants, bounds caller-controlled event counts, checks output byte-size derivation, and treats empty nonblocking eventfd drains -as normal, with the remaining invariants adjacent to the audited unsafe -expressions. `antidebug.rs` now documents ptrace and breakpoint-probe invariants, +as normal, with invariants adjacent to the audited unsafe expressions. +`antidebug.rs` now documents ptrace and breakpoint-probe invariants, reports ptrace OS errors, and saturates timing duration conversion instead of truncating. `socks5_server.rs` now rejects null pointers paired with nonzero lengths before building FFI slices, bounds caller lengths, documents stats @@ -133,9 +133,9 @@ now bounds module/function/path/log buffers, converts poisoned handle-store locks into normal FFI errors, rejects null argument/result buffers with nonzero lengths, checks guest-memory offsets with overflow-safe helpers, documents socket-fd adoption and caller-buffer invariants adjacent to the -remaining unsafe sites, and has Rust regressions for invalid C ABI inputs. +audited unsafe sites, and has Rust regressions for invalid C ABI inputs. `aproc.rs` now documents inherited-fd slice, `pre_exec`, and `openpty` -invariants adjacent to the remaining worker-launch unsafe sites. +invariants adjacent to the audited worker-launch unsafe sites. `duckdb_native.rs` now funnels path, SQL, text, and blob inputs through checked C ABI slice helpers, converts poisoned database/statement/result store locks into normal FFI errors, @@ -165,7 +165,7 @@ funnels hostname, path, PEM, pin, and read/write buffers through checked C ABI helpers, rejects null/nonzero and oversized buffers before slicing, rejects negative accepted fds before adopting them as `TcpStream`, and documents socket shutdown invariants used to break blocked TLS I/O. The generated inventory now -reports 356 annotated native unsafe sites and 13 remaining unsafe review sites. +reports 369 annotated native unsafe sites and 0 remaining unsafe review sites. Remaining work before closing K3-P1-01: @@ -176,7 +176,6 @@ Remaining work before closing K3-P1-01: nonblocking. - Add targeted scanner rules for null-return checks, integer width confusion, caller-supplied length trust, and bytevector pointer lifetime hazards. -- Add `SAFETY:` invariant comments near Rust unsafe sites. - Keep `vendor/jsqlite` in the TCB until consumers move to the Rust sqlite path. The accepted-risk decision is now recorded in `docs/reviews/2026-07-27-vendor-jsqlite-tcb.md`, and `make audit` runs --- a/docs/kimi3-security-recommmendations.md +++ b/docs/kimi3-security-recommmendations.md @@ -651,7 +651,7 @@ the current inventory and remediation trail now live in [ffi-audit.md](ffi-audit and returns a normal FFI error if the regex handle store is poisoned. `process_ctl.rs` now documents process-name, memory-lock, signal-probe, errno, FreeBSD sysctl/setproctitle, and executable-path output invariants - adjacent to its remaining unsafe sites, and rejects FreeBSD process-title + adjacent to its audited unsafe sites, and rejects FreeBSD process-title capacity overflow before allocating. `pcap_capture.rs` now routes its C ABI exports through panic containment, documents BPF ioctl, packet-buffer, timestamp, interface-list, and @@ -664,7 +664,7 @@ the current inventory and remediation trail now live in [ffi-audit.md](ffi-audit libc. `epoll.rs` now documents epoll/eventfd syscall and caller-output invariants, bounds caller-controlled event counts, checks output byte-size derivation, and treats empty nonblocking eventfd drains as normal, with the - remaining invariants adjacent to the audited unsafe expressions. + invariants adjacent to the audited unsafe expressions. `antidebug.rs` now documents ptrace and breakpoint-probe invariants, reports ptrace OS errors, and saturates timing duration conversion instead of truncating. `socks5_server.rs` now rejects null pointers paired with nonzero @@ -684,9 +684,9 @@ the current inventory and remediation trail now live in [ffi-audit.md](ffi-audit converts poisoned handle-store locks into normal FFI errors, rejects null argument/result buffers with nonzero lengths, checks guest-memory offsets with overflow-safe helpers, documents socket-fd adoption and caller-buffer - invariants adjacent to the remaining unsafe sites, and has Rust regressions + invariants adjacent to the audited unsafe sites, and has Rust regressions for invalid C ABI inputs. `aproc.rs` now documents inherited-fd slice, - `pre_exec`, and `openpty` invariants adjacent to the remaining + `pre_exec`, and `openpty` invariants adjacent to the audited worker-launch unsafe sites. `duckdb_native.rs` now validates path, SQL, text, blob, result-slot, result-index, and output-buffer inputs through shared helpers and converts poisoned handle-store locks into normal FFI errors. @@ -707,10 +707,10 @@ the current inventory and remediation trail now live in [ffi-audit.md](ffi-audit publication, and test-owned fd conversion invariants. `tls.rs` now validates hostname, path, PEM, pin, and read/write buffers through shared helpers, rejects invalid accepted fds before ownership transfer, and documents socket - shutdown invariants for blocked TLS I/O. The generated report now shows 356 - annotated native unsafe sites and 13 remaining unsafe review sites. - Remaining work: continue unsafe invariant comments across the rest of - `jerboa-native-rs`. + shutdown invariants for blocked TLS I/O. The generated report now shows 369 + annotated native unsafe sites and 0 remaining unsafe review sites. + Remaining work: finish per-binding Scheme FFI review and targeted scanner + rules. ### K3-P1-02 — TOCTOU-safe filesystem capability checks **Serves:** G2. **Effort:** 1 week. @@ -1198,7 +1198,7 @@ Track these in `docs/status.md` per release: | Confined worker exists | facade, audit lifecycle, output caps, deadline, process-group kill, memory rlimit pre-exec path, explicit sandbox-axis refusal, egress proxy env wiring, and Linux syscall/ptrace seccomp pre-exec setup landed; Landlock path/net and Seatbelt/Capsicum parity pending | yes, tested (P0-02) | | Unclassified raw `read`/FASL/`load` sites | scanner-driven manifest classification gate closed 2026-07-27; trusted build, compiler, REPL/dev, and test surfaces are centrally justified by exact source line | 0 | | FFI bindings audited | scanner inventory and provisional verdicts in [ffi-audit.md](ffi-audit.md); native exports gated and justified | 100% with final verdicts | -| Un-annotated Rust `unsafe` blocks | 13 unsafe review sites without nearby `SAFETY:` comments in [ffi-audit.md](ffi-audit.md) | 0 | +| Un-annotated Rust `unsafe` blocks | 0 unsafe review sites without nearby `SAFETY:` comments in [ffi-audit.md](ffi-audit.md) | 0 | | Fuzz corpora / crash regressions | 0 / 0 | per-parser corpora + every crash a test | | Exploit-shaped regression tests | ~0 | ≥ 1 per historical finding | | Sandboxed-by-default app entry | no | yes (P0-06) | --- a/docs/reviews/2026-07-27-native-export-review.sexp +++ b/docs/reviews/2026-07-27-native-export-review.sexp @@ -13,12 +13,12 @@ (export (symbol "embed_read_passphrase") (file "jerboa-native-rs/src/embed_crypto.rs") (line 213) (scheme-callers 0 (none))) (export (symbol "jerboa_aead_open") (file "jerboa-native-rs/src/crypto.rs") (line 431) (scheme-callers 1 (caller (file "lib/std/crypto/native-rust.ss") (line 161)))) (export (symbol "jerboa_aead_seal") (file "jerboa-native-rs/src/crypto.rs") (line 343) (scheme-callers 1 (caller (file "lib/std/crypto/native-rust.ss") (line 140)))) - (export (symbol "jerboa_antidebug_check_all") (file "jerboa-native-rs/src/antidebug.rs") (line 158) (scheme-callers 1 (caller (file "lib/std/os/antidebug.ss") (line 46)))) + (export (symbol "jerboa_antidebug_check_all") (file "jerboa-native-rs/src/antidebug.rs") (line 157) (scheme-callers 1 (caller (file "lib/std/os/antidebug.ss") (line 46)))) (export (symbol "jerboa_antidebug_check_breakpoint") (file "jerboa-native-rs/src/antidebug.rs") (line 105) (scheme-callers 1 (caller (file "lib/std/os/antidebug.ss") (line 42)))) (export (symbol "jerboa_antidebug_check_ld_preload") (file "jerboa-native-rs/src/antidebug.rs") (line 67) (scheme-callers 1 (caller (file "lib/std/os/antidebug.ss") (line 40)))) (export (symbol "jerboa_antidebug_check_tracer") (file "jerboa-native-rs/src/antidebug.rs") (line 37) (scheme-callers 1 (caller (file "lib/std/os/antidebug.ss") (line 38)))) (export (symbol "jerboa_antidebug_ptrace") (file "jerboa-native-rs/src/antidebug.rs") (line 8) (scheme-callers 1 (caller (file "lib/std/os/antidebug.ss") (line 36)))) - (export (symbol "jerboa_antidebug_timing_check") (file "jerboa-native-rs/src/antidebug.rs") (line 130) (scheme-callers 1 (caller (file "lib/std/os/antidebug.ss") (line 44)))) + (export (symbol "jerboa_antidebug_timing_check") (file "jerboa-native-rs/src/antidebug.rs") (line 129) (scheme-callers 1 (caller (file "lib/std/os/antidebug.ss") (line 44)))) (export (symbol "jerboa_aproc_killpg") (file "jerboa-native-rs/src/aproc.rs") (line 674) (scheme-callers 1 (caller (file "lib/std/os/aproc.ss") (line 155)))) (export (symbol "jerboa_aproc_set_nonblock") (file "jerboa-native-rs/src/aproc.rs") (line 644) (scheme-callers 1 (caller (file "lib/std/os/aproc.ss") (line 151)))) (export (symbol "jerboa_aproc_spawn") (file "jerboa-native-rs/src/aproc.rs") (line 152) (scheme-callers 2 (caller (file "lib/std/os/aproc.ss") (line 127)) (caller (file "lib/std/os/aproc.ss") (line 142)))) @@ -29,31 +29,31 @@ (export (symbol "jerboa_chacha20_open") (file "jerboa-native-rs/src/crypto.rs") (line 606) (scheme-callers 1 (caller (file "lib/std/crypto/native-rust.ss") (line 204)))) (export (symbol "jerboa_chacha20_seal") (file "jerboa-native-rs/src/crypto.rs") (line 519) (scheme-callers 1 (caller (file "lib/std/crypto/native-rust.ss") (line 182)))) (export (symbol "jerboa_deflate") (file "jerboa-native-rs/src/compress.rs") (line 9) (scheme-callers 1 (caller (file "lib/std/compress/native-rust.ss") (line 30)))) - (export (symbol "jerboa_duckdb_bind_blob") (file "jerboa-native-rs/src/duckdb_native.rs") (line 444) (scheme-callers 1 (caller (file "lib/std/db/duckdb-native.ss") (line 62)))) - (export (symbol "jerboa_duckdb_bind_bool") (file "jerboa-native-rs/src/duckdb_native.rs") (line 501) (scheme-callers 1 (caller (file "lib/std/db/duckdb-native.ss") (line 66)))) - (export (symbol "jerboa_duckdb_bind_double") (file "jerboa-native-rs/src/duckdb_native.rs") (line 387) (scheme-callers 1 (caller (file "lib/std/db/duckdb-native.ss") (line 58)))) - (export (symbol "jerboa_duckdb_bind_int") (file "jerboa-native-rs/src/duckdb_native.rs") (line 363) (scheme-callers 1 (caller (file "lib/std/db/duckdb-native.ss") (line 56)))) - (export (symbol "jerboa_duckdb_bind_null") (file "jerboa-native-rs/src/duckdb_native.rs") (line 477) (scheme-callers 1 (caller (file "lib/std/db/duckdb-native.ss") (line 64)))) - (export (symbol "jerboa_duckdb_bind_text") (file "jerboa-native-rs/src/duckdb_native.rs") (line 411) (scheme-callers 1 (caller (file "lib/std/db/duckdb-native.ss") (line 60)))) - (export (symbol "jerboa_duckdb_close") (file "jerboa-native-rs/src/duckdb_native.rs") (line 228) (scheme-callers 1 (caller (file "lib/std/db/duckdb-native.ss") (line 45)))) - (export (symbol "jerboa_duckdb_column_name") (file "jerboa-native-rs/src/duckdb_native.rs") (line 855) (scheme-callers 1 (caller (file "lib/std/db/duckdb-native.ss") (line 76)))) - (export (symbol "jerboa_duckdb_column_type") (file "jerboa-native-rs/src/duckdb_native.rs") (line 897) (scheme-callers 1 (caller (file "lib/std/db/duckdb-native.ss") (line 79)))) - (export (symbol "jerboa_duckdb_exec") (file "jerboa-native-rs/src/duckdb_native.rs") (line 264) (scheme-callers 2 (caller (file "lib/std/db/duckdb-native.ss") (line 47)) (caller (file "lib/std/db/duckdb-native.ss") (line 69)))) - (export (symbol "jerboa_duckdb_execute") (file "jerboa-native-rs/src/duckdb_native.rs") (line 598) (scheme-callers 1 (caller (file "lib/std/db/duckdb-native.ss") (line 69)))) - (export (symbol "jerboa_duckdb_finalize") (file "jerboa-native-rs/src/duckdb_native.rs") (line 1124) (scheme-callers 1 (caller (file "lib/std/db/duckdb-native.ss") (line 51)))) - (export (symbol "jerboa_duckdb_free_result") (file "jerboa-native-rs/src/duckdb_native.rs") (line 1107) (scheme-callers 1 (caller (file "lib/std/db/duckdb-native.ss") (line 96)))) - (export (symbol "jerboa_duckdb_ncols") (file "jerboa-native-rs/src/duckdb_native.rs") (line 843) (scheme-callers 1 (caller (file "lib/std/db/duckdb-native.ss") (line 74)))) - (export (symbol "jerboa_duckdb_nrows") (file "jerboa-native-rs/src/duckdb_native.rs") (line 831) (scheme-callers 1 (caller (file "lib/std/db/duckdb-native.ss") (line 72)))) - (export (symbol "jerboa_duckdb_open") (file "jerboa-native-rs/src/duckdb_native.rs") (line 185) (scheme-callers 1 (caller (file "lib/std/db/duckdb-native.ss") (line 43)))) - (export (symbol "jerboa_duckdb_prepare") (file "jerboa-native-rs/src/duckdb_native.rs") (line 296) (scheme-callers 1 (caller (file "lib/std/db/duckdb-native.ss") (line 49)))) - (export (symbol "jerboa_duckdb_reset") (file "jerboa-native-rs/src/duckdb_native.rs") (line 1142) (scheme-callers 1 (caller (file "lib/std/db/duckdb-native.ss") (line 53)))) - (export (symbol "jerboa_duckdb_value_blob") (file "jerboa-native-rs/src/duckdb_native.rs") (line 1069) (scheme-callers 1 (caller (file "lib/std/db/duckdb-native.ss") (line 92)))) - (export (symbol "jerboa_duckdb_value_bool") (file "jerboa-native-rs/src/duckdb_native.rs") (line 989) (scheme-callers 1 (caller (file "lib/std/db/duckdb-native.ss") (line 87)))) - (export (symbol "jerboa_duckdb_value_double") (file "jerboa-native-rs/src/duckdb_native.rs") (line 967) (scheme-callers 1 (caller (file "lib/std/db/duckdb-native.ss") (line 85)))) - (export (symbol "jerboa_duckdb_value_int") (file "jerboa-native-rs/src/duckdb_native.rs") (line 939) (scheme-callers 1 (caller (file "lib/std/db/duckdb-native.ss") (line 83)))) - (export (symbol "jerboa_duckdb_value_is_null") (file "jerboa-native-rs/src/duckdb_native.rs") (line 918) (scheme-callers 1 (caller (file "lib/std/db/duckdb-native.ss") (line 81)))) - (export (symbol "jerboa_duckdb_value_text") (file "jerboa-native-rs/src/duckdb_native.rs") (line 1023) (scheme-callers 1 (caller (file "lib/std/db/duckdb-native.ss") (line 89)))) - (export (symbol "jerboa_duckdb_version") (file "jerboa-native-rs/src/duckdb_native.rs") (line 1167) (scheme-callers 1 (caller (file "lib/std/db/duckdb-native.ss") (line 98)))) + (export (symbol "jerboa_duckdb_bind_blob") (file "jerboa-native-rs/src/duckdb_native.rs") (line 443) (scheme-callers 1 (caller (file "lib/std/db/duckdb-native.ss") (line 62)))) + (export (symbol "jerboa_duckdb_bind_bool") (file "jerboa-native-rs/src/duckdb_native.rs") (line 500) (scheme-callers 1 (caller (file "lib/std/db/duckdb-native.ss") (line 66)))) + (export (symbol "jerboa_duckdb_bind_double") (file "jerboa-native-rs/src/duckdb_native.rs") (line 386) (scheme-callers 1 (caller (file "lib/std/db/duckdb-native.ss") (line 58)))) + (export (symbol "jerboa_duckdb_bind_int") (file "jerboa-native-rs/src/duckdb_native.rs") (line 362) (scheme-callers 1 (caller (file "lib/std/db/duckdb-native.ss") (line 56)))) + (export (symbol "jerboa_duckdb_bind_null") (file "jerboa-native-rs/src/duckdb_native.rs") (line 476) (scheme-callers 1 (caller (file "lib/std/db/duckdb-native.ss") (line 64)))) + (export (symbol "jerboa_duckdb_bind_text") (file "jerboa-native-rs/src/duckdb_native.rs") (line 410) (scheme-callers 1 (caller (file "lib/std/db/duckdb-native.ss") (line 60)))) + (export (symbol "jerboa_duckdb_close") (file "jerboa-native-rs/src/duckdb_native.rs") (line 227) (scheme-callers 1 (caller (file "lib/std/db/duckdb-native.ss") (line 45)))) + (export (symbol "jerboa_duckdb_column_name") (file "jerboa-native-rs/src/duckdb_native.rs") (line 854) (scheme-callers 1 (caller (file "lib/std/db/duckdb-native.ss") (line 76)))) + (export (symbol "jerboa_duckdb_column_type") (file "jerboa-native-rs/src/duckdb_native.rs") (line 896) (scheme-callers 1 (caller (file "lib/std/db/duckdb-native.ss") (line 79)))) + (export (symbol "jerboa_duckdb_exec") (file "jerboa-native-rs/src/duckdb_native.rs") (line 263) (scheme-callers 2 (caller (file "lib/std/db/duckdb-native.ss") (line 47)) (caller (file "lib/std/db/duckdb-native.ss") (line 69)))) + (export (symbol "jerboa_duckdb_execute") (file "jerboa-native-rs/src/duckdb_native.rs") (line 597) (scheme-callers 1 (caller (file "lib/std/db/duckdb-native.ss") (line 69)))) + (export (symbol "jerboa_duckdb_finalize") (file "jerboa-native-rs/src/duckdb_native.rs") (line 1123) (scheme-callers 1 (caller (file "lib/std/db/duckdb-native.ss") (line 51)))) + (export (symbol "jerboa_duckdb_free_result") (file "jerboa-native-rs/src/duckdb_native.rs") (line 1106) (scheme-callers 1 (caller (file "lib/std/db/duckdb-native.ss") (line 96)))) + (export (symbol "jerboa_duckdb_ncols") (file "jerboa-native-rs/src/duckdb_native.rs") (line 842) (scheme-callers 1 (caller (file "lib/std/db/duckdb-native.ss") (line 74)))) + (export (symbol "jerboa_duckdb_nrows") (file "jerboa-native-rs/src/duckdb_native.rs") (line 830) (scheme-callers 1 (caller (file "lib/std/db/duckdb-native.ss") (line 72)))) + (export (symbol "jerboa_duckdb_open") (file "jerboa-native-rs/src/duckdb_native.rs") (line 184) (scheme-callers 1 (caller (file "lib/std/db/duckdb-native.ss") (line 43)))) + (export (symbol "jerboa_duckdb_prepare") (file "jerboa-native-rs/src/duckdb_native.rs") (line 295) (scheme-callers 1 (caller (file "lib/std/db/duckdb-native.ss") (line 49)))) + (export (symbol "jerboa_duckdb_reset") (file "jerboa-native-rs/src/duckdb_native.rs") (line 1141) (scheme-callers 1 (caller (file "lib/std/db/duckdb-native.ss") (line 53)))) + (export (symbol "jerboa_duckdb_value_blob") (file "jerboa-native-rs/src/duckdb_native.rs") (line 1068) (scheme-callers 1 (caller (file "lib/std/db/duckdb-native.ss") (line 92)))) + (export (symbol "jerboa_duckdb_value_bool") (file "jerboa-native-rs/src/duckdb_native.rs") (line 988) (scheme-callers 1 (caller (file "lib/std/db/duckdb-native.ss") (line 87)))) + (export (symbol "jerboa_duckdb_value_double") (file "jerboa-native-rs/src/duckdb_native.rs") (line 966) (scheme-callers 1 (caller (file "lib/std/db/duckdb-native.ss") (line 85)))) + (export (symbol "jerboa_duckdb_value_int") (file "jerboa-native-rs/src/duckdb_native.rs") (line 938) (scheme-callers 1 (caller (file "lib/std/db/duckdb-native.ss") (line 83)))) + (export (symbol "jerboa_duckdb_value_is_null") (file "jerboa-native-rs/src/duckdb_native.rs") (line 917) (scheme-callers 1 (caller (file "lib/std/db/duckdb-native.ss") (line 81)))) + (export (symbol "jerboa_duckdb_value_text") (file "jerboa-native-rs/src/duckdb_native.rs") (line 1022) (scheme-callers 1 (caller (file "lib/std/db/duckdb-native.ss") (line 89)))) + (export (symbol "jerboa_duckdb_version") (file "jerboa-native-rs/src/duckdb_native.rs") (line 1166) (scheme-callers 1 (caller (file "lib/std/db/duckdb-native.ss") (line 98)))) (export (symbol "jerboa_epoll_close") (file "jerboa-native-rs/src/epoll.rs") (line 106) (scheme-callers 1 (caller (file "lib/std/os/epoll-native.ss") (line 59)))) (export (symbol "jerboa_epoll_create") (file "jerboa-native-rs/src/epoll.rs") (line 7) (scheme-callers 2 (caller (file "lib/std/os/epoll-native.ss") (line 30)) (caller (file "lib/std/os/epoll-native.ss") (line 50)))) (export (symbol "jerboa_epoll_ctl") (file "jerboa-native-rs/src/epoll.rs") (line 25) (scheme-callers 1 (caller (file "lib/std/os/epoll-native.ss") (line 53)))) @@ -65,7 +65,7 @@ (export (symbol "jerboa_gzip") (file "jerboa-native-rs/src/compress.rs") (line 116) (scheme-callers 1 (caller (file "lib/std/compress/native-rust.ss") (line 36)))) (export (symbol "jerboa_hmac_sha256") (file "jerboa-native-rs/src/crypto.rs") (line 206) (scheme-callers 2 (caller (file "lib/std/crypto/native-rust.ss") (line 109)) (caller (file "lib/std/crypto/native-rust.ss") (line 120)))) (export (symbol "jerboa_hmac_sha256_verify") (file "jerboa-native-rs/src/crypto.rs") (line 245) (scheme-callers 1 (caller (file "lib/std/crypto/native-rust.ss") (line 120)))) - (export (symbol "jerboa_http_parse") (file "jerboa-native-rs/src/http_parse.rs") (line 30) (scheme-callers 4 (caller (file "lib/std/net/fiber-httpd.ss") (line 78)) (caller (file "lib/std/net/fiber-httpd.ss") (line 81)) (caller (file "lib/std/net/httpsd.ss") (line 28)) (caller (file "lib/std/net/httpsd.ss") (line 71)))) + (export (symbol "jerboa_http_parse") (file "jerboa-native-rs/src/http_parse.rs") (line 31) (scheme-callers 4 (caller (file "lib/std/net/fiber-httpd.ss") (line 78)) (caller (file "lib/std/net/fiber-httpd.ss") (line 81)) (caller (file "lib/std/net/httpsd.ss") (line 28)) (caller (file "lib/std/net/httpsd.ss") (line 71)))) (export (symbol "jerboa_inflate") (file "jerboa-native-rs/src/compress.rs") (line 55) (scheme-callers 1 (caller (file "lib/std/compress/native-rust.ss") (line 33)))) (export (symbol "jerboa_inotify_add_watch") (file "jerboa-native-rs/src/inotify_native.rs") (line 35) (scheme-callers 1 (caller (file "lib/std/os/inotify-native.ss") (line 65)))) (export (symbol "jerboa_inotify_close") (file "jerboa-native-rs/src/inotify_native.rs") (line 204) (scheme-callers 1 (caller (file "lib/std/os/inotify-native.ss") (line 71)))) @@ -109,10 +109,10 @@ (export (symbol "jerboa_regex_is_match") (file "jerboa-native-rs/src/regex_native.rs") (line 82) (scheme-callers 1 (caller (file "lib/std/regex-native.ss") (line 30)))) (export (symbol "jerboa_regex_replace_all") (file "jerboa-native-rs/src/regex_native.rs") (line 159) (scheme-callers 1 (caller (file "lib/std/regex-native.ss") (line 44)))) (export (symbol "jerboa_scrypt") (file "jerboa-native-rs/src/crypto.rs") (line 694) (scheme-callers 1 (caller (file "lib/std/crypto/native-rust.ss") (line 226)))) - (export (symbol "jerboa_seccomp_available") (file "jerboa-native-rs/src/seccomp.rs") (line 285) (scheme-callers 1 (caller (file "lib/std/os/seccomp.ss") (line 32)))) + (export (symbol "jerboa_seccomp_available") (file "jerboa-native-rs/src/seccomp.rs") (line 284) (scheme-callers 1 (caller (file "lib/std/os/seccomp.ss") (line 32)))) (export (symbol "jerboa_seccomp_lock") (file "jerboa-native-rs/src/seccomp.rs") (line 179) (scheme-callers 2 (caller (file "lib/std/os/seccomp.ss") (line 34)) (caller (file "lib/std/os/seccomp.ss") (line 36)))) (export (symbol "jerboa_seccomp_lock_strict") (file "jerboa-native-rs/src/seccomp.rs") (line 196) (scheme-callers 1 (caller (file "lib/std/os/seccomp.ss") (line 36)))) - (export (symbol "jerboa_secure_alloc") (file "jerboa-native-rs/src/secure_mem.rs") (line 30) (scheme-callers 1 (caller (file "lib/std/crypto/secure-mem.ss") (line 35)))) + (export (symbol "jerboa_secure_alloc") (file "jerboa-native-rs/src/secure_mem.rs") (line 31) (scheme-callers 1 (caller (file "lib/std/crypto/secure-mem.ss") (line 35)))) (export (symbol "jerboa_secure_dir_close") (file "jerboa-native-rs/src/secure_fs.rs") (line 497) (scheme-callers 2 (caller (file "lib/std/os/secure-output.ss") (line 33)) (caller (file "lib/std/os/secure-output.ss") (line 53)))) (export (symbol "jerboa_secure_dir_list") (file "jerboa-native-rs/src/secure_fs.rs") (line 681) (scheme-callers 1 (caller (file "lib/std/os/secure-output.ss") (line 62)))) (export (symbol "jerboa_secure_dir_mkdirs") (file "jerboa-native-rs/src/secure_fs.rs") (line 506) (scheme-callers 1 (caller (file "lib/std/os/secure-output.ss") (line 56)))) @@ -120,14 +120,14 @@ (export (symbol "jerboa_secure_dir_open_strict") (file "jerboa-native-rs/src/secure_fs.rs") (line 464) (scheme-callers 1 (caller (file "lib/std/os/secure-output.ss") (line 51)))) (export (symbol "jerboa_secure_entry_info") (file "jerboa-native-rs/src/secure_fs.rs") (line 536) (scheme-callers 1 (caller (file "lib/std/os/secure-output.ss") (line 59)))) (export (symbol "jerboa_secure_fd_close") (file "jerboa-native-rs/src/secure_fs.rs") (line 987) (scheme-callers 1 (caller (file "lib/std/os/secure-output.ss") (line 77)))) - (export (symbol "jerboa_secure_free") (file "jerboa-native-rs/src/secure_mem.rs") (line 132) (scheme-callers 1 (caller (file "lib/std/crypto/secure-mem.ss") (line 38)))) + (export (symbol "jerboa_secure_free") (file "jerboa-native-rs/src/secure_mem.rs") (line 133) (scheme-callers 1 (caller (file "lib/std/crypto/secure-mem.ss") (line 38)))) (export (symbol "jerboa_secure_output_abort") (file "jerboa-native-rs/src/secure_fs.rs") (line 978) (scheme-callers 1 (caller (file "lib/std/os/secure-output.ss") (line 75)))) (export (symbol "jerboa_secure_output_begin") (file "jerboa-native-rs/src/secure_fs.rs") (line 818) (scheme-callers 2 (caller (file "lib/std/os/secure-output.ss") (line 68)) (caller (file "lib/std/os/secure-output.ss") (line 71)))) (export (symbol "jerboa_secure_output_begin_checked") (file "jerboa-native-rs/src/secure_fs.rs") (line 878) (scheme-callers 1 (caller (file "lib/std/os/secure-output.ss") (line 71)))) (export (symbol "jerboa_secure_output_commit") (file "jerboa-native-rs/src/secure_fs.rs") (line 933) (scheme-callers 1 (caller (file "lib/std/os/secure-output.ss") (line 73)))) - (export (symbol "jerboa_secure_random_fill") (file "jerboa-native-rs/src/secure_mem.rs") (line 198) (scheme-callers 1 (caller (file "lib/std/crypto/secure-mem.ss") (line 44)))) + (export (symbol "jerboa_secure_random_fill") (file "jerboa-native-rs/src/secure_mem.rs") (line 199) (scheme-callers 1 (caller (file "lib/std/crypto/secure-mem.ss") (line 44)))) (export (symbol "jerboa_secure_read_file") (file "jerboa-native-rs/src/secure_fs.rs") (line 723) (scheme-callers 1 (caller (file "lib/std/os/secure-output.ss") (line 65)))) - (export (symbol "jerboa_secure_wipe") (file "jerboa-native-rs/src/secure_mem.rs") (line 183) (scheme-callers 1 (caller (file "lib/std/crypto/secure-mem.ss") (line 41)))) + (export (symbol "jerboa_secure_wipe") (file "jerboa-native-rs/src/secure_mem.rs") (line 184) (scheme-callers 1 (caller (file "lib/std/crypto/secure-mem.ss") (line 41)))) (export (symbol "jerboa_sha1") (file "jerboa-native-rs/src/crypto.rs") (line 71) (scheme-callers 1 (caller (file "lib/std/crypto/native-rust.ss") (line 60)))) (export (symbol "jerboa_sha256") (file "jerboa-native-rs/src/crypto.rs") (line 87) (scheme-callers 1 (caller (file "lib/std/crypto/native-rust.ss") (line 69)))) (export (symbol "jerboa_sha384") (file "jerboa-native-rs/src/crypto.rs") (line 184) (scheme-callers 1 (caller (file "lib/std/crypto/native-rust.ss") (line 78)))) @@ -191,7 +191,7 @@ (export (symbol "jerboa_wasm_memory_write") (file "jerboa-native-rs/src/wasm.rs") (line 652) (scheme-callers 1 (caller (file "lib/std/wasm/sandbox.ss") (line 106)))) (export (symbol "jerboa_wasm_module_free") (file "jerboa-native-rs/src/wasm.rs") (line 224) (scheme-callers 1 (caller (file "lib/std/wasm/sandbox.ss") (line 76)))) (export (symbol "jerboa_wasm_module_new") (file "jerboa-native-rs/src/wasm.rs") (line 183) (scheme-callers 1 (caller (file "lib/std/wasm/sandbox.ss") (line 70)))) - (export (symbol "jerboa_writev2") (file "jerboa-native-rs/src/http_parse.rs") (line 110) (scheme-callers 1 (caller (file "lib/std/net/io.ss") (line 81)))) + (export (symbol "jerboa_writev2") (file "jerboa-native-rs/src/http_parse.rs") (line 112) (scheme-callers 1 (caller (file "lib/std/net/io.ss") (line 81)))) (export (symbol "jerboa_x509_cert_fingerprint") (file "jerboa-native-rs/src/x509.rs") (line 607) (scheme-callers 1 (caller (file "lib/std/crypto/x509.ss") (line 31)))) (export (symbol "jerboa_x509_generate_self_signed") (file "jerboa-native-rs/src/x509.rs") (line 234) (scheme-callers 1 (caller (file "lib/std/crypto/x509.ss") (line 27)))) ) --- a/docs/status.md +++ b/docs/status.md @@ -25,7 +25,7 @@ release artifacts are built as Jerboa multicall binaries with `jerboa`, | Area | Current state | Remaining work | |---|---|---| | Kimi security handoff | [kimi3-security-recommmendations.md](kimi3-security-recommmendations.md) is the backlog. Dated evidence and review manifests live under [reviews/](reviews/). | Keep new security evidence in dated review records and summarize the current release state here. | -| FFI audit phase 5 | [ffi-audit.md](ffi-audit.md) records the scanner output, provisional Scheme binding verdicts, and native Rust export inventory. `make native-export-review-check` gates native export decisions. The worker-launch native path in `aproc.rs`/`seccomp.rs`, crypto FFI buffer path in `crypto.rs`, secure-memory region lifecycle in `secure_mem.rs`, secure filesystem capability boundary in `secure_fs.rs`, TLS native ABI in `tls.rs`, Ed25519/X25519 key-agreement buffers, compression buffers, HTTP parse/writev boundary, embed-crypto ABI, integrity ABI, regex-native ABI, process-control ABI, pcap ABI, inotify ABI, epoll/eventfd ABI, antidebug ABI, SOCKS5 server ABI, PostgreSQL native ABI, Landlock native ABI, wasmi sandbox ABI, SpiderMonkey WASM ABI, DuckDB native ABI, X.509 native ABI, and SQLite native ABI now have nearby `SAFETY:` comments or equivalent checked FFI invariants, with the generated unannotated unsafe-site count at 13. | Continue adding `SAFETY:` invariant comments near the remaining Rust unsafe sites. | +| FFI audit phase 5 | [ffi-audit.md](ffi-audit.md) records the scanner output, provisional Scheme binding verdicts, and native Rust export inventory. `make native-export-review-check` gates native export decisions. The worker-launch native path in `aproc.rs`/`seccomp.rs`, crypto FFI buffer path in `crypto.rs`, secure-memory region lifecycle in `secure_mem.rs`, secure filesystem capability boundary in `secure_fs.rs`, TLS native ABI in `tls.rs`, Ed25519/X25519 key-agreement buffers, compression buffers, HTTP parse/writev boundary, embed-crypto ABI, integrity ABI, regex-native ABI, process-control ABI, pcap ABI, inotify ABI, epoll/eventfd ABI, antidebug ABI, SOCKS5 server ABI, PostgreSQL native ABI, Landlock native ABI, wasmi sandbox ABI, SpiderMonkey WASM ABI, DuckDB native ABI, X.509 native ABI, and SQLite native ABI now have nearby `SAFETY:` comments or equivalent checked FFI invariants, with the generated unannotated unsafe-site count at 0. | Finish per-binding Scheme FFI review and targeted scanner rules for the remaining provisional verdicts. | | Native Rust exports | The native export review now has 190 exported functions: 183 tracked Scheme references and 7 retained standalone C/binary helpers. The previous 35 no-Scheme-reference removal candidates no longer have C ABI export markers. | Re-run `make native-export-review-check` whenever adding or removing native exports. | | Confined worker | `(std security worker)` provides the facade, audit lifecycle, output caps, deadlines, process-group kill, memory rlimit pre-exec setup, Linux syscall/ptrace seccomp pre-exec setup for requested axes, explicit sandbox-axis refusal, and egress proxy env wiring. | Install native Landlock path/net rules in the worker pre-exec path and keep Linux/macOS/FreeBSD parity tests current. | | Safe surface | Direct scripts default to the safe prelude; raw access requires `--unsafe-prelude` or `(jerboa prelude unsafe)`. | Continue moving risky APIs behind explicit unsafe imports as new modules land. | --- a/jerboa-native-rs/src/antidebug.rs +++ b/jerboa-native-rs/src/antidebug.rs @@ -108,10 +108,9 @@ pub extern "C" fn jerboa_antidebug_check_breakpoint(addr: *const u8) -> i32 { set_last_error("null address".to_string()); return -1; } - // SAFETY: null is rejected above. The public FFI contract requires - // addr to point to a readable byte, typically a code address owned by - // the current process. Volatile read prevents the compiler from - // optimizing away the breakpoint probe. + // Null is rejected above. The FFI contract requires addr to point to a + // readable byte, typically a code address owned by this process. + // SAFETY: volatile read preserves the intentional breakpoint probe. let byte = unsafe { std::ptr::read_volatile(addr) }; if byte == 0xCC { 1 --- a/jerboa-native-rs/src/duckdb_native.rs +++ b/jerboa-native-rs/src/duckdb_native.rs @@ -121,9 +121,8 @@ fn ffi_bytes<'a>(ptr: *const u8, len: usize, label: &str) -> Result<&'a [u8], () if len == 0 { return Ok(&[]); } - // SAFETY: length is bounded to `isize::MAX`, and null pointers paired with - // nonzero length were rejected above. The borrowed slice is consumed before - // returning across the C ABI. + // Length is bounded to `isize::MAX`, and null/nonzero was rejected above. + // SAFETY: the borrowed slice is consumed before returning across the C ABI. Ok(unsafe { std::slice::from_raw_parts(ptr, len) }) } --- a/jerboa-native-rs/src/embed_crypto.rs +++ b/jerboa-native-rs/src/embed_crypto.rs @@ -221,10 +221,9 @@ pub extern "C" fn embed_read_passphrase(prompt: *const u8, buf: *mut u8, buflen: if buf.is_null() || buflen <= 1 { return -1; } - // SAFETY: buf is non-null and buflen > 1 above. This block calls POSIX TTY - // functions with fixed C strings, restores terminal attributes after read - // setup succeeds, writes only within buf[0..buflen), and handles EINTR for - // prompt/newline writes and byte reads. + // buf is non-null and buflen > 1 above. The block restores terminal + // attributes after read setup succeeds and handles EINTR for TTY I/O. + // SAFETY: POSIX calls use fixed strings and write only within buf[0..buflen). unsafe { let path = b"/dev/tty\0".as_ptr() as *const c_char; let fd: c_int = open(path, O_RDWR); --- a/jerboa-native-rs/src/http_parse.rs +++ b/jerboa-native-rs/src/http_parse.rs @@ -27,6 +27,7 @@ pub const PARSE_OUT_SIZE: usize = 14 + MAX_HEADERS * 8; // 270 /// `buf` must point to `buf_len` readable bytes, and `out` must point to a /// writable `PARSE_OUT_SIZE`-byte buffer. Both pointers must remain valid for /// the duration of the call. +// SAFETY: the C ABI caller must uphold the documented pointer contracts. pub unsafe extern "C" fn jerboa_http_parse(buf: *const u8, buf_len: usize, out: *mut u8) -> i32 { if buf.is_null() || out.is_null() || buf_len == 0 { return -1; @@ -107,6 +108,7 @@ pub unsafe extern "C" fn jerboa_http_parse(buf: *const u8, buf_len: usize, out: /// /// `buf1` must point to `len1` readable bytes. If `len2 > 0`, `buf2` must point /// to `len2` readable bytes. The file descriptor must be valid for writing. +// SAFETY: the C ABI caller must uphold the documented buffer and fd contracts. pub unsafe extern "C" fn jerboa_writev2( fd: i32, buf1: *const u8, --- a/jerboa-native-rs/src/integrity.rs +++ b/jerboa-native-rs/src/integrity.rs @@ -33,9 +33,9 @@ fn read_self_exe() -> Result<Vec<u8>, String> { 12, /* KERN_PROC_PATHNAME */ -1, ]; - // SAFETY: sysctl receives a fixed four-integer MIB, a writable buffer - // with its length slot, and null new-value pointers because this is a - // read-only query for the current process pathname. + // sysctl receives a fixed MIB, a writable buffer with its length slot, + // and null new-value pointers for this read-only process path query. + // SAFETY: all pointers stay live for this synchronous sysctl call. let rc = unsafe { sysctl( mib.as_ptr(), --- a/jerboa-native-rs/src/lib.rs +++ b/jerboa-native-rs/src/lib.rs @@ -1,3 +1,4 @@ +// SAFETY: crate policy denies implicit unsafe operations inside unsafe fns. #![deny(unsafe_op_in_unsafe_fn)] mod antidebug; --- a/jerboa-native-rs/src/panic.rs +++ b/jerboa-native-rs/src/panic.rs @@ -12,9 +12,9 @@ pub extern "C" fn jerboa_last_error(buf: *mut u8, buf_len: usize) -> usize { let bytes = msg.as_bytes(); let copy_len = bytes.len().min(buf_len.saturating_sub(1)); if !buf.is_null() && copy_len > 0 { - // SAFETY: buf is non-null above, copy_len is bounded by - // buf_len.saturating_sub(1), and the following byte is reserved for - // the NUL terminator. + // buf is non-null, copy_len is bounded by buf_len.saturating_sub(1), + // and the following byte is reserved for the NUL terminator. + // SAFETY: the copy and terminator write stay within caller buffer. unsafe { std::ptr::copy_nonoverlapping(bytes.as_ptr(), buf, copy_len); *buf.add(copy_len) = 0; // null terminate --- a/jerboa-native-rs/src/pcap_capture.rs +++ b/jerboa-native-rs/src/pcap_capture.rs @@ -168,9 +168,9 @@ mod macos_bpf { bf_insns: &mut insn, }; - // SAFETY: fd is obtained from the live Sniffer, prog points to a stack - // BPF program with one instruction, and BIOCSETF expects a mutable - // bpf_program pointer for the ioctl duration only. + // fd is obtained from the live Sniffer, and prog points to a stack BPF + // program with one instruction. + // SAFETY: BIOCSETF uses the mutable bpf_program pointer for this ioctl. let rc = unsafe { libc::ioctl( fd, --- a/jerboa-native-rs/src/seccomp.rs +++ b/jerboa-native-rs/src/seccomp.rs @@ -96,9 +96,9 @@ fn install_filter(filter: &[SockFilter]) -> Result<(), String> { len: filter.len() as u16, filter: filter.as_ptr(), }; - // SAFETY: prog points at `filter`, which stays alive for the duration of - // this synchronous prctl call. SockFilter/SockFprog mirror Linux classic - // BPF layouts with #[repr(C)]. + // prog points at `filter`, which stays alive for the duration of this + // synchronous prctl call. SockFilter/SockFprog mirror Linux classic BPF. + // SAFETY: prctl consumes the #[repr(C)] BPF program during this call only. let rc = unsafe { libc::prctl( libc::PR_SET_SECCOMP, @@ -222,9 +222,8 @@ pub extern "C" fn jerboa_seccomp_lock_strict(allowed: *const i32, allowed_count: } let syscalls = if allowed_count > 0 { - // SAFETY: non-null and bounded by MAX_STRICT_SYSCALLS above; the - // Scheme caller passes an immutable syscall-number vector consumed - // during this call only. + // The pointer is non-null and bounded by MAX_STRICT_SYSCALLS above. + // SAFETY: the syscall-number slice is consumed during this call only. unsafe { std::slice::from_raw_parts(allowed, allowed_count) } } else { &[] --- a/jerboa-native-rs/src/secure_mem.rs +++ b/jerboa-native-rs/src/secure_mem.rs @@ -17,6 +17,7 @@ fn round_up_to_page(size: usize, page_size: usize) -> Option<usize> { .map(|n| (n / page_size) * page_size) } +// SAFETY: callers must provide a writable allocation of at least `size` bytes. unsafe fn secure_zero(ptr: *mut u8, size: usize) { for i in 0..size { // SAFETY: caller guarantees ptr points to a writable allocation of at @@ -72,9 +73,9 @@ pub extern "C" fn jerboa_secure_alloc(size: usize) -> *mut u8 { } }; - // SAFETY: base is the start of a `total`-byte mmap region. The second - // guard starts at page_size + rounded, which remains inside the mapping - // because total == page_size + rounded + page_size. + // base is the start of a `total`-byte mmap region. The second guard + // starts at page_size + rounded, which remains inside the mapping. + // SAFETY: both mprotect calls operate on page-aligned live mmap ranges. let protect_ok = unsafe { libc::mprotect(base, page_size, libc::PROT_NONE) == 0 && libc::mprotect(