security: restrict native loader system candidates
ober
f485a7077ca9e785866f62b3bb142cbe6dbf2397
--- a/docs/kimi3-security-recommmendations.md +++ b/docs/kimi3-security-recommmendations.md @@ -1143,7 +1143,9 @@ fix must add its scanner rule in the same commit (write it into `rm -rf` command. `jpkg env -- COMMAND` is disabled until the package CLI has an argv exec/status API, removing the last shell-concatenation path from `(std pkg commands)`. Terminal raw-mode restore now accepts only validated - `stty -g` state tokens before invoking the restore command. + `stty -g` state tokens before invoking the restore command. The shared native + loader now makes fixed-system-library bootstrap loads scanner-visible and + rejects non-literal system-library candidates at macro expansion time. ### K3-P2-06 — Documentation consistency pass **Serves:** G5. **Effort:** 2 days. --- a/docs/security-reference.md +++ b/docs/security-reference.md @@ -1008,6 +1008,10 @@ These are known gaps documented as current limitations, not implementation promi Git uninstall. `jpkg env -- COMMAND` fails closed until the package CLI has a real argv exec/status API instead of a shell-concatenated compatibility path. Terminal raw-mode restore accepts only validated `stty -g` state tokens. +- **Native loader system paths are literal-only.** `(std native-loader)` probes + libc from the process image or fixed operating-system paths, requires a clean + dynamic-loader environment before any fallback load, and rejects non-literal + candidates to `native-loader-try-system-symbol!` at macro expansion time. - **No red team evaluation.** No independent adversarial testing has been performed. - **Secure memory still exposes a raw region escape hatch.** The high-level `secure-bytevector` API is bounds-checked and integrated with --- a/docs/status.md +++ b/docs/status.md @@ -29,7 +29,7 @@ release artifacts are built as Jerboa multicall binaries with `jerboa`, | 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, Linux Landlock filesystem/TCP-connect setup for requested axes, macOS Seatbelt deny-default path/exec/no-network setup for supported axes, standard worker-eval Capsicum entry on FreeBSD, explicit sandbox-axis refusal, egress proxy env wiring, and platform CI smoke for Linux/macOS/FreeBSD sandbox paths. | Keep Linux/macOS/FreeBSD parity tests current; finish arbitrary-command/proxy-aware Capsicum worker paths. | | Fuzzing | `tests/fuzz/corpus/` has 15 checked-in seed inputs, `tests/fuzz/regression/` has 11 crash/rejection regressions, `make fuzz-smoke` runs the deterministic regression gate first, and GitHub CI runs smoke fuzzing normally plus deep fuzzing on scheduled daily runs. | Keep adding minimized corpus and regression inputs for every parser/security bug found. | -| Safe surface | Direct scripts default to the safe prelude; raw access requires `--unsafe-prelude` or `(jerboa prelude unsafe)`. Core compatibility helpers avoid shell construction for recursive directory creation and process-status cleanup; the retired `(jerboa registry)` surface fails closed for mutable Git uninstall; `jpkg env -- COMMAND` is disabled until there is an argv exec/status API; terminal raw-mode restore validates `stty -g` state tokens. | Continue moving risky APIs behind explicit unsafe imports as new modules land. | +| Safe surface | Direct scripts default to the safe prelude; raw access requires `--unsafe-prelude` or `(jerboa prelude unsafe)`. Core compatibility helpers avoid shell construction for recursive directory creation and process-status cleanup; the retired `(jerboa registry)` surface fails closed for mutable Git uninstall; `jpkg env -- COMMAND` is disabled until there is an argv exec/status API; terminal raw-mode restore validates `stty -g` state tokens; native-loader system candidates are literal fixed paths. | Continue moving risky APIs behind explicit unsafe imports as new modules land. | ## Compatibility Notes --- a/lib/std/native-loader.ss +++ b/lib/std/native-loader.ss @@ -87,7 +87,7 @@ (when path (native-loader-require-clean-environment! 'std/native-loader)) (guard (condition [else #f]) - (load-shared-object path) + (load-shared-object path) ; jerboa-security: suppress native-fasl-load-from-dynamic-path -- path is either #f or a fixed OS libc candidate from system-libc-paths; caller-controlled native paths use native-loader-validate-library! (bootstrap-symbols-available?))) (define system-libc-loaded @@ -136,24 +136,39 @@ ;; a new symbol visible to another library, so safe system-library loading is ;; exported as a macro and expands at each call site. (define-syntax native-loader-try-system-symbol! - (syntax-rules () - [(_ who symbol candidate ...) - (let ([required-symbol symbol]) - (define (try-candidate path) - (unless (or (not path) - (and (string? path) - (> (string-length path) 1) - (char=? (string-ref path 0) #\/))) - (error who - "system-library candidates must be #f or absolute paths" - path)) - (when path - (native-loader-require-clean-environment! who)) - (guard (condition [else #f]) - (load-shared-object path) - (foreign-entry? required-symbol))) - (or (foreign-entry? required-symbol) - (try-candidate candidate) ...))])) + (lambda (stx) + (define (literal-system-candidate? datum) + (or (not datum) + (and (string? datum) + (> (string-length datum) 1) + (char=? (string-ref datum 0) #\/)))) + (syntax-case stx () + [(_ who symbol candidate ...) + (let ([candidates (syntax->datum #'(candidate ...))]) + (let loop ([remaining candidates]) + (unless (null? remaining) + (unless (literal-system-candidate? (car remaining)) + (syntax-violation + 'native-loader-try-system-symbol! + "system-library candidates must be literal #f or absolute path strings" + stx)) + (loop (cdr remaining)))) + #'(let ([required-symbol symbol]) + (define (try-candidate path) + (unless (or (not path) + (and (string? path) + (> (string-length path) 1) + (char=? (string-ref path 0) #\/))) + (error who + "system-library candidates must be #f or absolute paths" + path)) + (when path + (native-loader-require-clean-environment! who)) + (guard (condition [else #f]) + (load-shared-object path) ; jerboa-security: suppress native-fasl-load-from-dynamic-path -- candidate is a macro-verified literal #f or absolute system-library path string; project native libraries use native-loader-validate-library! + (foreign-entry? required-symbol))) + (or (foreign-entry? required-symbol) + (try-candidate candidate) ...)))]))) (define-syntax native-loader-ensure-system-symbol! (syntax-rules () --- a/tests/test-native-loader-policy.sh +++ b/tests/test-native-loader-policy.sh @@ -219,13 +219,20 @@ cat > "$tmp/system-macro-negative.ss" <<'EOF' (exit 1)) EOF rm -f "$marker" -( +if ( cd "$tmp/cwd" JERBOA_LOADER_BARE_CANDIDATE="$(basename "$library")" \ JERBOA_SENTINEL_MARKER="$marker" \ "$scheme" --libdirs "$root/lib:$root/vendor/jsqlite/src" \ - --script "$tmp/system-macro-negative.ss" -) + --script "$tmp/system-macro-negative.ss" \ + >"$tmp/system-macro-negative.out" \ + 2>"$tmp/system-macro-negative.err" +); then + echo "system loader accepted a runtime candidate expression" >&2 + exit 1 +fi +grep -q 'system-library candidates must be literal #f or absolute path strings' \ + "$tmp/system-macro-negative.err" test ! -e "$marker" if test "$(id -u)" -ne 0; then