diff --git a/.github/workflows/ci.yml b/.gitea/workflows/ci.yml similarity index 63% rename from .github/workflows/ci.yml rename to .gitea/workflows/ci.yml index 976bddb..e9e395b 100644 --- a/.github/workflows/ci.yml +++ b/.gitea/workflows/ci.yml @@ -1,3 +1,15 @@ +# Gitea Actions workflow (not GitHub Actions — this project is hosted on +# Gitea, see CONTRIBUTING.md). Gitea Actions' YAML is GitHub-Actions- +# compatible, but two things here are Gitea-specific and need to match +# your actual instance, not just this repo: +# - `runs-on: ubuntu-latest` must match a label your registered Gitea +# runner (act_runner) actually advertises; there is no GitHub-hosted- +# runner equivalent, this is entirely self-hosted. +# - `uses: actions/checkout@v4` resolves against whatever action source +# your instance's runner is configured with (by default, Gitea Actions +# fetches actions from github.com unless DEFAULT_ACTIONS_URL is +# overridden); use `https://gitea.com/actions/checkout@v4` instead if +# your runner is configured to disallow GitHub as an action source. name: CI on: diff --git a/CHANGELOG.md b/CHANGELOG.md index 8b3d7a6..e1a4f41 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -47,10 +47,17 @@ pushed to any remote — there is no public release yet, only a local one. - **`backend_pack_new` was declared in `include/packfs.h` but never implemented** — any program calling it failed at link time. Implemented as a standalone, read-only `pack` `Backend`. -- `.github/workflows/ci.yml`'s sanitizer-build steps never passed - `-D_GNU_SOURCE` when compiling test files (only the library object - files got it), which was harmless until a test included `internal.h` - (needed for `pthread_rwlock_t`) and became a link failure. +- The CI config's (`.gitea/workflows/ci.yml`) sanitizer-build steps never + passed `-D_GNU_SOURCE` when compiling test files (only the library + object files got it), which was harmless until a test included + `internal.h` (needed for `pthread_rwlock_t`) and became a link failure. + +### Changed +- CI moved from a GitHub-Actions-convention config (`.github/workflows/`) + to Gitea Actions (`.gitea/workflows/`) — this project is hosted on + Gitea, not GitHub, and was never actually hosted on GitHub; only the + scaffolding's starting convention changed. `SECURITY.md`, `CLAUDE.md`, + `CONTRIBUTING.md`, and `README.md` updated to match. ### Documented - **`vfs.c`'s mount table (`MountSnapshot`) is O(n²) in mount count, the diff --git a/CLAUDE.md b/CLAUDE.md index e056fed..01d7b37 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -4,7 +4,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co ## Repository status -This repository contains a working v0 implementation of the `concept.md` specification: `include/packfs.h` (public API), `src/*.c` (implementation), `tests/test_*.c` (test suite), and open-source project scaffolding (`README.md`, `LICENSE`, `CONTRIBUTING.md`, `CHANGELOG.md`, `SECURITY.md`, `packfs.pc.in`, `.github/workflows/ci.yml`), alongside the frozen `concept.md` and this file. Every file under `src/` and `include/` carries an `SPDX-License-Identifier: MIT` tag; `include/packfs.h`'s `PACKFS_VERSION_*` macros are the single source of truth for the project's version — `pfs_version()` (runtime) and `packfs.pc` (generated by `make install`) are both derived from them, never maintained separately. +This repository contains a working v0 implementation of the `concept.md` specification: `include/packfs.h` (public API), `src/*.c` (implementation), `tests/test_*.c` (test suite), and open-source project scaffolding (`README.md`, `LICENSE`, `CONTRIBUTING.md`, `CHANGELOG.md`, `SECURITY.md`, `packfs.pc.in`, `.gitea/workflows/ci.yml` — this project is hosted on Gitea, not GitHub), alongside the frozen `concept.md` and this file. Every file under `src/` and `include/` carries an `SPDX-License-Identifier: MIT` tag; `include/packfs.h`'s `PACKFS_VERSION_*` macros are the single source of truth for the project's version — `pfs_version()` (runtime) and `packfs.pc` (generated by `make install`) are both derived from them, never maintained separately. ## Build, test, and lint commands @@ -17,7 +17,7 @@ make install PREFIX=/some/prefix A single test: `make build/test_mem && ./build/test_mem` (substitute any `test_*` basename). There is no separate lint step — the build itself uses `-Wall -Wextra -Wpedantic`, and a warning introduced by a change is a build failure, not something to leave in place. -Sanitizer builds are not wired into `make test` (they need per-file compilation with sanitizer flags plus `-D_GNU_SOURCE -Iinclude -Isrc`); see `.github/workflows/ci.yml` for the exact invocation, which CI runs on every push. **Any change to `upper.c`, `overlay.c`, or `vfs.c` must be verified under `-fsanitize=address,undefined` and `-fsanitize=thread` before being considered done** — this is not a formality: exactly this process caught a real use-after-free in the snapshot-reclamation logic during initial development (see the `reclaim_gate` note below), which the plain build and even repeated plain test runs never surfaced. +Sanitizer builds are not wired into `make test` (they need per-file compilation with sanitizer flags plus `-D_GNU_SOURCE -Iinclude -Isrc`); see `.gitea/workflows/ci.yml` (Gitea Actions) for the exact invocation, which CI runs on every push. **Any change to `upper.c`, `overlay.c`, or `vfs.c` must be verified under `-fsanitize=address,undefined` and `-fsanitize=thread` before being considered done** — this is not a formality: exactly this process caught a real use-after-free in the snapshot-reclamation logic during initial development (see the `reclaim_gate` note below), which the plain build and even repeated plain test runs never surfaced. **If your environment cannot run ThreadSanitizer at all, say so rather than skipping it silently.** TSan needs `personality(ADDR_NO_RANDOMIZE)` to disable ASLR for itself; some sandboxes block that syscall outright, in which case *every* TSan build fails identically (`FATAL: ThreadSanitizer: unexpected memory mapping`), including a trivial unrelated pthread program — that is the confirming test, not a PackFS-specific symptom. In that situation, ASan/UBSan are still run and still matter (they are what actually caught the `reclaim_gate` bug above), but they do not perform TSan's happens-before race analysis and are not a substitute for it; a change is TSan-verified only once it has passed on a machine or CI run that can actually execute it, not merely because ASan/UBSan passed. @@ -97,7 +97,7 @@ These are hard requirements stated in the spec, not stylistic suggestions — an ## Self-evaluation -**Methodology.** This file was checked against the repository's actual state (`make test` passing, including two new tests, `test_pack_write_perf` and the dedup-verification block added to `test_pack_overlay`; `include/`, `src/`, `tests/`, `bench/` present and matching the description below; confirmed by directory listing and a live build), against `concept.md` as frozen (for factual consistency of the constraints and architecture summarized above), and against the documentation standard stated in this file. This revision followed a deliberate audit for other instances of the file index's O(n²) shape elsewhere in the codebase — prompted by a request to leave no caveats undocumented — which found two more real issues, not zero: `pack_write`'s compaction dedup (O(n²), plus a latent hash-collision correctness bug, both now fixed) and the mount table (O(n²), confirmed and deliberately left unfixed, with the reasoning recorded). The audit also found and fixed a CI gap the new tests exposed — `.github/workflows/ci.yml`'s sanitizer-build steps never passed `-D_GNU_SOURCE` when compiling test files, which was harmless while no test included `internal.h` and became a real link failure once two did (`internal.h` needs it for `pthread_rwlock_t`) — and documented a sandbox flake (ASan/UBSan's own `DEADLYSIGNAL` startup race, distinct from and in addition to the already-documented TSan limitation) observed directly during this audit's own sanitizer runs, in `CONTRIBUTING.md` and cross-referenced here. Every finding below was confirmed empirically (direct `pack_write` timing, `bench/bench.c`'s new mount-scaling category, repeated sanitizer runs with `timeout`), not asserted. +**Methodology.** This file was checked against the repository's actual state (`make test` passing, including two new tests, `test_pack_write_perf` and the dedup-verification block added to `test_pack_overlay`; `include/`, `src/`, `tests/`, `bench/` present and matching the description below; confirmed by directory listing and a live build), against `concept.md` as frozen (for factual consistency of the constraints and architecture summarized above), and against the documentation standard stated in this file. This revision followed a deliberate audit for other instances of the file index's O(n²) shape elsewhere in the codebase — prompted by a request to leave no caveats undocumented — which found two more real issues, not zero: `pack_write`'s compaction dedup (O(n²), plus a latent hash-collision correctness bug, both now fixed) and the mount table (O(n²), confirmed and deliberately left unfixed, with the reasoning recorded). The audit also found and fixed a CI gap the new tests exposed — the CI config's (now `.gitea/workflows/ci.yml`; at the time of this finding it lived at `.github/workflows/ci.yml`, before this project's scaffolding was set up for Gitea hosting instead of the GitHub-Actions convention it started from — this repository was never actually hosted on GitHub) sanitizer-build steps never passed `-D_GNU_SOURCE` when compiling test files, which was harmless while no test included `internal.h` and became a real link failure once two did (`internal.h` needs it for `pthread_rwlock_t`) — and documented a sandbox flake (ASan/UBSan's own `DEADLYSIGNAL` startup race, distinct from and in addition to the already-documented TSan limitation) observed directly during this audit's own sanitizer runs, in `CONTRIBUTING.md` and cross-referenced here. Every finding below was confirmed empirically (direct `pack_write` timing, `bench/bench.c`'s new mount-scaling category, repeated sanitizer runs with `timeout`), not asserted. | Category | Grade | Notes | |---|---|---| diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 61aedc8..9e419c3 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -19,7 +19,7 @@ project is written in. ```sh make test # must pass before any PR -cc ... -fsanitize=address,undefined # ASan/UBSan: see .github/workflows/ci.yml for exact flags +cc ... -fsanitize=address,undefined # ASan/UBSan: see .gitea/workflows/ci.yml for exact flags cc ... -fsanitize=thread # TSan, for anything touching src/upper.c, src/overlay.c, or src/vfs.c ``` @@ -41,10 +41,15 @@ it or claiming verification that didn't happen — ASan/UBSan still catch real bugs (they found and fixed a genuine heap-use-after-free during this project's development, see the `reclaim_gate` note in `internal.h`) but do not do TSan's happens-before race analysis, so they are not a substitute -for it. CI (`.github/workflows/ci.yml`) runs on a normal, unrestricted -GitHub Actions VM where TSan is expected to work; treat a change as -TSan-verified only once it has actually passed there or on a local machine -that can run it, not merely because ASan/UBSan passed. +for it. CI (`.gitea/workflows/ci.yml`, Gitea Actions — this project is +hosted on Gitea, not GitHub) runs on this project's own self-hosted +runner; whether that runner can execute TSan depends on that runner's own +environment, which is not controlled by this repository the way a +hosted-runner VM would be — do not assume CI's TSan step passing means +what it would on an unrestricted machine without having actually checked +what environment the registered runner provides. Treat a change as +TSan-verified only once it has actually passed on a machine confirmed able +to run it, not merely because a CI step reported success. **A second, separate environment quirk, also observed directly rather than assumed:** in the same kind of sandboxed environment, an ASan/UBSan-built diff --git a/README.md b/README.md index 394d0ff..59ead0c 100644 --- a/README.md +++ b/README.md @@ -260,7 +260,7 @@ never mutates a buffer address a reader might be reading (Section 5.7): growth always allocates a new buffer and publishes it, never reallocates in place. `tests/test_concurrency.c` exercises this under concurrent reader and writer threads, and the suite is regularly run under ThreadSanitizer and -AddressSanitizer (see `.github/workflows/ci.yml`). +AddressSanitizer (see `.gitea/workflows/ci.yml`). ## Security @@ -274,7 +274,9 @@ silently assumed equivalent. Pack files are treated as untrusted input unless they came from this process's own compaction: every on-disk offset is bounds-checked and the pack's checksum is verified before any of it is trusted (Section 7). See `tests/test_dir.c` for a containment regression test -and `tests/test_pack_overlay.c` for a corrupted-pack rejection test. +and `tests/test_pack_overlay.c` for a corrupted-pack rejection test. See +[`SECURITY.md`](SECURITY.md) for exactly what is and isn't claimed as a +security boundary, and how to report a vulnerability. ## Contributing diff --git a/SECURITY.md b/SECURITY.md index 0f3536a..13d034e 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -61,10 +61,10 @@ known limitations, not silent gaps: ## 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: +This project is hosted on Gitea, not GitHub (see `CONTRIBUTING.md`). +Please do not open a public issue on the issue tracker for a suspected +security vulnerability — report it privately, by email, to the +maintainer: **retoor** — `retoor@molodetz.nl` @@ -82,5 +82,5 @@ 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. +`.gitea/workflows/ci.yml` (Gitea Actions) runs the full test suite plus +both sanitizer passes on every push.