Security hardening and release readiness
ober
90e3cdee478cd4c7a4373522604be91e2d0073ca
new file mode 100644 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,25 @@ +name: CI + +on: + push: + pull_request: + +jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Install packages + run: | + sudo apt-get update + sudo apt-get install -y build-essential curl ca-certificates pkg-config qt6-base-dev libgl1-mesa-dev + - name: Install Jerboa tools + run: ./support/ensure-jerboa.sh "${JERBOA_VERSION:-v0.2.3}" "$PWD/.jerboa/bin" + - name: Security + run: make security + - name: Test + run: make test + - name: Audit + run: make audit + - name: Release evidence + run: make release-evidence new file mode 100644 --- /dev/null +++ b/.github/workflows/security-baseline.yml @@ -0,0 +1,35 @@ +name: Security Baseline + +on: + push: + branches: [main, master] + pull_request: + workflow_dispatch: + +permissions: + contents: read + +jobs: + baseline: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Required release files + run: | + set -eu + test -f LICENSE + test -f SECURITY.md + test -f .gitignore + find . -maxdepth 1 -iname "README*" -type f | grep -q . + + - name: High-confidence secret scan + run: | + set -eu + pattern="(BEGIN (RSA|OPENSSH|EC|DSA|PRIVATE) KEY|ghp_[A-Za-z0-9_]{20,}|github_pat_[A-Za-z0-9_]{20,}|sk-(ant-api03|proj|svcacct)-[A-Za-z0-9_-]{30,}|AKIA[0-9A-Z]{16})" + matches="$(git grep -n -I -E "$pattern" -- . ":!*.png" ":!*.jpg" ":!*.jpeg" ":!*.gif" ":!*.so" ":!*.dylib" ":!*.o" ":!*.a" ":!*.boot" ":!*.tar.gz" || true)" + if [ -n "$matches" ]; then + echo "$matches" + echo "High-confidence secret pattern found." + exit 1 + fi --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,10 @@ *.so +*.dylib *.wpo .jerbuild-hashes +.jerboa/* +!.jerboa/ +!.jerboa/security.json +jerboa_qt_shim.dylib +qt_chez_shim.dylib +dist/ new file mode 100644 --- /dev/null +++ b/.jerboa/security.json @@ -0,0 +1,40 @@ +{ + "version": 1, + "repo": "jerboa-qt", + "extends": ["jerboa:ffi", "jerboa:gui"], + "paths": { + "production": ["*.c", "src/**/*.{ss,sls}", "lib/**/*.{ss,sls}", "scripts/**", "support/**", "Makefile"], + "tests": ["tests/**", "qt-test.ss", "support/native-boundary-corpus.ss"], + "generated": ["dist/**", ".jerboa/bin/**", "*.so", "*.dylib", "*.o", "*.wpo"], + "docs": ["README.md", "SECURITY.md", "docs/**", "*.md"] + }, + "policy": { + "failOn": ["critical", "high"], + "ffi": { + "allowed": true, + "requireLazyLoading": true, + "requireExplicitLengths": true, + "rejectEmbeddedNul": true, + "requireCollectSafeBlockingCalls": true + }, + "process": { "shellInterpolation": "deny" }, + "filesystem": { "privateArtifactCommit": "deny" }, + "releaseEvidence": { + "requireTargetNativeProofForProduction": true, + "sensitiveArtifactPolicy": "no-gui-text-clipboard-file-dialog-qprocess-screenshots-or-private-paths", + "sanitizer": "scripts/sanitize-evidence.sh", + "artifactArchivePolicy": "hashes-only", + "scratchBuildLogs": "not-archived" + }, + "targetProof": { + "environment": "JQT_TARGET_PROOF_FILE", + "requireEnvironment": "JQT_REQUIRE_TARGET_PROOF", + "proofMaterialValidation": [ + "max-64KiB", + "reject-private-paths-host-details-ssh-references", + "reject-high-confidence-secret-material" + ] + } + }, + "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 @@ -1,60 +1,222 @@ -JERBUILD ?= jerbuild -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 +JERBOA_VERSION ?= v0.2.3 +JERBOA_TOOL_DIR ?= $(CURDIR)/.jerboa/bin +JERBUILD ?= $(shell if [ -x "$(CURDIR)/../jerboa/dist/jerbuild" ]; then printf '%s\n' "$(CURDIR)/../jerboa/dist/jerbuild"; elif command -v jerbuild >/dev/null 2>&1; then command -v jerbuild; else printf '%s\n' "$(JERBOA_TOOL_DIR)/jerbuild"; fi) +JH = $(shell "$(JERBUILD)" --jerboa-home 2>/dev/null) LIBDIRS = lib:$(JH)/lib +DIST_DIR ?= $(CURDIR)/dist +TARGET_EVIDENCE_DIR ?= $(DIST_DIR)/target-evidence +RELEASE_EVIDENCE_DIR ?= $(DIST_DIR)/release-evidence -QT_VENDOR_DIR := $(HOME)/mine/qt-vendor -VENDOR_DIR := $(QT_VENDOR_DIR)/vendor +QT_VENDOR_DIR ?= $(CURDIR)/../qt-vendor +QT_FALLBACK_DIR ?= $(CURDIR)/../jerboa-emacs +QT_SHIM_INCLUDE_DIR ?= $(firstword $(wildcard $(QT_VENDOR_DIR)/vendor) $(wildcard $(QT_FALLBACK_DIR)/support/vendor-overrides) $(wildcard $(QT_FALLBACK_DIR)/vendor)) +QT_SHIM_LIB_DIR ?= $(firstword $(wildcard $(QT_VENDOR_DIR)/vendor) $(wildcard $(QT_FALLBACK_DIR))) QT_CFLAGS := $(shell pkg-config --cflags Qt6Widgets 2>/dev/null) QT_LIBS := $(shell pkg-config --libs Qt6Widgets 2>/dev/null || echo "-lQt6Widgets") CC ?= cc -CFLAGS ?= -O2 -fPIC -Wall +WARN_CFLAGS ?= -Wall -Wextra -Wformat=2 -Wshadow -Wpointer-arith -Wcast-align -Wwrite-strings +HARDEN_CFLAGS ?= -O2 -fPIC -fstack-protector-strong -D_FORTIFY_SOURCE=2 +CFLAGS ?= $(HARDEN_CFLAGS) $(WARN_CFLAGS) UNAME_S := $(shell uname -s) ifeq ($(UNAME_S),Darwin) SHARED_FLAG = -dynamiclib - SO_EXT = .so + SHLIB_EXT = .dylib LD_VAR = DYLD_LIBRARY_PATH + HARDEN_LDFLAGS ?= -Wl,-no_uuid else SHARED_FLAG = -shared - SO_EXT = .so + SHLIB_EXT = .so LD_VAR = LD_LIBRARY_PATH + HARDEN_LDFLAGS ?= -Wl,-z,relro,-z,now endif -SHIM := jerboa_qt_shim$(SO_EXT) +SHIM := jerboa_qt_shim$(SHLIB_EXT) -.PHONY: all build transpile test clean shim vendor-check +.PHONY: all build native transpile security test test-pure test-native example-smoke clean shim vendor-check \ + ensure-jerboa-tools audit qt-shim-provenance-check qt-shim-provenance-enforce \ + qt-advisory-check qt-advisory-enforce native-boundary-corpus sbom reproducibility-report target-evidence verify release-evidence all: build +ensure-jerboa-tools: + @if [ ! -x "$(JERBUILD)" ]; then \ + ./support/ensure-jerboa.sh "$(JERBOA_VERSION)" "$(JERBOA_TOOL_DIR)"; \ + fi + vendor-check: - @if [ ! -f $(VENDOR_DIR)/qt_shim.h ] || [ ! -f $(VENDOR_DIR)/libqt_shim$(SO_EXT) ]; then \ - echo "WARN: qt-vendor vendor missing (expected $(VENDOR_DIR))."; \ + @if [ -z "$(QT_SHIM_INCLUDE_DIR)" ] || [ -z "$(QT_SHIM_LIB_DIR)" ] || \ + [ ! -f "$(QT_SHIM_INCLUDE_DIR)/qt_shim.h" ] || \ + [ ! -f "$(QT_SHIM_LIB_DIR)/libqt_shim$(SHLIB_EXT)" ]; then \ + echo "WARN: Qt shim missing."; \ + echo "WARN: expected qt_shim.h in QT_SHIM_INCLUDE_DIR=$(QT_SHIM_INCLUDE_DIR)"; \ + echo "WARN: expected libqt_shim$(SHLIB_EXT) in QT_SHIM_LIB_DIR=$(QT_SHIM_LIB_DIR)"; \ echo "WARN: skip shim build."; exit 1; \ fi shim: vendor-check $(SHIM) -$(SHIM): jerboa_qt_shim.c $(VENDOR_DIR)/qt_shim.h - $(CC) $(CFLAGS) $(SHARED_FLAG) -I$(VENDOR_DIR) -o $@ $< \ - -L$(VENDOR_DIR) -lqt_shim -Wl,-rpath,$(VENDOR_DIR) $(QT_LIBS) +native: shim -transpile: +security: + @sh scripts/security-check.sh + +$(SHIM): jerboa_qt_shim.c Makefile + $(CC) $(CFLAGS) $(SHARED_FLAG) -I$(QT_SHIM_INCLUDE_DIR) -o $@ $< \ + -L$(QT_SHIM_LIB_DIR) -lqt_shim -Wl,-rpath,$(QT_SHIM_LIB_DIR) $(QT_LIBS) $(HARDEN_LDFLAGS) + +transpile: ensure-jerboa-tools $(JERBUILD) transpile src lib --force -build: transpile shim +build: transpile + +test: test-pure -test: build +test-pure: transpile + $(JERBUILD) exec --libdirs "$(LIBDIRS)" tests/pure.ss + +test-native: transpile shim + JERBOA_QT_LIB=$(CURDIR) \ + JERBOA_QT_SHIM_DIR=$(QT_SHIM_LIB_DIR) \ + $(LD_VAR)=$(CURDIR):$(QT_SHIM_LIB_DIR) \ + QT_QPA_PLATFORM=offscreen \ + $(JERBUILD) exec --libdirs "$(LIBDIRS)" qt-test.ss + +native-boundary-corpus: transpile shim support/native-boundary-corpus.ss JERBOA_QT_LIB=$(CURDIR) \ - $(LD_VAR)=$(CURDIR):$(VENDOR_DIR) \ + JERBOA_QT_SHIM_DIR=$(QT_SHIM_LIB_DIR) \ + $(LD_VAR)=$(CURDIR):$(QT_SHIM_LIB_DIR) \ QT_QPA_PLATFORM=offscreen \ - $(JERBUILD) exec --libdirs "$(LIBDIRS)" tests/qt-test.ss + QT_LOGGING_RULES=qt.qpa.fonts=false \ + $(JERBUILD) exec --libdirs "$(LIBDIRS)" support/native-boundary-corpus.ss + +example-smoke: transpile shim + @rm -f /tmp/jerboa-qt-example-hello.out /tmp/jerboa-qt-example-hello.err + @QT_QPA_PLATFORM=offscreen JERBOA_QT_SHIM_DIR="$(QT_SHIM_LIB_DIR)" \ + examples/hello.ss >/tmp/jerboa-qt-example-hello.out 2>/tmp/jerboa-qt-example-hello.err & \ + pid=$$!; \ + sleep 3; \ + if kill -0 $$pid 2>/dev/null; then \ + kill $$pid 2>/dev/null || true; \ + wait $$pid 2>/dev/null || true; \ + echo "example smoke passed: examples/hello.ss started and stayed alive"; \ + else \ + wait $$pid; rc=$$?; \ + echo "example smoke failed: examples/hello.ss exited early with status $$rc"; \ + echo "stdout:"; cat /tmp/jerboa-qt-example-hello.out; \ + echo "stderr:"; cat /tmp/jerboa-qt-example-hello.err; \ + exit $$rc; \ + fi + +audit: transpile + @echo "QT_SHIM_INCLUDE_DIR=$(QT_SHIM_INCLUDE_DIR)" + @echo "QT_SHIM_LIB_DIR=$(QT_SHIM_LIB_DIR)" + @if [ -f "$(QT_SHIM_INCLUDE_DIR)/qt_shim.h" ] && [ -f "$(QT_SHIM_LIB_DIR)/libqt_shim$(SHLIB_EXT)" ]; then \ + $(MAKE) shim; \ + if command -v otool >/dev/null 2>&1; then otool -L "$(SHIM)"; \ + elif command -v ldd >/dev/null 2>&1; then ldd "$(SHIM)"; \ + else echo "No native dependency inspector available."; fi; \ + else \ + echo "Qt native shim is not present; native audit skipped."; \ + fi + +qt-shim-provenance-check: + @REPO_ROOT="$(CURDIR)" \ + QT_SHIM_INCLUDE_DIR="$(QT_SHIM_INCLUDE_DIR)" \ + QT_SHIM_LIB_DIR="$(QT_SHIM_LIB_DIR)" \ + SHLIB_EXT="$(SHLIB_EXT)" \ + SHIM="$(SHIM)" \ + sh scripts/qt-shim-provenance-check.sh + +qt-shim-provenance-enforce: + @JERBOA_QT_STRICT_SHIM=1 \ + REPO_ROOT="$(CURDIR)" \ + QT_SHIM_INCLUDE_DIR="$(QT_SHIM_INCLUDE_DIR)" \ + QT_SHIM_LIB_DIR="$(QT_SHIM_LIB_DIR)" \ + SHLIB_EXT="$(SHLIB_EXT)" \ + SHIM="$(SHIM)" \ + sh scripts/qt-shim-provenance-check.sh + +qt-advisory-check: + @sh scripts/qt-advisory-check.sh + +qt-advisory-enforce: + @JERBOA_QT_STRICT_ADVISORY=1 sh scripts/qt-advisory-check.sh + +sbom: transpile + @REPO_ROOT="$(CURDIR)" \ + DIST_DIR="$(DIST_DIR)" \ + JERBUILD="$(JERBUILD)" \ + CC="$(CC)" \ + QT_SHIM_INCLUDE_DIR="$(QT_SHIM_INCLUDE_DIR)" \ + QT_SHIM_LIB_DIR="$(QT_SHIM_LIB_DIR)" \ + SHLIB_EXT="$(SHLIB_EXT)" \ + SHIM="$(SHIM)" \ + sh scripts/sbom.sh + +reproducibility-report: + @REPO_ROOT="$(CURDIR)" \ + DIST_DIR="$(DIST_DIR)" \ + JERBUILD="$(JERBUILD)" \ + MAKE="$(MAKE)" \ + QT_SHIM_INCLUDE_DIR="$(QT_SHIM_INCLUDE_DIR)" \ + QT_SHIM_LIB_DIR="$(QT_SHIM_LIB_DIR)" \ + SHLIB_EXT="$(SHLIB_EXT)" \ + SHIM="$(SHIM)" \ + sh scripts/reproducibility-report.sh + +target-evidence: + @TARGET_EVIDENCE_DIR="$(TARGET_EVIDENCE_DIR)" sh scripts/target-evidence.sh + +verify: security test-pure audit qt-shim-provenance-check qt-advisory-check sbom reproducibility-report target-evidence + +release-evidence: security + @rm -rf "$(RELEASE_EVIDENCE_DIR)" + @mkdir -p "$(RELEASE_EVIDENCE_DIR)" + @echo "==> Collecting git status" + @git status --short > "$(RELEASE_EVIDENCE_DIR)/git-status.txt" + @git rev-parse HEAD > "$(RELEASE_EVIDENCE_DIR)/git-head.txt" 2>/dev/null || true + @echo "==> Running pure tests" + @$(MAKE) test-pure > "$(RELEASE_EVIDENCE_DIR)/test-pure.log" 2>&1 + @if [ -f "$(QT_SHIM_INCLUDE_DIR)/qt_shim.h" ] && [ -f "$(QT_SHIM_LIB_DIR)/libqt_shim$(SHLIB_EXT)" ]; then \ + echo "==> Running native tests"; \ + $(MAKE) test-native > "$(RELEASE_EVIDENCE_DIR)/test-native.log" 2>&1; \ + echo "==> Running native-boundary corpus"; \ + $(MAKE) native-boundary-corpus > "$(RELEASE_EVIDENCE_DIR)/native-boundary-corpus.log" 2>&1; \ + else \ + echo "native tests skipped: Qt shim is absent" > "$(RELEASE_EVIDENCE_DIR)/test-native.log"; \ + echo "native-boundary corpus skipped: Qt shim is absent" > "$(RELEASE_EVIDENCE_DIR)/native-boundary-corpus.log"; \ + fi + @echo "==> Running native audit" + @$(MAKE) audit > "$(RELEASE_EVIDENCE_DIR)/audit.log" 2>&1 + @echo "==> Running reproducibility report" + @$(MAKE) reproducibility-report > "$(RELEASE_EVIDENCE_DIR)/reproducibility.log" 2>&1 + @rm -rf "$(RELEASE_EVIDENCE_DIR)/reproducibility" + @cp -R "$(DIST_DIR)/reproducibility" "$(RELEASE_EVIDENCE_DIR)/reproducibility" + @echo "==> Recording target native evidence status" + @$(MAKE) target-evidence > "$(RELEASE_EVIDENCE_DIR)/target-evidence.log" 2>&1 + @rm -rf "$(RELEASE_EVIDENCE_DIR)/target-evidence" + @cp -R "$(TARGET_EVIDENCE_DIR)" "$(RELEASE_EVIDENCE_DIR)/target-evidence" + @echo "==> Checking Qt shim provenance" + @$(MAKE) qt-shim-provenance-check > "$(RELEASE_EVIDENCE_DIR)/qt-shim-provenance.log" 2>&1 + @echo "==> Checking Qt advisory floor" + @$(MAKE) qt-advisory-check > "$(RELEASE_EVIDENCE_DIR)/qt-advisory.log" 2>&1 + @echo "==> Generating SBOM" + @$(MAKE) sbom > "$(RELEASE_EVIDENCE_DIR)/sbom.log" 2>&1 + @rm -rf "$(RELEASE_EVIDENCE_DIR)/sbom" + @cp -R "$(DIST_DIR)/sbom" "$(RELEASE_EVIDENCE_DIR)/sbom" + @find .jerboa src lib docs scripts support -type f -print | LC_ALL=C sort | xargs shasum -a 256 > "$(RELEASE_EVIDENCE_DIR)/source-hashes.sha256" + @grep -q '^target_evidence_status=present$$' "$(RELEASE_EVIDENCE_DIR)/target-evidence/status.txt" + @sh scripts/sanitize-evidence.sh "$(RELEASE_EVIDENCE_DIR)" + @sh scripts/sanitize-evidence.sh "$(DIST_DIR)/sbom" + @sh scripts/sanitize-evidence.sh "$(DIST_DIR)/reproducibility" + @sh scripts/sanitize-evidence.sh "$(TARGET_EVIDENCE_DIR)" + @echo "Release evidence written to $(RELEASE_EVIDENCE_DIR)" clean: - rm -f $(SHIM) qt_chez_shim.so - rm -rf lib + rm -f jerboa_qt_shim.so jerboa_qt_shim.dylib qt_chez_shim.so qt_chez_shim.dylib + rm -rf lib dist .jerboa/bin + -rmdir .jerboa 2>/dev/null || true --- a/README.md +++ b/README.md @@ -1,15 +1,15 @@ -# chez-qt +# jerboa-qt -Qt6 Widgets bindings for Chez Scheme. +Qt6 Widgets bindings for Jerboa Scheme. -Build desktop GUI applications with native Qt6 widgets from Chez Scheme, using a thin C++ shim and Chez's FFI. API-compatible with [qt-vendor](https://github.com/ober/qt-vendor). +Build desktop GUI applications with native Qt6 widgets from Jerboa, using a thin C++ shim and Chez's FFI underneath. API-compatible with [qt-vendor](https://github.com/ober/qt-vendor) at the high-level `qt-*` API layer. ## Requirements -- [Chez Scheme](https://cisco.github.io/ChezScheme/) 10.0+ +- Jerboa with `jerbuild` on `PATH` - Qt6 development libraries -- gcc -- The [qt-vendor](https://github.com/ober/qt-vendor) C++ shim (provides `vendor/qt_shim.h` and `vendor/libqt_shim.so`) +- C compiler +- A Qt shim provider. The Makefile first looks for `$HOME/mine/qt-vendor/vendor`, then falls back to `$HOME/mine/jerboa-emacs`. ### Linux (Ubuntu/Debian) @@ -41,21 +41,20 @@ make build cd .. ``` -Then clone and build chez-qt: +Then build jerboa-qt: ```sh -git clone https://github.com/ober/chez-qt.git -cd chez-qt +cd jerboa-qt make ``` -By default, the Makefile expects qt-vendor at `$HOME/mine/qt-vendor`. Override with: +By default, the Makefile uses `$HOME/mine/qt-vendor/vendor` when present and otherwise falls back to `$HOME/mine/jerboa-emacs`. Override with: ```sh -make QT_VENDOR_DIR=/path/to/qt-vendor +make QT_SHIM_INCLUDE_DIR=/path/to/include QT_SHIM_LIB_DIR=/path/to/lib ``` -This compiles the Chez-specific callback bridge (`qt_chez_shim.so`) and the Scheme libraries (`chez-qt/ffi.so`, `chez-qt/qt.so`). +This transpiles the Jerboa modules under `lib/` and builds the callback bridge (`jerboa_qt_shim.dylib` on macOS, `jerboa_qt_shim.so` elsewhere). ## Test @@ -63,12 +62,41 @@ This compiles the Chez-specific callback bridge (`qt_chez_shim.so`) and the Sche make test ``` -Tests run headless using Qt's offscreen platform plugin (`QT_QPA_PLATFORM=offscreen`). +`make test` runs pure import and validation checks that do not require native Qt libraries. + +Hosted native tests and native dependency inspection are separate gates: + +```sh +make test-native +make audit +make security +make target-evidence +make qt-shim-provenance-enforce +make qt-advisory-enforce +``` + +Native tests run headless using Qt's offscreen platform plugin (`QT_QPA_PLATFORM=offscreen`). + +## Security + +`jerboa-qt` is experimental until the production gates in `SECURITY.md`, +`docs/ffi-boundary.md`, `docs/shim-provenance.md`, +`docs/qt-advisory-review.md`, and `docs/threat-model.md` are complete. + +The Scheme modules are designed to import without loading native Qt libraries. Native libraries are resolved lazily on first FFI use, Scheme strings with embedded NUL bytes are rejected before native calls, and callback trampolines are registered when `qt-app-create` is called. + +`make release-evidence` records pure/native tests, native-boundary corpus +evidence, native linkage, Qt advisory status, external shim provenance, +sanitized target evidence, SBOM manifests, and hash-only reproducibility output +under `dist/release-evidence/`. `make target-evidence` fails closed when +`JQT_REQUIRE_TARGET_PROOF=1` is set without a marker-complete +`JQT_TARGET_PROOF_FILE`; overlarge, host-private, or high-confidence secret +proof files are rejected before copying. ## Usage ```scheme -(import (chez-qt qt)) +(import (jerboa-qt qt)) (define (main) (with-qt-app app @@ -110,7 +138,7 @@ The shebang automatically resolves library paths relative to the script location ```sh cd /tmp -/path/to/chez-qt/examples/hello.ss +/path/to/jerboa-qt/examples/hello.ss ``` Run headless (no display required): @@ -122,19 +150,20 @@ QT_QPA_PLATFORM=offscreen ./examples/hello.ss Or invoke manually without the shebang: ```sh -scheme --libdirs /path/to/chez-qt --script examples/hello.ss +JH="$(jerbuild --jerboa-home)" +JERBOA_QT_LIB="$PWD" \ +JERBOA_QT_SHIM_DIR="${JERBOA_QT_SHIM_DIR:-$HOME/mine/jerboa-emacs}" \ +DYLD_LIBRARY_PATH="$PWD:${JERBOA_QT_SHIM_DIR:-$HOME/mine/jerboa-emacs}:${DYLD_LIBRARY_PATH:-}" \ +QT_QPA_PLATFORM=offscreen \ +jerbuild exec --libdirs "lib:$JH/lib" examples/hello.ss ``` ### Using from the REPL -From the chez-qt directory: - -```sh -scheme --libdirs . -``` +From the jerboa-qt directory: ```scheme -> (import (chez-qt qt)) +> (import (jerboa-qt qt)) > (define app (qt-app-create)) > (define win (qt-main-window-create)) > (qt-main-window-set-title! win "REPL Window") @@ -145,17 +174,17 @@ scheme --libdirs . ## Differences from qt-vendor -| Aspect | qt-vendor | chez-qt | +| Aspect | qt-vendor | jerboa-qt | |--------|-----------|---------| -| Import | `(import :qt-vendor/qt)` | `(import (chez-qt qt))` | +| Import | `(import :qt-vendor/qt)` | `(import (jerboa-qt qt))` | | Define | `(def (name args) ...)` | `(define (name args) ...)` | | Main | `(def (main) ...)` in build target | `(define (main) ...) (main)` at end of script | | Keyword args | `parent: #f` | Not supported; positional or omitted | | Build | `other-Scheme build` | `make` | -| Run examples | `make demo-hello` | `scheme --libdirs . --script examples/hello.ss` | +| Run examples | `make demo-hello` | `./examples/hello.ss` | | FFI mechanism | Gambit `c-define` (compile-time) | Chez `foreign-callable` (runtime) | -The high-level API (`qt-*` functions) is identical between both projects. Code that uses only the `(chez-qt qt)` / `:qt-vendor/qt` API can be ported by changing the import line and using `define` instead of `def`. +The high-level API (`qt-*` functions) is identical between both projects. Code that uses only the `(jerboa-qt qt)` / `:qt-vendor/qt` API can be ported by changing the import line and using `define` instead of `def`. ## API Reference @@ -166,7 +195,6 @@ The high-level API (`qt-*` functions) is identical between both projects. Code t | `(qt-app-create)` | Create QApplication | | `(qt-app-exec! app)` | Run the event loop | | `(qt-app-quit! app)` | Quit the event loop | -| `(qt-app-process-events! app)` | Process pending events (cooperative polling) | | `(qt-app-destroy! app)` | Destroy application | | `(with-qt-app app body ...)` | Macro: create app, run body, destroy on exit | @@ -1570,11 +1598,11 @@ The binding uses a three-layer architecture: 1. **C++ shim** (`vendor/qt_shim.h`, `vendor/qt_shim.cpp` in qt-vendor) -- thin `extern "C"` wrappers around Qt6 C++ classes. All Qt objects are opaque `void*` handles. Compiled as `libqt_shim.so`. -2. **Chez callback bridge** (`qt_chez_shim.c`) -- stores Chez-provided `foreign-callable` function pointers and provides C wrapper functions that pass them to the C++ shim's signal connection APIs. Unlike Gambit's compile-time `c-define` trampolines, Chez creates callback pointers at runtime. +2. **Chez callback bridge** (`jerboa_qt_shim.c`) -- stores Chez-provided `foreign-callable` function pointers and provides C wrapper functions that pass them to the C++ shim's signal connection APIs. Unlike Gambit's compile-time `c-define` trampolines, Chez creates callback pointers at runtime. -3. **FFI layer** (`chez-qt/ffi.ss`) -- Chez `foreign-procedure` bindings for each C function. Loads `libqt_shim.so` and `qt_chez_shim.so` at library import time. Registers the four callback trampolines (void/string/int/bool) that dispatch to Scheme closures via a callback table. +3. **FFI layer** (`jerboa-qt/ffi.ss`) -- Jerboa FFI bindings for each C function. Imports are pure; `libqt_shim.so` and `jerboa_qt_shim.so` are loaded lazily on first native use, and C entries are resolved lazily with `c-lambda`. -4. **High-level API** (`chez-qt/qt.ss`) -- Idiomatic Chez Scheme wrappers with boolean conversions, the `with-qt-app` macro, and all the `qt-on-*!` signal registration functions. +4. **High-level API** (`jerboa-qt/qt.ss`) -- Idiomatic Jerboa wrappers with boolean conversions, the `with-qt-app` macro, and all the `qt-on-*!` signal registration functions. Callback trampolines are registered when `qt-app-create` is called. ### Callback Pattern new file mode 100644 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,50 @@ +# Security Policy + +`jerboa-qt` is experimental and is not production-ready until the release-plan +and production-readiness tracker gates are complete for this repository. + +## Current Posture + +- Importing `(jerboa-qt ffi)` and `(jerboa-qt qt)` must not load native Qt libraries. +- Native libraries and C entries are resolved lazily through `(jerboa ffi)`. +- Scheme string arguments are rejected if they contain embedded NUL bytes. +- Callback trampolines are registered lazily when `qt-app-create` is called. +- Public high-level APIs do not expose direct event-queue pumping; prefer `qt-app-exec!` with an optional tick callback. +- `make qt-shim-provenance-check` records the external `libqt_shim` source, + provider Git commit, local callback shim, and native binary hashes. +- `make qt-advisory-check` records whether the local Qt line satisfies the + reviewed security floor. The local release evidence currently records Qt + 6.11.1, which passes the CVE-2026-6210 floor for the 6.11 line. +- `make security` enforces required release files, generated-artifact ignores, + private-path and high-confidence secret checks, target proof documentation, + and release-evidence sanitizer wiring. +- `make target-evidence` records target native production blockers locally and + fails closed when `JQT_REQUIRE_TARGET_PROOF=1` is set without a + marker-complete `JQT_TARGET_PROOF_FILE`. Overlarge, host-private, and + high-confidence secret proof files are rejected before copying. +- `make release-evidence` archives pure/native test logs, linkage, sanitized + target evidence, SBOM manifests, Qt advisory status, shim provenance, and + hash-only reproducibility output. + +## Sensitive Data + +Applications built on this library can handle GUI text, editor buffers, clipboard contents, file-dialog paths, filesystem model data, QProcess inputs and outputs, and screenshots or drag/drop content. Treat those as user data and avoid logging or persisting them unless the application has an explicit policy. + +## Required Before Production Use + +- Enforce exact external `libqt_shim` source and binary provenance with + `make qt-shim-provenance-enforce`. +- Enforce the reviewed Qt advisory floor with `make qt-advisory-enforce`. +- Run hosted native tests on Linux and macOS with Qt offscreen. +- Run sanitizer builds for `jerboa_qt_shim.c` and the external Qt shim. +- Add regression coverage for null returned strings, NUL inputs, QObject ownership, callback disconnects, QProcess, file dialogs, clipboard, and event-loop reentrancy. +- Attach marker-complete target proof through `JQT_TARGET_PROOF_FILE` covering + hosted Linux/macOS native CI, Qt advisory status, external shim provenance, + QProcess policy smoke, sanitizer builds, quotas, callback lifecycle, and + external GUI/FFI review. +- Publish passing SBOM and reproducibility evidence for Qt, the external shim, + Chez/Jerboa, and local native code. + +## 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. --- a/compile-libs.ss +++ b/compile-libs.ss @@ -1,2 +1,2 @@ -;;; compile-libs.ss — Compile chez-qt libraries -(import (chez-qt ffi) (chez-qt qt)) +;;; compile-libs.ss — Compile jerboa-qt libraries +(import (jerboa-qt ffi) (jerboa-qt qt)) new file mode 100644 --- /dev/null +++ b/docs/ffi-boundary.md @@ -0,0 +1,44 @@ +# jerboa-qt FFI Boundary + +`jerboa-qt` binds Jerboa Scheme to Qt6 through two native libraries: + +- `libqt_shim`: the external C++ Qt shim that owns the Qt wrappers. +- `jerboa_qt_shim`: the local Chez callback bridge for Qt signal callbacks. + +The Scheme modules can now be imported without loading native Qt libraries. Native libraries are loaded lazily on the first FFI call through `(jerboa ffi)` `load-shared-object*`, then individual C entries are resolved lazily with `c-lambda`. + +## Current Controls + +- Importing `(jerboa-qt ffi)` or `(jerboa-qt qt)` is pure and must not require Qt, X11, Wayland, or a display server. +- Scheme string arguments are rejected if they contain embedded NUL bytes before native resolution or native calls. +- Callback trampolines are registered lazily when `qt-app-create` is called. +- `JERBOA_QT_SHIM_DIR` selects the external shim directory. `JERBOA_QT_LIB` selects the local callback shim directory. +- `make test` runs pure checks. `make test-native` is the gate for hosted native Qt behavior. +- `make qt-shim-provenance-check` records external shim source, binary, and + provider Git metadata. +- `make qt-advisory-check` records whether the local Qt version is on a + reviewed security floor. +- `make native-boundary-corpus` runs a deterministic offscreen corpus across + NUL rejection, QObject ownership transfer, QProcess, file-dialog pre-native + validation, clipboard, and callback/reentrancy paths. +- `make release-evidence` archives test, linkage, SBOM, advisory, provenance, + and reproducibility output. + +## Ownership + +Qt owns parented child widgets. Do not manually destroy a child after its parent has been destroyed. Prefer one explicit destroy call at the root widget or use `with-qt-app` for application lifetime. + +Callback IDs returned by `qt-on-*!` are Scheme-side references. Use `unregister-qt-handler!` or `qt-disconnect-all!` when disconnecting long-lived objects. + +## Known Gaps + +- The native shim ABI still uses `const char *` for most string arguments. The Scheme side rejects NUL bytes, but bytevector length-aware wrappers would be a stronger ABI. +- The external `libqt_shim` must never return null pointers for C strings that Chez decodes as `string`. Native tests must cover this with the pinned shim before production release. +- The Qt advisory floor must stay current. The local release evidence currently + records Qt 6.11.1, which passes the CVE-2026-6210 floor for the 6.11 line. +- QScintilla support is optional and must be tested against the exact native shim used by the release. +- Direct event-queue pumping remains restricted to the low-level FFI boundary. High-level callers should use `qt-app-exec!` with an optional tick callback instead. +- QProcess, clipboard, file dialog, and filesystem model APIs cross process, + desktop, and filesystem boundaries and require application-level policy. +- Production memory/render-time quotas and sanitizer evidence are still + required beyond the bounded local native-boundary corpus. new file mode 100644 --- /dev/null +++ b/docs/qt-advisory-review.md @@ -0,0 +1,27 @@ +# Qt Advisory Review + +`jerboa-qt` uses Qt Widgets, Gui, and Core through `pkg-config`. Production +release candidates must pass `make qt-advisory-enforce`. + +The local Homebrew Qt installation currently reports Qt 6.11.1 for QtCore, +QtGui, QtWidgets, and QtSvg. The Qt known vulnerabilities list records +CVE-2026-6210 as affecting Qt 6.9.0 through 6.11.0, with fixed versions at Qt +6.8.8 and Qt 6.11.1. The release gate therefore passes on the current local +6.11.1 installation and fails closed for older affected 6.9.x through 6.11.0 +installations. + +Reviewed sources: + +- `https://wiki.qt.io/List_of_known_vulnerabilities_in_Qt_products` +- `https://wiki.qt.io/Qt_6.11_Release` + +Run: + +```sh +make qt-advisory-check +make qt-advisory-enforce +``` + +The check exits successfully in report mode so `make release-evidence` can +archive the advisory status. The enforce target fails closed for production +when the local Qt line is missing or below the reviewed floor. new file mode 100644 --- /dev/null +++ b/docs/release-evidence.md @@ -0,0 +1,49 @@ +# Release Evidence + +`make release-evidence` writes ignored local evidence under +`dist/release-evidence/`. + +The evidence bundle contains: + +- pure test output; +- native test output when an external Qt shim is present; +- native linkage audit output; +- Qt shim source/binary provenance; +- Qt advisory status; +- target native evidence status for hosted Linux/macOS native CI, Qt advisory + review, external shim provenance, QProcess policy smoke, sanitizer builds, + memory/render-time quotas, callback lifecycle, and external GUI/FFI review; +- deterministic native-boundary corpus output for NUL rejection, QObject + ownership transfer, QProcess, file-dialog pre-native validation, clipboard, + and callback/reentrancy paths; +- SBOM-style manifests for Jerboa source, generated libraries, Qt metadata, and + native inputs; +- repeated transpile, local callback-shim, and native-boundary corpus + reproducibility output with hashes/status only. + +On the current local machine, native tests pass with the configured sibling Qt +shim provider. Homebrew Qt has been upgraded to 6.11.1, old 6.11.0 kegs were +cleaned, `make qt-advisory-enforce` passes, and the local callback shim links +to QtCore/QtGui/QtWidgets 6.11.1. + +Current local native-boundary corpus evidence records `cases=512`, +`null_string=128`, `qobject_ownership=96`, `qprocess=32`, +`file_dialog=64`, `clipboard=96`, `callback_reentrancy=96`, +`invariant_failures=0`, and `status=pass`. + +native_boundary_corpus_status: documented +native_boundary_corpus_cases_minimum: 512 +native_boundary_corpus_scope_status: null-qobject-qprocess-dialog-clipboard-callback + +`make target-evidence` writes `dist/target-evidence/status.txt`. Without a +reviewed proof file, target native items are recorded as `blocked-not-run`; this +is intentional local evidence and not a production pass. Set +`JQT_REQUIRE_TARGET_PROOF=1` and +`JQT_TARGET_PROOF_FILE=/path/to/proof.txt` to fail closed unless the proof file +contains all required review markers. Overlarge, host-private, and +high-confidence secret proof files fail closed before marker validation and +before copying into release evidence. + +Proof files must not include GUI text, clipboard data, file-dialog paths, +QProcess output, screenshots, credentials, raw host fingerprints, SSH clone +references, or private paths. new file mode 100644 --- /dev/null +++ b/docs/shim-provenance.md @@ -0,0 +1,26 @@ +# Qt Shim Provenance + +`jerboa-qt` needs two native shims: + +- external `libqt_shim`, currently provided by a configured sibling Qt vendor + checkout or sibling editor checkout; +- local `jerboa_qt_shim`, built from `jerboa_qt_shim.c` for callback + trampolines. + +Run: + +```sh +make qt-shim-provenance-check +make qt-shim-provenance-enforce +``` + +The check records: + +- whether `QT_SHIM_INCLUDE_DIR` and `QT_SHIM_LIB_DIR` are present; +- SHA-256 hashes for `qt_shim.h`, `qt_shim.cpp`, `libqt_shim`, local + `jerboa_qt_shim.c`, and the local callback shim binary when present; +- provider Git status and commit when the shim source comes from a Git checkout; +- whether the provider shim source files are dirty. + +Production releases must use a clean, reviewed provider checkout and archive the +release evidence generated by `make release-evidence`. new file mode 100644 --- /dev/null +++ b/docs/threat-model.md @@ -0,0 +1,58 @@ +# jerboa-qt Threat Model + +## Assets + +- GUI text entered by users. +- Clipboard contents. +- File dialog paths and editor buffers. +- Filesystem model paths and metadata. +- QProcess command arguments, output, and environment. +- Callback closures registered from Scheme. + +## Trust Boundaries + +- Scheme to native Qt shim through Chez FFI. +- Qt event loop back into Scheme callbacks. +- Desktop services: clipboard, dialogs, window system, drag/drop, and screen content. +- Filesystem and process execution APIs exposed through widgets and helpers. + +## Primary Risks + +- Embedded NUL bytes changing native string interpretation. +- Native shim null strings crashing Chez string conversion. +- Double destruction across Qt parent-child ownership. +- Reentrant event processing corrupting application state. +- Unsafe QProcess use with untrusted commands or arguments. +- Unintended disclosure through clipboard, file dialogs, screenshots, or drag/drop. + +## Current Controls + +- Pure imports do not load native libraries. +- Native resolution is lazy and centralized in `src/jerboa-qt/ffi.ss`. +- Scheme strings are rejected if they contain NUL bytes before native calls. +- Callback trampolines are initialized when a Qt app is created, not at module import. +- CI runs pure tests without requiring native Qt shim libraries. +- Native behavior is separated behind `make test-native` and `make audit`. +- External shim provenance is recorded with `make qt-shim-provenance-check`. +- Qt advisory status is recorded with `make qt-advisory-check`; local release + evidence currently records Qt 6.11.1, which passes the reviewed + CVE-2026-6210 floor. +- Release evidence records pure/native tests, linkage, provenance, SBOM, and