Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .agents/skills/atlas/assets/seed/catalog.json

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"citations":[{"path":"docs/coordination/diagnostic-execution.md","repository":"FS-GG/.github","revision":"1fac8e920392b9dca50b8ae315bcadb36216cd3f","sha256":"7d1f1bcdd177c826fe1a38d58cb1a710956ac96a8bb93cedb482be736da2ed92"}],"conflictsWith":[],"id":"environment/host-root-cannot-create-in-rootless-container-tree","kind":"lesson","recordedAt":"2026-10-23T13:13:26Z","repository":"FS-GG/.github","schema":"fsgg.atlas/1","status":"recorded","summary":"Observed 2026-10-23 during SC2 reserved-storage rehearsal r3 (#4746; #4516 comments 6363630329, 6363830442, 6363866613, 6363978038). Host-root `os.mkdir('fsgg-sc2-reserved', 0o711, dir_fd=<guest /var/tmp>)` failed with `OSError: [Errno 75] Value too large for defined data type` (EOVERFLOW).\n\nCause (kernel rule): the source comment names `may_o_create()` in fs/namei.c, which is the `open(O_CREAT)` path; `mkdir` goes through the equivalent creation check in the same file (`may_create_dentry()`, `may_create()` in older kernels), checked against upstream source in this PR's review. Both return -EOVERFLOW when the caller's fsuid/fsgid has no mapping in the target filesystem's user namespace (`fsuidgid_has_mapping`). A rootless Podman container's rootfs (overlay) is mounted inside the container's user namespace (here `uid_map 0 100000 65536`), and host uid 0 is not mapped there. So no host-root-owned object can ever be created in a rootless guest's tree, including the fdev container's `/var/tmp`. The design point `parent is host-root 0711` was infeasible; production would have failed identically. The same defect applied to the binder's per-operation leaf, which was created by host root with a `uid == 0` check one step after the parent. Removal is different: the delivery comment states that the uninstall's guest-leaf `rmdir` through nsenter as host root stays permitted, because it creates nothing and needs only CAP_DAC_OVERRIDE over inodes whose ids are mapped in the init namespace (asserted by the author and not challenged in review; rehearsal r4 then ran its guest-leaf `rmdir` with rc 0, #4516 comment 6363978038, accepted in 6363985779).\n\nRule (option A, decided and delivered as C1 r1d plus the binder): create the object as the guest's root. Read `uid_map`/`gid_map` relative to a held `/proc/<pid>` fd; require exactly one row with inside id 0, count >= 1 and outer id != 0; take that outer id (here 100000:100000). In a forked child holding the directory fd, call `setfsgid` then `setfsuid` (libc), confirm each by a second call returning the new value (else refuse), `mkdirat(dir_fd, name, mode)`, open the directory and `fchmod(mode)` (this beats the umask), `os._exit`, and return errno over a pipe so EEXIST stays `FileExistsError`. The caller's credentials never change, and any child crash, signal or failure refuses. Owner checks change from `uid == 0` to `uid == outer(guest 0)` (parent 0711, leaf 0700); host root and the unprivileged developer owner are refused. Alternatives considered: B, a target outside the overlay (needs a container recreate, not available for the live container); C, `podman exec --user root mkdir` (equal ownership, but outside the fd chain).\n\nTrust note: only the guest's root can rename or replace objects so created, which is the engine owner's `podman exec --user root`, the same boundary the identity-proof helper already accepts; the unprivileged guest user cannot write the 0711 parent. The accepted closure of #4746 also records that the guest `developer` account has in-container sudo, so that protection against agents inside the guest is cooperative, not adversarial: an agent could deny service but not escape the container boundary. Review accepted the residual that `fchmod` reopens by name, so guest root could swap the leaf in between; the identity and mount-id pins catch it. Binder pins fix the parent identity and mount id at bind time, and a parent swap after the bind does not move the mount.\n\nLimits: observed with Podman rootless on one host, a single-range uid_map and a kernel of the Home host; unit tests cannot prove the real root-in-userns path, which rests on rehearsal r4 (#4516 comment 6363978038) and the production install reported in the #4746 closure. A different uid_map shape (multiple rows, or inside 0 unmapped) is refused, not handled. Evidence scripts and hashes stay in Home custody. The cited document supplies only the generic diagnostic-execution failure-custody expectation. No operation is authorized by this record.","supersedes":[],"tags":["lesson","podman","rootless","userns","eoverflow","setfsuid","overlay","uid-map","trust","fdev","sc2","home"],"title":"Host root cannot create objects in a rootless container's tree (EOVERFLOW); create as the guest's mapped root via setfsuid in a forked child"}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"citations":[{"path":"docs/coordination/diagnostic-execution.md","repository":"FS-GG/.github","revision":"1fac8e920392b9dca50b8ae315bcadb36216cd3f","sha256":"7d1f1bcdd177c826fe1a38d58cb1a710956ac96a8bb93cedb482be736da2ed92"}],"conflictsWith":[],"id":"environment/libmount-fd-mount-proc-fd-target","kind":"lesson","recordedAt":"2026-10-23T13:13:26Z","repository":"FS-GG/.github","schema":"fsgg.atlas/1","status":"recorded","summary":"Observed 2026-10-23 on the Home host (util-linux/libmount 2.42.4, `mount --version` lists `fd-based-mount`) during SC2 reserved-storage rehearsal (#4746; #4516 comments 6363317030 and 6363451287). A helper that ran `mount --internal-only --no-canonicalize --types ext4 --options nodev,nosuid,nodiscard -- /dev/loopN /proc/self/fd/<target_fd>` (to pin the target by a held fd) failed with the generic rc 32 `wrong fs type, bad option, bad superblock ... missing codepage or helper program`, although the kernel log showed `EXT4-fs: mounted filesystem` immediately followed by `unmounting filesystem`.\n\nCause (from `LIBMOUNT_DEBUG=all`): libmount 2.42 mounts through the new API; `fsmount` succeeds and creates a detached mount, then `move_mount(to=/proc/self/fd/9)` fails with EINVAL (the `/proc/self/fd/N` magic link is not followed, so the target is the link itself). The detached mount is released on close, which is the kernel's immediate `unmounting`, and libmount reports rc 32. The earlier hypothesis that libmount re-checks the target by path was wrong and was corrected by the debug output. Diagnosis was a scratch-only run (64 MiB image) with four cases: the helper's exact call (rc 32), the same with debug (32), `LIBMOUNT_FORCE_MOUNT2=always` (classic mount(2), rc 0) and a plain path target (rc 0).\n\nRule: do not pass a `/proc/self/fd/N` magic link as the mount target to libmount with fd-based mounts. Mount on the path of a fixed, root-controlled target directory and compensate with identity checks. fdev preferred this path-target fix because it does not depend on a libmount environment knob (#4516 comment 6363337547), and kept `LIBMOUNT_FORCE_MOUNT2=always` (which also worked) as the fallback only if the path target could not be made race-safe. Delivered as SC2 helper r3 (#4516 comments 6363512123, admitted in 6363552165): before the mount, hold an `O_RDONLY|O_DIRECTORY|O_NOFOLLOW` fd on the target directory (created relative to the held operation-directory fd), require the operation directory's `lstat` to equal its recorded identity and `lstat(path)` to equal the held fd's identity; run the mount with the path as argv and no fd passed; after it, require `findmnt --mountpoint` to show exactly one ext4 mount with the loop's maj:min, flags and fresh UUID, the operation directory's identity unchanged, the re-opened mounted root's `st_dev` equal to the loop, and a `covers()` check that the held pre-mount fd's identity is unchanged while its `st_dev` differs from the mounted root's, proving the mount covers exactly that directory. Any failure takes the fail-closed path, which retains everything reached.\n\nResidual accepted in review: every path component is root-owned 0700, so only root can swap the target; the before and after checks detect such a swap, they do not prevent it, and review accepted it under the cooperative model.\n\nDiagnostic tip: a generic mount rc 32 with a kernel log showing a successful mount followed at once by an unmount means the failure is after the mount syscall; read `LIBMOUNT_DEBUG=all` before changing the filesystem or device options.\n\nLimits: only 2.42.4 was observed; the version where this began and whether later releases change it are not established. Other callers of `move_mount` with magic-link targets were not tested. The cited document only supplies the repository's generic failure-custody expectation for diagnostic execution; the findings rest on the cited issue comments, whose scratch log and scripts stay in Home custody. This is not authority for any mount.","supersedes":[],"tags":["lesson","libmount","util-linux","mount","move-mount","proc-self-fd","einval","fail-closed","sc2","home"],"title":"libmount 2.42.4 fd-based mounts cannot target /proc/self/fd magic links; mount on a path target with held-fd identity checks before and after"}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"citations":[{"path":"docs/coordination/diagnostic-execution.md","repository":"FS-GG/.github","revision":"1fac8e920392b9dca50b8ae315bcadb36216cd3f","sha256":"7d1f1bcdd177c826fe1a38d58cb1a710956ac96a8bb93cedb482be736da2ed92"}],"conflictsWith":[],"id":"environment/sc2-reserved-storage-delivered","kind":"contract","recordedAt":"2026-10-23T13:13:26Z","repository":"FS-GG/.github","schema":"fsgg.atlas/1","status":"recorded","summary":"Accepted 2026-10-23 as #4746 complete with gaps after independent closure review (https://github.com/FS-GG/.github/issues/4746#issuecomment-6365472089, which supersedes the stale `Blocked` body sections). This is a pointer to the acceptance, not a replacement: the issue and its comments remain canonical, and the record proves no current deployment.\n\nDelivered: SC2 reserved storage installed on Home and bound into the `fsharp-dev` container as operation `e995b57ee030f4b8b9d2f61df5ab4a76`. Route: the Home bounded-execution route selected by the #4777 decision (storage helper plus frozen profile `eb838b6c`, hash-pinned single-operation sudoers, prepare as a system unit, bind in a worker scope), with the original 580+20 s profile. Installed pins: profile `eb838b6c`, helper r3 `fff15283`, binder `8daf288b`, C1 `efc7719c`. Only three things changed from the frozen sources, each under a scoped fdev decision: the identity proof (helper r2, host-side `podman inspect` joined to `/proc` start ticks and the mount-namespace inode, because Podman does not populate `.containerenv` for unprivileged containers; the #4777 amendment, comment 6362009891, changed only this proof and left the profile and route unchanged, `--privileged` rejected), the path-target mount (r3; see environment/libmount-fd-mount-proc-fd-target) and guest-root creation (r1d; see environment/host-root-cannot-create-in-rootless-container-tree). I/O was accepted as a coverage-limit statement (#4516 comment 6360981315): bandwidth and IOPS stay unbounded on the shared HDD and the workload side is unbounded; capacity is bounded by the 256 GiB image and the inode table, and only the prepare unit gets IOWeight 50. Source: SystemAdmin PRs 166 and 167 merged; CI rests on Home's report because the repository was not readable from fdev.\n\nMeasured at acceptance: backing 274877906944 B; filesystem 273185574912 B (>= 208 GiB required); 2097138 free inodes (>= 1048576); one clock covered prepare and bind (`bound` at +3.21 s); 11 of 16 commands, 37-181 B each. Limits: prepare unit 100% CPU / 1G / 64 tasks / IOWeight 50; bind worker scope 50% / 64M / 8 tasks. Failure custody: `reclaim: prohibited`, `retention: until-explicit-disposition`, empty production `first-failure.json`; rehearsals r1 and r3 proved fail-closed retention and admitted cleanup. Exactly one new fdev mount; probe write/read OK and the parent not writable by the developer.\n\nGaps: (1) sudoers lifecycle: removal of the install line was admitted in the closure and reported done at 13:04Z (#4516 comment 6365545492); the uninstall line is kept for this operation only as the user-confirmed disposition route, runs only under a separate fdev admission and is removed after disposition. (2) Rebind and persistence were not delivered: no host `.mount` unit and no rebind verb, so an fdev restart loses the guest bind and a host reboot loses the loop and mount (the backing is retained); moved to #4650 (restart and reboot journeys), owner Home (#4650 comment 6365472594); gaps 1, 3 and 4 stay where they are. (3) Unchanged and out of scope: SC2.Client#49 image and native work, and the NVMe / 12 GiB / 6 CPU Unknown holds. (4) Accepted trust limits: the store owner (host uid 101000) is shared by all fdev agents and `developer` has in-container sudo, so isolation is the container boundary. (5) The two Atlas lessons named above were owed by fdev and are these records.\n\nVerify the live mount, pins and any uninstall admission with their owner before relying on this record. The cited document is the generic diagnostic-execution failure-custody expectation; the facts above rest on the cited issue comments.","supersedes":[],"tags":["contract","decision","sc2","reserved-storage","home","fdev","profile","closure","gaps"],"title":"SC2 reserved storage as delivered (#4746): op e995b57e, frozen profile eb838b6c, Home bounded route; rebind/persistence gap moved to #4650"}
2 changes: 1 addition & 1 deletion .claude/skills/atlas/assets/seed/catalog.json

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"citations":[{"path":"docs/coordination/diagnostic-execution.md","repository":"FS-GG/.github","revision":"1fac8e920392b9dca50b8ae315bcadb36216cd3f","sha256":"7d1f1bcdd177c826fe1a38d58cb1a710956ac96a8bb93cedb482be736da2ed92"}],"conflictsWith":[],"id":"environment/host-root-cannot-create-in-rootless-container-tree","kind":"lesson","recordedAt":"2026-10-23T13:13:26Z","repository":"FS-GG/.github","schema":"fsgg.atlas/1","status":"recorded","summary":"Observed 2026-10-23 during SC2 reserved-storage rehearsal r3 (#4746; #4516 comments 6363630329, 6363830442, 6363866613, 6363978038). Host-root `os.mkdir('fsgg-sc2-reserved', 0o711, dir_fd=<guest /var/tmp>)` failed with `OSError: [Errno 75] Value too large for defined data type` (EOVERFLOW).\n\nCause (kernel rule): the source comment names `may_o_create()` in fs/namei.c, which is the `open(O_CREAT)` path; `mkdir` goes through the equivalent creation check in the same file (`may_create_dentry()`, `may_create()` in older kernels), checked against upstream source in this PR's review. Both return -EOVERFLOW when the caller's fsuid/fsgid has no mapping in the target filesystem's user namespace (`fsuidgid_has_mapping`). A rootless Podman container's rootfs (overlay) is mounted inside the container's user namespace (here `uid_map 0 100000 65536`), and host uid 0 is not mapped there. So no host-root-owned object can ever be created in a rootless guest's tree, including the fdev container's `/var/tmp`. The design point `parent is host-root 0711` was infeasible; production would have failed identically. The same defect applied to the binder's per-operation leaf, which was created by host root with a `uid == 0` check one step after the parent. Removal is different: the delivery comment states that the uninstall's guest-leaf `rmdir` through nsenter as host root stays permitted, because it creates nothing and needs only CAP_DAC_OVERRIDE over inodes whose ids are mapped in the init namespace (asserted by the author and not challenged in review; rehearsal r4 then ran its guest-leaf `rmdir` with rc 0, #4516 comment 6363978038, accepted in 6363985779).\n\nRule (option A, decided and delivered as C1 r1d plus the binder): create the object as the guest's root. Read `uid_map`/`gid_map` relative to a held `/proc/<pid>` fd; require exactly one row with inside id 0, count >= 1 and outer id != 0; take that outer id (here 100000:100000). In a forked child holding the directory fd, call `setfsgid` then `setfsuid` (libc), confirm each by a second call returning the new value (else refuse), `mkdirat(dir_fd, name, mode)`, open the directory and `fchmod(mode)` (this beats the umask), `os._exit`, and return errno over a pipe so EEXIST stays `FileExistsError`. The caller's credentials never change, and any child crash, signal or failure refuses. Owner checks change from `uid == 0` to `uid == outer(guest 0)` (parent 0711, leaf 0700); host root and the unprivileged developer owner are refused. Alternatives considered: B, a target outside the overlay (needs a container recreate, not available for the live container); C, `podman exec --user root mkdir` (equal ownership, but outside the fd chain).\n\nTrust note: only the guest's root can rename or replace objects so created, which is the engine owner's `podman exec --user root`, the same boundary the identity-proof helper already accepts; the unprivileged guest user cannot write the 0711 parent. The accepted closure of #4746 also records that the guest `developer` account has in-container sudo, so that protection against agents inside the guest is cooperative, not adversarial: an agent could deny service but not escape the container boundary. Review accepted the residual that `fchmod` reopens by name, so guest root could swap the leaf in between; the identity and mount-id pins catch it. Binder pins fix the parent identity and mount id at bind time, and a parent swap after the bind does not move the mount.\n\nLimits: observed with Podman rootless on one host, a single-range uid_map and a kernel of the Home host; unit tests cannot prove the real root-in-userns path, which rests on rehearsal r4 (#4516 comment 6363978038) and the production install reported in the #4746 closure. A different uid_map shape (multiple rows, or inside 0 unmapped) is refused, not handled. Evidence scripts and hashes stay in Home custody. The cited document supplies only the generic diagnostic-execution failure-custody expectation. No operation is authorized by this record.","supersedes":[],"tags":["lesson","podman","rootless","userns","eoverflow","setfsuid","overlay","uid-map","trust","fdev","sc2","home"],"title":"Host root cannot create objects in a rootless container's tree (EOVERFLOW); create as the guest's mapped root via setfsuid in a forked child"}
Loading
Loading