Skip to content

[BUG] Permission Repair false positive: mo optimize runs diskutil resetUserPermissions on every run when GNU coreutils stat is in PATH #1196

Description

@ewhanhan

Describe the bug

Same root-cause family as #79 and #943: a bare stat call resolves to GNU coreutils' stat when gnubin is earlier in PATH. This instance is in needs_permissions_repair() (lib/optimize/tasks.sh) — and unlike #943 it fails silently, by returning a wrong answer instead of crashing:

owner=$(stat -f %Su "$HOME" 2> /dev/null || echo "")
if [[ -n "$owner" && "$owner" != "$USER" ]]; then
    return 0   # "repair needed"
fi

With GNU stat, -f means "filesystem mode" and %Su is treated as a filename. The command exits 1 (the %Su "file" doesn't exist — that error goes to stderr, suppressed by 2>/dev/null) but still prints a multi-line filesystem report for $HOME to stdout:

$ stat -f %Su "$HOME"
stat: cannot read file system information for '%Su': No such file or directory
  File: "/Users/xxx"
    ID: 10000110000001a Namelen: ?       Type: apfs
Block size: 4096       Fundamental block size: 4096
...

So owner captures that multi-line text, which is non-empty and never equals $USER, and the function always returns 0. Result: every mo optimize run executes sudo diskutil resetUserPermissions / $uid, which traverses the entire home directory resetting ownership/ACLs — several minutes on a typical dev machine — and can strip custom ACLs the user set intentionally. It then reports success ("User directory permissions repaired"), so the misfire is invisible.

Actual home directory permissions are fine (verified with /usr/bin/stat below).

STAT_BSD already exists for exactly this reason (lib/core/base.sh) — this call site just doesn't use it.

Steps to reproduce

  1. Have GNU coreutils' gnubin before /usr/bin in PATH (brew install coreutils, PATH="/opt/homebrew/opt/coreutils/libexec/gnubin:$PATH")
  2. Run sudo -v && mo optimize
  3. The "Permission Repair" step runs the full multi-minute diskutil resetUserPermissions on every invocation, despite correct permissions

The detection logic in isolation:

$ v=$(stat -f %Su "$HOME" 2>/dev/null || echo ""); [[ -n "$v" && "$v" != "$USER" ]] && echo "false positive"
false positive

Expected behavior

With correct ownership/writability, the step should print "User directory permissions already optimal" and skip the repair. One-word fix:

owner=$($STAT_BSD -f %Su "$HOME" 2> /dev/null || echo "")

Given #79 and #943 fixed the same class at other call sites, it may be worth a sweep for remaining bare stat calls.

Debug logs

Debug output
➤ Permission Repair
[DEBUG] === Disk Permissions Repair ===
[DEBUG] Reset user directory permissions
[DEBUG] Method: Run diskutil resetUserPermissions on user home directory
[DEBUG] Condition: Only runs if permissions issues are detected
[DEBUG] Expected outcome: Fixed file access issues, correct ownership
[DEBUG] Risk Level: MEDIUM, Requires sudo, modifies permissions
- Repairing disk permissions...

Verification that actual permissions are healthy (BSD stat):

$ /usr/bin/stat -f "%N owner=%Su mode=%Sp" "$HOME" "$HOME/Library" "$HOME/Library/Preferences"
/Users/xxx owner=xxx mode=drwxr-x---
/Users/xxx/Library owner=xxx mode=drwx------
/Users/xxx/Library/Preferences owner=xxx mode=drwx------

Environment

Mole version 1.44.1
macOS: 26.5.2
Architecture: arm64
Kernel: 25.5.0
SIP: Enabled
Disk Free: 213.21GB
Install: Manual
Shell: /bin/zsh

Additional context

Related: #79 (introduced the STAT_BSD compatibility helper), #943 (same root cause in the Periodic Maintenance freshness check, fixed in 4aaad95). This call site was likely missed because it misdetects silently rather than erroring.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions