Hi,
I'm using sctk for a lock screen / password field and noticed that KeyEvent::utf8 is built as a String on every key press. It goes through State::key_get_utf8 / compose::State::utf8, and again on modifier changes during key repeat. So every character typed ends up in its own heap allocation, which is freed without being cleared.
For most apps that's just a small allocation per key. For anything typing a password, though, it means the password is left scattered across freed memory one character at a time, even if the app itself keeps its copy in locked, zeroed memory. It can then show up in a core dump or in swap.
Other password inputs go out of their way to avoid exactly this:
- swaylock reads each key as a code point with
xkb_state_key_get_utf32 (seat.c) and appends it straight into a page-aligned buffer that is mlocked and wiped with a volatile loop after submit or 10 s of idle (password-buffer.c, password.c). The typed text never goes through a heap string.
- GTK 4's
GtkPasswordEntry keeps its text in a GtkPasswordEntryBuffer, which grows it with gtk_secure_realloc (gtkpasswordentrybuffer.c). That allocator mlocks its pages, marks them MADV_DONTDUMP, and zeroes them when freed (gtksecurememory.c).
- In Rust,
secrecy (SecretString, built on zeroize) is the usual way to hold a password so it "isn't accidentally copied" and is "securely wiped from memory when dropped". rpassword reads a password from the terminal one char at a time, and ships a SafeString that zeroes itself with write_volatile on drop.
All of these only work if nothing below them keeps its own copy of each key, and with sctk there is currently no way for a client to avoid that: the String is allocated and freed before the client ever sees the event.
libxkbcommon doesn't need to allocate here: xkb_state_key_get_utf8 and xkb_compose_state_get_utf8 write into a caller-provided buffer, and the xkbcommon crate only builds the String afterwards. winit's Wayland backend doesn't allocate per key either: it decodes into a reused scratch buffer and returns a SmolStr.
Would you be open to changing utf8 to hold the text inline? I have a patch that replaces it with a small KeyText type: a 64-byte buffer (the size libxkbcommon recommends) that derefs to str. Code using event.utf8.as_deref() keeps compiling. It is still a breaking change for anyone who needs the String itself.
Patch: MalpenZibo#1
Hi,
I'm using sctk for a lock screen / password field and noticed that
KeyEvent::utf8is built as aStringon every key press. It goes throughState::key_get_utf8/compose::State::utf8, and again on modifier changes during key repeat. So every character typed ends up in its own heap allocation, which is freed without being cleared.For most apps that's just a small allocation per key. For anything typing a password, though, it means the password is left scattered across freed memory one character at a time, even if the app itself keeps its copy in locked, zeroed memory. It can then show up in a core dump or in swap.
Other password inputs go out of their way to avoid exactly this:
xkb_state_key_get_utf32(seat.c) and appends it straight into a page-aligned buffer that ismlocked and wiped with a volatile loop after submit or 10 s of idle (password-buffer.c, password.c). The typed text never goes through a heap string.GtkPasswordEntrykeeps its text in aGtkPasswordEntryBuffer, which grows it withgtk_secure_realloc(gtkpasswordentrybuffer.c). That allocatormlocks its pages, marks themMADV_DONTDUMP, and zeroes them when freed (gtksecurememory.c).secrecy(SecretString, built onzeroize) is the usual way to hold a password so it "isn't accidentally copied" and is "securely wiped from memory when dropped".rpasswordreads a password from the terminal onecharat a time, and ships aSafeStringthat zeroes itself withwrite_volatileon drop.All of these only work if nothing below them keeps its own copy of each key, and with sctk there is currently no way for a client to avoid that: the
Stringis allocated and freed before the client ever sees the event.libxkbcommon doesn't need to allocate here:
xkb_state_key_get_utf8andxkb_compose_state_get_utf8write into a caller-provided buffer, and the xkbcommon crate only builds theStringafterwards. winit's Wayland backend doesn't allocate per key either: it decodes into a reused scratch buffer and returns aSmolStr.Would you be open to changing
utf8to hold the text inline? I have a patch that replaces it with a smallKeyTexttype: a 64-byte buffer (the size libxkbcommon recommends) that derefs tostr. Code usingevent.utf8.as_deref()keeps compiling. It is still a breaking change for anyone who needs theStringitself.Patch: MalpenZibo#1