feat(scheduler-core): implement recording directory provisioning and path validation (task 9.1)
Adds --record-dir CLI flag and sched_validate_record_filename() with canonical-path containment (realpath()-based, not textual-prefix) plus separator/".." rejection before any filesystem access. Adversarial review found and this task fixed two CWE-59 inode-aliasing bypasses: a direct hard link inside record_dir aliasing an outside file, and a compound variant where a symlink pointed at an in-directory file itself hard-linked outside record_dir. Both closed via st_nlink checks on the direct entry and the fully-resolved symlink target respectively, each verified with a live RED->GREEN reproduction. TOCTOU residual risk documented and deferred to task 9.2 (O_NOFOLLOW + fstat() re-check). 18/18 recording subtests, 24/24 scheduler suite, clang-format clean.
This commit is contained in:
parent
31ef5d7093
commit
4e5547ee55
8 changed files with 889 additions and 30 deletions
|
|
@ -216,12 +216,13 @@
|
|||
|
||||
- [ ] 9. Core: Recording Control
|
||||
|
||||
- [ ] 9.1 Implement recording directory provisioning and path validation
|
||||
- [x] 9.1 Implement recording directory provisioning and path validation
|
||||
- Add a scheduler startup CLI flag (parsed in the same argument-parsing pass as the rest of normal-mode's flags) for the recording directory — never settable from any request-path field — defaulting to a fixed system path
|
||||
- Validate any caller-supplied record file name before ever opening it for writing: reject any value containing a path separator or a `..` segment, resolve the final path beneath the recording directory, and confirm containment before opening
|
||||
- Observable: a record-file name containing a path separator or `..` is rejected before any filesystem access occurs; a bare filename resolves to a path inside the recording directory and is accepted
|
||||
- _Requirements: 5.10_
|
||||
- _Depends: 8.1_
|
||||
- _Verified: 2026-08-04 — `--record-dir` CLI flag added to main.c/scheduler.h (record_dir field, never a request-path field). sched_validate_record_filename() (new recording.c/recording.h) rejects any `filename` containing `/` or `..` before any filesystem call, then resolves via realpath() and confirms canonical (not textual-prefix) containment beneath record_dir via path_is_contained(); rejects pre-existing non-regular entries (-EEXIST) and a non-directory record_dir (-ENOTDIR). Adversarial security review found and this task fixed two real CWE-59 inode-aliasing bypasses across 3 rounds: round 1 found a direct hard link inside record_dir aliasing an outside file passed validation (S_ISREG doesn't distinguish a hard-linked regular file from a genuine one) — fixed with an entry_st.st_nlink > 1 check (-EACCES) in the direct-entry branch, live RED→GREEN reproduced. Round 2 re-review found a compound variant still open: a symlink inside record_dir pointing at an in-directory file itself hard-linked outside record_dir was still accepted, since the S_ISLNK branch checked containment but never st_nlink on the resolved target — fixed by stat()'ing the already-realpath()-resolved target and applying the same S_ISREG && st_nlink>1 check, generalizing correctly to multi-hop symlink chains, live RED→GREEN reproduced. Round 3 re-review independently reproduced both fixes via a standalone PoC harness and additionally hunted for further variants (2-hop chains, symlink-to-symlink escape, FIFO/socket direct-naming) — all clean, no new bypass found; one non-blocking observation logged (symlink resolving to a contained-but-non-regular target is accepted rather than rejected, pre-existing since round 1, not the CWE-22/CWE-59 escape class, already covered by task 9.2's planned O_NOFOLLOW hardening) — APPROVED. TOCTOU residual risk (validation-to-eventual-fopen() window) explicitly documented in recording.h and deferred to task 9.2 (O_NOFOLLOW + fstat()-based st_nlink==1 re-check on the opened fd), not this task's scope. 18/18 recording subtests (16 original + 2 hard-link/compound regressions), 24/24 scheduler suite, clang-format clean — all independently re-verified by me via a fresh `meson compile`/`meson test --suite scheduler` run on current HEAD before marking complete._
|
||||
|
||||
- [ ] 9.2 Implement the record start/stop lifecycle
|
||||
- On start: take a full snapshot, write the replay header followed by the initial snapshot entry, begin tracking recording state
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue