* [BUG] lookup_user_key() calls commit_creds() (hitting BUG_ON() when called from io_uring through AF_ALG setsockopt; BPF probably also affected)
@ 2026-09-11 17:42 Jann Horn
2026-09-18 1:39 ` Jarkko Sakkinen
0 siblings, 1 reply; 2+ messages in thread
From: Jann Horn @ 2026-09-11 17:42 UTC (permalink / raw)
To: David Howells, Jarkko Sakkinen, keyrings, Roberto Sassu,
Kumar Kartikeya Dwivedi, Song Liu, Alexei Starovoitov
Cc: Herbert Xu, David S. Miller, linux-crypto, Jens Axboe, io-uring,
Daniel Borkmann, Andrii Nakryiko, Eduard Zingerman,
Martin KaFai Lau, Yonghong Song, Jiri Olsa, Emil Tsalapatis,
Ihor Solodrai, bpf
When lookup_user_key() is called on a special keyring ID
(KEY_SPEC_*_KEYRING), it can end up calling commit_creds() on several
codepaths:
lookup_user_key
install_thread_keyring [KEY_SPEC_THREAD_KEYRING + KEY_LOOKUP_CREATE]
commit_creds
install_process_keyring [KEY_SPEC_PROCESS_KEYRING + KEY_LOOKUP_CREATE]
commit_creds
join_session_keyring [KEY_SPEC_SESSION_KEYRING + KEY_LOOKUP_CREATE]
commit_creds
install_session_keyring [KEY_SPEC_SESSION_KEYRING]
commit_creds
commit_creds() is only allowed when the process does not have
overridden credentials. commit_creds() also requires that no
non-refcounted pointers obtained from current_cred() are live in any
callers.
lookup_user_key() is used by lots of keyctl operations, where it can
make sense to refer to special keyring IDs; but it is also used by
other subsystems (such as AF_ALG, nvdimm, fscrypt, BPF, ...) to look
up user-specified key IDs. In that case, special keyring IDs don't
make sense (because they refer to keyrings, not individual
cryptographic keys), and calling commit_creds() is unsafe.
It would be nice if we could ensure that commit_creds() can only be
called when KEY_LOOKUP_CREATE is set (which is only used for special
key IDs); maybe we could replace the
install_session_keyring(user_session) call with something that just
grabs a reference to the session keyring and returns it?
I made a reproducer for the lookup_user_key() call in AF_ALG, but I
think several other callers of lookup_user_key() are probably also
affected, like bpf_lookup_user_key() and nvdimm_lookup_user_key().
bpf_lookup_user_key() will probably require separate fixing; it seems
to be intentionally designed to be able to look up and create special
key IDs, but is part of the generic_kfunc_set kfunc set, which can
probably run in some contexts with overridden creds, like in arbitrary
LSM hooks. That seems inherently broken.
Reproducer
==========
As an example, when I use io_uring with ->personality to indirectly
call setsockopt(af_alg_sock, SOL_ALG, ALG_SET_KEY_BY_KEY_SERIAL,
&KEY_SPEC_SESSION_KEYRING, sizeof(int)), the kernel will go down the
following call graph.
sys_io_uring_enter
io_submit_sqes
io_submit_sqe
io_init_req
[set REQ_F_CREDS if sqe->personality is set]
io_queue_sqe
io_issue_sqe
__io_issue_sqe
override_creds [if REQ_F_CREDS is set and creds differ]
io_uring_cmd
io_uring_cmd_sock
io_uring_cmd_setsockopt
do_sock_setsockopt
alg_setsockopt
alg_setkey_by_key_serial
lookup_key
lookup_user_key
install_session_keyring
commit_creds
BUG_ON(task->cred != old)
revert_creds
Running this reproducer:
#define _GNU_SOURCE
#include <err.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/mman.h>
#include <sys/socket.h>
#include <sys/syscall.h>
#include <linux/if_alg.h>
#include <linux/io_uring.h>
#include <linux/keyctl.h>
#define SYSCHK(x) ({ \
typeof(x) __res = (x); \
if (__res == (typeof(x))-1) \
err(1, "SYSCHK(" #x ")"); \
__res; \
})
int main(void) {
int alg_sock = SYSCHK(socket(AF_ALG, SOCK_SEQPACKET, 0));
struct sockaddr_alg addr = {
.salg_family = AF_ALG,
.salg_type = "hash",
.salg_name = "sha1"
};
SYSCHK(bind(alg_sock, (struct sockaddr *)&addr, sizeof(addr)));
struct io_uring_params params = { .flags = IORING_SETUP_NO_SQARRAY };
int uring_fd = SYSCHK(syscall(__NR_io_uring_setup, /*entries=*/4, ¶ms));
// let io_uring grab a reference to the current creds
int personality = SYSCHK(syscall(__NR_io_uring_register, uring_fd,
IORING_REGISTER_PERSONALITY, NULL, 0));
// change our creds so that the io_uring personality is different
from current creds
SYSCHK(syscall(__NR_keyctl, KEYCTL_GET_KEYRING_ID,
KEY_SPEC_SESSION_KEYRING, 0, 0, 0));
char *ring_region = SYSCHK(mmap(NULL, 0x1000, PROT_READ|PROT_WRITE,
MAP_SHARED, uring_fd, IORING_OFF_CQ_RING));
struct io_uring_sqe *sqes = SYSCHK(mmap(NULL, 0x1000,
PROT_READ|PROT_WRITE, MAP_SHARED, uring_fd, IORING_OFF_SQES));
int key_serial = KEY_SPEC_SESSION_KEYRING;
sqes[0] = (struct io_uring_sqe) {
.opcode = IORING_OP_URING_CMD,
.fd = alg_sock,
.personality = personality,
/* specific to uring_cmd */
.cmd_op = SOCKET_URING_OP_SETSOCKOPT,
.optname = ALG_SET_KEY_BY_KEY_SERIAL,
.optval = (unsigned long)&key_serial,
.optlen = sizeof(key_serial),
.level = SOL_ALG,
};
(*(unsigned int *)(ring_region + params.sq_off.tail))++;
int submitted = SYSCHK(syscall(__NR_io_uring_enter, uring_fd,
/*to_submit=*/1, /*min_complete=*/1,
/*flags=*/0, /*sig=*/NULL, /*sigsz=*/0));
printf("submitted %d and completed\n", submitted);
}
I get this splat:
[ 543.190354][ T111] kernel BUG at kernel/cred.c:376!
[...]
[ 543.196705][ T111] Call Trace:
[...]
[ 543.197319][ T111] lookup_user_key+0x261/0xbe0
[...]
[ 543.197869][ T111] alg_setkey_by_key_serial+0xbc/0x300
[ 543.198153][ T111] alg_setsockopt+0x1a2/0x2a0
[...]
[ 543.198610][ T111] do_sock_setsockopt+0x103/0x130
[ 543.198863][ T111] io_uring_cmd_sock+0x257/0x8b0
[...]
[ 543.199608][ T111] io_uring_cmd+0x1f3/0x2a0
[...]
[ 543.200103][ T111] __io_issue_sqe+0xd8/0x2a0
[ 543.200335][ T111] io_issue_sqe+0xde/0x8c0
[...]
[ 543.201100][ T111] io_submit_sqes+0x7a6/0x1060
[ 543.201380][ T111] __se_sys_io_uring_enter+0x218/0xf40
[...]
[ 543.202158][ T111] __x64_sys_io_uring_enter+0x82/0xa0
[...]
[ 543.202780][ T111] x64_sys_call+0x175f/0x2600
[ 543.203086][ T111] do_syscall_64+0xbe/0x360
[ 543.203348][ T111] entry_SYSCALL_64_after_hwframe+0x76/0x7e
^ permalink raw reply [flat|nested] 2+ messages in thread
* Re: [BUG] lookup_user_key() calls commit_creds() (hitting BUG_ON() when called from io_uring through AF_ALG setsockopt; BPF probably also affected)
2026-09-11 17:42 [BUG] lookup_user_key() calls commit_creds() (hitting BUG_ON() when called from io_uring through AF_ALG setsockopt; BPF probably also affected) Jann Horn
@ 2026-09-18 1:39 ` Jarkko Sakkinen
0 siblings, 0 replies; 2+ messages in thread
From: Jarkko Sakkinen @ 2026-09-18 1:39 UTC (permalink / raw)
To: Jann Horn
Cc: David Howells, keyrings, Roberto Sassu, Kumar Kartikeya Dwivedi,
Song Liu, Alexei Starovoitov, Herbert Xu, David S. Miller,
linux-crypto, Jens Axboe, io-uring, Daniel Borkmann,
Andrii Nakryiko, Eduard Zingerman, Martin KaFai Lau,
Yonghong Song, Jiri Olsa, Emil Tsalapatis, Ihor Solodrai, bpf
On Fri, Sep 11, 2026 at 07:42:50PM +0200, Jann Horn wrote:
> When lookup_user_key() is called on a special keyring ID
> (KEY_SPEC_*_KEYRING), it can end up calling commit_creds() on several
> codepaths:
>
> lookup_user_key
> install_thread_keyring [KEY_SPEC_THREAD_KEYRING + KEY_LOOKUP_CREATE]
> commit_creds
> install_process_keyring [KEY_SPEC_PROCESS_KEYRING + KEY_LOOKUP_CREATE]
> commit_creds
> join_session_keyring [KEY_SPEC_SESSION_KEYRING + KEY_LOOKUP_CREATE]
> commit_creds
> install_session_keyring [KEY_SPEC_SESSION_KEYRING]
> commit_creds
>
> commit_creds() is only allowed when the process does not have
> overridden credentials. commit_creds() also requires that no
> non-refcounted pointers obtained from current_cred() are live in any
> callers.
>
> lookup_user_key() is used by lots of keyctl operations, where it can
> make sense to refer to special keyring IDs; but it is also used by
> other subsystems (such as AF_ALG, nvdimm, fscrypt, BPF, ...) to look
> up user-specified key IDs. In that case, special keyring IDs don't
> make sense (because they refer to keyrings, not individual
> cryptographic keys), and calling commit_creds() is unsafe.
>
> It would be nice if we could ensure that commit_creds() can only be
> called when KEY_LOOKUP_CREATE is set (which is only used for special
> key IDs); maybe we could replace the
> install_session_keyring(user_session) call with something that just
> grabs a reference to the session keyring and returns it?
>
> I made a reproducer for the lookup_user_key() call in AF_ALG, but I
> think several other callers of lookup_user_key() are probably also
> affected, like bpf_lookup_user_key() and nvdimm_lookup_user_key().
>
> bpf_lookup_user_key() will probably require separate fixing; it seems
> to be intentionally designed to be able to look up and create special
> key IDs, but is part of the generic_kfunc_set kfunc set, which can
> probably run in some contexts with overridden creds, like in arbitrary
> LSM hooks. That seems inherently broken.
>
> Reproducer
> ==========
> As an example, when I use io_uring with ->personality to indirectly
> call setsockopt(af_alg_sock, SOL_ALG, ALG_SET_KEY_BY_KEY_SERIAL,
> &KEY_SPEC_SESSION_KEYRING, sizeof(int)), the kernel will go down the
> following call graph.
>
> sys_io_uring_enter
> io_submit_sqes
> io_submit_sqe
> io_init_req
> [set REQ_F_CREDS if sqe->personality is set]
> io_queue_sqe
> io_issue_sqe
> __io_issue_sqe
> override_creds [if REQ_F_CREDS is set and creds differ]
> io_uring_cmd
> io_uring_cmd_sock
> io_uring_cmd_setsockopt
> do_sock_setsockopt
> alg_setsockopt
> alg_setkey_by_key_serial
> lookup_key
> lookup_user_key
> install_session_keyring
> commit_creds
> BUG_ON(task->cred != old)
> revert_creds
>
> Running this reproducer:
>
> #define _GNU_SOURCE
> #include <err.h>
> #include <stdio.h>
> #include <unistd.h>
> #include <sys/mman.h>
> #include <sys/socket.h>
> #include <sys/syscall.h>
> #include <linux/if_alg.h>
> #include <linux/io_uring.h>
> #include <linux/keyctl.h>
>
> #define SYSCHK(x) ({ \
> typeof(x) __res = (x); \
> if (__res == (typeof(x))-1) \
> err(1, "SYSCHK(" #x ")"); \
> __res; \
> })
>
> int main(void) {
> int alg_sock = SYSCHK(socket(AF_ALG, SOCK_SEQPACKET, 0));
> struct sockaddr_alg addr = {
> .salg_family = AF_ALG,
> .salg_type = "hash",
> .salg_name = "sha1"
> };
> SYSCHK(bind(alg_sock, (struct sockaddr *)&addr, sizeof(addr)));
>
> struct io_uring_params params = { .flags = IORING_SETUP_NO_SQARRAY };
> int uring_fd = SYSCHK(syscall(__NR_io_uring_setup, /*entries=*/4, ¶ms));
>
> // let io_uring grab a reference to the current creds
> int personality = SYSCHK(syscall(__NR_io_uring_register, uring_fd,
> IORING_REGISTER_PERSONALITY, NULL, 0));
> // change our creds so that the io_uring personality is different
> from current creds
> SYSCHK(syscall(__NR_keyctl, KEYCTL_GET_KEYRING_ID,
> KEY_SPEC_SESSION_KEYRING, 0, 0, 0));
>
> char *ring_region = SYSCHK(mmap(NULL, 0x1000, PROT_READ|PROT_WRITE,
> MAP_SHARED, uring_fd, IORING_OFF_CQ_RING));
> struct io_uring_sqe *sqes = SYSCHK(mmap(NULL, 0x1000,
> PROT_READ|PROT_WRITE, MAP_SHARED, uring_fd, IORING_OFF_SQES));
>
> int key_serial = KEY_SPEC_SESSION_KEYRING;
> sqes[0] = (struct io_uring_sqe) {
> .opcode = IORING_OP_URING_CMD,
> .fd = alg_sock,
> .personality = personality,
>
> /* specific to uring_cmd */
> .cmd_op = SOCKET_URING_OP_SETSOCKOPT,
> .optname = ALG_SET_KEY_BY_KEY_SERIAL,
> .optval = (unsigned long)&key_serial,
> .optlen = sizeof(key_serial),
> .level = SOL_ALG,
> };
> (*(unsigned int *)(ring_region + params.sq_off.tail))++;
>
> int submitted = SYSCHK(syscall(__NR_io_uring_enter, uring_fd,
> /*to_submit=*/1, /*min_complete=*/1,
> /*flags=*/0, /*sig=*/NULL, /*sigsz=*/0));
> printf("submitted %d and completed\n", submitted);
> }
>
>
> I get this splat:
>
> [ 543.190354][ T111] kernel BUG at kernel/cred.c:376!
> [...]
> [ 543.196705][ T111] Call Trace:
> [...]
> [ 543.197319][ T111] lookup_user_key+0x261/0xbe0
> [...]
> [ 543.197869][ T111] alg_setkey_by_key_serial+0xbc/0x300
> [ 543.198153][ T111] alg_setsockopt+0x1a2/0x2a0
> [...]
> [ 543.198610][ T111] do_sock_setsockopt+0x103/0x130
> [ 543.198863][ T111] io_uring_cmd_sock+0x257/0x8b0
> [...]
> [ 543.199608][ T111] io_uring_cmd+0x1f3/0x2a0
> [...]
> [ 543.200103][ T111] __io_issue_sqe+0xd8/0x2a0
> [ 543.200335][ T111] io_issue_sqe+0xde/0x8c0
> [...]
> [ 543.201100][ T111] io_submit_sqes+0x7a6/0x1060
> [ 543.201380][ T111] __se_sys_io_uring_enter+0x218/0xf40
> [...]
> [ 543.202158][ T111] __x64_sys_io_uring_enter+0x82/0xa0
> [...]
> [ 543.202780][ T111] x64_sys_call+0x175f/0x2600
> [ 543.203086][ T111] do_syscall_64+0xbe/0x360
> [ 543.203348][ T111] entry_SYSCALL_64_after_hwframe+0x76/0x7e
Just got this while going through inbox.
I'll run the reproducer with a test environment and respond properly
later.
BR, Jarkko
^ permalink raw reply [flat|nested] 2+ messages in thread
end of thread, other threads:[~2026-09-18 1:39 UTC | newest]
Thread overview: 2+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-09-11 17:42 [BUG] lookup_user_key() calls commit_creds() (hitting BUG_ON() when called from io_uring through AF_ALG setsockopt; BPF probably also affected) Jann Horn
2026-09-18 1:39 ` Jarkko Sakkinen
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox