Skip to content

Auto-dismount volumes on system sleep and screen lock on macOS#1818

Open
damianrickard wants to merge 1 commit into
veracrypt:masterfrom
damianrickard:feature/macos-dismount-on-sleep-and-lock
Open

Auto-dismount volumes on system sleep and screen lock on macOS#1818
damianrickard wants to merge 1 commit into
veracrypt:masterfrom
damianrickard:feature/macos-dismount-on-sleep-and-lock

Conversation

@damianrickard

@damianrickard damianrickard commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

On macOS, mounted volumes could remain mounted, with their keys resident in memory, when the system went to sleep or the screen was locked. The existing auto-dismount triggers do not cover these events on macOS:

  • Power-suspend dismount is connected only under wxHAS_POWER_EVENTS, which wxWidgets does not define on macOS.
  • Screen-saver dismount in MainFrame::OnTimer uses the Windows-only SPI_GETSCREENSAVERRUNNING path.

Implementation

  • Adds a macOS-only Objective-C++ observer for NSWorkspaceWillSleepNotification and the distributed screen lock/unlock notifications.
  • Treats distributed lock notifications as untrusted hints and verifies the current session state before acting. Unknown or malformed state is kept distinct from unlocked state, and dismount runs only when the session transitions into the locked state.
  • Uses a dedicated, non-interactive security-event dismount path that honors BackgroundTaskEnabled, DismountOnPowerSaving, DismountOnScreenSaver, and ForceAutoDismount. Failures are logged per volume without displaying UI while the session is locked or the system is preparing to sleep.
  • Performs cache/token cleanup conservatively when a macOS dismount may have partially succeeded.
  • Adds a bounded, monotonic retry window for FUSE-T auxiliary control-file reads during security events, so transient mount readiness does not cause mounted volumes to be missed. Existing specific-volume retry behavior is preserved.
  • Exposes the power-saving auto-dismount preference on macOS and relabels the screen-saver preference for session-lock behavior.
  • Installs and removes the native observer with the GUI lifecycle.

The native observer and security-event behavior are macOS-only. The mounted-volume API extension defaults to the existing behavior for other callers and platforms.

@idrassi

idrassi commented Jul 8, 2026

Copy link
Copy Markdown
Member

Thanks for the PR. I agree this addresses a real macOS gap: the existing wx power event path is not active on macOS, and the screen saver auto dismount logic is Windows only.

Before merging, I think a few points need to be addressed:

  1. Please add an exception boundary around the native macOS notification callbacks. The callbacks currently enter the VeraCrypt dismount path directly from Cocoa notification dispatch, and failures from GetMountedVolumes or the dismount handling should not escape through AppKit notification delivery.

  2. The macOS UI shouldn't keep the existing "Screen saver is launched" wording if the behavior is actually "screen is locked". There is already an IDC_PREF_UNMOUNT_SESSION_LOCKED language key, so please use that on macOS, or also handle the screen saver notification so the preference text matches the behavior. Also please fix or avoid the existing LINUX_ENTERING_POVERSAWING typo now that the power saving checkbox is visible on macOS.

  3. Please explicitly address the forced dismount behavior. This path reuses OnAutoDismountAllEvent, and AutoDismountVolumes currently defaults to alwaysForce=true, which bypasses the ForceAutoDismount preference. That is important for screen lock behavior because it can detach busy volumes.

  4. Since com.apple.screenIsLocked is a de facto but undocumented distributed notification, please treat it as untrusted and verify that the session is actually locked before dismounting. Otherwise another local process may be able to trigger VeraCrypt auto dismount by posting the same notification.

  5. Please include validation notes for the macOS versions we support, and ideally both MacFUSE and FUSE-T on Intel and Apple Silicon. In particular, please cover lock screen, screen saver both with and without immediate lock, Apple menu sleep, lid close sleep, and idle sleep.

The overall approach is reasonable, but I would prefer these points fixed or explicitly validated before merging.

Comment on lines +22 to +32
- (void) systemWillSleep: (NSNotification *) notification
{
(void) notification;
VeraCrypt::OnMacOSXSystemWillSleep ();
}

- (void) screenLocked: (NSNotification *) notification
{
(void) notification;
VeraCrypt::OnMacOSXScreenLocked ();
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add an exception boundary before returning to Cocoa notification delivery. These callbacks enter the VeraCrypt dismount path, and GetMountedVolumes can still throw before or after DismountVolumes. Exceptions shouldn't escape through AppKit notification dispatch.

Comment thread src/Main/MacOSXSleepLock.mm Outdated
Comment on lines +57 to +65
// Screen lock. macOS exposes no public notification for this; the
// com.apple.screenIsLocked distributed notification is the long-standing
// de-facto mechanism used to detect it.
[[NSDistributedNotificationCenter defaultCenter]
addObserver: SleepLockObserver
selector: @selector (screenLocked:)
name: @"com.apple.screenIsLocked"
object: nil];
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please treat this distributed notification as untrusted and verify that the session is actually locked before dismounting. Otherwise another local process may be able to post the same notification and trigger VeraCrypt forced dismount.

Comment on lines +1046 to +1059
void OnMacOSXSystemWillSleep ()
{
if (Gui && Gui->GetPreferences().BackgroundTaskEnabled && Gui->GetPreferences().DismountOnPowerSaving)
Gui->OnAutoDismountAllEvent();
}

// Called from MacOSXSleepLock.mm on the main thread when the screen is
// locked. macOS has no equivalent of the Windows screen-saver polling used
// by MainFrame::OnTimer, so screen-lock dismount is wired up here instead.
void OnMacOSXScreenLocked ()
{
if (Gui && Gui->GetPreferences().BackgroundTaskEnabled && Gui->GetPreferences().DismountOnScreenSaver)
Gui->OnAutoDismountAllEvent();
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This calls OnAutoDismountAllEvent, which currently uses AutoDismountVolumes with alwaysForce=true. Please make that behavior explicit and intentional for macOS sleep and screen lock, or use a path that honors ForceAutoDismount. Screen lock can otherwise detach busy volumes.

Comment on lines +298 to 307
// On macOS, wxHAS_POWER_EVENTS is undefined but sleep is handled by a
// native observer (see MacOSXSleepLock), so keep the checkbox visible.
#if !defined(wxHAS_POWER_EVENTS) && !defined(TC_MACOSX)
DismountOnPowerSavingCheckBox->Show (false);
#endif

#ifdef TC_MACOSX
DismountOnScreenSaverCheckBox->Show (false);
// The "screen saver" checkbox drives screen-lock dismount on macOS (see
// MacOSXSleepLock), so it is left visible here.
DismountOnLogOffCheckBox->SetLabel (LangString["LINUX_VC_QUITS"]);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please align the visible preference text with the macOS behavior. If this means screen lock, please use the existing IDC_PREF_UNMOUNT_SESSION_LOCKED label on macOS, or also handle the actual screen saver notification. Also, making the power saving checkbox visible exposes the existing LINUX_ENTERING_POVERSAWING typo, which should be fixed or avoided.

@damianrickard
damianrickard force-pushed the feature/macos-dismount-on-sleep-and-lock branch from 03b0047 to af355cc Compare July 8, 2026 16:18
@damianrickard

Copy link
Copy Markdown
Contributor Author

Thanks for the detailed review — all five points are addressed:

  1. Exception boundary: the Cocoa notification callbacks now wrap the VeraCrypt work in try { … } catch (...), so nothing (including GetMountedVolumes throwing) can unwind through AppKit notification dispatch. The dismount is best-effort, since UI can't be shown reliably mid-lock/mid-sleep.
  2. Untrusted notification: com.apple.screenIsLocked is now treated as a hint only — the handler verifies the real session state via CGSessionCopyCurrentDictionary (CGSSessionScreenIsLocked) before dismounting, with a short re-check to tolerate notification/state ordering.
  3. Force behavior: the sleep/lock paths now call AutoDismountVolumes with alwaysForce = false, so the ForceAutoDismount preference decides whether busy volumes are detached — matching the Windows auto-dismount events, which all pass bForceAutoDismount.
  4. UI text: on macOS the checkbox is relabeled with the existing IDC_PREF_UNMOUNT_SESSION_LOCKED string (already translated everywhere). I also fixed the LINUX_ENTERING_POVERSAWING typo → LINUX_ENTERING_POWERSAVING, which also corrects the label on the Linux builds that show this checkbox.

Rebased onto current master; builds clean and --text --test passes.

Validation so far (Apple Silicon, macOS 26.5.1, FUSE-T 1.2.7), end-to-end with a real mounted volume:

  • Real screen lock, idle volume → dismounted.
  • Spoofed com.apple.screenIsLocked posted from a separate local process → not dismounted (the session-lock check rejects it).
  • Real lock, busy volume, ForceAutoDismount off → stays mounted (preference honored).
  • The GUI handled every callback with no exception escaping and a clean stderr.

I don't have MacFUSE or an Intel machine set up here, so I haven't yet covered MacFUSE, Intel, or the individual sleep variants (Apple-menu / lid-close / idle). I'll report on those separately rather than claim them now.

@idrassi

idrassi commented Jul 14, 2026

Copy link
Copy Markdown
Member

Thanks for the update and addressing the points.

I did a new review and I don't think this is ready to approve yet because some reliability gaps remain:

  1. Please register the distributed notification with NSNotificationSuspensionBehaviorDeliverImmediately. The current overload defaults to coalescing, so delivery can be deferred while VeraCrypt is inactive or hidden. If it arrives after unlock, SessionIsLocked rejects it. Since the notification is untrusted, please also add debounce or edge tracking and periodic state reconciliation.
  2. A single retry after one second isn't sufficient. During lid close, the run loop may sleep before that retry executes. If only the screen lock option is enabled, volumes may remain mounted throughout sleep.
  3. GetMountedVolumes can silently omit a FUSE T volume when its control file is temporarily unavailable. The all volume path tries only once, so the callback sees an empty list and doesn't retry or log the failure. Please use bounded retries for these security events and log the final failure.

Please also revise the IOKit note. NSWorkspaceWillSleepNotification allows up to 30 seconds while handling the callback, while IOKit can't guarantee completion for forced sleep such as lid close or Apple menu sleep.

wxWidgets does not expose power-suspend events on macOS, and the existing screen-saver polling path is Windows-only. Observe NSWorkspace sleep notifications and the long-standing distributed lock-state hints after the GUI is initialized, then route verified events through the existing auto-dismount preferences.

Treat distributed notifications only as hints: request immediate delivery, verify the tri-state CGSession lock state, observe unlock transitions, and reconcile it from the existing two-second background timer. A sleep event honors either the power-saving or session-lock preference so lid-close sleep cannot bypass a lock-only configuration.

Use a UI-free per-volume dismount path that preserves ForceAutoDismount, continues after failures, logs process status and error output, and performs credential cleanup conservatively after partial macOS dismounts. Security-event enumeration gives transient FUSE-T control files a shared monotonic retry deadline and logs every unresolved control path with its actual attempt count, while ordinary enumeration remains single-attempt.

Expose and relabel the existing macOS preference controls. Sleep handling remains synchronous and best-effort within the NSWorkspace notification window; this change does not add an IOKit power assertion or claim to delay system sleep.
@damianrickard
damianrickard force-pushed the feature/macos-dismount-on-sleep-and-lock branch from af355cc to 0c642cd Compare July 17, 2026 01:08
@damianrickard

Copy link
Copy Markdown
Contributor Author

Update: I've reworked this PR on top of current master and force-pushed the result as a single commit.

The update preserves the auto-dismount-on-sleep/lock behavior while tightening macOS lock-state handling, FUSE-T volume-path discovery, and retry logging.

Validation completed:

  • git diff --check against current master
  • macOS arm64 static GUI build using wxWidgets 3.2.10 and FUSE-T, targeting macOS 12, with deprecated declarations treated as errors
  • App signature verification with codesign --verify --deep --strict
  • VeraCrypt algorithm self-tests using --text --test
  • Upstream Linux CI, including GUI and console package build, installation, and tests

Not covered in this final pass:

  • Intel macOS hardware
  • The MacFUSE-specific runtime path; FUSE-T was used locally
  • The complete physical lock/unlock, sleep/wake, and lid-close/open matrix was not rerun
  • macOS 12 runtime compatibility was not exercised; the build targeted macOS 12, but locally installed third-party dylibs produced newer-minimum-OS linker warnings
  • Separate local Linux runtime testing beyond the upstream CI workflow

The updated Linux CI is green. Please re-review the current head when convenient.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants