kit

kit
git clone https://git.ryansepassi.com/git/kit.git
Log | Files | Refs | README

commit 36feeb5b099d9e835e67636a5ec87911283aead4
parent c717fb5b4469200bea55ac296bb38372c7d33218
Author: Ryan Sepassi <rsepassi@gmail.com>
Date:   Tue,  9 Jun 2026 20:56:58 -0700

test: add test-dist target with read-side adversarial CAS coverage

`make test-dist` is now the one-command verification of the dist subsystem
(content-addressed store + signed packaging). It aggregates the CAS and pkg
driver corpora (test-driver-cas + test-driver-pkg), which already cover
round-trip, integrity/tamper detection, the trust store, TOFU, and
format-mismatch rejection.

Adds read-side adversarial cases to test/cas/run.sh. The existing suite only
rejected unsafe paths at *creation* (`cas add-tree --map`); these craft a
malicious CAS tree object directly — bypassing add-tree — and assert it is
rejected at materialize/verify time, the real attack surface for an untrusted
tree fetched from a remote or unpacked from a package:
  - parent-dir traversal (`../escape.txt`): rejected, and nothing is written
    outside the -C target (containment asserted explicitly).
  - absolute path (`/...`): rejected.
  - embedded NUL in a path: rejected, never silently truncated.
These exercise the read-path validator (dist_tree_path_valid) and the tree KV
NUL check, and guard the dist hardening landed in the previous commit.

cas: 48 pass / 0 fail / 0 skip; pkg: 182 pass / 0 fail / 0 skip.

Diffstat:
Mmk/test.mk | 8++++++++
Mtest/cas/run.sh | 65+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 73 insertions(+), 0 deletions(-)

diff --git a/mk/test.mk b/mk/test.mk @@ -72,6 +72,7 @@ TEST_TARGETS = \ test-driver-objcopy \ test-driver-objdump \ test-driver-pkg \ + test-dist \ test-driver-strings \ test-driver-tools \ test-driver-wasm \ @@ -300,6 +301,13 @@ test-driver-objdump: bin test-driver-pkg: bin @KIT=$(abspath $(BIN)) sh test/pkg/run.sh +# test-dist: one-command verification of the dist subsystem (content-addressed +# store + signed packaging). Runs the CAS and pkg driver corpora, which cover +# round-trip, integrity/tamper detection, trust store, and the read-side +# adversarial cases (untrusted-tree path-traversal and NUL-byte rejection) in +# test/cas/run.sh. +test-dist: test-driver-cas test-driver-pkg + test-driver-strings: bin @KIT=$(abspath $(BIN)) sh test/strings/run.sh diff --git a/test/cas/run.sh b/test/cas/run.sh @@ -147,5 +147,70 @@ else skip_test "cas-corruption-tests" fi +# --------------------------------------------------------------------------- +# Read-side adversarial trees. A CAS tree object is untrusted input (it may be +# fetched from a remote or unpacked from a package). Crafting a malicious tree +# object directly — bypassing `cas add-tree`, which validates paths at creation +# — must still be rejected at materialize/verify time. This exercises the +# read-path validator (dist_tree_path_valid) and the tree KV NUL-byte check, +# the real attack surface for an untrusted tree. +# --------------------------------------------------------------------------- +blake2b_id() { "$KIT" hash -a blake2b "$1" | awk '{print $1}'; } + +# Store a hand-crafted tree object under its own content id; echoes the id. +place_tree_object() { + src=$1 + id=$(blake2b_id "$src") + dest=$(cas_object_path "$work/cas" tree "$id") + mkdir -p "$(dirname "$dest")" + cp "$src" "$dest" + printf '%s\n' "$id" +} + +printf 'good/ok.txt - %s/src/share/a.txt\n' "$work" > "$work/adv.map" +run_ok "cas-adv-base-tree" "$KIT" cas add-tree --cas "$work/cas" --map "$work/adv.map" +adv_tree_id=$(first_hex_id "$work/cas-adv-base-tree.out") +if [ -n "$adv_tree_id" ]; then + adv_base=$(cas_object_path "$work/cas" tree "$adv_tree_id") + + # 1) parent-dir traversal: reject, and write nothing outside the -C target. + sed 's#path = good/ok.txt#path = ../escape.txt#' "$adv_base" > "$work/adv-traverse.tree" + tid=$(place_tree_object "$work/adv-traverse.tree") + run_fail "cas-materialize-traversal-rejected" \ + "$KIT" cas materialize --cas "$work/cas" "$tid" -C "$work/out/adv-traverse" + if [ -e "$work/out/escape.txt" ]; then + echo "materialize escaped the target directory via ../" \ + > "$work/cas-materialize-traversal-contained.diag" + not_ok "cas-materialize-traversal-contained" \ + "$work/cas-materialize-traversal-contained.diag" + else + ok "cas-materialize-traversal-contained" + fi + run_fail "cas-verify-traversal-rejected" \ + "$KIT" cas verify-tree --cas "$work/cas" "$tid" + + # 2) absolute path. + sed 's#path = good/ok.txt#path = /tmp/kit-cas-escape.txt#' "$adv_base" > "$work/adv-abs.tree" + tid=$(place_tree_object "$work/adv-abs.tree") + run_fail "cas-materialize-absolute-rejected" \ + "$KIT" cas materialize --cas "$work/cas" "$tid" -C "$work/out/adv-abs" + + # 3) embedded NUL in a path: must be rejected, never silently truncated. + if command -v perl >/dev/null 2>&1; then + perl -pe 's{path = good/ok\.txt}{path = good/ok\0evil.txt}' "$adv_base" \ + > "$work/adv-nul.tree" + tid=$(place_tree_object "$work/adv-nul.tree") + run_fail "cas-materialize-nul-rejected" \ + "$KIT" cas materialize --cas "$work/cas" "$tid" -C "$work/out/adv-nul" + run_fail "cas-verify-nul-rejected" \ + "$KIT" cas verify-tree --cas "$work/cas" "$tid" + else + skip_test "cas-materialize-nul-rejected" + skip_test "cas-verify-nul-rejected" + fi +else + skip_test "cas-read-side-adversarial" +fi + kit_summary cas kit_exit