run.sh (9341B)
1 #!/bin/sh 2 # Driver-level checks for the shared kit CAS/tree utilities. 3 4 set -u 5 6 script_dir=$(cd "$(dirname "$0")" && pwd) 7 repo_root=$(cd "$script_dir/../.." && pwd) 8 9 KIT="${KIT:-$repo_root/build/kit}" 10 11 if [ ! -x "$KIT" ]; then 12 echo "cas: kit binary not found at $KIT" >&2 13 exit 2 14 fi 15 16 work=$(mktemp -d "${TMPDIR:-/tmp}/kit-cas-test.XXXXXX") 17 trap 'rm -rf "$work"' EXIT 18 19 mkdir -p "$work/src/bin" "$work/src/share" "$work/cas" "$work/out" 20 21 # Type-K mode-P kit: ok/run_ok/run_fail/contains/same_file/... + the CAS 22 # helpers (first_hex_id/cas_object_path/tree_blob_for_path), all recording 23 # through the unified kit_* counters over $work. 24 KIT_KIT_DIR="$repo_root/test/lib" 25 . "$repo_root/test/lib/kit_sh_kit.sh" 26 kit_report_init 27 28 make_fixtures() { 29 printf alpha > "$work/src/share/a.txt" 30 cp "$work/src/share/a.txt" "$work/src/share/a-copy.txt" 31 : > "$work/src/share/empty.dat" 32 { 33 printf '#!/bin/sh\n' 34 printf 'printf "hello from cas\\n"\n' 35 } > "$work/src/bin/tool.sh" 36 chmod +x "$work/src/bin/tool.sh" 37 } 38 39 make_fixtures 40 41 run_ok "cas-add-blob" "$KIT" cas add-blob --cas "$work/cas" "$work/src/share/a.txt" 42 blob_id=$(first_hex_id "$work/cas-add-blob.out") 43 if [ -n "$blob_id" ]; then 44 ok "cas-add-blob-id" 45 else 46 echo "could not parse blob id from add-blob output" > "$work/cas-add-blob-id.diag" 47 not_ok "cas-add-blob-id" "$work/cas-add-blob-id.diag" 48 fi 49 blob_file=$(cas_object_path "$work/cas" blob "$blob_id") 50 assert_file_exists "cas-add-blob-object" "$blob_file" 51 same_file "cas-add-blob-content" "$work/src/share/a.txt" "$blob_file" 52 53 run_ok "cas-add-tree-root" "$KIT" cas add-tree --cas "$work/cas" --root "$work/src" 54 tree_id=$(first_hex_id "$work/cas-add-tree-root.out") 55 if [ -n "$tree_id" ]; then 56 ok "cas-add-tree-id" 57 else 58 echo "could not parse tree id from add-tree output" > "$work/cas-add-tree-id.diag" 59 not_ok "cas-add-tree-id" "$work/cas-add-tree-id.diag" 60 fi 61 tree_file=$(cas_object_path "$work/cas" tree "$tree_id") 62 assert_file_exists "cas-add-tree-object" "$tree_file" 63 contains "cas-tree-magic" "$tree_file" "kit-tree 1" 64 contains "cas-tree-hash" "$tree_file" "hash = blake2b-256" 65 contains "cas-tree-blob-format" "$tree_file" "blob = kit-blob-v1" 66 contains "cas-tree-regular-path" "$tree_file" "path = share/a.txt" 67 contains "cas-tree-exec-path" "$tree_file" "path = bin/tool.sh" 68 contains "cas-tree-file-mode" "$tree_file" "mode = -" 69 contains "cas-tree-exec-mode" "$tree_file" "mode = x" 70 71 run_ok "cas-inspect-tree" "$KIT" cas inspect-tree --cas "$work/cas" "$tree_id" 72 contains "cas-inspect-tree-magic" "$work/cas-inspect-tree.out" "kit-tree 1" 73 contains "cas-inspect-tree-path" "$work/cas-inspect-tree.out" "path = share/a.txt" 74 75 run_ok "cas-verify-tree" "$KIT" cas verify-tree --cas "$work/cas" "$tree_id" 76 77 run_ok "cas-materialize-tree" "$KIT" cas materialize --cas "$work/cas" "$tree_id" -C "$work/out/root" 78 same_file "cas-materialize-a" "$work/src/share/a.txt" "$work/out/root/share/a.txt" 79 same_file "cas-materialize-a-copy" "$work/src/share/a-copy.txt" "$work/out/root/share/a-copy.txt" 80 same_file "cas-materialize-empty" "$work/src/share/empty.dat" "$work/out/root/share/empty.dat" 81 same_file "cas-materialize-tool" "$work/src/bin/tool.sh" "$work/out/root/bin/tool.sh" 82 is_executable "cas-materialize-exec-mode" "$work/out/root/bin/tool.sh" 83 84 cat > "$work/map.txt" <<EOF 85 mapped/tool.sh x $work/src/bin/tool.sh 86 mapped/a.txt - $work/src/share/a.txt 87 EOF 88 run_ok "cas-add-tree-map" "$KIT" cas add-tree --cas "$work/cas" --map "$work/map.txt" 89 map_tree_id=$(first_hex_id "$work/cas-add-tree-map.out") 90 if [ -n "$map_tree_id" ]; then 91 ok "cas-add-tree-map-id" 92 else 93 echo "could not parse map tree id from add-tree output" > "$work/cas-add-tree-map-id.diag" 94 not_ok "cas-add-tree-map-id" "$work/cas-add-tree-map-id.diag" 95 fi 96 run_ok "cas-verify-map-tree" "$KIT" cas verify-tree --cas "$work/cas" "$map_tree_id" 97 run_ok "cas-materialize-map-tree" "$KIT" cas materialize --cas "$work/cas" "$map_tree_id" -C "$work/out/map" 98 same_file "cas-map-materialize-a" "$work/src/share/a.txt" "$work/out/map/mapped/a.txt" 99 same_file "cas-map-materialize-tool" "$work/src/bin/tool.sh" "$work/out/map/mapped/tool.sh" 100 is_executable "cas-map-materialize-exec-mode" "$work/out/map/mapped/tool.sh" 101 102 cat > "$work/dup.map" <<EOF 103 same.txt - $work/src/share/a.txt 104 same.txt - $work/src/share/a-copy.txt 105 EOF 106 run_fail "cas-add-tree-duplicate-path-fails" "$KIT" cas add-tree --cas "$work/cas" --map "$work/dup.map" 107 108 cat > "$work/unsafe.map" <<EOF 109 ../escape.txt - $work/src/share/a.txt 110 EOF 111 run_fail "cas-add-tree-unsafe-path-fails" "$KIT" cas add-tree --cas "$work/cas" --map "$work/unsafe.map" 112 113 cat > "$work/absolute.map" <<EOF 114 /absolute.txt - $work/src/share/a.txt 115 EOF 116 run_fail "cas-add-tree-absolute-path-fails" "$KIT" cas add-tree --cas "$work/cas" --map "$work/absolute.map" 117 118 cat > "$work/empty-component.map" <<EOF 119 bad//component.txt - $work/src/share/a.txt 120 EOF 121 run_fail "cas-add-tree-empty-component-fails" "$KIT" cas add-tree --cas "$work/cas" --map "$work/empty-component.map" 122 123 cat > "$work/dot-component.map" <<EOF 124 bad/./component.txt - $work/src/share/a.txt 125 EOF 126 run_fail "cas-add-tree-dot-component-fails" "$KIT" cas add-tree --cas "$work/cas" --map "$work/dot-component.map" 127 128 cat > "$work/colon.map" <<EOF 129 bad:name.txt - $work/src/share/a.txt 130 EOF 131 run_fail "cas-add-tree-colon-path-fails" "$KIT" cas add-tree --cas "$work/cas" --map "$work/colon.map" 132 133 if [ -n "$tree_id" ]; then 134 cp -R "$work/cas" "$work/bad-cas-mutated" 135 a_blob=$(tree_blob_for_path "$tree_file" "share/a.txt") 136 printf tamper >> "$(cas_object_path "$work/bad-cas-mutated" blob "$a_blob")" 137 run_fail "cas-verify-mutated-blob-fails" "$KIT" cas verify-tree --cas "$work/bad-cas-mutated" "$tree_id" 138 run_fail "cas-materialize-mutated-blob-fails" "$KIT" cas materialize --cas "$work/bad-cas-mutated" "$tree_id" -C "$work/out/bad-mutated" 139 140 cp -R "$work/cas" "$work/bad-cas-missing" 141 rm -f "$(cas_object_path "$work/bad-cas-missing" blob "$a_blob")" 142 run_fail "cas-verify-missing-blob-fails" "$KIT" cas verify-tree --cas "$work/bad-cas-missing" "$tree_id" 143 144 mkdir -p "$work/empty-cas" 145 run_fail "cas-verify-missing-tree-fails" "$KIT" cas verify-tree --cas "$work/empty-cas" "$tree_id" 146 else 147 skip_test "cas-corruption-tests" 148 fi 149 150 # --------------------------------------------------------------------------- 151 # Read-side adversarial trees. A CAS tree object is untrusted input (it may be 152 # fetched from a remote or unpacked from a package). Crafting a malicious tree 153 # object directly — bypassing `cas add-tree`, which validates paths at creation 154 # — must still be rejected at materialize/verify time. This exercises the 155 # read-path validator (dist_tree_path_valid) and the tree KV NUL-byte check, 156 # the real attack surface for an untrusted tree. 157 # --------------------------------------------------------------------------- 158 blake2b_id() { "$KIT" hash -a blake2b "$1" | awk '{print $1}'; } 159 160 # Store a hand-crafted tree object under its own content id; echoes the id. 161 place_tree_object() { 162 src=$1 163 id=$(blake2b_id "$src") 164 dest=$(cas_object_path "$work/cas" tree "$id") 165 mkdir -p "$(dirname "$dest")" 166 cp "$src" "$dest" 167 printf '%s\n' "$id" 168 } 169 170 printf 'good/ok.txt - %s/src/share/a.txt\n' "$work" > "$work/adv.map" 171 run_ok "cas-adv-base-tree" "$KIT" cas add-tree --cas "$work/cas" --map "$work/adv.map" 172 adv_tree_id=$(first_hex_id "$work/cas-adv-base-tree.out") 173 if [ -n "$adv_tree_id" ]; then 174 adv_base=$(cas_object_path "$work/cas" tree "$adv_tree_id") 175 176 # 1) parent-dir traversal: reject, and write nothing outside the -C target. 177 sed 's#path = good/ok.txt#path = ../escape.txt#' "$adv_base" > "$work/adv-traverse.tree" 178 tid=$(place_tree_object "$work/adv-traverse.tree") 179 run_fail "cas-materialize-traversal-rejected" \ 180 "$KIT" cas materialize --cas "$work/cas" "$tid" -C "$work/out/adv-traverse" 181 if [ -e "$work/out/escape.txt" ]; then 182 echo "materialize escaped the target directory via ../" \ 183 > "$work/cas-materialize-traversal-contained.diag" 184 not_ok "cas-materialize-traversal-contained" \ 185 "$work/cas-materialize-traversal-contained.diag" 186 else 187 ok "cas-materialize-traversal-contained" 188 fi 189 run_fail "cas-verify-traversal-rejected" \ 190 "$KIT" cas verify-tree --cas "$work/cas" "$tid" 191 192 # 2) absolute path. 193 sed 's#path = good/ok.txt#path = /tmp/kit-cas-escape.txt#' "$adv_base" > "$work/adv-abs.tree" 194 tid=$(place_tree_object "$work/adv-abs.tree") 195 run_fail "cas-materialize-absolute-rejected" \ 196 "$KIT" cas materialize --cas "$work/cas" "$tid" -C "$work/out/adv-abs" 197 198 # 3) embedded NUL in a path: must be rejected, never silently truncated. 199 if command -v perl >/dev/null 2>&1; then 200 perl -pe 's{path = good/ok\.txt}{path = good/ok\0evil.txt}' "$adv_base" \ 201 > "$work/adv-nul.tree" 202 tid=$(place_tree_object "$work/adv-nul.tree") 203 run_fail "cas-materialize-nul-rejected" \ 204 "$KIT" cas materialize --cas "$work/cas" "$tid" -C "$work/out/adv-nul" 205 run_fail "cas-verify-nul-rejected" \ 206 "$KIT" cas verify-tree --cas "$work/cas" "$tid" 207 else 208 skip_test "cas-materialize-nul-rejected" 209 skip_test "cas-verify-nul-rejected" 210 fi 211 else 212 skip_test "cas-read-side-adversarial" 213 fi 214 215 kit_summary cas 216 kit_exit