Skip to content

Clear special bits when hardening Windows VM dirs - #12019

Closed
ram-devv1 wants to merge 1 commit into
omacom:quattrofrom
ram-devv1:fix/windows-vm-setgid-chmod
Closed

ram-devv1 wants to merge 1 commit into
omacom:quattrofrom
ram-devv1:fix/windows-vm-setgid-chmod

Conversation

@ram-devv1

Copy link
Copy Markdown

Fixes #11940, fixes #9630.

Numeric chmod preserves setuid/setgid on directories (man chmod), so a 2700 ~/Windows source survived the bare chmod 0700 preflight in prepare_caller_mounts and tripped the == 700 guard on every launch, with no diagnostic. The setgid bit is re-applied each session by the guest Samba share, so this recurred after every VM run.

  • Clear special bits explicitly with chmod u-s,g-s after the numeric hardening, in both prepare_caller_mounts and prepare_user_mount_sources.
  • Emit the actual modes on the mismatch branch instead of failing silently.

Verified: 2700 -> 700 through the same /proc FD path; windows-vm-test and windows-vm-compose-test pass.

@llstrk

llstrk commented Sep 27, 2026

Copy link
Copy Markdown

Automated AI review

Community review: Independent automated community review, unaffiliated with the Omarchy team, intended to help prepare PRs for their review.

Verified: in the tested scope, the change clears set-ID bits on both hardening paths. The existing Windows VM tests that ran still pass, but they also pass without the fix, so a regression test would be useful.

Both hardening paths now reach mode 700 from set-ID sources, and the exact == 700 checks pass.

  • GNU chmod keeps setuid/setgid on a directory for a numeric mode of fewer than five digits (gnulib lib/modechange.c, lines 156-158 at the coreutils 9.11 submodule). The added chmod u-s,g-s clears them. Through the same /proc/$BASHPID/fd/N path, on coreutils 9.11:

    Start mode after chmod 0700 after chmod u-s,g-s
    2700 2700 700
    2777 2700 700
    4700 4700 700
    6777 6700 700
    1700 (sticky) 700 700
  • Running the script's own functions in a private user and mount namespace (non-root development mode):

    Case merge-base head
    __priv_write_compose, shared source 2777 fails (rc 2), nothing mounted succeeds, both sources 700
    prepare_caller_mounts then mounts_ready, shared 2700 fails succeeds
    anchors mounted, then shared set to 2777 (the dockur case), then prepare_caller_mounts fails, mounts_ready still fails succeeds, mounts_ready passes
    migrate_legacy_compose (reached from launch, status, stop and remove), ~/Windows 2700 "Could not migrate..." migrates, both 700
    storage 4700 (setuid) fails succeeds
  • If the new chmod fails in prepare_caller_mounts, both descriptors are closed and nothing is mounted, as with the existing chmod. prepare_user_mount_sources returns non-zero if either chmod fails. With the symbolic chmod forced to do nothing (fault injection), the new message prints the actual modes where the merge-base printed nothing.

  • The only other exact-mode check, mounted_leaf_matches, runs after prepare_caller_mounts has re-hardened the same directories on every root (pkexec) path (launch, install, remove, compose migration). The docker-group fast path runs it earlier as a precheck and falls back to pkexec when it fails. Other chmod sites either check only & 022 or never re-check the mode.

  • The patch applies cleanly to current quattro and gives the same results there.

No test covers the set-ID case

windows-vm-test.sh and windows-vm-compose-test.sh, cited in the description, pass unchanged on the merge-base and on current quattro, because none of their cases uses a set-ID directory. windows-vm-test.sh only greps files (the script and its Hyprland rule); it never runs the script.

Impact: nothing in the suite would catch a later change back to a numeric-only chmod.

Suggested change: a section like this at the end of windows-vm-compose-test.sh fails on the merge-base and on current quattro, and passes with this PR:

# dockur marks an empty /shared setgid (2777) at container start, and a numeric
# chmod keeps that bit on a directory. Both hardening steps must clear it.
reset_case
mkdir -p "$HOME/.windows" "$HOME/Windows"
chmod 2777 "$HOME/Windows"
prepare_user_mount_sources
[[ $(stat -Lc '%a' "$HOME/Windows") == 700 ]] || fail "user-side hardening kept the setgid bit"
write 4G 2 64G setgid pw UTC
chmod 2777 "$HOME/Windows"
prepare_caller_mounts || fail "privileged preflight rejected a setgid shared source"
[[ $(stat -Lc '%a' "$HOME/Windows") == 700 ]] || fail "privileged preflight kept the setgid bit"
mounts_ready || fail "re-hardened mounts were rejected"
pass "both hardening steps clear the setgid bit dockur re-applies to the shared source"

On the merge-base it stops at the user-side assertion. The root-side half was checked separately: it fails on the merge-base.

Linked issues: #11940 and #9630 were closed as duplicates of #9334, which is still open. Adding Fixes #9334 would close the tracking issue on merge.

Related PRs: several open PRs change the same chmod lines, some with set-ID tests: #9605, #10338, #10411, #12264, #12323, #13154 and #13213. #10113 takes the opposite approach: it relaxes mounted_leaf_matches and the preflight check to accept any owner-rwx mode, including 2777, instead of clearing the bits. It conflicts textually with this PR. If both approaches were combined, a 2777 shared source would pass mounts_ready, so the direct docker-group path would skip re-hardening.

Wording: dockur's samba.sh (master 1e99833c) sets the mode with chmod 2777 (lines 139 and 159) only when the share is empty at container start (lines 112-114). "Inheriting setgid" in the code comment and "re-applied each session" in the description are therefore slightly off. A non-empty ~/Windows keeps its mode.

Optional: the new message could name $LEGACY_STORAGE and $LEGACY_SHARED (the ~/.windows and ~/Windows paths users know) instead of storage=/shared=, and show something like unknown when stat fails.


Review information

Test scope: pinned head c638b62 against merge-base f2b419d, and current quattro (3faafba) with and without the patch. The repository's Windows VM tests and namespace probes of the script's functions ran in an isolated runtime with GNU coreutils 9.11, non-root. Tests used a synthetic home directory. windows-vm-mount-boundary-test.sh needs subordinate ID mapping and skipped, so the root (pkexec) path was checked by reading the code: the chmod block has no root-specific branch. No real Docker, dockur container, VM or pkexec was used. dockur's behavior comes from its master source; the untagged image users pull was not pinned.

AI process: Opus 5.5 Medium coordination and synthesis, Opus 5.5 Xhigh technical review and final fact check, GPT 6 Sol Xhigh search for related issues, Opus 5.5 Medium editorial check.

Opt out: To stop receiving these reviews, reply to this comment saying so.

@omarchybot

Copy link
Copy Markdown
Collaborator

Thanks for tracking this down. The cause you found is right, and the bug is still on quattro: bin/omarchy-windows-vm still hardens both mount sources with a bare chmod 0700 at lines 583 and 1018, which keeps a directory's setgid bit, so the exact-700 check that follows rejects ~/Windows once the guest has marked it 2777. Your change does clear the bits at both sites, and printing the actual modes on a mismatch is a real improvement over the silent failure.

Nine open pull requests fix this one bug (tracked in #9334): this one, #9605, #10113, #10338, #10411, #12264, #12323, #13154 and #13213. Only one of them should be brought to the maintainer as ready, and this isn't the one we're recommending, for two reasons:

The fix we're recommending to the maintainer is #9605. It clears the bits in one call at both mount-source sites and also at the credentials directory, which is hardened the same way at line 1061, and its test covers both the user and the root path. #9605 was opened by this bot account, so the maintainer should weigh that. #12323 has the most thorough root-path tests of the contributor pull requests and is the closest alternative. The choice is the maintainer's, and nothing has been marked ready for this bug yet.

This was checked by Claude Opus 5.5, which compared all nine diffs against quattro at 8b4eae6. Codex at medium effort was asked separately which fix to bring forward, without being told the first answer. It ranked #9605 first and #12323 second, and also raised the second-step hazard above. That second opinion may have been able to read this session, so its independence is not guaranteed. Nothing was run on a worker, because a fix that is not being brought forward is not verified separately. This is now waiting on the maintainer.

@omarchybot

Copy link
Copy Markdown
Collaborator

Closing this alternative after #12323 merged into quattro. The merged fix explicitly clears setuid/setgid in both mount-source hardening paths, keeps the exact mode-700 boundary, adds a diagnostic, and covers this launch/install failure. This alternative is no longer needed for that bug.

Scope checked by GPT-6 in Codex and Codex Medium against the discussion and landed change; review independence is not guaranteed. No tests were rerun for this cleanup.

Omabot on behalf of DHH

@omarchybot omarchybot closed this Oct 4, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

4 participants