From b8cfd30ff3aa363568470b95eb08e7f431607752 Mon Sep 17 00:00:00 2001 From: Nils Pukropp Date: Wed, 7 Oct 2026 03:44:55 +0200 Subject: [PATCH] fix(core): give the kernel-keyring unlock owner read access A new key grants its owner only "view"; read needs possession, which a process has only when its session keyring links to the user keyring. A systemd user service or a container does not guarantee that, so the daemon could store the unlock and then fail to read it back (EACCES). Set the permissions explicitly after add_key. --- crates/pdfs-core/src/kernelkey.rs | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/crates/pdfs-core/src/kernelkey.rs b/crates/pdfs-core/src/kernelkey.rs index 0e6fd66..a090cb8 100644 --- a/crates/pdfs-core/src/kernelkey.rs +++ b/crates/pdfs-core/src/kernelkey.rs @@ -30,9 +30,21 @@ const KEY_TYPE: &[u8] = b"user\0"; const KEY_SPEC_USER_KEYRING: i32 = -4; const KEYCTL_REVOKE: libc::c_long = 3; +const KEYCTL_SETPERM: libc::c_long = 5; const KEYCTL_SEARCH: libc::c_long = 10; const KEYCTL_READ: libc::c_long = 11; +/// Possessor and owner may view, read, write, search, link and set attributes; +/// group and others get nothing. +/// +/// A new key's default grants the owner only *view*, and read access to the +/// possessor. A process possesses the user keyring only when its session +/// keyring links to it, which a systemd user service or a container does not +/// guarantee — so with the default, the daemon could store a key it then +/// cannot read back. The owner is the same user that could ptrace the daemon +/// anyway, so granting it read access gives up nothing. +const PERM_OWNER_AND_POSSESSOR: u32 = 0x3f3f_0000; + fn last_error() -> io::Result { Err(io::Error::last_os_error()) } @@ -88,6 +100,21 @@ pub fn store(secret: &[u8]) -> io::Result<()> { if id < 0 { return last_error(); } + // SAFETY: plain integer arguments. + let rc = unsafe { + libc::syscall( + libc::SYS_keyctl, + KEYCTL_SETPERM, + id, + PERM_OWNER_AND_POSSESSOR, + ) + }; + if rc < 0 { + // A key whose permissions are unknown is not one to leave behind. + let err = io::Error::last_os_error(); + let _ = clear(); + return Err(err); + } Ok(()) }