data: add P1 prevention artifacts (foreign-callable guard, content-hash truncation, thread-parameter, code-gen positional, callable-guard recipe)
ober
3bcb0d6f79159b5c632eff01fc66405aaebe3ea8
--- a/data/anti-patterns.sexp +++ b/data/anti-patterns.sexp @@ -4917,4 +4917,49 @@ ("title" . "AEAD transport without sequence/AAD/replay protection") - ("tools" "jerboa_howto" "jerboa_security_scan"))) + ("tools" "jerboa_howto" "jerboa_security_scan")) + (("advice" + . + "When wiring jpkg install to call jerbuild for source transpilation, invoke the multicall as `<self> jerbuild transpile --force <src> <lib>` — the C main in support/multicall-main.c reads argv[1] and sets JERBOA_MULTICALL_NAME=jerbuild before booting. JERBOA_SELF_EXE gives the absolute path to the running binary. Set JERBOA_PKG_PATH (or auto-discover .jpkg/lib/<env>/<root>/ in the CWD) so library-directories picks up the transpiled .sls without an env wrapper.") + ("avoid" + . + "Do not invoke the multicall binary as `JERBOA_MULTICALL_NAME=jerbuild jerboa transpile ...` expecting the env var to select the mode — the C main unconditionally overwrites JERBOA_MULTICALL_NAME based on basename(argv[0]) and argv[1] before booting Chez, so a process can't override its own mode via env. Also do not pass `--force` before the subcommand (`jerboa --force transpile src lib`); the arg parser treats the first positional as the subcommand.") + ("id" . "jpkg-multicall-jerbuild-subcommand") ("kinds") + ("pattern" + . + "JERBOA_MULTICALL_NAME=jerbuild.*jerboa transpile") + ("severity" . "medium") ("tags") + ("title" + . + "Selecting jerbuild subcommand inside the multicall binary") + ("tools")) + (("advice" + . + "Use make-thread-parameter for per-thread/per-request context (Chez implements it as SMP-safe thread-local storage); parameterize then rebinds only in the current worker thread. Each httpd worker (fork-thread) gets its own isolated context with no global lock. If some shared state genuinely needs protection, use a fine-grained lock around only that state, not the whole handler.") + ("avoid" + . + "Using make-parameter for per-request/per-thread context in a concurrent web framework. Chez make-parameter is NOT thread-local: when two threads parameterize the same parameter concurrently, the later binding overrides the earlier one — so concurrent requests corrupt each other's context. The workaround of wrapping the whole handler in a global mutex serializes ALL requests (single-threaded throughput + slow-request DoS). Seen in jerboa-sinatra handler.ss.") + ("id" . "make-parameter-not-thread-local") + ("kinds" "concurrency" "correctness" "security") + ("pattern" . "") ("severity" . "high") + ("tags" "make-parameter" "make-thread-parameter" + "concurrency" "web-framework" "mutex" "doa" "chez") + ("title" + . + "Using make-parameter for per-request context (not thread-local)") + ("tools" "jerboa_howto")) + (("advice" + . + "Use defstruct/defrecord with named field accessors for structured data; use define-enum for magic numbers; use match to destructure. This makes an inserted/removed field a compile-time error instead of a silent runtime misindex. For code generators, emit structured data, not positional tuples.") + ("avoid" + . + "Hand-rolled records as vectors/lists accessed by vector-ref/list-ref with magic indices, especially in code generators where a field-position mistake shifts every accessor — a recurring off-by-one source. Two files disagreeing on arity silently misindexes everything.") + ("id" . "code-gen-positional-records") + ("kinds" "correctness") ("pattern" . "") + ("severity" . "medium") + ("tags" "defstruct" "positional" "vector-ref" "off-by-one" + "code-gen" "record") + ("title" + . + "Positional records in code generators (off-by-one magnet)") + ("tools" "jerboa_howto"))) --- a/data/cookbooks.sexp +++ b/data/cookbooks.sexp @@ -6982,4 +6982,16 @@ "ub") ("title" . - "Catch Rust panics at the extern \"C\" boundary"))) + "Catch Rust panics at the extern \"C\" boundary")) + (("code" + . + ";; WRONG: a foreign-callable body that raises lets the condition\n;; escape into C as an uncaught exception -> image reset/abort.\n;; (define fc-step\n;; (foreign-callable (lambda (stmt) ... (val->text ...)) ...)) ;; BUG\n\n;; RIGHT: wrap every foreign-callable body in a guard:\n(define fc-step\n (foreign-callable\n (lambda (stmt)\n (guard (e [(sqlite-error? e) (sqlite-error-code e)] ;; preserve typed code\n [#t SQLITE_ERROR]) ;; default\n ... (val->text ...) ...))\n ...))\n\n;; Match the guard style of fc-open/fc-prepare if they already guard.\n;; Key: the guard must return a C-compatible error code, never re-raise.") ("id" . "foreign-callable-guard") + ("imports" "(chezscheme)") + ("notes" + . + "Seen in jerboa-sqlite cshim.ss: fc-step/fc-colint/fc-coltext/fc-coltype/fc-colcount were unguarded (only fc-open/fc-prepare guarded). A condition during step/column access (utf8->string on invalid-UTF-8 BLOB, int64 overflow on a huge TEXT number, SQLITE_CORRUPT mid-step) escaped into C as an uncaught exception in a foreign callback, causing image reset/abort. Wrap EVERY foreign-callable body in (guard (e [#t <errcode>]) ...) so a raised condition returns a clean error code to C. Also saturate int64 conversions (val->int64) to the int64 range instead of handing a bignum to an integer-64 return.") + ("tags" "ffi" "foreign-callable" "guard" "sqlite" "c" + "abort" "error-code") + ("title" + . + "Guard every foreign-callable body (prevent conditions escaping into C)"))) --- a/data/security-rules.sexp +++ b/data/security-rules.sexp @@ -1226,4 +1226,22 @@ "Parsing zipinfo/bsdtar -tv output line-by-line silently drops archive members whose names contain CR/LF/NUL (the name splits across lines and the parser returns #f), letting a malicious archive hide a payload. Use NUL-delimited/native listing, or fail closed (flag the container as suspicious) on unparseable member names; reject names with NUL/control chars and insert -- before member args. Seen in jerboa-virus.") ("pattern" . "zipinfo|bsdtar|tar -tv|unzip") ("scope" . "scheme") - ("severity" . "high"))) + ("severity" . "high")) + (("id" . "foreign-callable-without-guard") + ("message" + . + "A foreign-callable (Chez FFI callback) body not wrapped in (guard (e [#t <errcode>]) ...) lets any raised condition — utf8->string on invalid-UTF-8 BLOB, int64 overflow, SQLITE_CORRUPT, a typed error — escape into C as an uncaught exception in a foreign callback, causing image reset/abort. Wrap EVERY foreign-callable body in a guard that maps conditions to a clean error code, matching the guarded open/prepare style.") + ("pattern" + . + "foreign-callable|fc-step|fc-col|fc-open|fc-prepare|c-callable") + ("scope" . "ffi-boundary") + ("severity" . "high")) + (("id" . "content-hash-truncated-for-addressing") + ("message" + . + "Truncating a cryptographic hash (e.g. SHA-256 to 8 bytes / 64-bit) for content addressing makes collision resistance birthday-bound at ~2^32 work, which is feasible — a collision lets an attacker substitute one content-addressed value for another. Use a full-width or ≥16-byte hash for content addressing; if a format constrains the slot width, store the full digest alongside and verify on read (collision detection).") + ("pattern" + . + "content-hash|sha256.*truncat|subvector.*0.*8|bytevector-copy.*0.*8") + ("scope" . "scheme") + ("severity" . "medium")))