fix: audit fix — reduce password copies in memory (v0.8.4)

- attempt_login takes Zeroizing<String> by value, eliminating the redundant
  Zeroizing::new(password.to_string()) that doubled the Rust-owned copy.
- Clear password_entry's internal buffer immediately after extracting the
  password, shortening the window during which the GTK GString persists in
  non-zeroizable libc memory.
This commit is contained in:
nevaforget 2026-04-24 12:52:59 +02:00
parent 48d363bb18
commit 35f1a17cdf
4 changed files with 15 additions and 5 deletions

2
Cargo.lock generated
View File

@ -575,7 +575,7 @@ dependencies = [
[[package]]
name = "moongreet"
version = "0.8.3"
version = "0.8.4"
dependencies = [
"gdk-pixbuf",
"gdk4",

View File

@ -1,6 +1,6 @@
[package]
name = "moongreet"
version = "0.8.3"
version = "0.8.4"
edition = "2024"
description = "A greetd greeter for Wayland with GTK4 and Layer Shell"
license = "MIT"

View File

@ -1,5 +1,12 @@
# Decisions
## 2026-04-24 Audit fix: shrink password-in-memory window (v0.8.4)
- **Who**: ClaudeCode, Dom
- **Why**: Security audit flagged the GTK password path as holding more copies of the plaintext password in memory than necessary. `attempt_login` wrapped the already-`Zeroizing<String>` caller value into a second `Zeroizing<String>` (`password.to_string()`), and the GTK `GString` backing `entry.text()` persisted in libc malloc'd memory until the allocator reused the page.
- **Tradeoffs**: The GTK `GString` and the libc `strdup` copy on the PAM FFI boundary remain non-zeroizable — this is an inherent GTK/libc limitation, already documented in CLAUDE.md. This change reduces the Rust-owned copies to one and clears the `PasswordEntry` text field immediately after extraction to shorten the GTK-side window.
- **How**: (1) `attempt_login` now takes `password: Zeroizing<String>` by value instead of `&str`, moving ownership into the `spawn_blocking` closure. (2) The redundant `Zeroizing::new(password.to_string())` inside `attempt_login` is removed. (3) `password_entry.set_text("")` is called right after the password is extracted from the activate handler, shortening the lifetime of the GTK-internal buffer.
## 2026-04-21 Ship polkit rule in moongreet instead of moonarch (v0.8.3)
- **Who**: ClaudeCode, Dom

View File

@ -493,6 +493,10 @@ pub fn create_greeter_window(
let Some(user) = user else { return };
let password = Zeroizing::new(entry.text().to_string());
// Clear the GTK entry's internal buffer as early as possible. GTK allocates
// the backing `GString` via libc malloc, which `zeroize` cannot reach — the
// best we can do is shorten the window during which it resides in memory.
entry.set_text("");
let session = get_selected_session(&session_dropdown, &sessions_rc);
let Some(session) = session else {
@ -502,7 +506,7 @@ pub fn create_greeter_window(
attempt_login(
&user,
&password,
password,
&session,
strings,
&state,
@ -953,7 +957,7 @@ fn set_login_sensitive(
#[allow(clippy::too_many_arguments)]
fn attempt_login(
user: &User,
password: &str,
password: Zeroizing<String>,
session: &Session,
strings: &'static Strings,
state: &Rc<RefCell<GreeterState>>,
@ -992,7 +996,6 @@ fn attempt_login(
set_login_sensitive(password_entry, session_dropdown, false);
let username = user.username.clone();
let password = Zeroizing::new(password.to_string());
let exec_cmd = session.exec_cmd.clone();
let session_name = session.name.clone();
let greetd_sock = state.borrow().greetd_sock.clone();