The real cause of the unlock SIGSEGV: gtk4-session-lock destroys the lock
windows itself when the lock ends (per its header), so unlock_callback's
`unlock(); app.quit();` destroyed them a second time — surface already gone
→ gdk_surface_get_display NULL → crash in gtk_window_destroy. Reproduces on
a plain single-monitor lock/unlock, no suspend needed.
unlock_callback now calls only unlock(); a new connect_unlocked handler
quits the app after the library finishes teardown. Mirrors the upstream
examples/simple.rs exactly.
Reverts the v0.6.15/v0.6.16 monitor-pruning + LockscreenHandles.monitor:
it was a misdiagnosis (the crash is monitor-independent) and manually
manipulated library-managed lock windows, which the upstream example
explicitly warns against. Monitor removal is left to the library.
Resume-unlock SIGSEGV: connect_monitor only adds windows to all_handles,
never removes them when a monitor powers off on suspend. gtk4-session-lock
unmaps and drops its own ref, but the GtkApplication and all_handles keep
theirs, so the orphaned window survives until unlock — where destroying it
dereferences its now-NULL monitor association and crashes.
Watch display.monitors() and prune handles whose monitor is no longer
valid, releasing the app ref via remove_window (the lib already unmapped
and dereffed the window — we must not destroy it ourselves).
Revert the earlier idle_add_local_once deferral: the logs proved it
ineffective (crash happens inside the idle trampoline). Diagnostic unlock
logging kept until a suspend/resume cycle confirms the fix.
The PAM stack only ever had `auth include login` — no account module.
auth.rs nevertheless called pam_acct_mgmt after pam_authenticate, which
fell back to /etc/pam.d/other (pam_deny) and rejected every password.
On the FP side, the same call was wrapped in a spawn_blocking + 2s
resume_async retry path that triggered a use-after-free in
gtk_window_destroy (20+ SIGSEGVs in 6 days).
- auth.rs: remove pam_acct_mgmt extern + call; return pam_authenticate
result directly. Lockout still works via pam_faillock in the auth stack.
- auth.rs: drop check_account() and its tests (FP path no longer needs it).
- lockscreen.rs::start_fingerprint: on success go straight to
label.set_text + fp.stop() + cb(); no PAM acct check, no resume retry.
- fingerprint.rs: remove resume_async() — no caller left.
- config/moonlock-pam: keep single `auth include login` line, matching
swaylock/gtklock pattern.
- CLAUDE.md, DECISIONS.md updated.
- Update CLAUDE.md and README.md to reflect the blur range [0,200] that
the code has clamped to since v0.6.8.
- Move the // SYNC: comment above the /// doc on MAX_BLUR_DIMENSION so
rustdoc renders one coherent paragraph instead of a truncated sentence.
- Narrow check_account visibility to pub(crate) and document the caller
precondition (username must come from users::get_current_user()).
- Gate MOONLOCK_DEBUG behind #[cfg(debug_assertions)]. Release builds
always run at LevelFilter::Info so a session script cannot escalate
journal verbosity to leak fprintd / D-Bus internals.
- Document why pam_setcred is deliberately not called in authenticate().
- Release profile: lto = "fat" instead of "thin" — doubles release build
time for better cross-crate inlining on the auth + i18n hot paths.
Remove the Nyx persona block from CLAUDE.md and rewrite prior
DECISIONS entries from Nyx and leftover Ragnar to ClaudeCode for
consistency with the rest of the ecosystem.
moonlock crashed with segfault in libgtk-4.so after suspend/resume when
HDMI monitors disconnected and reconnected, invalidating GDK monitor
objects that statically created windows still referenced.
Replace manual monitor iteration with connect_monitor signal (v1_2) that
fires both at lock time and on hotplug. Windows are now created on demand
per monitor event and auto-unmap when their monitor disappears.
Third triple audit (quality, performance, security). Key fixes:
- Blur padding offset: texture at (-pad,-pad) prevents edge darkening on all sides
- Wallpaper loads after lock.lock() — disk I/O no longer delays lock acquisition
- begin_verification disconnects old signal handler before registering new one
- resume_async resets failed_attempts to prevent premature exhaustion
- Unknown VerifyStatus with done=true triggers restart instead of hanging
- symlink_metadata() replaces separate is_file()+is_symlink() (TOCTOU)
- faillock_warning dead code removed, blur sigma clamped to [0,100]
- Redundant Zeroizing<Vec<u8>> removed, on_verify_status restricted to pub(crate)
- Warn logging for non-UTF-8 GECOS and avatar path errors
- Default impl for FingerprintListener, 3 new tests (47 total)
Address findings from second triple audit (quality, performance, security):
- Wrap PAM CString password in Zeroizing<CString> to wipe on drop (S-H1)
- Add check_account() for pam_acct_mgmt after fingerprint unlock,
with resume_async() to restart FP on transient failure (S-M1)
- 30s PAM timeout with generation counter to prevent stale result
interference from parallel auth attempts (S-M3)
- Downscale wallpaper to max 1920px before GPU blur, reducing work
by ~4x on 4K wallpapers (P-M1)
- exit(1) instead of return on no-monitor after lock.lock() (Q-2.1)
Wallpaper is installed by moonarch to /usr/share/moonarch/wallpaper.jpg.
Embedding a 374K JPEG in the binary was redundant. Without a wallpaper
file, GTK background color (Catppuccin Mocha base) shows through.
Close the only exploitable auth bypass: validate VerifyStatus signal sender
against fprintd's unique bus name. Fix fingerprint D-Bus lifecycle so devices
are properly released on verify-match and async restarts check the running
flag between awaits.
Security: num_msg guard in PAM callback, symlink rejection for background_path,
peek icon disabled, TOML parse errors logged, panic hook before logging.
Performance: blur and avatar textures cached across monitors, release profile
with LTO/strip.
- restart_verify() now async via spawn_future_local (was blocking main thread)
- stop() uses 3s timeout instead of unbounded
- load_strings() caches locale detection in OnceLock (was reading /etc/locale.conf on every call)
- child_get() replaced with child_value().get() for graceful D-Bus type mismatch handling
- Eliminate redundant password clone in auth path (direct move into spawn_blocking)
- Add on_exhausted callback: hides fp_label after MAX_FP_ATTEMPTS
- Set running=false before on_success callback (prevent double-unlock)
- Add 4 unit tests for on_verify_status state machine
- Document GLib-GString/CString zeroize limitation in CLAUDE.md
PAM conv callback: check msg_style (password only for ECHO_OFF),
handle strdup OOM with proper cleanup, null-check PAM handle.
Fingerprint: self-wire D-Bus g-signal in start() via Rc<RefCell<>>
and connect_local — VerifyStatus signals are now actually dispatched.
VerifyStop before VerifyStart in restart_verify.
Lockscreen: password entry stays active after faillock threshold
(PAM decides lockout, not UI), use Zeroizing<String> from GTK entry.
Release builds exit(1) without ext-session-lock-v1 support.
Config: fingerprint_enabled as Option<bool> so empty user config
does not override system config.
Dead code: remove unused i18n strings and fingerprint accessors,
parameterize faillock_warning max_attempts.
Complete rewrite of the Wayland lockscreen from Python/PyGObject to
Rust/gtk4-rs for memory safety in security-critical PAM code and
consistency with the moonset/moongreet Rust ecosystem.
Modules: main, lockscreen, auth (PAM FFI), fingerprint (fprintd D-Bus),
config, i18n, users, power. 37 unit tests.
Security: PAM conversation callback with Zeroizing password, panic hook
that never unlocks, root check, ext-session-lock-v1 compositor policy,
absolute loginctl path, avatar symlink rejection.