kit

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

commit 665e83ecd46ad0bd3d827a8ba8d3edc99c6f0a6f
parent 56755e162db1b89015e3320757a37fcfb8ddbfeb
Author: Ryan Sepassi <rsepassi@gmail.com>
Date:   Thu, 28 May 2026 18:43:23 -0700

Tighten package verification checks

Diffstat:
Mdoc/DISTRIBUTE.md | 3+++
Mdriver/dist/manifest.c | 19+++++++++++++++++++
Mdriver/dist/manifest.h | 2++
Mdriver/pkg.c | 5+++++
Mtest/pkg/run.sh | 19++++++++++++++++++-
5 files changed, 47 insertions(+), 1 deletion(-)

diff --git a/doc/DISTRIBUTE.md b/doc/DISTRIBUTE.md @@ -118,6 +118,9 @@ Top-level fields: | `blake2b` | no | dependency package id | | `key` | no | expected signer key id | +Artifact paths are always relative unpack paths. Absolute paths, empty path +components, `.`, `..`, backslashes, and drive-style `:` are rejected. + ## Merkle tree Artifacts are split into fixed 64 KiB raw chunks. The final chunk may be diff --git a/driver/dist/manifest.c b/driver/dist/manifest.c @@ -113,6 +113,23 @@ static int kind_valid(const char* k) { strcmp(k, "source") == 0; } +int dist_manifest_path_valid(const char* p) { + size_t start = 0, i; + if (!p[0] || p[0] == '/') return 0; + for (i = 0;; ++i) { + char c = p[i]; + if (c == '\\' || c == ':') return 0; + if (c == '/' || c == '\0') { + size_t n = i - start; + if (n == 0) return 0; + if (n == 1 && p[start] == '.') return 0; + if (n == 2 && p[start] == '.' && p[start + 1] == '.') return 0; + if (c == '\0') return 1; + start = i + 1u; + } + } +} + static int parse_u64(const char* s, uint64_t* out) { char* end = NULL; unsigned long long v; @@ -225,6 +242,8 @@ int dist_manifest_parse(const uint8_t* data, size_t len, DistManifest* m, return set_err(err, errcap, "bad artifact id"); seen |= F_ID; } else if (strcmp(key, "path") == 0) { + if (!dist_manifest_path_valid(val)) + return set_err(err, errcap, "unsafe artifact path"); if (copy_field(art->path, sizeof art->path, val, err, errcap)) return DIST_ERR; seen |= F_PATH; diff --git a/driver/dist/manifest.h b/driver/dist/manifest.h @@ -46,6 +46,8 @@ typedef struct DistManifest { int dist_manifest_emit(const DistManifest* m, CfreeWriter* out); +int dist_manifest_path_valid(const char* path); + int dist_manifest_parse(const uint8_t* data, size_t len, DistManifest* m, char* err, size_t errcap); diff --git a/driver/pkg.c b/driver/pkg.c @@ -161,6 +161,10 @@ static int pkg_load_inputs(const CfreeContext* ctx, const char** files, for (i = 0; i < n_files; ++i) { in[i].src = files[i]; in[i].path = driver_basename(files[i]); + if (!dist_manifest_path_valid(in[i].path)) { + driver_errf(PKG_TOOL, "create: unsafe artifact path: %s", in[i].path); + return DIST_ERR; + } if (pkg_read_file(ctx, files[i], &in[i].fd) != DIST_OK) { driver_errf(PKG_TOOL, "create: cannot read file: %s", files[i]); return DIST_ERR; @@ -971,6 +975,7 @@ static int pkg_verify_native(DriverEnv* env, const CfreeContext* ctx, if (memcmp(d.package_id, v.package_id, DIST_BLAKE2B_LEN) != 0 || d.index_offset != h.index_offset || d.index_size != h.index_size || d.content_offset != h.content_offset || d.content_size != h.content_size || + d.chunk_size != h.chunk_size || d.alignment != h.alignment || d.chunk_size != DIST_CFPKG_CHUNK_SIZE_DEFAULT || d.alignment != DIST_CFPKG_ALIGNMENT) { driver_errf(PKG_TOOL, "encoding descriptor does not match package layout"); diff --git a/test/pkg/run.sh b/test/pkg/run.sh @@ -18,7 +18,8 @@ trap 'rm -rf "$work"' EXIT HOME="$work/home" CFREE_TRUSTED_KEYS="$work/trusted_keys" -export HOME CFREE_TRUSTED_KEYS +SOURCE_DATE_EPOCH=1 +export HOME CFREE_TRUSTED_KEYS SOURCE_DATE_EPOCH mkdir -p "$HOME" "$work/in" "$work/pkg" "$work/unpack" pass=0 @@ -433,6 +434,8 @@ if have_cmd perl; then descriptor_off=$(cfpkg_field_offset "$work/pkg/matrix-none.cfpkg" 4) index_off=$(cfpkg_field_offset "$work/pkg/matrix-none.cfpkg" 10) content_off=$(cfpkg_field_offset "$work/pkg/matrix-none.cfpkg" 12) + alignment_off=$((16 + 8 * 14)) + chunk_size_off=$((16 + 8 * 15)) cp "$work/pkg/matrix-none.cfpkg" "$work/pkg/bad-native-descriptor.cfpkg" flip_byte "$work/pkg/bad-native-descriptor.cfpkg" "$descriptor_off" @@ -448,6 +451,16 @@ if have_cmd perl; then flip_byte "$work/pkg/bad-native-content.cfpkg" "$content_off" run_fail "native-mutated-content-fails" "$CFREE" pkg verify -p "$work/key.pub" \ "$work/pkg/bad-native-content.cfpkg" + + cp "$work/pkg/matrix-none.cfpkg" "$work/pkg/bad-native-alignment.cfpkg" + flip_byte "$work/pkg/bad-native-alignment.cfpkg" "$alignment_off" + run_fail "native-mutated-header-alignment-fails" "$CFREE" pkg verify -p "$work/key.pub" \ + "$work/pkg/bad-native-alignment.cfpkg" + + cp "$work/pkg/matrix-none.cfpkg" "$work/pkg/bad-native-chunk-size.cfpkg" + flip_byte "$work/pkg/bad-native-chunk-size.cfpkg" "$chunk_size_off" + run_fail "native-mutated-header-chunk-size-fails" "$CFREE" pkg verify -p "$work/key.pub" \ + "$work/pkg/bad-native-chunk-size.cfpkg" else skip_test "native-region-offset-corruption-tests" fi @@ -466,6 +479,10 @@ printf two > "$work/dup/b/same.dat" run_fail "pkg-create-duplicate-artifact-path-fails" "$CFREE" pkg create \ --name matrix-test --version 1.0.0 --format tar.gz -s "$work/key.key" \ -o "$work/pkg/duplicate.tar.gz" "$work/dup/a/same.dat" "$work/dup/b/same.dat" +printf unsafe > "$work/in/bad\\path.dat" +run_fail "pkg-create-unsafe-artifact-path-fails" "$CFREE" pkg create \ + --name matrix-test --version 1.0.0 --format tar.gz -s "$work/key.key" \ + -o "$work/pkg/unsafe-path.tar.gz" "$work/in/bad\\path.dat" run_fail "pkg-verify-missing-file-fails" "$CFREE" pkg verify -p "$work/key.pub" \ "$work/pkg/does-not-exist.tar.gz" printf not-a-package > "$work/not-a-package.cfpkg"