Security hardening and release readiness
ober
8121ef45c9e1da402fc00df7d2395e3b17c22f7a
new file mode 100644 --- /dev/null +++ b/.build.yml @@ -0,0 +1,26 @@ +image: debian/stable +packages: + - build-essential + - ca-certificates + - cargo + - curl + - git + - pkg-config + - ripgrep + - rustc +environment: + JERBOA_VERSION: v0.2.3 +sources: + - https://git.sr.ht/~lisp/jerboa-coreutils +tasks: + - install-jerbuild: | + curl -fsSL "https://github.com/jerboa-lang/jerboa/releases/download/${JERBOA_VERSION}/jerbuild-linux-x86_64" -o "$HOME/jerbuild" + chmod +x "$HOME/jerbuild" + - install-cargo-audit: | + cargo install cargo-audit --locked + - verify: | + cd jerboa-coreutils + JERBUILD="$HOME/jerbuild" make verify + - release-evidence: | + cd jerboa-coreutils + JERBUILD="$HOME/jerbuild" make release-evidence new file mode 100644 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,39 @@ +name: CI + +on: + push: + branches: [main, master] + pull_request: + workflow_dispatch: + +permissions: + contents: read + +jobs: + verify: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Install build tools + run: sudo apt-get update && sudo apt-get install -y build-essential curl ca-certificates git ripgrep pkg-config + + - name: Install Rust + uses: dtolnay/rust-toolchain@stable + + - name: Install cargo-audit + run: cargo install cargo-audit --locked + + - name: Install jerbuild + run: | + set -eux + curl -fsSL "https://github.com/jerboa-lang/jerboa/releases/download/${JERBOA_VERSION}/jerbuild-linux-x86_64" -o /usr/local/bin/jerbuild + chmod +x /usr/local/bin/jerbuild + env: + JERBOA_VERSION: v0.2.3 + + - name: Verify + run: JERBUILD=/usr/local/bin/jerbuild make verify + + - name: Release evidence + run: JERBUILD=/usr/local/bin/jerbuild make release-evidence --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,7 @@ # jerbuild whole-program build scratch .build/ +dist/ # Native binary + per-applet symlinks (rebuilt with `make binary`) /jerboa-coreutils new file mode 100644 --- /dev/null +++ b/.jerboa/security.json @@ -0,0 +1,61 @@ +{ + "version": 1, + "repo": "jerboa-coreutils", + "extends": ["jerboa:cli", "jerboa:ffi", "jerboa:filesystem", "jerboa:process-tools"], + "classification": "multi-call local coreutils implementation with filesystem, process, terminal, checksum, and optional grep regex surfaces", + "license": "Apache-2.0", + "paths": { + "production": ["main-binary.ss", "src/**/*.ss", "lib/**/*.sls", "support/*.c", "support/*.list", ".jerbuild", "Makefile"], + "tests": ["tests/**", "support/import-check.ss", "tools/**"], + "generated": ["dist/**", ".build/**", "bin/**", "jerboa-coreutils", "jerboa-coreutils-*", "*.so", "*.dylib", "*.wpo", "*.o", "*.a", "vendor/**/target/**"], + "vendor": ["vendor/jerboa-native-rs/**"], + "docs": ["README.md", "SECURITY.md", "docs/**", "*.md"] + }, + "capabilities": { + "network": "none expected in project source", + "ffi": "coreutils C shim, libc compatibility calls, PCRE2 runtime loading for grep, and jerboa-native-rs crypto for checksum applets", + "filesystemRead": "coreutils applets intentionally read caller-selected paths", + "filesystemWrite": "file-modifying applets intentionally write/delete caller-selected paths", + "shell": "process applets intentionally execute requested commands; checked wrappers validate and audit caller-requested command strings; fixed host-tool fallbacks are compatibility boundaries", + "kernelSandbox": "not claimed; non-enforcing std seccomp/Landlock stubs are not used as production controls", + "hostileInput": "command-line arguments, file paths, file contents, environment, terminal state, regex patterns" + }, + "policy": { + "failOn": ["critical", "high", "medium"], + "imports": { "directChezscheme": "deny in source, entry, support checks, and tools" }, + "ffi": { "allowedOnlyIn": ["src/jerboa-coreutils/**/*.ss", "support/libcoreutils.c", ".jerbuild"] }, + "process": { "allowedOnlyIn": ["src/jerboa-coreutils/**/*.ss", "Makefile", "tests/**"] }, + "network": { "allowed": false }, + "eval": { "stringEval": "deny", "bareRead": "deny", "allowReadEval": false } + }, + "releaseGates": [ + "make security", + "make test", + "make verify", + "make focused-security-tests", + "make sbom", + "make reproducibility-report", + "make target-evidence", + "make release-evidence" + ], + "targetEvidence": { + "script": "scripts/target-evidence.sh", + "optionalProofFileEnv": "JCOREUTILS_TARGET_PROOF_FILE", + "requiredProofEnv": "JCOREUTILS_REQUIRE_TARGET_PROOF", + "maxProofBytes": 65536, + "targetProofStatus": "target_coreutils_proof_status", + "productionStatus": "production_coreutils_status", + "sensitiveArtifactPolicy": "no-file-content-private-paths-host-data-or-command-output-captures", + "requiredMarkers": [ + "release_host_sbom_reproducibility_status=reviewed", + "pre_publication_history_secret_scan_status=reviewed", + "native_crate_rustsec_status=reviewed", + "external_ffi_review_status=reviewed", + "filesystem_process_review_status=reviewed", + "pcre2_target_linkage_status=reviewed", + "cross_platform_smoke_status=reviewed", + "sensitive_artifact_policy=no-file-content-private-paths-host-data-or-command-output-captures" + ] + }, + "suppressions": [] +} new file mode 100644 --- /dev/null +++ b/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. --- a/Makefile +++ b/Makefile @@ -10,19 +10,38 @@ # separately built Chez. Cross targets additionally need the per-target cross # Chez (built once in $(JERBOA_HOME)) and the matching C cross toolchain. -JERBOA_HOME ?= $(HOME)/mine/jerboa -JERBUILD ?= jerbuild +JERBOA_HOME ?= $(CURDIR)/../jerboa +JERBOA_VERSION ?= v0.2.3 +JERBOA_TOOL_DIR ?= $(CURDIR)/../jerboa/dist +ifeq ($(origin JERBUILD),undefined) +ifneq ($(wildcard $(JERBOA_TOOL_DIR)/jerbuild),) +JERBUILD := $(JERBOA_TOOL_DIR)/jerbuild +else +JERBUILD := jerbuild +endif +endif # Bundled stdlib (used by the host `exec`/test helpers). JH := $(shell $(JERBUILD) --jerboa-home 2>/dev/null) +ifeq ($(JH),) +$(error jerbuild not found on PATH (or '$(JERBUILD) --jerboa-home' failed). Install jerbuild, or set JERBUILD=/path/to/jerbuild) +endif BIN := jerboa-coreutils BIN_DIR := $(HOME)/.local/bin +EVIDENCE_DIR ?= dist/release-evidence +SBOM_DIR ?= dist/sbom +REPRO_DIR ?= dist/reproducibility +TARGET_EVIDENCE_DIR ?= dist/target-evidence +LIBDIRS := lib:$(JH)/lib # Vendored Rust crate (crypto FFI for the checksum applets), linked by jerbuild -# per .jerbuild. Sparse-cloned from the jerboa monorepo by `make vendor-deps`. -NATIVE_DIR := vendor/jerboa-native-rs +# per .jerbuild. `make vendor-deps` refreshes it from the adjacent patched +# Jerboa checkout when present; otherwise it sparse-clones from the remote. +LOCAL_JERBOA_NATIVE_DIR ?= $(CURDIR)/../jerboa/jerboa-native-rs +NATIVE_DIR ?= vendor/jerboa-native-rs JERBOA_REMOTE ?= https://git.sr.ht/~lisp/jerboa +CARGO_AUDIT ?= cargo audit # Multi-call applet names. The binary dispatches on argv[0]; these are the # symlink names created next to $(BIN) by `make symlinks`. @@ -68,7 +87,7 @@ FREEBSD_CC_ENV := CC_x86_64_unknown_freebsd="$(FREEBSD_AMD64_CC)" \ AR_x86_64_unknown_freebsd="$(LLVM_AR)" \ CARGO_TARGET_X86_64_UNKNOWN_FREEBSD_LINKER="$(FREEBSD_AMD64_CC)" -.PHONY: all help gen binary symlinks test clean vendor-deps vendor-clean \ +.PHONY: all help gen binary symlinks test import-check focused-security-tests security audit verify sbom reproducibility-report target-evidence release-evidence clean clean-generated vendor-deps vendor-clean \ linux linux-amd64 linux-arm64 freebsd freebsd-amd64 \ cross-check-amd64 cross-check-arm64 cross-check-freebsd @@ -82,36 +101,53 @@ help: @echo " make gen Transpile src/ .ss -> lib/ .sls (jerbuild transpile)" @echo " make binary Build native ./$(BIN) for this host (default applet build)" @echo " make symlinks Create per-applet symlinks (cat, ls, ...) next to ./$(BIN)" + @echo " make security Static release/security checks" + @echo " make verify Security, audit, import check, and smoke tests" + @echo " make release-evidence Generate dist/release-evidence" @echo " make linux-amd64 Cross-build $(BIN)-linux-amd64 (static musl)" @echo " make linux-arm64 Cross-build $(BIN)-linux-arm64 (static musl)" @echo " make freebsd-amd64 Cross-build $(BIN)-freebsd-amd64" @echo " make test Build + smoke-test core applets" + @echo " make focused-security-tests Run symlink/process argument security smokes" @echo " make install Install ./$(BIN) + applet symlinks to $(BIN_DIR)" @echo " make clean Remove build artifacts" @echo "" @echo " Applets ($(words $(UTILS))): dispatched by argv[0] basename." # Sparse-clone just the jerboa-native-rs crate from the jerboa monorepo so a -# fresh checkout populates vendor/. No-op once vendor/jerboa-native-rs exists. -vendor-deps: $(NATIVE_DIR) -$(NATIVE_DIR): - @mkdir -p vendor - @echo "=== Sparse-cloning jerboa-native-rs from $(JERBOA_REMOTE) ===" - @git clone --depth 1 --filter=blob:none --sparse $(JERBOA_REMOTE) vendor/.jerboa-monorepo - @git -C vendor/.jerboa-monorepo sparse-checkout set jerboa-native-rs - @mv vendor/.jerboa-monorepo/jerboa-native-rs $(NATIVE_DIR) - @rm -rf vendor/.jerboa-monorepo +# fresh checkout populates vendor/. In this monorepo workspace, prefer the +# adjacent patched checkout so release audit and binary inputs use the same +# RustSec-clean lockfile. +vendor-deps: + @if [ "$(NATIVE_DIR)" != "vendor/jerboa-native-rs" ]; then \ + test -f "$(NATIVE_DIR)/Cargo.toml" || { echo "ERROR: NATIVE_DIR missing Cargo.toml: $(NATIVE_DIR)" >&2; exit 1; }; \ + elif [ -f "$(LOCAL_JERBOA_NATIVE_DIR)/Cargo.toml" ]; then \ + mkdir -p vendor; \ + echo "=== Syncing patched jerboa-native-rs from $(LOCAL_JERBOA_NATIVE_DIR) ==="; \ + rm -rf "$(NATIVE_DIR)"; \ + mkdir -p "$(NATIVE_DIR)"; \ + rsync -a --delete --exclude target/ "$(LOCAL_JERBOA_NATIVE_DIR)/" "$(NATIVE_DIR)/"; \ + elif [ ! -f "$(NATIVE_DIR)/Cargo.toml" ]; then \ + mkdir -p vendor; \ + echo "=== Sparse-cloning jerboa-native-rs from $(JERBOA_REMOTE) ==="; \ + rm -rf vendor/.jerboa-monorepo; \ + git clone --depth 1 --filter=blob:none --sparse $(JERBOA_REMOTE) vendor/.jerboa-monorepo; \ + git -C vendor/.jerboa-monorepo sparse-checkout set jerboa-native-rs; \ + mv vendor/.jerboa-monorepo/jerboa-native-rs "$(NATIVE_DIR)"; \ + rm -rf vendor/.jerboa-monorepo; \ + fi + @test -f "$(NATIVE_DIR)/Cargo.toml" vendor-clean: rm -rf vendor/.jerboa-monorepo # Transpile Jerboa source to R6RS libraries jerbuild can whole-program compile. gen: vendor-deps - $(JERBUILD) transpile src lib + @$(JERBUILD) transpile src lib # Native multi-call binary for the host OS/arch. Reads .jerbuild. binary: gen - $(JERBUILD) build + @$(JERBUILD) build @$(MAKE) --no-print-directory symlinks # Per-applet symlinks in bin/ pointing at the host ./$(BIN). Invoke applets as @@ -198,7 +234,84 @@ test: binary @printf 'apple\nbanana\n' | bin/grep an | grep -q banana && echo "PASS: grep" @echo "=== ok ===" +focused-security-tests: binary scripts/focused-security-tests.sh + @mkdir -p dist/focused-security-tests + @REPO_ROOT="$(CURDIR)" sh scripts/focused-security-tests.sh > dist/focused-security-tests/result.txt + @cat dist/focused-security-tests/result.txt + +import-check: gen + @$(JERBUILD) exec --libdirs "$(LIBDIRS)" support/import-check.ss + +security: clean-generated scripts/security-check.sh + scripts/security-check.sh + +audit: security vendor-deps + @echo "==> jerbuild" + @$(JERBUILD) --version + @echo "==> C compiler" + @cc --version 2>/dev/null | head -1 || cc -v 2>&1 | head -1 + @echo "==> Rust crypto crate" + @cargo metadata --format-version 1 --manifest-path "$(NATIVE_DIR)/Cargo.toml" >/dev/null + @if ! $(CARGO_AUDIT) --version >/dev/null 2>&1; then \ + echo "ERROR: cargo-audit is required for release evidence. Install with: cargo install cargo-audit --locked" >&2; \ + exit 1; \ + fi + @if [ -f "$(NATIVE_DIR)/Cargo.lock" ]; then \ + $(CARGO_AUDIT) --file "$(NATIVE_DIR)/Cargo.lock" -D warnings; \ + else \ + (cd "$(NATIVE_DIR)" && $(CARGO_AUDIT) -D warnings); \ + fi + +verify: security audit import-check test focused-security-tests target-evidence + +sbom: vendor-deps scripts/sbom.sh + @REPO_ROOT="$(CURDIR)" SBOM_DIR="$(SBOM_DIR)" JERBUILD="$(JERBUILD)" JERBOA_VERSION="$(JERBOA_VERSION)" sh scripts/sbom.sh + +reproducibility-report: vendor-deps scripts/reproducibility-report.sh + @REPO_ROOT="$(CURDIR)" REPRO_DIR="$(REPRO_DIR)" MAKE="$(MAKE)" sh scripts/reproducibility-report.sh + +target-evidence: scripts/target-evidence.sh + @REPO_ROOT="$(CURDIR)" TARGET_EVIDENCE_DIR="$(TARGET_EVIDENCE_DIR)" sh scripts/target-evidence.sh + +release-evidence: verify + $(MAKE) reproducibility-report + $(MAKE) sbom + rm -rf $(EVIDENCE_DIR) + mkdir -p $(EVIDENCE_DIR) + { git rev-parse HEAD 2>/dev/null || true; } > $(EVIDENCE_DIR)/git-commit.txt + git status --short > $(EVIDENCE_DIR)/git-status.txt + git diff --stat > $(EVIDENCE_DIR)/diff-stat.txt + { printf 'JERBOA_VERSION=%s\n' '$(JERBOA_VERSION)'; printf 'jerbuild_version='; $(JERBUILD) --version; if [ -n '$(JH)' ]; then printf 'jerboa_home_status=present\n'; else printf 'jerboa_home_status=missing\n'; fi; printf 'platform='; uname -srm; } > $(EVIDENCE_DIR)/build-env.txt + $(MAKE) security > $(EVIDENCE_DIR)/security.log 2>&1 + $(MAKE) audit > $(EVIDENCE_DIR)/audit.log 2>&1 + $(MAKE) import-check > $(EVIDENCE_DIR)/import-check.log 2>&1 + $(MAKE) focused-security-tests > $(EVIDENCE_DIR)/focused-security-tests.log 2>&1 + $(MAKE) test > $(EVIDENCE_DIR)/test.log 2>&1 + @if [ -f "$(NATIVE_DIR)/Cargo.toml" ]; then cargo metadata --format-version 1 --manifest-path "$(NATIVE_DIR)/Cargo.toml" > $(EVIDENCE_DIR)/cargo-metadata.json; fi + @if command -v otool >/dev/null 2>&1 && [ -f "$(BIN)" ]; then otool -L "$(BIN)" > $(EVIDENCE_DIR)/binary-linkage.txt; elif command -v ldd >/dev/null 2>&1 && [ -f "$(BIN)" ]; then ldd "$(BIN)" > $(EVIDENCE_DIR)/binary-linkage.txt; fi + @if [ -f "$(BIN)" ]; then shasum -a 256 "$(BIN)" > $(EVIDENCE_DIR)/binary-sha256.txt; fi + { find . \( -path './.git' -o -path './dist' -o -path './.build' -o -path './bin' -o -path './vendor/*/target' \) -prune -o -type f -not -name '$(BIN)' -not -name '$(BIN)-linux-amd64' -not -name '$(BIN)-linux-arm64' -not -name '$(BIN)-freebsd-amd64' -not -name '*.so' -not -name '*.dylib' -not -name '*.wpo' -not -name '*.o' -not -name '*.a' -print | LC_ALL=C sort | while IFS= read -r f; do shasum -a 256 "$$f"; done; } > $(EVIDENCE_DIR)/source-sha256.txt + rm -rf $(EVIDENCE_DIR)/sbom $(EVIDENCE_DIR)/reproducibility + cp -R $(SBOM_DIR) $(EVIDENCE_DIR)/sbom + cp -R $(REPRO_DIR) $(EVIDENCE_DIR)/reproducibility + cp -R $(TARGET_EVIDENCE_DIR) $(EVIDENCE_DIR)/target-evidence + cp -R dist/focused-security-tests $(EVIDENCE_DIR)/focused-security-tests + sh scripts/sanitize-evidence.sh "$(EVIDENCE_DIR)" + sh scripts/sanitize-evidence.sh "$(SBOM_DIR)" + sh scripts/sanitize-evidence.sh "$(REPRO_DIR)" + sh scripts/sanitize-evidence.sh "$(TARGET_EVIDENCE_DIR)" + test "$$(grep '^status=' "$(EVIDENCE_DIR)/reproducibility/report.txt" | cut -d= -f2)" = "match" + clean: rm -f $(BIN) $(BIN)-linux-amd64 $(BIN)-linux-arm64 $(BIN)-freebsd-amd64 + rm -rf bin .build dist + find . -maxdepth 1 -type l -exec sh -c 'for p do [ "$$(readlink "$$p")" = "$(BIN)" ] && rm -f "$$p"; done' sh {} + + rm -f support/libcoreutils.so + find lib -name '*.so' -o -name '*.wpo' -o -name '*.hash' | xargs rm -f 2>/dev/null || true + +clean-generated: + rm -f $(BIN) $(BIN)-linux-amd64 $(BIN)-linux-arm64 $(BIN)-freebsd-amd64 rm -rf bin .build + find . -maxdepth 1 -type l -exec sh -c 'for p do [ "$$(readlink "$$p")" = "$(BIN)" ] && rm -f "$$p"; done' sh {} + + rm -f support/libcoreutils.so find lib -name '*.so' -o -name '*.wpo' -o -name '*.hash' | xargs rm -f 2>/dev/null || true --- a/README.md +++ b/README.md @@ -1,8 +1,6 @@ # jerboa-coreutils -GNU coreutils implemented in [Jerboa](https://github.com/jafourni/jerboa) — Gerbil Scheme syntax and APIs running on stock Chez Scheme. - -This is a port of [gerbil-coreutils](https://github.com/jafourni/gerbil-coreutils) to the Jerboa platform, demonstrating that the same utility code works on an entirely different Scheme backend. +GNU coreutils implemented in [Jerboa](https://github.com/jafourni/jerboa) on stock Chez Scheme. ## Architecture @@ -56,7 +54,7 @@ jerboa-coreutils/ ├── src/jerboa-coreutils/ # 113 utilities — Jerboa SOURCE (.ss) │ ├── common.ss # Shared error handling, exit codes │ ├── common/io.ss # File/line processing utilities -│ ├── common/security.ss # seccomp/landlock/audit (graceful degrade) +│ ├── common/security.ss # Checked sinks, audit, taint, FFI helpers │ ├── grep/pcre2.ss # PCRE2 FFI bindings (lazy dlopen) │ └── true.ss ... grep.ss # Individual utilities ├── lib/jerboa-coreutils/ # GENERATED R6RS libraries (jerbuild transpile) @@ -97,16 +95,44 @@ jerboa-coreutils/ **Phase 19-24 - Advanced:** who, split, dircolors, install, shred, pinky, basenc, b2sum, sum, od, csplit, pr, chcon, runcon, ptx, cp, mv, rm, ls, dd, stty, stdbuf, dir, vdir -## How It Differs from gerbil-coreutils +## Release Gates + +```bash +make security +make verify +make sbom +make reproducibility-report +make target-evidence +make release-evidence +``` -| Aspect | gerbil-coreutils | jerboa-coreutils | -|--------|-----------------|------------------| -| Backend | Gerbil compiler + Gambit VM | Chez Scheme 10.x | -| FFI | `begin-ffi` with inline C | C shim library + `foreign-procedure` | -| Build | `gerbil build` | `make binary` (jerbuild: transpile + whole-program) | -| Format | `.ss` Gerbil modules | `.ss` Jerboa source → `.sls` (generated) | -| Getopt | 1-arg callback | 2-arg callback (cmd, hash) | +`make verify` runs static security checks, dependency/native audit metadata, +module import checks, focused symlink/process-argument security smokes, and the +multicall smoke test suite. `make release-evidence` writes +`dist/release-evidence`, including SBOM/toolchain data and repeated-build +reproducibility evidence. + +Production support is still blocked until `make target-evidence` records a +marker-complete proof through `JCOREUTILS_TARGET_PROOF_FILE`; production +release hosts should set `JCOREUTILS_REQUIRE_TARGET_PROOF=1` so missing or +incomplete proof fails closed. The proof is a summary of release-host SBOM and +reproducibility review, history secret review, native RustSec review, external +FFI review, filesystem/process review, PCRE2 target linkage, and cross-platform +smoke status. It must not contain raw command output, file contents, private +paths, hostnames, customer data, tokens, or keys. + +The current source scanner posture is clean at medium-or-higher severity. +Kernel sandboxing is not claimed; deployments that need seccomp or Landlock +must wrap the binary externally or add verified native enforcement. +Rust advisory status is a fail-closed release gate: `make audit` runs +`cargo audit --file <Cargo.lock> -D warnings` for the native crate, and missing +`cargo-audit` or any advisory finding fails release evidence. + +This repository intentionally has filesystem, process, terminal, and FFI +surfaces because it implements coreutils applets. The current production policy +is documented in `docs/threat-model.md`, `docs/ffi-boundary.md`, and +`docs/process-and-filesystem-policy.md`. ## License -Same as gerbil-coreutils. +Apache-2.0. See `LICENSE`. new file mode 100644 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,53 @@ +# Security Policy + +`jerboa-coreutils` is a local multicall implementation of coreutils. It +intentionally reads, writes, deletes, links, stats, and transforms +caller-selected files, and several applets intentionally execute +caller-requested processes. + +Production claims require the release gates tracked in `~/Release-plan.md` and +the repository production-readiness tracker to be complete for this repository. + +## Supported Status + +No public production-support commitment exists yet. Security-sensitive releases +must be cut from a clean checkout after: + +- `git status --short` shows only intentional release changes. +- `make security`, `make verify`, and `make release-evidence` pass. +- The working tree secret scan is clean, and history has been reviewed or + intentionally reset before first public release. +- Native and Rust dependency audit evidence is recorded. +- `jerboa_security_scan` reports no medium-or-higher findings for the source + set. +- SBOM/toolchain provenance, clean-build reproducibility comparison, and + external review are recorded or explicitly waived. +- `make target-evidence` records a reviewed target proof through + `JCOREUTILS_TARGET_PROOF_FILE`, or records the local blocked status. Set + `JCOREUTILS_REQUIRE_TARGET_PROOF=1` on production release hosts. + +## Security Expectations + +- No network capability in project source. +- No broad direct `(chezscheme)` imports in runtime, entry, support checks, or + tools. +- No Scheme reader evaluation on untrusted inputs. +- Native build artifacts must not be committed as source. +- PCRE2 is loaded only for `grep`. +- Process execution is limited to applets whose purpose requires it. +- Production claims must not rely on non-enforcing seccomp/Landlock stubs; use + an external sandbox or add verified native enforcement for deployments that + require kernel sandboxing. +- Finding or command output can include file contents, paths, users, process + data, and environment values; treat logs and release evidence accordingly. +- Target proof files must be marker-only summaries. Do not store raw command + output, file contents, private host paths, hostnames, customer data, tokens, + or keys in release evidence. +- Do not commit secrets, private keys, tokens, production `.env` files, + operational hostnames, customer/user data, or private infrastructure details. + +## Reporting + +Before public release, report issues privately to the repository owner. After +public release, replace this section with a dedicated advisory contact, +supported versions, and disclosure window. new file mode 100644 --- /dev/null +++ b/docs/ffi-boundary.md @@ -0,0 +1,28 @@ +# FFI Boundary + +The host binary links `support/libcoreutils.c` through `.jerbuild` and registers +the project shim symbols listed in `support/coreutils-ffi-symbols.list`. + +The checksum applets use the vendored `jerboa-native-rs` crate with the +`crypto` feature and no default features. + +`grep` loads PCRE2 lazily when regex matching is requested. `grep` is the only +applet expected to load PCRE2. + +Coreutils FFI definitions use `define-coreutils-foreign` from +`common/security.ss`. That wrapper resolves `foreign-procedure` bindings lazily +on first use and calls Jerboa's static-aware loader helper instead of invoking +`load-shared-object` during module initialization. + +Several compatibility applets still bind libc directly for portability, but +path-heavy or stateful project helpers live in `support/libcoreutils.c`. New +path FFI crossings must validate through `checked-path-value` and should prefer +NUL-free bytevector/length C shim entry points. Blocking native calls should be +marked `__collect_safe` where Chez permits it. + +Compatibility buffers in `support/libcoreutils.c` are caller-owned or +thread-local; process-global mutable result buffers are not part of the +supported release boundary. + +Native artifacts such as `support/libcoreutils.so`, `.so`, `.wpo`, `.a`, and +the built `jerboa-coreutils` binary are release outputs, not source files. new file mode 100644 --- /dev/null +++ b/docs/process-and-filesystem-policy.md @@ -0,0 +1,43 @@ +# Process And Filesystem Policy + +Coreutils applets intentionally expose filesystem and process operations. The +release posture is therefore deny-new-surface rather than deny-all-sinks. + +Allowed filesystem behavior: + +- read caller-selected files, +- write or delete caller-selected files for modifying applets, +- use `/dev/urandom` for random bytes, +- read `/proc`, `/sys`, and terminal metadata for system-information applets. + +Allowed process behavior: + +- process applets such as `env`, `chroot`, `nice`, `nohup`, and `timeout` + execute caller-requested commands, +- process-inspection applets may call fixed host tools as compatibility + fallbacks, +- process-signaling applets may send caller-requested signals. + +Current hardened posture: + +- file open/write/delete sinks use checked wrappers with path validation, + embedded-NUL rejection, taint unwrapping, and audit hooks, +- caller-requested process execution uses checked command wrappers before + crossing into the host process API, +- compatibility host-tool fallbacks are fixed-command boundaries, not + attacker-selected executable paths, +- known path-heavy FFI crossings validate paths before crossing into C, and + symlink reads use a bounded bytevector C shim. + +Kernel sandboxing is not claimed here. The repository no longer calls +non-enforcing std seccomp/Landlock stubs as production controls; deployments +that require kernel policy must use an external sandbox or a verified native +enforcement layer. + +Remaining release evidence work: + +- run marker-complete target proof on the release host for native audit, + reproducibility, PCRE2 linkage, filesystem/process review, cross-platform + smoke, history secret review, and external FFI review, +- keep target proof marker-only; do not preserve raw command output, file + contents, private paths, hostnames, customer data, or secrets. new file mode 100644 --- /dev/null +++ b/docs/release-evidence.md @@ -0,0 +1,71 @@ +# Release Evidence + +`make release-evidence` writes release evidence to `dist/release-evidence`. +The coreutils reproducibility report uses `make clean-generated` between +measured builds. That removes generated binaries, symlinks, native shims, and +compiled artifacts while leaving ignored `dist/` evidence in place; `dist/` is +excluded from source manifests. + +The bundle includes: + +- git commit, status, and diff summary, +- Jerboa toolchain metadata, +- security, audit, import-check, and test logs, +- focused symlink deletion and process-argument security smoke output, +- Cargo metadata for the vendored native crypto crate when present, +- `sbom/`, with source/release-input hashes, Jerboa/C/Rust/PCRE2 toolchain + metadata, Cargo metadata, and vendored native crate hashes, +- `reproducibility/report.txt`, comparing repeated binary, generated-library, + generated-input, normalized Jerbuild metadata, source-manifest, and applet + symlink manifests, +- `target-evidence/`, a local blocked-status record or a reviewed target proof + copied with a SHA-256 sidecar, +- binary linkage and SHA-256 digest, +- source-file SHA-256 digests. + +Review `git-status.txt` and `diff-stat.txt` before publishing. Native build +outputs and vendored `target/` directories are excluded from source hashes. +The audit log must include a fail-closed RustSec run for the native crate using +`cargo audit --file <Cargo.lock> -D warnings`. Missing `cargo-audit` or an +advisory finding fails release evidence. + +Release evidence must remain host-neutral. Use status fields such as +`jerboa_home_status=present` instead of private toolchain paths, and `uname +-srm` instead of full host-identifying platform banners. Do not archive raw +file contents, command output captures from target hosts, private paths, +hostnames, customer data, or secrets as production proof. + +## Target Production Proof + +`scripts/target-evidence.sh` writes `dist/target-evidence/status.txt`. +Without a proof file, local release evidence records: + +- `target_evidence_status: local-release-proof-recorded` +- `target_coreutils_proof_status: not-run` +- `release_host_sbom_reproducibility_status: blocked-not-run` +- `pre_publication_history_secret_scan_status: blocked-not-run` +- `native_crate_rustsec_status: blocked-not-run` +- `external_ffi_review_status: blocked-not-run` +- `filesystem_process_review_status: blocked-not-run` +- `pcre2_target_linkage_status: blocked-not-run` +- `cross_platform_smoke_status: blocked-not-run` +- `production_coreutils_status: blocked-not-run` + +To attach reviewed target evidence, set `JCOREUTILS_TARGET_PROOF_FILE` to a +file that contains these exact markers: + +```text +release_host_sbom_reproducibility_status=reviewed +pre_publication_history_secret_scan_status=reviewed +native_crate_rustsec_status=reviewed +external_ffi_review_status=reviewed +filesystem_process_review_status=reviewed +pcre2_target_linkage_status=reviewed +cross_platform_smoke_status=reviewed +sensitive_artifact_policy=no-file-content-private-paths-host-data-or-command-output-captures +``` + +Set `JCOREUTILS_REQUIRE_TARGET_PROOF=1` on production release hosts to fail +closed when the proof file is missing or incomplete. Proof files must be marker +summaries only; do not include file contents, command output captures, private +paths, hostnames, customer data, tokens, or keys. new file mode 100644 --- /dev/null +++ b/docs/threat-model.md @@ -0,0 +1,39 @@ +# Threat Model + +`jerboa-coreutils` is a local multi-call command-line binary. It intentionally +operates on caller-selected files, directories, processes, terminals, and +environment variables. + +## Assets + +- Caller files and directory trees. +- Process IDs and process-control operations. +- Terminal state. +- File contents printed to stdout/stderr. +- Checksum inputs and outputs. + +## Trust Boundaries + +- Command-line arguments and environment variables are untrusted. +- File contents and paths are untrusted. +- Regex patterns for `grep` are untrusted. +- FFI crosses into `support/libcoreutils.c`, libc compatibility calls, PCRE2, + and the vendored Jerboa native crypto crate. +- Process applets intentionally cross into child-process execution. + +## Security Requirements + +- No network capability in this repository. +- No broad direct `(chezscheme)` imports in source or tooling. +- No Scheme reader evaluation on untrusted inputs. +- C launcher writes must handle EINTR and short writes. +- Native build artifacts must not be committed as source. +- File open/write/delete and caller-requested process sinks route through + checked wrappers that reject embedded NULs and keep audit hooks visible. +- FFI bindings are resolved lazily instead of loading native objects during + module initialization; compatibility C buffers are thread-local. +- Kernel sandboxing is not claimed by this repository. Deployments requiring + seccomp or Landlock enforcement must wrap the binary externally or add and + verify a native enforcement layer. +- Focused traversal, symlink, process-argument, reproducibility, and external + review evidence remain first-public-release requirements. --- a/gaps.md +++ b/gaps.md @@ -28,10 +28,11 @@ splitting it into `-l` and `-a`. Similarly `-n3` is not parsed as `-n 3`. Only ~18 utilities register `--version`. The rest do not respond to it. -### ~~1.4 Compile warning from `std/sugar`~~ **FIXED** +### 1.4 Compile warning from bundled `std/sugar` -The `with-catch` arity warning is suppressed via the `%apply1` indirection in -`~/mine/jerboa/lib/std/sugar.sls`. No warning appears at runtime. +`jerbuild` currently emits a compile-time warning from the bundled +`std/sugar.sls` cache during whole-program builds. This warning is outside the +coreutils source tree and has not affected the local smoke tests. --- @@ -59,14 +60,16 @@ without the broken `(bytevector->hex digest)` wrapper. ## 3. FFI / Foreign Procedure Issues — ALL FIXED -### ~~3.1 Missing `load-shared-object` in `id` and `groups`~~ **FIXED** +### ~~3.1 Missing native symbol loading in `id` and `groups`~~ **FIXED** -Both now have `(define _load-ffi (begin (load-shared-object #f) (void)))`. +Coreutils FFI definitions now resolve lazily through +`define-coreutils-foreign`; applet modules no longer call `load-shared-object` +during initialization. ### ~~3.2 Broken `open-process-ports` usage in `readlink`~~ **FIXED** -`readlink` and `realpath` now use proper `let-values` destructuring for -`open-process-ports`' 4-value return. +`readlink` now uses the bounded `coreutils_readlink_bv` C shim. `realpath` +uses the direct project C shim instead of a subprocess parser. --- new file mode 100644 --- /dev/null +++ b/huihui.md @@ -0,0 +1,260 @@ +# Security Audit Report — jerboa-coreutils + +## Summary + +Audited 111 `.ss` source files in `src/jerboa-coreutils/`. + +**Key finding**: No critical vulnerabilities (code execution, arbitrary file writes) found. The codebase shows good security hygiene with CSPRNGs, audit logging infrastructure, and seccomp/landlock support. + +--- + +## I. Path Traversal & NUL Injection (HIGH) + +### Issue 1.1: NUL validation not universal +**Files**: `common.ss`, `io.ss`, `cp.ss`, `ls.ss`, `grep.ss` +**Severity**: Medium + +NUL byte checks are present in helpers (`path-within-base?`, `safe-path-join`) but not consistently applied to all FFI path arguments. + +```ss +;; common.ss:86-93 — good validation, but only used by helpers +(when (string-contains? path (string #\nul)) + (error 'path-within-base? "path contains NUL byte")) +``` + +**Risk**: Commands that directly pass paths to FFI (`chmod`, `chown`, etc.) may accept untrusted input with NUL bytes. + +**Fix**: Wrap FFI path arguments with a helper that checks for `#\nul`. + +--- + +### Issue 1.2: String concatenation without validation +**Files**: `cp.ss:54`, `ls.ss:202-209` + +```ss +;; cp.ss:54 +(string-append dst "/" (path-basename src)) + +;; ls.ss:202-209 +(string-append path "/" name) +``` + +**Risk**: If paths contain NUL bytes, they may bypass checks in FFI calls or escape validation. + +--- + +## II. Symlink & TOCTOU Vulnerabilities (MEDIUM) + +### Issue 2.1: Interactive overwrite creates TOCTOU window +**File**: `cp.ss:97-103` + +```ss +(when interactive + (unless (confirm-overwrite dst) + ...)) +``` +User input delay allows an attacker to replace the destination with a symlink. + +--- + +### Issue 2.2: Symlink escapes during recursive copy +**File**: `cp.ss:147-156` + +```ss +(when (and (eq? s-type 'symlink) + (let ((target (read-symlink s))) + (and target + (> (string-length target) 0) + (eqv? (string-ref target 0) #\/) + (not (path-within-base? target src))))) +``` +✅ Good check that warns when symlink points outside source tree. + +--- + +### Issue 2.3: Path stripping in ln may skip validation +**File**: `ln.ss` + +No explicit symlink escape check for hardlinks, though hardlinks cannot cross filesystems by default. + +--- + +## III. FFI/foreign-procedure Risks (MEDIUM) + +### Issue 3.1: Missing bounds checks in path arguments +**Files**: `cp.ss`, `rm.ss`, `mv.ss`, `ln.ss`, `chown.ss` + +FFI procs like: +```ss +(define ffi-unlink (foreign-procedure "unlink" (string) int)) +``` +have no explicit length limits on path strings; Chez passes C pointers directly. + +--- + +### Issue 3.2: Inconsistent FFI error propagation