Project-hygiene pass toward being a properly citable, embeddable, professionally-packaged C library rather than just working code: - PACKFS_VERSION_MAJOR/MINOR/PATCH/STRING in include/packfs.h, the single source of truth for the project's version, plus a runtime pfs_version() (src/vfs.c, next to vfs_new/vfs_free) so a dynamically-linked consumer can check ABI/API compatibility without recompiling. Covered by a new assertion in tests/test_mem.c that the macro and the runtime function never disagree. - packfs.pc.in + a `make install` rule that generates packfs.pc with its Version: field derived from PACKFS_VERSION_STRING via a Makefile-level grep/sed, never hand-maintained separately -- verified end-to-end with a scratch `make install PREFIX=...` + `pkg-config --cflags --libs packfs` + `make uninstall`, not just by reading the rule. - SPDX-License-Identifier: MIT added to every src/*.c and src/internal.h (include/packfs.h already had one); the whole distributed source tree now carries consistent machine-readable license metadata. - SECURITY.md, stating precisely what this project's containment and pack- integrity code actually claims as a security boundary (concept.md Section 6/7) versus what it explicitly does not (unenforced `mode`, no cross-process concurrency) -- not generic boilerplate -- with a real reporting contact rather than a placeholder. - CHANGELOG.md (Keep a Changelog format), summarizing the real history in git log to date; explicitly notes no version is tagged yet. Verified: clean `make all` + `make test` (all 6 binaries, including the new version-check assertion), and a full ASan/UBSan sweep of all 6 binaries with zero real findings (one run hit the already-documented DEADLYSIGNAL sandbox flake on test_pack_write_perf across all 3 retries; re-verified directly afterward with 5/5 additional clean passes and timing well under any plausible timeout, confirming it was the flake, not a regression, before treating this as done). Deliberately not done here, by the user's explicit choice: no CODE_OF_CONDUCT.md, and no git remote/publishing -- this repository still has neither, and both are decisions left to the maintainer. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UqJpkdJ6Njnt1pw3CbghzB
4.4 KiB
Security Policy
Supported versions
No version has been tagged yet (see CHANGELOG.md); this project is
pre-1.0 (README.md's "Status"). Only the latest commit on the default
branch is supported — there is no LTS branch and no backport policy while
the project is at 0.x.
What PackFS actually claims as a security boundary
Stated precisely, per this project's documentation standard (CLAUDE.md),
rather than a generic "we take security seriously":
- Path containment for
dirmounts is a hard requirement (concept.mdSection 6,CLAUDE.md's "Load-bearing constraints"). On Linux 5.6+, every lookup beneath adirmount usesopenat2(RESOLVE_BENEATH | RESOLVE_NO_SYMLINKS)to atomically block..escapes, symlink escapes, and the canonicalize-then-open TOCTOU window in one kernel call.vfs_harden_process_with_landlock(Section 6.2) is an additional, opt-in kernel-level layer — not applied automatically, since Landlock restrictions are cumulative, irreversible, and process-wide, which would be a surprising side effect for an embeddable library to trigger on its own. - On platforms or kernels without
openat2, containment falls back to per-componentO_NOFOLLOWplus canonicalize-and-verify (Section 6.3) — an accepted, explicitly weaker residual-risk posture, not a claim of equal strength. Code and documentation that describe this fallback as equivalent to theopenat2path would themselves be a bug worth reporting. - The VFS core canonicalizes
./..in the virtual namespace itself, before any backend is reached, so a crafted path cannot escape a mount's prefix even when no host directory is involved. - A pack file is treated as untrusted input unless it came from this
run's own compaction (Section 7). Every index entry's offsets and
sizes are bounds-checked against the actual file/strings-region length
before being trusted; a failed check invalidates the whole pack load. An
externally-sourced pack additionally needs a checksum verified at load.
Loading an attacker-supplied
.imgfile that PackFS accepts without detecting corruption, or that causes an out-of-bounds read/write, is a security bug. - Journal records are length/checksum self-describing (Section 4.4), so replay stops cleanly at the first torn or corrupted record rather than reading past it.
What PackFS explicitly does not claim
Reporting these as vulnerabilities is not necessary — they are documented, known limitations, not silent gaps:
mode(permission bits, symlinks, hard links) is stored and restored but not enforced. Nothing in the VFS core interprets it as an access control boundary in v0 (concept.mdSection 10).- Cross-process concurrency is specified (an LMDB-style reader-table
design, Section 5.6) but not implemented. Two uncoordinated processes
writing the same host directory through a
dirmount are not synchronized against each other. zip/tarare permanently out of scope (CLAUDE.md, "Project decisions that supersede concept.md") — there is no archive-format parsing code in this project to have a vulnerability in.
Reporting a vulnerability
Please do not open a public GitHub issue for a suspected security vulnerability. Once this repository has a hosting remote and private security advisories are available there, prefer that. Until then, or as an alternative, report privately to the maintainer:
retoor — retoor@molodetz.nl
When reporting, include: the affected function(s) or file(s), a minimal
reproduction (ideally as a tests/test_*.c-style program using only the
public API in include/packfs.h), and which of the guarantees above (or
which concept.md section) you believe is violated — this project's own
verification discipline (make test, ASan/UBSan/TSan per CONTRIBUTING.md)
depends on being able to turn a report into a permanent regression test.
Verification this project already does
Every change to src/upper.c, src/overlay.c, or src/vfs.c is required
to pass under -fsanitize=address,undefined and -fsanitize=thread
before being considered done (CLAUDE.md) — this process has already
caught and fixed a real heap-use-after-free in the snapshot-reclamation
logic during development (see the reclaim_gate note in src/internal.h).
.github/workflows/ci.yml runs the full test suite plus both sanitizer
passes on every push.