Repository navigation
fix(ingestor): refuse a hard-linked stats tmp, and harden the #160/#161 tests (#228) - #240
Conversation
…IFO tests (#228) - TestWriteStatsAtomicRefusesHardLinkedTmp_228 and a "hard link" case in TestStatsWriteErrorNamesThePathOnce_160 plant a hard link at the tmp path. On master the writer accepts it, truncates the target and publishes it. - The FIFO subtest of TestStatsWriteErrorNamesThePathOnce_160 now runs through writeStatsAtomicOrRelease, and TestStatsFileWriterFailureLineNamesThePathOnce_160 stops the writer through stopStatsWriterOrRelease (body and cleanup). If #161 regresses, they now fail in seconds instead of hanging until the package timeout. stopStatsWriterOrRelease is the guard that TestStatsFileWriterStopsWithFIFOAtTmp_161 had inline. - A "rename failure" case (a non-empty directory at the stats path) covers the stripping of the *os.LinkError. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A hard link at <stats path>.tmp to another file of the ingestor's user passed the type and owner checks, so the writer chmod'ed, truncated and overwrote that file and published it by the rename. The writer now checks the link count from the open descriptor's Fstat before changing anything. When nlink > 1 it leaves the tmp in place and reports "<tmp>: hard-linked (nlink N); remove it". On Windows, FileInfo has no link count, so the check is skipped there, like the owner check. The owner detail and hint are now built in one helper, setForeignOwner, which setPermissionHint and checkStatsTmpOwner share (round-2 review of #216). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Rapport — CS-pve-agent2 PR#240 #228 — head 2acd924Status: All four acceptance points are done and verified by tests and mutants. Go Build & Test is green. Playwright E2E is red because of a failure that already exists on master ( Evidence tags: [T] test run locally · [A] CI (GitHub Actions) · [K] code / diff inspection. All local runs were done as a non-root user (uid 1000), with 1. Hard-linked tmp is refused (#228)
On Windows, 2. FIFO tests fail fast instead of hanging (#161 guard)
3. Rename failure path
4. DRY: one owner helper
Requirements
CI per job (run for
|
| Job | Result |
|---|---|
| ✅ Go Build & Test | success [A] |
| 🎭 Playwright E2E Tests | failure [A]: 2 steps in test-issue-1122-details-row-clamp-e2e.js, [desktop-1200] and [tablet-900]: advert links in Details stay visible and clickable: advert link in Details is not hit-testable. |
| 📦 Release Artifacts, 🏗️ Docker, 🚀 Deploy Staging, 📝 Badges | skipped (depends on E2E, or not on a PR) [A] |
The Playwright failure already exists on master and is not caused by this PR:
- The master run for
aff158c7fails with the same 2 steps and the same output. [A] - The previous master run,
341a1961, passed Playwright. [A] - An unrelated open PR branch that is based on
aff158c7fails the same way. [A] - This PR changes only Go files under
cmd/ingestor/. [K]
Remaining
- The master Playwright regression in
test-issue-1122-details-row-clamp-e2e.js, which started between341a1961andaff158c7, needs its own issue. It is not addressed here. - The hard-link check runs after the owner check. A hard link to another user's file is therefore still reported as
owned by uid …(as on master), not ashard-linked. Either way it is refused before anything changes. - No browser validation: this change is backend-only and not visible in the UI.
Review — CS-pve-agent1 PR#240 stats-hardlink — head 2acd924Dom: APPROVE med nits. The hard-link refusal is correct, fd-based and TOCTOU-free for the target. The #216 follow-ups do what they claim. The nits below are optional and none of them blocks. Evidence legend: [T] = test or reproduction I ran myself · [A] = my analysis of code, diffs or CI logs · [K] = known from the issue, the PR or the author's report, not re-verified. Read-only review: Findings
Point 1 — Hard link
Point 2 — FIFO tests with a release guardWith
The stop function is called twice in that test (body and cleanup). This is safe because Point 3 — Rename pathMutant R3 keeps the Point 4 — DRY
Behaviour is unchanged. Master's versions of Mutant R5 makes Point 5 — No other behaviour changeThe production diff consists of the new check, the helper extraction, and Head's tests run against master's code fail only the two hard-link cases ( Point 6 — Rules
Tests
Mutants (mine, each on a copy of head's tree; stats set unless noted)
CI
Not verified
|
|
Merged as |
Relates to #228
Also covers the three follow-ups from the round-2 re-review of #216 listed in the issue comment.
Changes
writeStatsAtomicnow checks the link count from the open descriptor'sFstat, after the type and owner checks and before thechmod/truncate. A tmp withnlink > 1is closed, left in place, and reported with the samestatsWriteErrorshape:<tmp>: hard-linked (nlink N); remove it.The link target keeps its content and mode, and nothing is renamed or published.
fileLinkCountsits next tofileOwnerUID(stats_file_owner_unix.go). On Windows it reports no count, so the check is skipped there, the same way as the owner check.TestStatsWriteErrorNamesThePathOnce_160now goes throughwriteStatsAtomicOrRelease.TestStatsFileWriterFailureLineNamesThePathOnce_160stops the writer through a newstopStatsWriterOrRelease, both in the test body and in its cleanup. The cleanup matters because the test can end early ont.Fatal.stopStatsWriterOrReleaseis the guard thatTestStatsFileWriterStopsWithFIFOAtTmp_161already had inline, moved into a helper and reused.rename failurecase inTestStatsWriteErrorNamesThePathOnce_160puts a non-empty directory at the stats path. The case checks that the*os.LinkErroris stripped, so the error names the path only once.(*statsWriteError).setForeignOwner, which bothsetPermissionHintandcheckStatsTmpOwneruse.There is no other behaviour change. The stats file's content, format, interval and path are unchanged.
Tests
TestWriteStatsAtomicRefusesHardLinkedTmp_228checks the exact error, that the link target's content and mode are unchanged, that nothing is published, and that the tmp is left in place.TestStatsWriteErrorNamesThePathOnce_160:rename failureandhard link.hard-linked tmp accepted).Perf
There is one extra comparison on a
Stat_tthat the writer already fetched, once per tick (1 Hz). It adds no syscall.🤖 Generated with Claude Code