From 264d8fd2794fabe76a89abd5d4f5f8c1ed5fad85 Mon Sep 17 00:00:00 2001 From: Daniel Borkmann Date: Fri, 28 Aug 2026 19:52:17 +0200 Subject: bpf, keys: Add a bpf keyring for program signature validation BPF program signatures can currently be verified against one of the system keyrings (builtin, secondary, platform) or against an arbitrary user/session caller-supplied keyring named through keyring_id. There is nothing in between: the system keyrings need a kernel rebuild or a vouched-for enrollment to rotate a key, while a caller-supplied keyring is fully controlled by the loader and therefore carries no trust on its own (unless explicitly combined with BPF LSM to protect against key tampering). Add a dedicated bpf keyring to fill that gap, modelled after the dm-verity keyring which was added in commit 033724b1c627 ("dm-verity: add dm-verity keyring") and which can eventually be used also via systemd [0] through the same enrollment method as in dm-verity's case. It is selected with the new KEY_SPEC_BPF_KEYRING special key id and gives an operator a place to enroll a BPF-only signing key at boot, specifically scoped to BPF program loading and nothing else in the kernel's trust hierarchy. The id is reserved from the KEY_SPEC space so that the latter is not linked into any process keyring, and lookup_user_key() resolves KEY_SPEC_BPF_KEYRING constant instead of having to look it up via /proc/keys first. By default the keyring is sealed empty at init. Systems that want to provision keys pass bpf.keyring_unsealed=1, which leaves the keyring open for the initrd to add keys to. The keyring is only ever consulted once it is both non-empty and restricted. An unrestricted keyring is ignored. Signed-off-by: Daniel Borkmann Link: https://github.com/systemd/systemd/pull/43549 [0] Link: https://lore.kernel.org/r/20260828175227.1537793-2-daniel@iogearbox.net Signed-off-by: Alexei Starovoitov --- include/uapi/linux/keyctl.h | 1 + 1 file changed, 1 insertion(+) (limited to 'include/uapi/linux') diff --git a/include/uapi/linux/keyctl.h b/include/uapi/linux/keyctl.h index 4c8884eea808..fa85b9760391 100644 --- a/include/uapi/linux/keyctl.h +++ b/include/uapi/linux/keyctl.h @@ -24,6 +24,7 @@ #define KEY_SPEC_GROUP_KEYRING -6 /* - key ID for GID-specific keyring */ #define KEY_SPEC_REQKEY_AUTH_KEY -7 /* - key ID for assumed request_key auth key */ #define KEY_SPEC_REQUESTOR_KEYRING -8 /* - key ID for request_key() dest keyring */ +#define KEY_SPEC_BPF_KEYRING -9 /* - key ID for the BPF-specific keyring */ /* request-key default keyrings */ #define KEY_REQKEY_DEFL_NO_CHANGE -1 -- cgit v1.2.3 From 1041fca2ee8a9236255c5819d685ec34e89b4928 Mon Sep 17 00:00:00 2001 From: Andrew Halaney Date: Thu, 8 Oct 2026 15:57:35 -0500 Subject: keys: add KEY_SPEC_DM_VERITY_KEYRING The .dm-verity keyring is not linked into any process keyring, so request_key() and KEYCTL_SEARCH cannot find it. Userspace has to scrape /proc/keys for the serial to use. That's not great, one could put in a bogus description containing a newline to confuse userspace further. For example, thanks to AI models, which I run as root but you could show off as any old user: # DESC=$'.dm-verity: 1\n0badf00d I--Q--- 1 perm 082f0000 0 0 keyring .dm-verity' # k=$(echo -n "1234567" | keyctl padd user "$DESC" @s) # keyctl setperm "$k" 0x3f010001 # cat /proc/keys | grep dm-verity 10402023 I------ 2 perm 082f0000 0 0 keyring .dm-verity: empty 12e0bee6 I--Q--- 1 perm 3f010001 0 0 user .dm-verity: 1 0badf00d I--Q--- 1 perm 082f0000 0 0 keyring .dm-verity: 7 0x0badf00d is not a keyring, it is the text inside our user's key description. Telling the two apart needs a KEYCTL_DESCRIBE per row which is annoying to do. i.e.: # keyctl rdescribe 0x10402023 keyring;0;0;082f0000;.dm-verity # keyctl rdescribe 0x0badf00d keyctl_describe: Required key not available Let's just give it a well known identifier, i.e.: # keyctl rdescribe -10 keyring;0;0;082f0000;.dm-verity This is exactly what commit 264d8fd2794f ("bpf, keys: Add a bpf keyring for program signature validation") did, and was the inspiration for this commit minus the spinlock bits required due to dm-verity being a module. Suggested-by: Christian Brauner (Amutable) Reviewed-by: Jarkko Sakkinen Reviewed-by: Christian Brauner (Amutable) Signed-off-by: Andrew Halaney Link: https://patch.msgid.link/20261008-ajhalaney-dmverity-key-identifier-v2-1-008456d189cb@amutable.com Signed-off-by: Christian Brauner (Amutable) --- drivers/md/dm-verity-verify-sig.c | 3 +++ include/linux/key.h | 1 + include/uapi/linux/keyctl.h | 1 + security/keys/process_keys.c | 33 +++++++++++++++++++++++++++++++++ 4 files changed, 38 insertions(+) (limited to 'include/uapi/linux') diff --git a/drivers/md/dm-verity-verify-sig.c b/drivers/md/dm-verity-verify-sig.c index b2b55c41e2cb..da509dcdd7e5 100644 --- a/drivers/md/dm-verity-verify-sig.c +++ b/drivers/md/dm-verity-verify-sig.c @@ -189,11 +189,14 @@ int __init dm_verity_verify_sig_init(void) keyring_restrict(make_key_ref(dm_verity_keyring, true), NULL, NULL)) panic("dm-verity can't seal keyring\n"); + key_register_dm_verity_keyring(dm_verity_keyring); + return 0; } void dm_verity_verify_sig_exit(void) { + key_register_dm_verity_keyring(NULL); key_revoke(dm_verity_keyring); key_put(dm_verity_keyring); } diff --git a/include/linux/key.h b/include/linux/key.h index bd10fe45819d..ae8d3314fd93 100644 --- a/include/linux/key.h +++ b/include/linux/key.h @@ -441,6 +441,7 @@ extern int keyring_restrict(key_ref_t keyring, const char *type, const char *restriction); extern void key_register_bpf_keyring(struct key *keyring); +extern void key_register_dm_verity_keyring(struct key *keyring); extern struct key *key_lookup(key_serial_t id); diff --git a/include/uapi/linux/keyctl.h b/include/uapi/linux/keyctl.h index fa85b9760391..75923941f1b3 100644 --- a/include/uapi/linux/keyctl.h +++ b/include/uapi/linux/keyctl.h @@ -25,6 +25,7 @@ #define KEY_SPEC_REQKEY_AUTH_KEY -7 /* - key ID for assumed request_key auth key */ #define KEY_SPEC_REQUESTOR_KEYRING -8 /* - key ID for request_key() dest keyring */ #define KEY_SPEC_BPF_KEYRING -9 /* - key ID for the BPF-specific keyring */ +#define KEY_SPEC_DM_VERITY_KEYRING -10 /* - key ID for the .dm-verity keyring */ /* request-key default keyrings */ #define KEY_REQKEY_DEFL_NO_CHANGE -1 diff --git a/security/keys/process_keys.c b/security/keys/process_keys.c index 44358388e395..dba3df41638b 100644 --- a/security/keys/process_keys.c +++ b/security/keys/process_keys.c @@ -25,6 +25,10 @@ static DEFINE_MUTEX(key_session_mutex); /* BPF keyring reachable through KEY_SPEC_BPF_KEYRING */ static struct key *bpf_keyring __ro_after_init; +/* dm-verity keyring reachable through KEY_SPEC_DM_VERITY_KEYRING */ +static struct key *dm_verity_keyring; +static DEFINE_SPINLOCK(dm_verity_keyring_lock); + /* The root user's tracking struct */ struct key_user root_key_user = { .usage = REFCOUNT_INIT(3), @@ -607,6 +611,24 @@ void key_register_bpf_keyring(struct key *keyring) bpf_keyring = keyring; } +/** + * key_register_dm_verity_keyring - Publish the keyring for KEY_SPEC_DM_VERITY_KEYRING + * @keyring: The keyring to publish, or NULL to withdraw it + * + * Make @keyring reachable by userspace through the KEY_SPEC_DM_VERITY_KEYRING + * special key ID, so that provisioning it does not require scraping its + * serial out of /proc/keys first. dm-verity can be unloaded as a module + * and the keyring deregistered, as such serialize access with + * dm_verity_keyring_lock. + */ +void key_register_dm_verity_keyring(struct key *keyring) +{ + spin_lock(&dm_verity_keyring_lock); + dm_verity_keyring = keyring; + spin_unlock(&dm_verity_keyring_lock); +} +EXPORT_SYMBOL_GPL(key_register_dm_verity_keyring); + /* * Look up a key ID given us by userspace with a given permissions mask to get * the key it refers to. @@ -766,6 +788,17 @@ try_again: key_ref = make_key_ref(key, 0); break; + case KEY_SPEC_DM_VERITY_KEYRING: + spin_lock(&dm_verity_keyring_lock); + key = dm_verity_keyring; + if (key) + __key_get(key); + spin_unlock(&dm_verity_keyring_lock); + if (!key) + goto error; + key_ref = make_key_ref(key, 0); + break; + default: key_ref = ERR_PTR(-EINVAL); if (id < 1) -- cgit v1.2.3 From 5ab0054676ff947af49855c6daaa1ce3270148c1 Mon Sep 17 00:00:00 2001 From: Andrew Halaney Date: Thu, 8 Oct 2026 15:57:36 -0500 Subject: keys: add KEY_SPEC_FS_VERITY_KEYRING Same problem as the dm-verity keyring: .fs-verity is allocated unlinked, so the only way for userspace to name it is to scrape /proc/keys. Suggested-by: Christian Brauner (Amutable) Reviewed-by: Jarkko Sakkinen Reviewed-by: Christian Brauner (Amutable) Signed-off-by: Andrew Halaney Link: https://patch.msgid.link/20261008-ajhalaney-dmverity-key-identifier-v2-2-008456d189cb@amutable.com Signed-off-by: Christian Brauner (Amutable) --- fs/verity/signature.c | 2 ++ include/linux/key.h | 1 + include/uapi/linux/keyctl.h | 1 + security/keys/process_keys.c | 25 +++++++++++++++++++++++++ 4 files changed, 29 insertions(+) (limited to 'include/uapi/linux') diff --git a/fs/verity/signature.c b/fs/verity/signature.c index 0302a4e506ec..07657e913b22 100644 --- a/fs/verity/signature.c +++ b/fs/verity/signature.c @@ -135,4 +135,6 @@ void __init fsverity_init_signature(void) KEY_ALLOC_NOT_IN_QUOTA, NULL, NULL); if (IS_ERR(fsverity_keyring)) panic("failed to allocate \".fs-verity\" keyring"); + + key_register_fs_verity_keyring(fsverity_keyring); } diff --git a/include/linux/key.h b/include/linux/key.h index ae8d3314fd93..dd386c56278c 100644 --- a/include/linux/key.h +++ b/include/linux/key.h @@ -442,6 +442,7 @@ extern int keyring_restrict(key_ref_t keyring, const char *type, extern void key_register_bpf_keyring(struct key *keyring); extern void key_register_dm_verity_keyring(struct key *keyring); +extern void key_register_fs_verity_keyring(struct key *keyring); extern struct key *key_lookup(key_serial_t id); diff --git a/include/uapi/linux/keyctl.h b/include/uapi/linux/keyctl.h index 75923941f1b3..00cc0942203e 100644 --- a/include/uapi/linux/keyctl.h +++ b/include/uapi/linux/keyctl.h @@ -26,6 +26,7 @@ #define KEY_SPEC_REQUESTOR_KEYRING -8 /* - key ID for request_key() dest keyring */ #define KEY_SPEC_BPF_KEYRING -9 /* - key ID for the BPF-specific keyring */ #define KEY_SPEC_DM_VERITY_KEYRING -10 /* - key ID for the .dm-verity keyring */ +#define KEY_SPEC_FS_VERITY_KEYRING -11 /* - key ID for the .fs-verity keyring */ /* request-key default keyrings */ #define KEY_REQKEY_DEFL_NO_CHANGE -1 diff --git a/security/keys/process_keys.c b/security/keys/process_keys.c index dba3df41638b..655c58a96fcb 100644 --- a/security/keys/process_keys.c +++ b/security/keys/process_keys.c @@ -29,6 +29,9 @@ static struct key *bpf_keyring __ro_after_init; static struct key *dm_verity_keyring; static DEFINE_SPINLOCK(dm_verity_keyring_lock); +/* fs-verity keyring reachable through KEY_SPEC_FS_VERITY_KEYRING */ +static struct key *fs_verity_keyring __ro_after_init; + /* The root user's tracking struct */ struct key_user root_key_user = { .usage = REFCOUNT_INIT(3), @@ -629,6 +632,20 @@ void key_register_dm_verity_keyring(struct key *keyring) } EXPORT_SYMBOL_GPL(key_register_dm_verity_keyring); +/** + * key_register_fs_verity_keyring - Publish the keyring for KEY_SPEC_FS_VERITY_KEYRING + * @keyring: The keyring to publish + * + * Make @keyring reachable by userspace through the KEY_SPEC_FS_VERITY_KEYRING + * special key ID, so that provisioning it does not require scraping its + * serial out of /proc/keys first. Called once, from an initcall, and never + * undone. + */ +void key_register_fs_verity_keyring(struct key *keyring) +{ + fs_verity_keyring = keyring; +} + /* * Look up a key ID given us by userspace with a given permissions mask to get * the key it refers to. @@ -799,6 +816,14 @@ try_again: key_ref = make_key_ref(key, 0); break; + case KEY_SPEC_FS_VERITY_KEYRING: + key = fs_verity_keyring; + if (!key) + goto error; + __key_get(key); + key_ref = make_key_ref(key, 0); + break; + default: key_ref = ERR_PTR(-EINVAL); if (id < 1) -- cgit v1.2.3