Add hardware hardening report smoke
ober
2659a56a7fd98b0c35f577e4ad94f78e66667c49
--- a/.builds/ci.yml +++ b/.builds/ci.yml @@ -16,7 +16,7 @@ sources: tasks: - build-and-test: | cd jerboa - make jerboa + make security-hardware-smoke make data-check make test make fuzz-smoke --- a/Makefile +++ b/Makefile @@ -57,7 +57,7 @@ TYPED_LLVMIR_PARITY_FN ?= sample_typed_llvmir_smoke::main LLVM_BIN ?= $(shell if command -v llvm-as >/dev/null 2>&1; then dirname "$$(command -v llvm-as)"; elif [ -x /opt/homebrew/opt/llvm/bin/llvm-as ]; then echo /opt/homebrew/opt/llvm/bin; elif [ -x /usr/local/opt/llvm/bin/llvm-as ]; then echo /usr/local/opt/llvm/bin; fi) TYPED_WRAPPER_DIR ?= build/typed/jerboa -.PHONY: help chez chez-cross build binary binary-typed binary-typed-smoke binary-cross native-cross pure-audit typecheck typed-rust typed-llvmir typed-llvmir-check typed-llvmir-smoke typed-llvmir-parity typed-wrappers typed-build typed-wrapper-smoke typed-split-tree-smoke typed-test typed-clean test test-known-flaky test-reader test-core test-runtime test-try-debug test-stdlib test-ffi test-modules test-expanded test-contract test-ergo test-sqlite-robustness test-limits-primitives test-typed-core test-typed-parser test-typed-checker test-typed-rust test-typed-llvmir test-typed-wrappers test-pure-audit test-features test-wrappers test-phase4a test-phase4b test-phase4c test-phase4d test-phase4e test-phase4f test-phase5 test-phase5e test-phase6 test-phase7 test-phase8 test-functional test-repl test-security test-security-profile test-native test-gaps native clean-native audit-native clean security security-production security-profile fuzz fuzz-smoke fuzz-deep fuzz-reader-fuzz fuzz-json-fuzz fuzz-http2-fuzz fuzz-dns-fuzz fuzz-pregexp-fuzz fuzz-csv-fuzz fuzz-base64-fuzz fuzz-hex-fuzz fuzz-uri-fuzz fuzz-format-fuzz fuzz-router-fuzz fuzz-sandbox-fuzz test-rawstring test-regex test-rx test-peg test-regex-all check-docs check-docs-strict data-check docker-build docker-push +.PHONY: help chez chez-cross build binary binary-typed binary-typed-smoke binary-cross native-cross pure-audit typecheck typed-rust typed-llvmir typed-llvmir-check typed-llvmir-smoke typed-llvmir-parity typed-wrappers typed-build typed-wrapper-smoke typed-split-tree-smoke typed-test typed-clean test test-known-flaky test-reader test-core test-runtime test-try-debug test-stdlib test-ffi test-modules test-expanded test-contract test-ergo test-sqlite-robustness test-limits-primitives test-typed-core test-typed-parser test-typed-checker test-typed-rust test-typed-llvmir test-typed-wrappers test-pure-audit test-features test-wrappers test-phase4a test-phase4b test-phase4c test-phase4d test-phase4e test-phase4f test-phase5 test-phase5e test-phase6 test-phase7 test-phase8 test-functional test-repl test-security test-security-profile test-native test-gaps native clean-native audit-native clean security security-production security-profile security-hardware-smoke fuzz fuzz-smoke fuzz-deep fuzz-reader-fuzz fuzz-json-fuzz fuzz-http2-fuzz fuzz-dns-fuzz fuzz-pregexp-fuzz fuzz-csv-fuzz fuzz-base64-fuzz fuzz-hex-fuzz fuzz-uri-fuzz fuzz-format-fuzz fuzz-router-fuzz fuzz-sandbox-fuzz test-rawstring test-regex test-rx test-peg test-regex-all check-docs check-docs-strict data-check docker-build docker-push .PHONY: check-cross-tools docker fuzz-websocket-fuzz jlsp jlsp-freebsd-amd64 \ jlsp-install jlsp-linux-amd64 jlsp-portable jmcp-freebsd-amd64 \ jmcp-freebsd-arm64 jmcp-linux-amd64 jmcp-linux-arm64 \ @@ -180,6 +180,7 @@ help: @echo " test-wrappers FFI wrapper module tests" @echo " test-security Security tests" @echo " security Run profile-aware local security checks" + @echo " security-hardware-smoke Report/check binary hardware hardening" @echo " test-native Rust native library tests" @echo " test-all All test suites combined" @echo " test-phase2 Phase 2 feature tests" @@ -1451,6 +1452,26 @@ security-production: security-profile test-security-profile security-profile: @test -f .jerboa/security.json +security-hardware-smoke: jerboa + @echo "--- Hardware hardening smoke ---" + @JERBOA_SECURITY_REPORT=1 dist/jerboa + @if [ "$(HOST_UNAME_S):$(HOST_UNAME_M)" = "Linux:x86_64" ]; then \ + if [ "$${JERBOA_BINARY_HARDEN:-1}" = "0" ] || [ "$${JERBOA_CET_ELF_NOTES:-1}" = "0" ]; then \ + echo " ELF CET note check: skipped by environment"; \ + elif command -v readelf >/dev/null 2>&1; then \ + if readelf -n dist/jerboa 2>/dev/null | grep -Eq 'IBT|SHSTK'; then \ + echo " ELF CET notes: PASS"; \ + else \ + echo " ELF CET notes: FAIL (expected IBT/SHSTK GNU property notes)" >&2; \ + exit 1; \ + fi; \ + else \ + echo " ELF CET note check: skipped (readelf not installed)"; \ + fi; \ + else \ + echo " ELF CET note check: skipped on $(HOST_UNAME_S)/$(HOST_UNAME_M)"; \ + fi + # Rust native library RUST_NATIVE_DIR = jerboa-native-rs UNAME_S := $(shell uname -s) --- a/data/cookbooks.sexp +++ b/data/cookbooks.sexp @@ -6198,15 +6198,15 @@ "Build jsh base without embed passphrase prompt")) (("code" . - ";; Pattern for Chez/Jerboa binary builders that generate a C launcher:\n;; 1. Derive flags from the target Chez machine type, not the build host.\n;; 2. Add launcher CFLAGS before compiling generated main.c / C shims:\n;; Linux x86_64: -fstack-protector-strong -D_FORTIFY_SOURCE=2 -fstack-clash-protection -fcf-protection=full\n;; Linux arm64: -fstack-protector-strong -D_FORTIFY_SOURCE=2 -fstack-clash-protection -mbranch-protection=standard\n;; ELF other: -fstack-protector-strong -D_FORTIFY_SOURCE=2\n;; 3. Add ELF LDFLAGS on Linux/FreeBSD/OpenBSD/NetBSD:\n;; -Wl,-z,relro,-z,now\n;; and on Linux x86_64, when supported: -Wl,-z,ibt,-z,shstk\n;; 4. Add an optional Linux x86_64 C startup hook before Sscheme_init:\n;; syscall(SYS_arch_prctl, ARCH_SHSTK_ENABLE, ARCH_SHSTK_SHSTK)\n;; Gate it behind JERBOA_ENABLE_SHSTK=1 or JERBOA_ENABLE_SHSTK=try.\n;; 5. Keep escape hatches:\n;; JERBOA_BINARY_HARDEN=0 disables launcher hardening for debugging.\n;; JERBOA_CET_ELF_NOTES=0 disables -z ibt/-z shstk for older linkers.") ("id" . "generated-binary-launcher-hardening") ("imports") + ";; Pattern for Chez/Jerboa binary builders that generate a C launcher:\n;; 1. Derive flags from the target Chez machine type, not the build host.\n;; 2. Add launcher CFLAGS before compiling generated main.c / C shims:\n;; Linux x86_64: -fstack-protector-strong -D_FORTIFY_SOURCE=2 -fstack-clash-protection -fcf-protection=full\n;; Linux arm64: -fstack-protector-strong -D_FORTIFY_SOURCE=2 -fstack-clash-protection -mbranch-protection=standard\n;; ELF other: -fstack-protector-strong -D_FORTIFY_SOURCE=2\n;; 3. Add ELF LDFLAGS on Linux/FreeBSD/OpenBSD/NetBSD:\n;; -Wl,-z,relro,-z,now\n;; and on Linux x86_64, when supported: -Wl,-z,ibt,-z,shstk\n;; 4. Add an optional Linux x86_64 C startup hook before Sscheme_init:\n;; syscall(SYS_arch_prctl, ARCH_SHSTK_ENABLE, ARCH_SHSTK_SHSTK)\n;; Gate it behind JERBOA_ENABLE_SHSTK=1 or JERBOA_ENABLE_SHSTK=try.\n;; 5. Add a no-side-effect report path:\n;; JERBOA_SECURITY_REPORT=1 ./binary\n;; It should print compiled CET/BTI/PAC state plus SHSTK runtime status,\n;; then exit before Sscheme_init and without enabling SHSTK.\n;; 6. Keep escape hatches:\n;; JERBOA_BINARY_HARDEN=0 disables launcher hardening for debugging.\n;; JERBOA_CET_ELF_NOTES=0 disables -z ibt/-z shstk for older linkers.\n;; 7. Add a smoke target that runs the report path and, on Linux x86_64,\n;; verifies readelf -n shows IBT/SHSTK notes when available.") ("id" . "generated-binary-launcher-hardening") ("imports") ("notes" . - "Do not infer target hardening from the current host when cross-building. For jerbuild binary, use the csv-dir machine leaf when --csv-dir is supplied. Do not enable SHSTK by default for arbitrary Scheme programs until Chez continuation restoration is shadow-stack-aware; call/cc can violate SHSTK's LIFO assumptions.") + "Do not infer target hardening from the current host when cross-building. For jerbuild binary, use the csv-dir machine leaf when --csv-dir is supplied. Do not enable SHSTK by default for arbitrary Scheme programs until Chez continuation restoration is shadow-stack-aware; call/cc can violate SHSTK's LIFO assumptions. A security report path is safe by default because it exits before booting Scheme or enabling SHSTK.") ("tags" "binary" "hardening" "CET" "SHSTK" "launcher" "jerbuild") ("title" . - "Harden generated binary launchers for CET/BTI targets")) + "Harden and report generated binary launcher security state")) (("code" . "#!chezscheme\n(import (chezscheme))\n\n(define source-path-placeholder \"<app>\")\n\n(define (sanitized-source-object sfd bfp efp)\n (if (source-file-descriptor? sfd)\n (make-source-object\n (source-file-descriptor\n source-path-placeholder\n (source-file-descriptor-checksum sfd))\n bfp\n efp)\n #f))\n\n(define (strip-fasl-metadata! path)\n (when (file-exists? path)\n (let ([tmp (format \"~a.stripped.~a\" path (get-process-id))])\n (strip-fasl-file path tmp\n (fasl-strip-options inspector-source\n profile-source\n source-annotations))\n (rename-file tmp path))))\n\n(current-make-source-object sanitized-source-object)\n(debug-level 0)\n(generate-inspector-information #f)\n(generate-procedure-source-information #f)\n(enable-error-source-expression #f)\n\n(compile-program \"main.ss\")\n(strip-fasl-metadata! \"main.so\")") ("id" . "sanitize-chez-source-paths-in-release-builds") @@ -6217,4 +6217,16 @@ ("tags" "chezscheme" "source-paths" "current-make-source-object" "fasl" "release-build" "strip-fasl-file") - ("title" . "Sanitize Chez source paths in release builds"))) + ("title" . "Sanitize Chez source paths in release builds")) + (("code" + . + ";; During header generation, store bytes through a reversible transform.\n(define embed-xor-key #xA7)\n\n(define (write-encoded-c-array bv var-name out)\n (let ([n (bytevector-length bv)])\n (display (format \"static const unsigned char ~a[] = {\\n\" var-name) out)\n (let loop ([i 0])\n (when (< i n)\n (let ([stored (bitwise-xor (bytevector-u8-ref bv i) embed-xor-key)])\n (display (format \"0x~2,'0x,\" stored) out))\n (when (= (mod (+ i 1) 16) 0) (newline out))\n (loop (+ i 1))))\n (display \"};\\n\" out)\n (display (format \"static const unsigned int ~a_size = sizeof(~a);\\n\" var-name var-name) out)))\n\n;; In the generated C launcher, decode before registering boot files or\n;; writing the WPO program to memfd/tmpfile:\n;;\n;; #define JSH_BLOB_XOR_KEY 0xA7\n;; static unsigned char *decode_blob(const unsigned char *src, size_t size) {\n;; unsigned char *dst = malloc(size ? size : 1);\n;; if (!dst) { perror(\"malloc blob\"); exit(1); }\n;; for (size_t i = 0; i < size; i++) dst[i] = src[i] ^ JSH_BLOB_XOR_KEY;\n;; return dst;\n;; }\n;;\n;; unsigned char *petite_plain = decode_blob(petite_boot, petite_boot_size);\n;; Sregister_boot_file_bytes(\"petite\", petite_plain, petite_boot_size);") ("id" . "encode-embedded-chez-blobs-in-c-launcher") + ("imports" "(chezscheme)") + ("notes" + . + "This keeps raw FASL/source/help strings out of the final ELF's static data, which strip(1) cannot remove. Keep decoded buffers alive at least through Sbuild_heap/Sscheme_program initialization; freeing after Sscheme_deinit is a conservative default. This is obfuscation/string-surface reduction, not cryptographic secrecy.") + ("tags" "chez" "boot" "wpo" "static-binary" "c-launcher" + "strings") + ("title" + . + "Encode embedded Chez boot and WPO blobs in generated C launchers"))) --- a/docs/chez-hardening.md +++ b/docs/chez-hardening.md @@ -337,12 +337,17 @@ JERBOA_ENABLE_SHSTK=try ./dist/jerboa ... # continue if unsupported This covers `make binary`, `make jerboa`, standalone `jerbuild`, `jerbuild binary`, `make jmcp`, and native `make jlsp`. The hook is deliberately not enabled by default until the continuation-aware kernel work in 6.4 lands. +Set `JERBOA_SECURITY_REPORT=1` on those generated launchers to print the +compiled CET/BTI/PAC state and the current SHSTK runtime status, then exit +without booting Scheme or enabling SHSTK. The same launcher build paths also apply target-aware hardening flags: Linux x86_64 gets `-fcf-protection=full` and, by default, `-Wl,-z,ibt,-z,shstk`; Linux arm64 gets `-mbranch-protection=standard`; ELF targets get `-Wl,-z,relro,-z,now`. Use `JERBOA_BINARY_HARDEN=0` for debugging or `JERBOA_CET_ELF_NOTES=0` for an older linker that lacks CET note support. +Run `make security-hardware-smoke` to exercise this report path and verify +Linux x86_64 ELF CET notes when `readelf` is available. ### 6.4 Future Work: Continuation-Aware Shadow-Stack Management --- a/docs/secure.md +++ b/docs/secure.md @@ -50,6 +50,8 @@ Every indirect jump/call target must begin with an `ENDBR64` instruction. If con **Runtime SHSTK**: Because Chez continuations can violate shadow-stack LIFO assumptions, runtime SHSTK is explicit: run binaries with `JERBOA_ENABLE_SHSTK=1` to require it, or `JERBOA_ENABLE_SHSTK=try` to enable it when the CPU/kernel support it and continue otherwise. +**Runtime reporting**: Set `JERBOA_SECURITY_REPORT=1` to make generated launchers print their compiled CET/BTI/PAC state and current SHSTK runtime status, then exit without booting Scheme or enabling SHSTK. + ### Standard Hardening CFLAGS Rebuild Chez and all C code with: @@ -77,6 +79,8 @@ now apply target-aware C/link hardening to their launcher C code. Linux x86_64 gets CET CFLAGS plus `-z ibt`/`-z shstk` ELF notes by default; Linux arm64 gets `-mbranch-protection=standard`; ELF targets get RELRO/NOW. Set `JERBOA_BINARY_HARDEN=0` to disable launcher hardening for debugging. +Run `make security-hardware-smoke` to build the multicall binary, print its +security report, and check Linux x86_64 ELF CET notes when `readelf` is present. **NOT YET DONE — downstream projects**: jerboa-secmon and jerboa-dns have their own build scripts (`build-secmon-musl.ss`, `build-secmon-musl.sh`, `Dockerfile`) that hardcode --- a/jerbuild.ss +++ b/jerbuild.ss @@ -2043,33 +2043,104 @@ #ifndef ARCH_SHSTK_ENABLE #define ARCH_SHSTK_ENABLE 0x5001 #endif +#ifndef ARCH_SHSTK_STATUS +#define ARCH_SHSTK_STATUS 0x5005 +#endif #ifndef ARCH_SHSTK_SHSTK #define ARCH_SHSTK_SHSTK (1ULL << 0) #endif +#endif -static int env_flag_enabled(const char *v) { +static int jerboa_env_flag_enabled(const char *v) { return v && *v && strcmp(v, \"0\") != 0 && strcmp(v, \"false\") != 0 && + strcmp(v, \"False\") != 0 && strcmp(v, \"FALSE\") != 0 && strcmp(v, \"no\") != 0 && + strcmp(v, \"No\") != 0 && strcmp(v, \"NO\") != 0 && strcmp(v, \"off\") != 0 && + strcmp(v, \"Off\") != 0 && strcmp(v, \"OFF\") != 0; } -static void maybe_enable_shadow_stack(void) { +static int jerboa_security_report_requested(void) { + return jerboa_env_flag_enabled(getenv(\"JERBOA_SECURITY_REPORT\")); +} + +static void jerboa_maybe_enable_shadow_stack(const char *program_name) { +#if defined(__linux__) && defined(__x86_64__) const char *v = getenv(\"JERBOA_ENABLE_SHSTK\"); - if (!env_flag_enabled(v)) return; + if (!jerboa_env_flag_enabled(v)) return; if (syscall(SYS_arch_prctl, ARCH_SHSTK_ENABLE, ARCH_SHSTK_SHSTK) != 0) { if (strcmp(v, \"try\") == 0) return; - fprintf(stderr, \"jerboa: ARCH_SHSTK_ENABLE failed: %s\\n\", strerror(errno)); + fprintf(stderr, \"%s: ARCH_SHSTK_ENABLE failed: %s\\n\", + program_name ? program_name : \"jerboa\", strerror(errno)); exit(127); } +#else + (void)program_name; +#endif } + +static void jerboa_print_security_report(const char *program_name) { + const char *request = getenv(\"JERBOA_ENABLE_SHSTK\"); + printf(\"program: %s\\n\", program_name ? program_name : \"jerboa\"); +#if defined(__linux__) + printf(\"os: linux\\n\"); +#elif defined(__APPLE__) + printf(\"os: macos\\n\"); +#elif defined(__FreeBSD__) + printf(\"os: freebsd\\n\"); +#elif defined(__OpenBSD__) + printf(\"os: openbsd\\n\"); +#elif defined(__NetBSD__) + printf(\"os: netbsd\\n\"); +#else + printf(\"os: unknown\\n\"); +#endif +#if defined(__x86_64__) + printf(\"arch: x86_64\\n\"); +#elif defined(__aarch64__) || defined(__arm64__) + printf(\"arch: arm64\\n\"); +#else + printf(\"arch: unknown\\n\"); +#endif +#if defined(__CET__) + printf(\"cet-compiled: yes (__CET__=%d)\\n\", __CET__); #else -static void maybe_enable_shadow_stack(void) { } + printf(\"cet-compiled: no\\n\"); #endif +#if defined(__ARM_FEATURE_BTI_DEFAULT) + printf(\"arm-bti-compiled: yes\\n\"); +#else + printf(\"arm-bti-compiled: no\\n\"); +#endif +#if defined(__ARM_FEATURE_PAC_DEFAULT) + printf(\"arm-pac-compiled: yes (__ARM_FEATURE_PAC_DEFAULT=%d)\\n\", + __ARM_FEATURE_PAC_DEFAULT); +#else + printf(\"arm-pac-compiled: no\\n\"); +#endif +#if defined(__linux__) && defined(__x86_64__) + { + unsigned long features = 0; + errno = 0; + if (syscall(SYS_arch_prctl, ARCH_SHSTK_STATUS, &features) == 0) { + printf(\"shstk-runtime: %s (mask=0x%lx)\\n\", + (features & ARCH_SHSTK_SHSTK) ? \"enabled\" : \"disabled\", + features); + } else { + printf(\"shstk-runtime: unavailable (errno=%d %s)\\n\", + errno, strerror(errno)); + } + } +#else + printf(\"shstk-runtime: not-applicable\\n\"); +#endif + printf(\"shstk-request: %s\\n\", request && *request ? request : \"<unset>\"); +} static const char *write_program_tmpfile(void) { static char path[] = \"/tmp/jerboa-prog-XXXXXX\"; @@ -2084,7 +2155,12 @@ static const char *write_program_tmpfile(void) { } int main(int argc, const char *argv[]) { - maybe_enable_shadow_stack(); + if (jerboa_security_report_requested()) { + jerboa_print_security_report(\"jerboa\"); + return 0; + } + + jerboa_maybe_enable_shadow_stack(\"jerboa\"); Sscheme_init(NULL); Sregister_boot_file_bytes(\"petite\", --- a/lsp/build-binary.ss +++ b/lsp/build-binary.ss @@ -311,31 +311,14 @@ (fprintf out "#include \"jerboa_lsp_scheme_boot.h\"\n") (fprintf out "#include \"jerboa_lsp_boot.h\"\n") (fprintf out "#include \"jerboa_lsp_program.h\"\n") + (fprintf out "#include \"hardening-report.h\"\n") (fprintf out "\n") - (fprintf out "#if defined(__linux__) && defined(__x86_64__)\n") - (fprintf out "#ifndef ARCH_SHSTK_ENABLE\n") - (fprintf out "#define ARCH_SHSTK_ENABLE 0x5001\n") - (fprintf out "#endif\n") - (fprintf out "#ifndef ARCH_SHSTK_SHSTK\n") - (fprintf out "#define ARCH_SHSTK_SHSTK (1ULL << 0)\n") - (fprintf out "#endif\n") - (fprintf out "static int env_flag_enabled(const char *v) {\n") - (fprintf out " return v && *v && strcmp(v, \"0\") && strcmp(v, \"false\") && strcmp(v, \"FALSE\") && strcmp(v, \"no\") && strcmp(v, \"NO\") && strcmp(v, \"off\") && strcmp(v, \"OFF\");\n") - (fprintf out "}\n") - (fprintf out "static void maybe_enable_shadow_stack(void) {\n") - (fprintf out " const char *v = getenv(\"JERBOA_ENABLE_SHSTK\");\n") - (fprintf out " if (!env_flag_enabled(v)) return;\n") - (fprintf out " if (syscall(SYS_arch_prctl, ARCH_SHSTK_ENABLE, ARCH_SHSTK_SHSTK) != 0) {\n") - (fprintf out " if (strcmp(v, \"try\") == 0) return;\n") - (fprintf out " fprintf(stderr, \"jlsp: ARCH_SHSTK_ENABLE failed: %s\\n\", strerror(errno));\n") - (fprintf out " exit(127);\n") - (fprintf out " }\n") - (fprintf out "}\n") - (fprintf out "#else\n") - (fprintf out "static void maybe_enable_shadow_stack(void) { }\n") - (fprintf out "#endif\n\n") (fprintf out "int main(int argc, char *argv[]) {\n") - (fprintf out " maybe_enable_shadow_stack();\n") + (fprintf out " if (jerboa_security_report_requested()) {\n") + (fprintf out " jerboa_print_security_report(\"jlsp\");\n") + (fprintf out " return 0;\n") + (fprintf out " }\n") + (fprintf out " jerboa_maybe_enable_shadow_stack(\"jlsp\");\n") (fprintf out " /* Extract program .so to a temp file */\n") (fprintf out " char prog_path[256];\n") (fprintf out " const char *tmpdir = getenv(\"TMPDIR\");\n") @@ -380,7 +363,7 @@ (let ((cc (or (getenv "CC") "cc")) (harden-cflags (binary-hardening-cflags machine-string)) (harden-ldflags (binary-hardening-ldflags machine-string))) - (let ((rc (system (format "~a -c -I~a -O2~a -o jerboa-lsp-main.o jerboa-lsp-main.c" + (let ((rc (system (format "~a -c -I~a -Isupport -O2~a -o jerboa-lsp-main.o jerboa-lsp-main.c" cc chez-dir harden-cflags)))) (unless (= rc 0) (printf "Error: C compilation failed~n") --- a/mcp/build-jmcp.sh +++ b/mcp/build-jmcp.sh @@ -198,29 +198,8 @@ cat > "$builder" <<'SCHEME' (display "#include \"scheme.h\"\n" out) (display "#include \"petite_boot.h\"\n" out) (display "#include \"scheme_boot.h\"\n" out) - (display "#include \"jmcp_program.h\"\n\n" out) - (display "#if defined(__linux__) && defined(__x86_64__)\n" out) - (display "#ifndef ARCH_SHSTK_ENABLE\n" out) - (display "#define ARCH_SHSTK_ENABLE 0x5001\n" out) - (display "#endif\n" out) - (display "#ifndef ARCH_SHSTK_SHSTK\n" out) - (display "#define ARCH_SHSTK_SHSTK (1ULL << 0)\n" out) - (display "#endif\n" out) - (display "static int env_flag_enabled(const char *v) {\n" out) - (display " return v && *v && strcmp(v, \"0\") && strcmp(v, \"false\") && strcmp(v, \"FALSE\") && strcmp(v, \"no\") && strcmp(v, \"NO\") && strcmp(v, \"off\") && strcmp(v, \"OFF\");\n" out) - (display "}\n" out) - (display "static void maybe_enable_shadow_stack(void) {\n" out) - (display " const char *v = getenv(\"JERBOA_ENABLE_SHSTK\");\n" out) - (display " if (!env_flag_enabled(v)) return;\n" out) - (display " if (syscall(SYS_arch_prctl, ARCH_SHSTK_ENABLE, ARCH_SHSTK_SHSTK) != 0) {\n" out) - (display " if (strcmp(v, \"try\") == 0) return;\n" out) - (display " fprintf(stderr, \"jmcp: ARCH_SHSTK_ENABLE failed: %s\\n\", strerror(errno));\n" out) - (display " exit(127);\n" out) - (display " }\n" out) - (display "}\n" out) - (display "#else\n" out) - (display "static void maybe_enable_shadow_stack(void) { }\n" out) - (display "#endif\n\n" out) + (display "#include \"jmcp_program.h\"\n" out) + (display "#include \"hardening-report.h\"\n\n" out) (when static? (display "void *dlopen(const char *f, int flags) { (void)flags; return f ? NULL : (void*)1; }\n" out) (display "void *dlsym(void *h, const char *s) { (void)h; (void)s; return NULL; }\n" out) @@ -247,7 +226,11 @@ cat > "$builder" <<'SCHEME' (display " return 0;\n" out) (display "}\n\n" out) (display "int main(int argc, char *argv[]) {\n" out) - (display " maybe_enable_shadow_stack();\n" out) + (display " if (jerboa_security_report_requested()) {\n" out) + (display " jerboa_print_security_report(\"jmcp\");\n" out) + (display " return 0;\n" out) + (display " }\n" out) + (display " jerboa_maybe_enable_shadow_stack(\"jmcp\");\n" out) (display " setenv(\"JERBOA_STATIC\", \"1\", 1);\n" out) (display " char prog_path[1024];\n" out) (display " if (write_program(prog_path, sizeof(prog_path)) != 0) return 1;\n" out) @@ -328,31 +311,33 @@ cat > "$builder" <<'SCHEME' (loop (read in) #f)])))) 'replace)))) -(define (default-link-command cc target-os machine static? chez-dir output main-c) +(define (default-link-command cc target-os machine static? chez-dir support-dir output main-c) (let* ([ccq (or cc "cc")] [outq (shell-quote output)] [mainq (shell-quote main-c)] [chezq (shell-quote chez-dir)] + [supportq (shell-quote support-dir)] [harden-cflags (target-hardening-cflags target-os machine)] [harden-ldflags (target-hardening-ldflags target-os machine)]) (cond [(and static? (string=? target-os "linux")) - (format "~a -O2~a -static -Wl,--export-dynamic -I~a -o ~a ~a ~a/libkernel.a ~a/libz.a ~a/liblz4.a~a -Wl,--defsym=_dl_find_object=0 -lm -ldl -lpthread" - ccq harden-cflags chezq outq mainq chezq chezq chezq harden-ldflags)] + (format "~a -O2~a -static -Wl,--export-dynamic -I~a -I~a -o ~a ~a ~a/libkernel.a ~a/libz.a ~a/liblz4.a~a -Wl,--defsym=_dl_find_object=0 -lm -ldl -lpthread" + ccq harden-cflags supportq chezq outq mainq chezq chezq chezq harden-ldflags)] [(string=? target-os "freebsd") - (format "~a -O2~a -I~a -o ~a ~a ~a/libkernel.a ~a/libz.a ~a/liblz4.a~a -lm -lthr -lutil -lncurses" - ccq harden-cflags chezq outq mainq chezq chezq chezq harden-ldflags)] + (format "~a -O2~a -I~a -I~a -o ~a ~a ~a/libkernel.a ~a/libz.a ~a/liblz4.a~a -lm -lthr -lutil -lncurses" + ccq harden-cflags supportq chezq outq mainq chezq chezq chezq harden-ldflags)] [(string=? target-os "macos") - (format "~a -O2 -I~a -o ~a ~a ~a/libkernel.a ~a/libz.a ~a/liblz4.a -lm -lpthread -lncurses -liconv" - ccq chezq outq mainq chezq chezq chezq)] + (format "~a -O2 -I~a -I~a -o ~a ~a ~a/libkernel.a ~a/libz.a ~a/liblz4.a -lm -lpthread -lncurses -liconv" + ccq supportq chezq outq mainq chezq chezq chezq)] [else - (format "~a -O2~a -I~a -o ~a ~a -L~a -lkernel -llz4 -lz~a -lm -ldl -lpthread -lncurses" - ccq harden-cflags chezq outq mainq chezq harden-ldflags)]))) + (format "~a -O2~a -I~a -I~a -o ~a ~a -L~a -lkernel -llz4 -lz~a -lm -ldl -lpthread -lncurses" + ccq harden-cflags supportq chezq outq mainq chezq harden-ldflags)]))) (define repo-dir (current-directory)) (define entry (env "JMCP_ENTRY" (format "~a/bin/jerboa-mcp.ss" repo-dir))) (define output (env "JMCP_OUTPUT" "jmcp")) (define build-dir (env "JMCP_BUILD_DIR" (format "~a/.build/~a" repo-dir output))) +(define support-dir (format "~a/support" repo-dir)) (define cross-prefix (getenv "JMCP_CROSS_PREFIX")) (define xpatch (getenv "JMCP_XPATCH")) (define target-os (env "JMCP_TARGET_OS" @@ -437,7 +422,7 @@ cat > "$builder" <<'SCHEME' (printf "==> link~n") (let* ([cc (env "CC" "cc")] [cmd (or (getenv "JMCP_LINK_COMMAND") - (default-link-command cc target-os machine static? chez-dir output main-c))]) + (default-link-command cc target-os machine static? chez-dir support-dir output main-c))]) (run cmd)) (printf "~n=== jmcp binary ready: ~a ===~n" output) --- a/support/build-binary.sh +++ b/support/build-binary.sh @@ -289,38 +289,7 @@ cat > "${OUTPUT}-main.c" <<'CMAIN' #ifdef JERBOA_HAS_TYPED_SYMBOLS #include "typed_symbols.h" #endif - -#if defined(__linux__) && defined(__x86_64__) -#ifndef ARCH_SHSTK_ENABLE -#define ARCH_SHSTK_ENABLE 0x5001 -#endif -#ifndef ARCH_SHSTK_SHSTK -#define ARCH_SHSTK_SHSTK (1ULL << 0) -#endif - -static int env_flag_enabled(const char *v) { - return v && *v && - strcmp(v, "0") != 0 && - strcmp(v, "false") != 0 && - strcmp(v, "FALSE") != 0 && - strcmp(v, "no") != 0 && - strcmp(v, "NO") != 0 && - strcmp(v, "off") != 0 && - strcmp(v, "OFF") != 0; -} - -static void maybe_enable_shadow_stack(void) { - const char *v = getenv("JERBOA_ENABLE_SHSTK"); - if (!env_flag_enabled(v)) return; - if (syscall(SYS_arch_prctl, ARCH_SHSTK_ENABLE, ARCH_SHSTK_SHSTK) != 0) { - if (strcmp(v, "try") == 0) return; - fprintf(stderr, "jerboa: ARCH_SHSTK_ENABLE failed: %s\n", strerror(errno)); - exit(127); - } -} -#else -static void maybe_enable_shadow_stack(void) { } -#endif +#include "hardening-report.h" static const char *write_program_tmpfile(void) { static char path[] = "/tmp/jerboa-prog-XXXXXX"; @@ -335,7 +304,12 @@ static const char *write_program_tmpfile(void) { } int main(int argc, const char *argv[]) { - maybe_enable_shadow_stack(); + if (jerboa_security_report_requested()) { + jerboa_print_security_report("jerboa"); + return 0; + } + + jerboa_maybe_enable_shadow_stack("jerboa"); #ifdef JERBOA_BINARY_STATIC_ENV setenv("JERBOA_STATIC", "1", 1); @@ -395,7 +369,7 @@ if [ -n "${JERBOA_TYPED_RUST_ARCHIVE:-}" ]; then fi # shellcheck disable=SC2086 -$CC -I. -I"$CSV_DIR" -O2 $HARDEN_CFLAGS $STATIC_CFLAGS $TYPED_SYMBOLS_CFLAGS \ +$CC -I. -I"$JERBOA_HOME/support" -I"$CSV_DIR" -O2 $HARDEN_CFLAGS $STATIC_CFLAGS $TYPED_SYMBOLS_CFLAGS \ -o "$OUTPUT" \ "${OUTPUT}-main.c" \ "$CSV_DIR/libkernel.a" \ --- a/support/build-jerbuild.sh +++ b/support/build-jerbuild.sh @@ -281,38 +281,7 @@ cat > "${OUTPUT}-main.c" <<'CMAIN' #include "program_boot.h" #include "bundle_tar.h" #include "bundle_meta.h" - -#if defined(__linux__) && defined(__x86_64__) -#ifndef ARCH_SHSTK_ENABLE -#define ARCH_SHSTK_ENABLE 0x5001 -#endif -#ifndef ARCH_SHSTK_SHSTK -#define ARCH_SHSTK_SHSTK (1ULL << 0) -#endif - -static int env_flag_enabled(const char *v) { - return v && *v && - strcmp(v, "0") != 0 && - strcmp(v, "false") != 0 && - strcmp(v, "FALSE") != 0 && - strcmp(v, "no") != 0 && - strcmp(v, "NO") != 0 && - strcmp(v, "off") != 0 && - strcmp(v, "OFF") != 0; -} - -static void maybe_enable_shadow_stack(void) { - const char *v = getenv("JERBOA_ENABLE_SHSTK"); - if (!env_flag_enabled(v)) return; - if (syscall(SYS_arch_prctl, ARCH_SHSTK_ENABLE, ARCH_SHSTK_SHSTK) != 0) { - if (strcmp(v, "try") == 0) return; - fprintf(stderr, "jerbuild: ARCH_SHSTK_ENABLE failed: %s\n", strerror(errno)); - exit(127); - } -} -#else -static void maybe_enable_shadow_stack(void) { } -#endif +#include "hardening-report.h" /* Cache layout: * $XDG_CACHE_HOME/jerbuild/<sha256>/ (or ~/.cache/jerbuild/<sha256>/) @@ -424,7 +393,12 @@ static const char *write_program_tmpfile(void) { } int main(int argc, const char *argv[]) { - maybe_enable_shadow_stack(); + if (jerboa_security_report_requested()) { + jerboa_print_security_report("jerbuild"); + return 0; + } + + jerboa_maybe_enable_shadow_stack("jerbuild"); /* Fast paths that avoid booting Chez. */ if (argc == 2 && strcmp(argv[1], "--jerboa-home") == 0) { @@ -500,7 +474,7 @@ for a in liblz4.a libz.a; do done # shellcheck disable=SC2086 -$CC -I. -I"$CSV_DIR" -O2 $HARDEN_CFLAGS \ +$CC -I. -I"$JERBOA_HOME/support" -I"$CSV_DIR" -O2 $HARDEN_CFLAGS \ -o "$OUTPUT" \ "${OUTPUT}-main.c" \ "$CSV_DIR/libkernel.a" \ new file mode 100644 --- /dev/null +++ b/support/hardening-report.h @@ -0,0 +1,134 @@ +/* Shared launcher hardening helpers for generated Jerboa/Chez binaries. + * + * Normal startup behavior is unchanged unless one of the documented + * environment variables is set: + * JERBOA_ENABLE_SHSTK=1|try opt in to Linux x86_64 shadow stacks + * JERBOA_SECURITY_REPORT=1 print build/runtime hardening state and exit + */ +#ifndef JERBOA_HARDENING_REPORT_H +#define JERBOA_HARDENING_REPORT_H + +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#if defined(__linux__) && defined(__x86_64__) +#include <sys/syscall.h> +#include <unistd.h> +#endif + +#if defined(__linux__) && defined(__x86_64__) +#ifndef ARCH_SHSTK_ENABLE +#define ARCH_SHSTK_ENABLE 0x5001 +#endif +#ifndef ARCH_SHSTK_STATUS +#define ARCH_SHSTK_STATUS 0x5005 +#endif +#ifndef ARCH_SHSTK_SHSTK +#define ARCH_SHSTK_SHSTK (1ULL << 0) +#endif +#endif + +static int jerboa_env_flag_enabled(const char *v) { + return v && *v && + strcmp(v, "0") != 0 && + strcmp(v, "false") != 0 && + strcmp(v, "False") != 0 && + strcmp(v, "FALSE") != 0 && + strcmp(v, "no") != 0 && + strcmp(v, "No") != 0 && + strcmp(v, "NO") != 0 && + strcmp(v, "off") != 0 && + strcmp(v, "Off") != 0 && + strcmp(v, "OFF") != 0; +} + +static int jerboa_security_report_requested(void) { + return jerboa_env_flag_enabled(getenv("JERBOA_SECURITY_REPORT")); +} + +static void jerboa_maybe_enable_shadow_stack(const char *program_name) { +#if defined(__linux__) && defined(__x86_64__) + const char *v = getenv("JERBOA_ENABLE_SHSTK"); + if (!jerboa_env_flag_enabled(v)) return; + if (syscall(SYS_arch_prctl, ARCH_SHSTK_ENABLE, ARCH_SHSTK_SHSTK) != 0) { + if (strcmp(v, "try") == 0) return; + fprintf(stderr, "%s: ARCH_SHSTK_ENABLE failed: %s\n", + program_name ? program_name : "jerboa", strerror(errno)); + exit(127); + } +#else + (void)program_name; +#endif +} + +static void jerboa_print_security_report(const char *program_name) { + const char *request = getenv("JERBOA_ENABLE_SHSTK"); + + printf("program: %s\n", program_name ? program_name : "jerboa"); + +#if defined(__linux__) + printf("os: linux\n"); +#elif defined(__APPLE__) + printf("os: macos\n"); +#elif defined(__FreeBSD__) + printf("os: freebsd\n"); +#elif defined(__OpenBSD__) + printf("os: openbsd\n"); +#elif defined(__NetBSD__) + printf("os: netbsd\n"); +#else + printf("os: unknown\n"); +#endif + +#if defined(__x86_64__) + printf("arch: x86_64\n"); +#elif defined(__aarch64__) + printf("arch: arm64\n"); +#elif defined(__arm64__) + printf("arch: arm64\n"); +#else + printf("arch: unknown\n"); +#endif + +#if defined(__CET__) + printf("cet-compiled: yes (__CET__=%d)\n", __CET__); +#else + printf("cet-compiled: no\n"); +#endif + +#if defined(__ARM_FEATURE_BTI_DEFAULT) + printf("arm-bti-compiled: yes\n"); +#else + printf("arm-bti-compiled: no\n"); +#endif + +#if defined(__ARM_FEATURE_PAC_DEFAULT) + printf("arm-pac-compiled: yes (__ARM_FEATURE_PAC_DEFAULT=%d)\n", + __ARM_FEATURE_PAC_DEFAULT); +#else + printf("arm-pac-compiled: no\n"); +#endif + +#if defined(__linux__) && defined(__x86_64__) + { + unsigned long features = 0; + errno = 0; + if (syscall(SYS_arch_prctl, ARCH_SHSTK_STATUS, &features) == 0) { + printf("shstk-runtime: %s (mask=0x%lx)\n", + (features & ARCH_SHSTK_SHSTK) ? "enabled" : "disabled", + features); + } else { + printf("shstk-runtime: unavailable (errno=%d %s)\n", + errno, strerror(errno)); + } + } +#else + printf("shstk-runtime: not-applicable\n"); +#endif + + printf("shstk-request: %s\n", request && *request ? request : "<unset>"); +} + +#endif --- a/support/multicall-main.c +++ b/support/multicall-main.c @@ -47,38 +47,7 @@ #include "program_boot.h" #include "bundle_tar.h" #include "bundle_meta.h" - -#if defined(__linux__) && defined(__x86_64__) -#ifndef ARCH_SHSTK_ENABLE -#define ARCH_SHSTK_ENABLE 0x5001 -#endif -#ifndef ARCH_SHSTK_SHSTK -#define ARCH_SHSTK_SHSTK (1ULL << 0) -#endif - -static int env_flag_enabled(const char *v) { - return v && *v && - strcmp(v, "0") != 0 && - strcmp(v, "false") != 0 && - strcmp(v, "FALSE") != 0 && - strcmp(v, "no") != 0 && - strcmp(v, "NO") != 0 && - strcmp(v, "off") != 0 && - strcmp(v, "OFF") != 0; -} - -static void maybe_enable_shadow_stack(void) { - const char *v = getenv("JERBOA_ENABLE_SHSTK"); - if (!env_flag_enabled(v)) return; - if (syscall(SYS_arch_prctl, ARCH_SHSTK_ENABLE, ARCH_SHSTK_SHSTK) != 0) { - if (strcmp(v, "try") == 0) return; - fprintf(stderr, "jerboa: ARCH_SHSTK_ENABLE failed: %s\n", strerror(errno)); - exit(127); - } -} -#else -static void maybe_enable_shadow_stack(void) { } -#endif +#include "hardening-report.h" /* ── jerbuild lib bundle: extracted to a per-sha cache dir on demand ─────── * Layout: $XDG_CACHE_HOME/jerbuild/<sha256>/ (or ~/.cache/jerbuild/<sha256>/) @@ -310,13 +279,18 @@ int main(int argc, const char *argv[]) { if (!strcmp(mode, "lsp")) mode = "jlsp"; if (!strcmp(mode, "pkg")) mode = "jpkg"; + if (jerboa_security_report_requested()) { + jerboa_print_security_report(mode); + return 0; + } + /* Clean runtime mode: `jerboa runtime [script [args...]]` boots the * language runtime WITHOUT the embedded program image. Used by * `jerbuild binary` on a target host to run the WPO compile in a fresh * process with an empty library table, so bundled stdlib sources recompile * to native .wpo (cross-built .wpo carry the build host's machine-type). */ if (!strcmp(name, "jerboa") && argc > 1 && !strcmp(argv[1], "runtime")) { - maybe_enable_shadow_stack(); + jerboa_maybe_enable_shadow_stack("jerboa"); Sscheme_init(NULL); Sregister_boot_file_bytes("petite", (void *)petite_boot_data, petite_boot_size); Sregister_boot_file_bytes("scheme", (void *)scheme_boot_data, scheme_boot_size); @@ -353,7 +327,7 @@ int main(int argc, const char *argv[]) { } setenv("JERBOA_MULTICALL_NAME", mode, 1); - maybe_enable_shadow_stack(); + jerboa_maybe_enable_shadow_stack(mode); Sscheme_init(NULL); Sregister_boot_file_bytes("petite", (void *)petite_boot_data, petite_boot_size); Sregister_boot_file_bytes("scheme", (void *)scheme_boot_data, scheme_boot_size);