Skip to content

KeyEvent::utf8 allocates a String on every key press #542

Description

@MalpenZibo

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

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions