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.
The per-unlock window-state dump was scaffolding to prove the stale-window
hypothesis behind v0.6.15. With the fix in place, normal logging covers
validation: the prune handler already logs on monitor removal, and
coredumpctl shows whether the crash recurs. Remove the diagnostic block
and its all_handles_dbg clone; restore all_handles to its original spot.
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.
Align the power-confirm flow to moonset's ActionDef pattern, in lockstep
with moongreet: a PowerAction table + create_power_button factory replace
the two hand-wired reboot/shutdown handlers and the loose-param
show_power_confirm. Add an in-flight re-trigger guard
(power_box.set_sensitive(false)) and clear a stale error_label when
showing a new prompt.
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.
- fingerprint: split cleanup_dbus into a sync take_cleanup_proxy() + async
perform_dbus_cleanup(). resume_async now awaits VerifyStop+Release before
re-claiming, so fprintd cannot reject the Claim on a slow bus. stop()
still spawns the cleanup fire-and-forget.
- fingerprint: remove failed_attempts = 0 from resume_async. An attacker
with sensor control could otherwise cycle verify-match → account-fail →
resume and never trip the 10-attempt cap.
- lockscreen: open the wallpaper with O_NOFOLLOW and build the texture
from bytes, closing the TOCTOU between the symlink check and Texture::
from_file.
- lockscreen: clear password_entry immediately after extracting the
Zeroizing<String>, shortening the window the GLib GString copy stays in
libc-malloc'd memory.
- init_fingerprint_async: hoist username before the await so a concurrent
connect_monitor signal (hotplug / suspend-resume) cannot cause a RefCell
panic. Re-borrow after the await for signal wiring.
- set_avatar_from_file: decode via gio::File::read_future +
Pixbuf::from_stream_at_scale_future so the GTK main thread stays
responsive during monitor hotplug. Default icon shown while loading.
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)
Colloid-Catppuccin theme loaded via ~/.config/gtk-4.0/gtk.css at
PRIORITY_USER (800) was overriding moonlock's PRIORITY_APPLICATION (600),
causing avatar to lose its circular border-radius.
- Use STYLE_PROVIDER_PRIORITY_USER for app CSS provider
- Replace border-radius: 50% with 9999px (GTK4 CSS percentage quirk)
GskBlurNode samples pixels outside texture bounds as transparent,
causing visible darkening at wallpaper edges. Fix renders the texture
with 3x-sigma padding before blur, then clips back to original size.
Symmetric fix with moonset v0.7.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.
First launch with blur blurs and saves to ~/.cache/moonlock/.
Subsequent starts load the cached PNG directly. Cache invalidates
when wallpaper path, size, mtime, or sigma changes.
Adds dirs crate for cache directory resolution.
Gaussian blur applied at texture load time when `background_blur` is
set in moonlock.toml. Refactored wallpaper loading from per-window
Picture::for_filename() to shared gdk::Texture pattern (matching
moonset/moongreet), avoiding redundant JPEG decoding on multi-monitor.
loginctl has no reboot/poweroff subcommands — these are systemctl
commands. The error was silently swallowed because stderr wasn't
captured and logs went to a non-existent directory.
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.