132 lines
7.0 KiB
Markdown
132 lines
7.0 KiB
Markdown
---
|
|||
|
|
phase: 01-federation-mesh-hardening
|
||
|
|
plan: 20
|
||
|
|
subsystem: infra
|
||
|
|
tags: [tor, doctor, shell, mesh, permissions]
|
||
|
|
|
||
|
|
requires:
|
||
|
|
- phase: 01-federation-mesh-hardening
|
||
|
|
provides: "scripts/container-doctor.sh's fix_* convention and the archipelago-doctor.timer (OnUnitActiveSec=5min) that drives it"
|
||
|
|
provides:
|
||
|
|
- "A permission predicate that tests the property that matters (group/other denied) instead of exact-matching one octal string"
|
||
|
|
- "A 30-minute Tor restart backoff, so a restart storm is structurally impossible even if some future condition makes the fix fire repeatedly"
|
||
|
|
affects: [tor, mesh, doctor]
|
||
|
|
|
||
|
|
tech-stack:
|
||
|
|
added: []
|
||
|
|
patterns:
|
||
|
|
- "Assert the property, not the representation: `2700` and `700` both deny group and other, which is the security property. Exact-matching the string `700` turned Tor's own setgid bit into a permanent false positive."
|
||
|
|
- "Pair a predicate fix with a rate limit: the predicate stops today's loop, the backoff stops the whole class of loop. One is a fix, both is a guarantee."
|
||
|
|
|
||
|
|
key-files:
|
||
|
|
created: []
|
||
|
|
modified:
|
||
|
|
- scripts/container-doctor.sh
|
||
|
|
|
||
|
|
key-decisions:
|
||
|
|
- "Compare the low three digits of `stat -c '%a'` rather than the whole string, so both `700` and `2700` read as correct while `750`, `707` and `2755` are still corrected."
|
||
|
|
- "The 'already correct' path logs at debug level only. The original defect was invisible precisely because 'Fixed permissions on … (2700 -> 700)' looked like the doctor working — a healthy node should stay quiet."
|
||
|
|
- "Backoff state lives in `/var/lib/archipelago/doctor-tor-last-restart` with a 1800s window, and non-numeric/missing content reads as 0 so a corrupt state file fails open into 'restart allowed' rather than wedging the fix permanently."
|
||
|
|
|
||
|
|
requirements-completed: [FED-09]
|
||
|
|
|
||
|
|
coverage:
|
||
|
|
- id: D1
|
||
|
|
description: "The doctor no longer restarts Tor on every run — Tor uptime grows past the doctor's 5-minute interval"
|
||
|
|
requirement: "FED-09"
|
||
|
|
verification:
|
||
|
|
- kind: manual_procedural
|
||
|
|
ref: "archi-dev-box 2026-08-02: tor@default active since 2026-08-01 15:25:45 — 15+ hours continuous, spanning ~180 doctor intervals"
|
||
|
|
status: pass
|
||
|
|
human_judgment: false
|
||
|
|
- id: D2
|
||
|
|
description: "A hidden-service directory at Tor's own 2700 is recognised as correct — no chmod, no restart"
|
||
|
|
requirement: "FED-09"
|
||
|
|
verification:
|
||
|
|
- kind: other
|
||
|
|
ref: "Predicate exercised directly on tmpdirs: 2700 -> low3=700 -> ACCEPTED; 700 -> ACCEPTED"
|
||
|
|
status: pass
|
||
|
|
- kind: manual_procedural
|
||
|
|
ref: "archi-dev-box: zero 'Fixed permissions … hidden_service' lines across 542 doctor log entries in 6 hours"
|
||
|
|
status: pass
|
||
|
|
human_judgment: false
|
||
|
|
- id: D3
|
||
|
|
description: "A genuinely insecure directory (group/other readable) is still corrected"
|
||
|
|
requirement: "FED-09"
|
||
|
|
verification:
|
||
|
|
- kind: other
|
||
|
|
ref: "Predicate exercised on 750, 707 and 2755 — all three fall through to the corrective chmod"
|
||
|
|
status: pass
|
||
|
|
human_judgment: false
|
||
|
|
- id: D4
|
||
|
|
description: "Even on a real fix, Tor cannot be restarted more than once per backoff window"
|
||
|
|
requirement: "FED-09"
|
||
|
|
verification:
|
||
|
|
- kind: other
|
||
|
|
ref: "TOR_RESTART_BACKOFF_SECONDS=1800 gates the restart against TOR_RESTART_STATE_FILE; non-numeric state reads as 0"
|
||
|
|
status: pass
|
||
|
|
human_judgment: false
|
||
|
|
- id: D5
|
||
|
|
description: "Tor retains its consensus/HSDir cache long enough to resolve .onion addresses"
|
||
|
|
requirement: "FED-09"
|
||
|
|
verification:
|
||
|
|
- kind: manual_procedural
|
||
|
|
ref: "Post-OTA checkpoint on an affected node — NOT RUN (see below). 15h of unbroken Tor uptime on archi-dev-box is the necessary precondition and is met."
|
||
|
|
status: deferred
|
||
|
|
human_judgment: true
|
||
|
|
|
||
|
|
duration: 30min
|
||
|
|
completed: 2026-08-02
|
||
|
|
status: complete-pending-post-ota-checkpoint
|
||
|
|
---
|
||
|
|
|
||
|
|
# Phase 1 Plan 20: Stop the Doctor Fighting Tor Over the Setgid Bit (FED-09) Summary
|
||
|
|
|
||
|
|
**The container doctor treated Tor's own `2700` hidden-service directory as a permission defect, "fixed" it, and restarted Tor — every five minutes, forever. Tor never lived long enough to build a usable HSDir cache, so onion lookups failed and the mesh's Tor fallback died with it.**
|
||
|
|
|
||
|
|
## Performance
|
||
|
|
|
||
|
|
- **Duration:** ~30 min (code committed 2026-07-31 as `4435f95e`; verification and this SUMMARY 2026-08-02)
|
||
|
|
- **Tasks:** 1/2 (Task 2 is a post-OTA checkpoint — see below)
|
||
|
|
- **Files modified:** 1
|
||
|
|
|
||
|
|
## Accomplishments
|
||
|
|
|
||
|
|
- The predicate now compares the low three digits of `stat -c '%a'`, so Tor's setgid `2700` and a plain `700` both read as correct, while `750`, `707` and `2755` are still corrected. Verified directly against all five modes.
|
||
|
|
- A 30-minute restart backoff (`/var/lib/archipelago/doctor-tor-last-restart`) makes a restart storm impossible even if some future condition makes the fix fire repeatedly. The predicate fixes today's bug; the backoff retires the class.
|
||
|
|
- The "already correct" branch logs at debug level so a healthy node stays quiet — the original defect hid inside a log line that read like success.
|
||
|
|
|
||
|
|
## Evidence on a real node (archi-dev-box, 2026-08-02)
|
||
|
|
|
||
|
|
- `tor@default` active since **2026-08-01 15:25:45** — over 15 hours continuous, spanning roughly 180 doctor intervals. Before the fix, observed restarts were 13:07:56 → 13:13:14 → 13:18:39 → 13:23:57, each within a second of a timer firing.
|
||
|
|
- **Zero** `Fixed permissions … hidden_service` lines across **542** doctor log entries in the preceding 6 hours.
|
||
|
|
|
||
|
|
## Deviations from Plan
|
||
|
|
|
||
|
|
None. The implementation matches the plan's action exactly: predicate corrected at the property level, backoff added, logging made quiet-when-healthy.
|
||
|
|
|
||
|
|
**Process note:** the code landed on 2026-07-31 as `4435f95e` but no SUMMARY was written and the roadmap entry was never ticked, so the plan looked unstarted for two days. That is the bookkeeping gap this file closes.
|
||
|
|
|
||
|
|
## Known Stubs
|
||
|
|
|
||
|
|
None.
|
||
|
|
|
||
|
|
## Threat Flags
|
||
|
|
|
||
|
|
- The plan's prohibition — never loosen hidden-service permissions — holds: group and other access is still denied on every path. `2700` is accepted precisely *because* it denies them; the setgid bit is orthogonal to that property.
|
||
|
|
- **Scope boundary respected:** the FIPS direct-transport `connect_fail` (problem 1 in the diagnosis) was deliberately NOT touched here. It belongs with FED-03's structured review of the transport/dial layer.
|
||
|
|
|
||
|
|
## Outstanding: post-OTA checkpoint (Task 2)
|
||
|
|
|
||
|
|
Task 2 verifies on an **affected** node after the release lands, and explicitly forbids deploying
|
||
|
|
directly to a user's device (`archy-x250-mad2` or any node that is not ours) — verification is
|
||
|
|
post-OTA only. Remaining there:
|
||
|
|
|
||
|
|
1. Tor uptime keeps growing across three doctor intervals (met on archi-dev-box; needs repeating on an affected node)
|
||
|
|
2. No recurring "Fixed permissions" lines (met on archi-dev-box)
|
||
|
|
3. Onion resolution works — a mesh send to a Tor-only peer succeeds and `No more HSDir available to query` is gone
|
||
|
|
4. The doctor still corrects a deliberately `chmod 750`'d hidden-service directory
|
||
|
|
|
||
|
|
Items 3 and 4 are the ones genuinely outstanding; 1 and 2 already have real-node evidence.
|
||
|
|
</content>
|