Skip to content

Windows VM 4.0.2: launch fails silently after polkit auth (setgid chmod check can never pass) #9334

Closed

Description

@bpidperygora

Edit: the original root-cause analysis below blamed a kernel regression. That was wrong, and I've rewritten this post. The bit is GNU chmod semantics, not the kernel — thanks to @murilocb for independently landing on the same conclusion in a comment. The failure itself and the code path are unchanged.

Windows VM launch silently fails after polkit auth on 4.0.2: the privileged mount-source check can never pass once the source dir has the setgid bit, because the chmod the script uses cannot clear it.

What happened

After upgrading 4.0.1 → 4.0.2, launching the Windows VM (Apps menu → Windows, omarchy-windows-vm launch) shows the polkit password dialog and then nothing happens. No error, no window. Every retry asks for the password again with no result.

The elevated step exits non-zero and all of its stderr is discarded, because the desktop entry runs with Terminal=false — so from the user's perspective the password is accepted and then nothing happens at all.

Root cause

prepare_caller_mounts() hardens both mount sources by running chmod 0700 on the pinned fds and then requires the resulting mode to be exactly 700:

chmod 0700 -- "/proc/$BASHPID/fd/$storage_fd" "/proc/$BASHPID/fd/$shared_fd" || { ...; return 1; }
storage_mode=$(stat -Lc '%a' "/proc/$BASHPID/fd/$storage_fd" 2>/dev/null) || storage_mode=""
shared_mode=$(stat -Lc '%a' "/proc/$BASHPID/fd/$shared_fd" 2>/dev/null) || shared_mode=""
[[ $storage_mode != 700 || $shared_mode != 700 ]] && { ...; return 1; }

GNU chmod preserves a directory's setuid/setgid bits for numeric modes shorter than five octal digits. This is documented, long-standing coreutils behavior. chmod 0700 is therefore incapable of producing 700 from 2700, and the check that follows can never pass. Measured on this box (chmod (GNU coreutils) 9.11, kernel 7.1.9-arch1-2, btrfs):

command on a 2777 directory resulting mode
chmod 700 2700
chmod 0700 2700 ← what the script does
chmod 00700 700
chmod g-s 777

It behaves the same through the /proc/$BASHPID/fd/N indirection the script actually uses:

$ exec {fd}<~/Windows                          # mode 2777
$ chmod 0700 -- "/proc/$BASHPID/fd/$fd"
$ stat -Lc %a "/proc/$BASHPID/fd/$fd"          # 2700  → != 700 → return 1

Deterministic on/off proof that this single bit is what kills the launch:

$ stat -c %a ~/Windows                                 # 2700
$ pkexec /usr/bin/omarchy-windows-vm __priv up; echo $?  # 1, no output at all
$ chmod g-s ~/Windows                                  # 700
$ pkexec /usr/bin/omarchy-windows-vm __priv up; echo $?  # 0, container comes up

This is not a one-off from an old install. dockurr/windows chmods the shared mount to 2777 on every container start — after a successful __priv up_wait here, ~/Windows is drwxrwsrwx. So the bit is re-applied by every launch, and since the same strict mode == 700 comparison also lives in mounted_leaf_matches() (and therefore mounts_ready), every launch after the first one fails identically. Recreating the directory buys exactly one launch.

Steps to reproduce

  1. Have a Windows VM whose ~/Windows carries the setgid bit — either from a ≤ 4.0.1 install (drwx--S---) or simply from having started the container once on 4.0.2 (drwxrwsrwx).
  2. Apps menu → Windows, enter the polkit password.
  3. Nothing happens, no error, no window.

Minimal repro of the underlying chmod behavior, no VM involved:

mkdir -m 2700 /tmp/sgdir
chmod 0700 /tmp/sgdir; stat -c %a /tmp/sgdir   # 2700
chmod 00700 /tmp/sgdir; stat -c %a /tmp/sgdir  # 700

Workaround

Strip the bit with a form of chmod that actually clears it, before every launch:

chmod 00700 ~/Windows ~/.windows

If a launch already failed once, the root-owned bind anchor still pins the old inode when you recreate the directory instead of chmod'ing it, and the next attempt dies with protected mount ... no longer matches its home source. In that case unmount the anchor first — see the follow-up comment below. Chmod'ing in place avoids that entirely, since the inode never changes.

This has to be repeated after every container run, so in practice it needs to be wrapped around the launch.

Expected

One or more of:

  • prepare_caller_mounts() and mounted_leaf_matches() clear the bit with a mode that works — chmod 00700, or chmod u-s,g-s followed by chmod 0700 — rather than chmod 0700.
  • Alternatively, relax the comparison to what the hardening actually cares about: correct owner and no group/other access bits, rather than a strict == 700.
  • The failure surfaces to the user (desktop notification, or a visible terminal) instead of dying silently after a successful password prompt. This is the part that makes it undiagnosable for a normal user.

System details

  • Omarchy 4.0.2-1 (upgraded from 4.0.1; VM originally installed in the 4.0.0 era)
  • Kernel: 7.1.9-arch1-2
  • coreutils 9.11
  • Hyprland 0.56.2, SDDM
  • Intel CoffeeLake-H (UHD Graphics 630)
  • Filesystem: btrfs (/@home subvolume, zstd)

Activity

  1. changed the title [-]Title[/-] [+]Windows docker BUG[/+] on Aug 31, 2026
  2. changed the title [-]Windows docker BUG[/-] [+]Windows VM 4.0.2: launch fails silently after polkit auth (setgid chmod check can never pass)[/+] on Aug 31, 2026
  3. bpidperygora commented on Aug 31, 2026

    @bpidperygora
    Author

    Follow-up, and a correction to the workaround in the post: recreating ~/Windows alone does NOT get you going. If the launch failed at least once, the root-owned bind anchor at /var/lib/omarchy/windows/mounts/users/1000/shared still pins the old inode, and the next attempt dies with "protected mount ... no longer matches its home source". The sequence that actually worked here:

    1. pkexec umount /var/lib/omarchy/windows/mounts/users/1000/shared
    2. mv ~/Windows ~/Windows.old && mkdir -m 700 ~/Windows (move your files back from .old)
    3. launch again — the first elevated start re-pins the anchor

    Caveat: this holds for exactly one boot. dockurr/windows sets SGID on /shared (that's ~/Windows on the host) on every container start, chmod can't strip it on this kernel, and the same check also runs in __priv_up_wait on every start — not just during the legacy compose migration. So until either side changes, each launch after a container run fails the same way again.

  4. Pinox commented on Aug 31, 2026

    @Pinox

    Yes Windows is currently broken, after upgrade.

    Now on :
    Image

    Tried resetting PC, then reinstalling windows again. It fails at authentication. Entering linux root does not pass auth and Windows container fails to start.

    General question. Is there any way we can save all our settings on Omarchy so that we don't have to reinstall everything again (plugins / app) when resetting our PC's ? Would be great especially now when these types of issues can be common, an option to import previous Omarchy app/plugin settings would be usefull.

  5. murilocb commented on Aug 31, 2026

    @murilocb

    Hit this too (4.0.1 → 4.0.2 upgraded this morning, ~/Windows at drwx--S---), and I can confirm the failing check. But the root cause isn't the kernel — it's GNU chmod, and that changes the fix.

    chmod preserves setuid/setgid on directories when the numeric mode has 4 or fewer octal digits. This is documented, long-standing coreutils behavior, not a 7.1.9 regression:

    GNU chmod preserves a directory's set-user-ID and set-group-ID bits unless you explicitly specify otherwise. […] To clear these bits for directories with a numeric mode requires an explicit leading zero like 00755.

    Same box as the report, same kernel, btrfs:

    $ mkdir -m 2700 sgtest        # 2700
    $ chmod   700 sgtest  -> 2700
    $ chmod  0700 sgtest  -> 2700   # ← what the script does
    $ chmod 00700 sgtest  ->  700
    $ chmod  g-s  sgtest  ->  700
    

    And the syscall itself has no such behavior — the kernel clears SGID exactly as expected:

    $ python3 -c "import os; os.chmod('sgtest', 0o700); print(oct(os.stat('sgtest').st_mode & 0o7777))"
    0o700
    

    chmod (GNU coreutils) 9.11, kernel 7.1.9-arch1-2, btrfs. So pkexec bash -c 'chmod 700 …' printing 2700 is coreutils declining to touch the bit, not the kernel refusing to clear it. Nothing to flag to Arch, and (FWIW) an unprivileged user can still set SGID on a directory they own — chmod 2700 above ran as a normal user.

    I verified this is the exact failure path in prepare_caller_mounts(), including through the /proc/$BASHPID/fd/N indirection the script actually uses:

    $ exec {fd}<"sgtest/."                              # mode 2700
    $ chmod 0700 -- "/proc/$BASHPID/fd/$fd"
    $ stat -Lc %a "/proc/$BASHPID/fd/$fd"  ->  2700     # != 700 → return 1
    

    Corroborating evidence that it dies right there: after a failed launch the anchors exist but were never bound —

    /home/<user>/.windows                                  dev=56 ino=1210399
    /var/lib/omarchy/windows/mounts/users/1000/storage     dev=31 ino=952798
    

    prepare_mount_anchor() had run (both anchors created, timestamped at the first launch attempt), bind_mount_leaf() never did.

    Implications for the fix

    The strict == 700 check is fine and doesn't need relaxing — the chmod just needs to actually do what it intends:

    -  chmod 0700 -- "/proc/$BASHPID/fd/$storage_fd" "/proc/$BASHPID/fd/$shared_fd" || {
    +  chmod 00700 -- "/proc/$BASHPID/fd/$storage_fd" "/proc/$BASHPID/fd/$shared_fd" || {

    (or symbolic u=rwx,go=,g-s, which is self-documenting about the intent). This also disposes of the recurring-failure caveat in the follow-up comment: if dockurr/windows re-applies SGID to /shared on every container start, a correct chmod strips it again on each launch and the check keeps passing — no per-boot breakage.

    Simpler user workaround

    No need to move the directory or unmount the anchor:

    chmod g-s ~/Windows

    The inode is unchanged, so the pinned anchor stays valid and the next launch re-binds normally. Fixed it here on the first try.

    The silent-failure half of the report stands on its own and is worth fixing regardless — Terminal=false in windows-vm.desktop means every one of these return 1s is invisible, and the user just sees the polkit dialog accept their password and nothing happen.

  6. slinn-01 commented on Aug 31, 2026

    @slinn-01

    Confirmed independently on Omarchy 4.0.2-1 after upgrading from 4.0.1. A traced privileged launch showed the exact failure: numeric chmod 0700 returned success but left the shared source at 2700; the following exact-mode check rejected it before Docker startup. I also observed Dockurr set the empty shared folder to 2777 while the guest was running, so this can recur unless launch explicitly clears special bits.

    There is already a targeted fix in #9322. I checked it in a clean checkout: its Windows VM compose/security suite passes with the new setgid fixture, and the approach keeps the strict 700 validation while changing normalization to chmod u=rwx,go=,a-s. I posted full validation details on the PR: #9322 (comment)

    No additional issue opened to avoid duplication.

  7. ewertonmattoslumon commented on Aug 31, 2026

    @ewertonmattoslumon

    Independent confirmation from a second machine (Avell A60 MUV laptop, same 7.1.9-arch1-2 kernel, btrfs): identical failure — works once, then every subsequent launch fails silently because dockurr/windows re-sets SGID on the shared source dir on every container start and the script's chmod can't strip it back off. Matches the __priv_up_wait-runs-every-start behavior in the follow-up comment above.

    One addition that may help pin the actual fix: the failing call in prepare_caller_mounts() is the 4-digit form chmod 0700 -- .... That form does not reliably clear SGID on a directory under GNU coreutils (tested here, coreutils 9.11 / kernel 7.1.9-arch1-2):

    $ mkdir -m 2700 d && chmod 0700 d  && stat -c %a d   # 2700 — script's own form, still SGID
    $ mkdir -m 2700 d && chmod 00700 d && stat -c %a d   # 700  — one more leading zero clears it
    $ mkdir -m 2700 d && chmod g-s d   && stat -c %a d   # 700  — symbolic form clears it
    $ mkdir -m 2700 d && python3 -c "import os;os.chmod('d',0o700)" && stat -c %a d   # 700 — raw syscall, no coreutils involved
    

    Per man chmod, GNU coreutils intentionally preserves setuid/setgid on directories for a numeric mode unless it has an extra leading zero (00755, not 0700) or the bits are cleared symbolically (g-s) — protection against a recursive chmod -R 755 silently stripping SGID off a shared directory. chmod 0700 reads to coreutils as an ordinary 4-digit mode, not an explicit clear, so it's swallowed by that protection every time — this is coreutils semantics, not the kernel/SGID behavior suspected above. Changing that one call to chmod 00700 (or following it with chmod g-s,u-s --) should fix it directly, without needing the unmount/recreate workaround.

    Filed by Claude Sonnet 5 via Claude Code.

  8. christianguenter2 commented on Aug 31, 2026

    @christianguenter2

    Confirmed on Omarchy 4.0.2-1 with an AMD Ryzen AI 5 340.

    ~/Windows was mode 2700. The privileged helper's chmod 0700
    left it at 2700, causing prepare_caller_mounts() to fail silently.

    Explicitly clearing setgid fixed it:

      chmod g-s ~/Windows
    

    Afterward, the VM migration completed and Windows launched successfully.

    This appears to be documented GNU coreutils behavior rather than a kernel
    regression: numeric modes of four or fewer digits preserve set-ID bits on
    directories. Using chmod 00700 should explicitly clear them.

  9. drewdavies commented on Sep 1, 2026

    @drewdavies

    remove is blocked by the same gate, so the tool can't recover itself.

    __priv_remove() opens with the same assert_mounts_safe call that __priv_up_wait() does (bin/omarchy-windows-vm:935), so it fails at the identical setgid check, and remove_windows() then exits before any user-side cleanup:

      if [[ -f $COMPOSE_FILE ]]; then
        priv remove || {
          echo "❌ Windows VM removal stopped before user-side cleanup; inspect the VM data before retrying." >&2
          exit 1   # :1378-1380 (.desktop, ~/.config/windows, ~/.windows) never run
        }
      fi

    remove always elevates ([[ $action != write_compose && $action != remove ]], :89), so there is no docker-group path around it.

    Repro on 4.0.2-1 from a working install:

    1. chmod g+s ~/Windows — or just run the VM once; dockurr sets SGID on /shared
    2. omarchy-windows-vm launch → ❌ Failed to start Windows VM!
    3. omarchy-windows-vm remove, authorize the polkit prompt → ❌ Windows VM removal stopped before user-side cleanup

    After step 3 here, /var/lib/omarchy/windows/docker-compose.yml, the dockurr/windows image and ~/.windows/data.img were all still present.

    This is likely the explanation for the "tried resetting PC, then reinstalling windows again, it fails" report above: install doesn't fail early either, because prepare_user_mount_sources()'s chmod 0700 (:1018) returns 0 while leaving the bit — it just fails again at the same check during bring-up.

    #9322 already covers this: patching prepare_caller_mounts() fixes remove and install along with launch, since all three reach it via assert_mounts_safe. No further code change needed — flagging only that the blast radius is wider than launch, in case a removal-path fixture is worth adding.

    Env: Omarchy 4.0.2-1, kernel 7.1.9-arch1-2, GNU coreutils 9.11, btrfs. chmod g-s ~/Windows cleared it with the anchor inode intact and launch came straight up.

    Filed by Claude Opus 5 via Claude Code.

  10. changed the title [-]Windows VM 4.0.2: launch fails silently after polkit auth (setgid chmod check can never pass)[/-] [+]Windows VM 4.0.2: launch fails silently after polkit auth (chmod 0700 can never clear the setgid bit the check requires gone)[/+] on Sep 1, 2026
  11. bpidperygora commented on Sep 1, 2026

    @bpidperygora
    Author

    Rewrote the issue body: the root cause is GNU chmod preserving setuid/setgid on directories for numeric modes shorter than five digits, not a kernel regression. Confirmed independently by @murilocb above — nothing to flag to Arch, and my earlier claim that an unprivileged user can no longer set SGID was wrong too.

    Also worth pulling out of the body, since it changes the severity: this isn't limited to installs upgraded from ≤ 4.0.1. dockurr/windows sets the shared mount to 2777 on every container start, so a fresh 4.0.2 install hits the identical failure from its second launch onward.

    @Pinox this is why reinstalling doesn't help. As a stopgap, chmod 00700 ~/Windows ~/.windows immediately before each launch clears the bit (chmod 0700 will not).

  12. changed the title [-]Windows VM 4.0.2: launch fails silently after polkit auth (chmod 0700 can never clear the setgid bit the check requires gone)[/-] [+]Windows VM 4.0.2: launch fails silently after polkit auth (setgid chmod check can never pass)[/+] on Sep 1, 2026
  13. Pinox commented on Sep 1, 2026

    @Pinox

    thanks @bpidperygora. Yea installation did not do the trick. I'm a new user on Linux so getting some "practice" sorting out my installation flow of apps on Omarchy ;)) as these issues are highly probably and expected in a fast moving distro. Actually glad this is happening to me, must say I hope this projects moves in the direction of "software as code" removing all friction when these things happen especially if general consumers start embracing something like Omarchy.

  14. davidboulay commented on Sep 5, 2026

    @davidboulay

    The chmod root cause above is right and I won't re-litigate it — independently reproduced here on 4.0.2-1 (kernel 7.1.9-arch1-2, coreutils 9.11, btrfs): ~/Windows at 2700, chmod 0700 returning 0 without clearing it, and prepare_caller_mounts() returning 1 with no output at all.

    One correction worth making, because it changes the user stopgap and may matter for the fixtures in #9322 / #10046 / #10113: dockurr/windows does not re-apply 2777 on every container start. It only does so while the shared folder is empty.

    Both chmod 2777 calls in the image's /run/samba.sh (dockurr/windows:latest, image 0cff9eb0e7ae) are gated on an emptiness probe taken at the top of addShare():

    112:  if [ -z "$(ls -A "$dir")" ]; then
    113:    empty="Y"
    114:  fi
    ...
    128:  elif probe=$(mktemp "$dir/.samba-write-test.XXXXXX" 2>/dev/null); then
    ...
    139:  elif [[ "$empty" == "Y" ]] && chmod 2777 "$dir" 2>/dev/null; then
    ...
    154:  if [[ "$writable" == "Y" ]]; then
    156:    if [[ "$empty" == "Y" ]]; then
    158:      # Keep newly created content in the shared group through the setgid bit.
    159:      if ! chmod 2777 "$dir"; then

    A non-empty ~/Windows takes the write probe at :128 instead — mktemp inside the share succeeds because samba runs as root, so a 700 mode doesn't block it — and lands on writable=Y / empty=N. No chmod runs at all, and the setgid bit is never re-applied.

    A/B on this machine, same install, nothing else changed:

    ~/Windows at container start mode after a full start → RDP session → stop
    empty 2777, then 2700 once the script's own chmod 0700 runs → every later launch fails
    one file in it 700, unchanged

    The second row is a real launch, not a staged one: Apps menu → Windows, ~5 minutes of RDP, closed normally so launch_windows ran priv down. ~/Windows was still drwx------ 700 afterward, and the next launch came straight up.

    So as a stopgap that survives instead of needing to be repeated before every launch: chmod g-s ~/Windows once, then leave any file in the folder. Samba's default hide dot files = yes is not overridden in the generated smb.conf, so a dotfile stays out of the guest's view.

    Two notes for the fix PRs:

    • A regression fixture has to start the container against an empty share to reproduce this. One with any content in the share will pass while the bug is still live.
    • fix(windows-vm): accept dockur 2777 shared mount mode on launch #10113 (accept 2777 on launch) is loosening the check for a mode the container only ever writes into an empty share — worth weighing against clearing the bits outright, since the empty-share case is also the one where none of the user's data is at stake.

    Env: Omarchy 4.0.2-1, kernel 7.1.9-arch1-2, GNU coreutils 9.11, docker 1:29.7.2-1, btrfs.

    Filed by Claude Opus 5 via Claude Code.

  15. josedamianm commented on Sep 22, 2026

    @josedamianm

    Still reproduces on the edge channel (omarchy-dev 4.0.0.r2186.gee8ebf6-1, coreutils 9.11-2, kernel 7.2.5-4-omarchy, docker 29.8.1). So this isn't fixed in current source either. The chmod 0700 in prepare_caller_mounts is unchanged.

    After one VM boot, ~/Windows is at 2777. Trace from pkexec bash -x /usr/bin/omarchy-windows-vm __priv up_wait:

    + chmod 0700 -- /proc/21779/fd/11 /proc/21779/fd/12
    + storage_mode=700
    + shared_mode=2700
    + [[ 2700 != 700 ]]
    + return 1
    

    Posted by Claude Opus 5 via Claude Code.

  16. Endless3Cyb3r commented on Sep 25, 2026

    @Endless3Cyb3r

    Still happens on Omarchy 4.0.4-1 (GNU coreutils 9.11, AMD Ryzen 7 9800X3D / RX 9070 XT).

    What I see:

    • While the VM is running, the container sets ~/Windows to 2777.
    • On the next launch, prepare_caller_mounts runs chmod 0700. GNU chmod keeps setgid on directories, so the folder is left at 2700.
    • The $shared_mode != 700 check then fails, and the launch aborts with a bare "Failed to start Windows VM!".

    It's 100% reproducible after any successful session, and clearing the bit by hand only fixes it for one boot.

    $ mkdir d && chmod 2777 d && chmod 0700 d && stat -c %a d
    2700
    $ chmod 00700 d && stat -c %a d
    700
    

    #12323 looks like the right fix. Until it lands, my workaround is to point the windows-vm.desktop Exec at a wrapper that runs chmod g-s ~/Windows ~/.windows before omarchy-windows-vm launch.

  17. dofus-ai commented on Oct 2, 2026

    @dofus-ai

    Confirmed on Omarchy 4.0.4-1 using the Quickshell menu's Windows entry.

    Reproduction:

    1. Launch Windows once and close the RDP session.
    2. Run stat -c '%a %n' ~/Windows; the shared directory is 2700 (drwx--S---).
    3. Launch Windows again from Quickshell, or run omarchy windows vm launch --keep-alive.

    Observed:

    Starting Windows VM (this may prompt for authorization)...
    ❌ Failed to start Windows VM!
       Try checking: omarchy-windows-vm status
    

    The container is not started on the failing attempt. The helper runs chmod 0700 but GNU chmod preserves the directory's setgid bit, so the subsequent exact-700 check sees 2700 and exits silently. The Windows container logs show that it booted successfully when the directory is corrected.

    Workaround confirmed:

    chmod g-s ~/Windows
    omarchy windows vm launch --keep-alive

    After clearing setgid, the VM starts and the xfreerdp window opens. The setgid bit is reintroduced by the container on a later startup, so the workaround has to be repeated until an upstream fix lands.

  18. Knutsi commented on Oct 3, 2026

    @Knutsi

    Also hit this on 4.0.4-1 (/home on btrfs): after a reboot, launch failed silently with ~/Windows at 2700.

    On "clearing the bit by hand only lasts one boot": the bit comes from dockurr/windows samba.sh, which runs chmod 2777 on the share when it is empty. If you also leave a file in the share, the bit stops coming back:

    chmod g-s ~/Windows && touch ~/Windows/.keep
    

    After that, ~/Windows and ~/.windows have stayed at 700 through several VM up/down cycles and a reboot here. It's a workaround, not a fix. prepare_caller_mounts still needs to clear setgid explicitly (chmod 00700 or g-s), as the open PRs do.


    Posted with help from Claude Code.

  19. omarchybot commented on Oct 4, 2026

    @omarchybot
    Collaborator

    Update: #12323 has merged into quattro. The reported numeric-chmod/setgid rejection is fixed in both mount-source hardening paths on quattro. Launch, install and removal re-harden the sources without weakening the exact mode-700 check.

    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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions