| Seq | Timestamp | Type | Name | Duration |
|---|---|---|---|---|
| 0/0 | 2026/10/21 00:04 | flow | finding-triage |
3mArgs:null Results: {
"Introduced": true,
"Reasoning": "The crash is a NULL pointer dereference (KASAN null-ptr-deref at offset 0xd8, corresponding to the `uid` field of `struct key`) in `keyctl_session_to_parent()`:\n```c\nif ((pcred-\u003esession_keyring \u0026\u0026\n !uid_eq(pcred-\u003esession_keyring-\u003euid, mycred-\u003eeuid)) ||\n !uid_eq(mycred-\u003esession_keyring-\u003euid, mycred-\u003eeuid))\n goto unlock;\n```\n\nPrior to patch 1 (\"KEYS: Return user session keyring on lookup\"), when a process without a session keyring looked up `KEY_SPEC_SESSION_KEYRING` without `KEY_LOOKUP_CREATE`, `lookup_user_key()` invoked `install_session_keyring(user_session)` to install the user session keyring into the calling task's credentials (`ctx.cred-\u003esession_keyring`). As a result, in `keyctl_session_to_parent()`, `mycred = current_cred()` was guaranteed to have a non-NULL `mycred-\u003esession_keyring`.\n\nPatch 1 modified `lookup_user_key()` to return the user session keyring reference directly without installing it into the caller's credentials when `KEY_LOOKUP_CREATE` is not specified. In `keyctl_session_to_parent()`, `lookup_user_key(KEY_SPEC_SESSION_KEYRING, 0, KEY_NEED_LINK)` is called with `lflags = 0`. Consequently, `mycred-\u003esession_keyring` remains NULL. Later in `keyctl_session_to_parent()`, `mycred-\u003esession_keyring-\u003euid` is unconditionally dereferenced at line 1707, triggering the null-pointer dereference GPF.\n\nTherefore, the crash was directly introduced by the tested patch series."
} |
| 1/1 | 2026/10/21 00:04 | action | prepare-finding-overview |
0mArgs:null Results: {
"DiffStat": "Documentation/security/keys/core.rst | 7 ++--\n security/keys/process_keys.c | 65 +++++++++++++++++++-----------------\n 2 files changed, 40 insertions(+), 32 deletions(-)",
"HasCover": false,
"PatchCount": 2,
"PatchList": "The patch series contains 2 patches:\n[1] [PATCH v2 1/2] KEYS: Return user session keyring on lookup\n[2] [PATCH v2 2/2] KEYS: Reject keyring creation with overridden credentials"
} |
| 2/1 | 2026/10/21 00:04 | agent | finding-triage-evaluator |
3mModel:core Args: null Results: {
"Introduced": true,
"Reasoning": "The crash is a NULL pointer dereference (KASAN null-ptr-deref at offset 0xd8, corresponding to the `uid` field of `struct key`) in `keyctl_session_to_parent()`:\n```c\nif ((pcred-\u003esession_keyring \u0026\u0026\n !uid_eq(pcred-\u003esession_keyring-\u003euid, mycred-\u003eeuid)) ||\n !uid_eq(mycred-\u003esession_keyring-\u003euid, mycred-\u003eeuid))\n goto unlock;\n```\n\nPrior to patch 1 (\"KEYS: Return user session keyring on lookup\"), when a process without a session keyring looked up `KEY_SPEC_SESSION_KEYRING` without `KEY_LOOKUP_CREATE`, `lookup_user_key()` invoked `install_session_keyring(user_session)` to install the user session keyring into the calling task's credentials (`ctx.cred-\u003esession_keyring`). As a result, in `keyctl_session_to_parent()`, `mycred = current_cred()` was guaranteed to have a non-NULL `mycred-\u003esession_keyring`.\n\nPatch 1 modified `lookup_user_key()` to return the user session keyring reference directly without installing it into the caller's credentials when `KEY_LOOKUP_CREATE` is not specified. In `keyctl_session_to_parent()`, `lookup_user_key(KEY_SPEC_SESSION_KEYRING, 0, KEY_NEED_LINK)` is called with `lflags = 0`. Consequently, `mycred-\u003esession_keyring` remains NULL. Later in `keyctl_session_to_parent()`, `mycred-\u003esession_keyring-\u003euid` is unconditionally dereferenced at line 1707, triggering the null-pointer dereference GPF.\n\nTherefore, the crash was directly introduced by the tested patch series."
}Instruction: You are an expert Linux kernel maintainer and security engineer specializing
in crash triage and root-cause analysis.
Your job is to review a kernel crash report (finding) and determine if it was introduced / caused
by the patch series being tested, or if it is a pre-existing / unrelated bug.
IMPORTANT: The entire patch series has ALREADY been applied and committed as a single squashed commit
at HEAD (or on top of the base commit) in your workspace.
Do NOT rely on your internal knowledge of the kernel. You must actively use your tools to examine
the source code, inspect workspace diffs, and confirm any assumptions.
Available Tools:
- git-show: View the squashed patch series commit or historical commits. Call with Commit="HEAD" (default)
and optional 'File' parameter to restrict the diff to a specific file or directory, or 'Stat=true' for a diffstat.
- series-patches: Browse the original patch series. Call without arguments to list all patch titles,
or PatchNum=<N> to view the full description and diff of a specific patch (or cover letter, if present).
- read-file & codesearch-dir-index: Read exact source code files and directory contents in the workspace.
- grepper: Regex search across files in the workspace (git grep).
- git-blame: Inspect line authorship and find which commit last modified specific lines of code.
Triage Procedure:
1. Analyze the Crash Report:
- Identify the crash type (e.g. NULL dereference, KASAN use-after-free, out-of-bounds read/write,
assertion failure, deadlock).
- Carefully examine the stack trace(s) — both the faulting call stack and, if applicable (e.g., KASAN UAF),
the allocation and free stacks.
- Note the faulting function, file name, line numbers, and the specific variables or data structures involved.
2. Inspect Workspace Changes & Patch Series:
- Check if any files or functions from the stack trace appear in the modified files overview or the patch series.
- Use git-show with Commit="HEAD" and 'File' to inspect the exact diffs for relevant files
in the squashed commit.
- If needed, use series-patches with PatchNum=<N> to inspect the author's commit description
and rationale for individual patches in the series.
- Use read-file to inspect the actual source code at the faulting lines.
3. Determine Causality:
- Set Introduced=true ONLY IF you are absolutely certain:
* There is clear, unambiguous, and conclusive evidence that the crash was directly introduced
or caused by the patch series.
* The crash occurs directly in new or modified code introduced by the patch series, or the patch series
altered data structures, locking, object lifetimes, or preconditions in a way that directly
triggered the failure in existing code.
- Set Introduced=false IF:
* There is any doubt, ambiguity, or lack of conclusive proof that the patch series caused the bug.
* The crash occurs in an unrelated subsystem or unmodified code path whose behavior is independent
of the patch series.
* The crash is a pre-existing kernel issue that was randomly triggered during fuzzing.
4. Formulate Output:
- Set Introduced to true or false.
- In Reasoning, provide a clear, step-by-step technical explanation detailing the crash type,
the key stack frames, whether the affected code/structures were modified by the patch series,
and the causal justification for your verdict.
Prefer calling several tools at the same time to save round-trips.
Use set-results tool to provide results of the analysis.
It must be called exactly once before the final reply.
Ignore results of this tool.
Prompt: Target architecture: amd64 The kernel crash report to triage is: Oops: general protection fault, probably for non-canonical address 0xdffffc000000001b: 0000 [#1] SMP KASAN PTI KASAN: null-ptr-deref in range [0x00000000000000d8-0x00000000000000df] CPU: 0 UID: 0 PID: 5730 Comm: syz.0.6 Not tainted syzkaller #0 PREEMPT(full) Hardware name: QEMU Standard PC (Q35 + ICH9, 2009), BIOS 1.16.2-debian-1.16.2-1 04/01/2014 RIP: 0010:keyctl_session_to_parent+0x825/0xa30 Code: 35 3b fd e9 fe fa ff ff e8 38 35 3b fd e9 f4 fa ff ff e8 2e 35 3b fd 48 8b 54 24 20 48 81 c2 d8 00 00 00 48 89 d0 48 c1 e8 03 <0f> b6 04 18 84 c0 0f 85 b2 01 00 00 8b 2a 89 ef 8b 5c 24 1c 89 de RSP: 0018:ffffc9000621fc70 EFLAGS: 00010006 RAX: 000000000000001b RBX: dffffc0000000000 RCX: ffff88810b603c00 RDX: 00000000000000d8 RSI: 0000000000000000 RDI: 0000000000000000 RBP: 0000000000000000 R08: 0000000000000003 R09: 0000000000000004 R10: dffffc0000000000 R11: fffff52000c43f7c R12: ffff88811a010000 R13: 0000000000000000 R14: ffff88810c2631a8 R15: 0000000000000000 FS: 00007f7f2068b6c0(0000) GS:ffff88818c4bf000(0000) knlGS:0000000000000000 CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033 CR2: 00007f7f2068aff8 CR3: 000000010cae8000 CR4: 00000000000006f0 Call Trace: <TASK> __se_sys_keyctl+0x780/0x9d0 do_syscall_64+0x166/0x520 entry_SYSCALL_64_after_hwframe+0x77/0x7f RIP: 0033:0x7f7f1f79f559 Code: ff c3 66 2e 0f 1f 84 00 00 00 00 00 0f 1f 44 00 00 48 89 f8 48 89 f7 48 89 d6 48 89 ca 4d 89 c2 4d 89 c8 4c 8b 4c 24 08 0f 05 <48> 3d 01 f0 ff ff 73 01 c3 48 c7 c1 e8 ff ff ff f7 d8 64 89 01 48 RSP: 002b:00007f7f2068b028 EFLAGS: 00000246 ORIG_RAX: 00000000000000fa RAX: ffffffffffffffda RBX: 00007f7f1fa26270 RCX: 00007f7f1f79f559 RDX: 0000000000000000 RSI: 0000000000000000 RDI: 0000000000000012 RBP: 00007f7f1f836b20 R08: 0000000000000000 R09: 0000000000000000 R10: 0000000000000000 R11: 0000000000000246 R12: 0000000000000000 R13: 00007f7f1fa26308 R14: 00007f7f1fa26270 R15: 00007ffe789f59b8 </TASK> Modules linked in: ---[ end trace 0000000000000000 ]--- RIP: 0010:keyctl_session_to_parent+0x825/0xa30 Code: 35 3b fd e9 fe fa ff ff e8 38 35 3b fd e9 f4 fa ff ff e8 2e 35 3b fd 48 8b 54 24 20 48 81 c2 d8 00 00 00 48 89 d0 48 c1 e8 03 <0f> b6 04 18 84 c0 0f 85 b2 01 00 00 8b 2a 89 ef 8b 5c 24 1c 89 de RSP: 0018:ffffc9000621fc70 EFLAGS: 00010006 RAX: 000000000000001b RBX: dffffc0000000000 RCX: ffff88810b603c00 RDX: 00000000000000d8 RSI: 0000000000000000 RDI: 0000000000000000 RBP: 0000000000000000 R08: 0000000000000003 R09: 0000000000000004 R10: dffffc0000000000 R11: fffff52000c43f7c R12: ffff88811a010000 R13: 0000000000000000 R14: ffff88810c2631a8 R15: 0000000000000000 FS: 00007f7f2068b6c0(0000) GS:ffff88818c4bf000(0000) knlGS:0000000000000000 CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033 CR2: 00007f7f2068aff8 CR3: 000000010cae8000 CR4: 00000000000006f0 Overview of modified files in the workspace: Documentation/security/keys/core.rst | 7 ++-- security/keys/process_keys.c | 65 +++++++++++++++++++----------------- 2 files changed, 40 insertions(+), 32 deletions(-) The patch series contains 2 patches: [1] [PATCH v2 1/2] KEYS: Return user session keyring on lookup [2] [PATCH v2 2/2] KEYS: Reject keyring creation with overridden credentials |
| 3/2 | 2026/10/21 00:04 | llm | finding-triage-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 4818 output: 24 thoughts: 299 Thoughts: **Kernel Crash Triage - Let's Dig In** Okay, the user wants me to look into a kernel crash, an "Oops: general protection fault." The error message points to a null-pointer dereference within the key management subsystem. Specifically, KASAN flagged a dereference at a non-canonical address, which is always fun. The crash occurred within `keyctl_session_to_parent`, which the call trace identifies. Let's start with this function, but the whole subsystem is potentially involved, so I'll keep the broader context in mind. The call stack traces through `__se_sys_keyctl` and `do_syscall_64`, which means we're in the user-space interface, handling a system call. This suggests a user-space application might be triggering this issue through the `keyctl` system call. The specific function call is `keyctl_session_to_parent`, which is suspicious. The system reported that two files were modified, and the path names are very relevant here. Both relate to security keys. Let's see what changed! `Documentation/security/keys/core.rst` - that's documentation, so it's good to understand the context and intent of the changes. It's only a minor change, so I'll review it, but not hold my breath that it's related. `security/keys/process_keys.c` - This is the primary file containing the function where the crash happened, and it appears heavily modified! Bingo! The code related to my crash is likely in this file! It looks like we have a patch series here. I'll need to go through both of them to understand the code change context. * **\[1] \[PATCH v2 1/2] KEYS: Return user session keyring on lookup**: This seems like a potential place for issues to hide. We're changing the behavior of a user session keyring lookup. Returning a different keyring is subtle. Is the return value handled correctly everywhere? Any potential null pointer dereferences? * **\[2] \[PATCH v2 2/2] KEYS: Reject keyring creation with overridden credentials**: This patch seems less relevant at first glance. It restricts keyring creation, but I still need to be aware of the context. Could this change indirectly influence the path leading to `keyctl_session_to_parent`? I need to understand how the changes impact this particular function. I'll start with the git diff of `security/keys/process_keys.c` and then step through the patches in order, paying special attention to the logic around `keyctl_session_to_parent`. This is going to require careful code review! |
| 4/2 | 2026/10/21 00:04 | tool | git-show |
0mArgs:{
"Stat": true
}Results: {
"Output": "commit 0ad768bd79d950c54402c3fd038d5e7f332af15e\nAuthor: syz-cluster \u003ctriage@syzkaller.com\u003e\nDate: Wed Oct 21 00:04:36 2026 +0000\n\n syz-cluster: applied patch under review\n\n Documentation/security/keys/core.rst | 7 ++--\n security/keys/process_keys.c | 65 +++++++++++++++++++-----------------\n 2 files changed, 40 insertions(+), 32 deletions(-)\n"
} |
| 5/2 | 2026/10/21 00:04 | tool | series-patches |
0mError:missing argument "PatchNum" Args: null Results: {
"error": "missing argument \"PatchNum\""
} |
| 6/2 | 2026/10/21 00:04 | llm | finding-triage-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 5893 output: 31 thoughts: 29 Thoughts: Here's a summary of my thinking, formatted for an expert audience: **Analyzing the Patch Application Strategy** Okay, I'm thinking about how to approach this patch application. My first instinct is to use `series-patches` with `PatchNum=1` and `PatchNum=2`. This allows for a controlled, granular application of the patches in a specific order. I can leverage the benefits of applying them sequentially. Then, to get a comprehensive view of the changes introduced by *both* patches, I'll use `git show` on the resulting diff. This should give me the full, consolidated set of changes. This way, I can avoid having to look at the individual patch's content or having to diff it manually. |
| 7/2 | 2026/10/21 00:04 | tool | git-show |
0mArgs:{
"Commit": "HEAD"
}Results: {
"Output": "commit 0ad768bd79d950c54402c3fd038d5e7f332af15e\nAuthor: syz-cluster \u003ctriage@syzkaller.com\u003e\nDate: Wed Oct 21 00:04:36 2026 +0000\n\n syz-cluster: applied patch under review\n\ndiff --git a/Documentation/security/keys/core.rst b/Documentation/security/keys/core.rst\nindex 326b8a9738289..9779ce71df479 100644\n--- a/Documentation/security/keys/core.rst\n+++ b/Documentation/security/keys/core.rst\n@@ -165,8 +165,11 @@ The key service provides a number of features besides keys:\n When a process changes its real UID, if it used to have no session key, it\n will be subscribed to the default session key for the new UID.\n \n- If a process attempts to access its session key when it doesn't have one,\n- it will be subscribed to the default for its current UID.\n+ If a process attempts to access its session keyring when it doesn't have\n+ one, the default user session keyring for its current UID is returned\n+ without being installed into its credentials. If creation is requested,\n+ an anonymous session keyring is installed, failing with -EPERM if\n+ credentials are overridden.\n \n * Each user has two quotas against which the keys they own are tracked. One\n limits the total number of keys and keyrings, the other limits the total\ndiff --git a/security/keys/process_keys.c b/security/keys/process_keys.c\nindex 1a51a36ce32a8..ab7f13ccebb1c 100644\n--- a/security/keys/process_keys.c\n+++ b/security/keys/process_keys.c\n@@ -347,28 +347,11 @@ int install_session_keyring_to_cred(struct cred *cred, struct key *keyring)\n }\n \n /*\n- * Install the given keyring as the session keyring of the current task,\n- * replacing the existing one if any. If the given keyring is NULL, then\n- * install a new anonymous session keyring.\n- *\n- * Return: 0 on success; -errno on failure.\n+ * Determine whether the current task is running with overridden credentials.\n */\n-static int install_session_keyring(struct key *keyring)\n+static bool cred_overridden(void)\n {\n-\tstruct cred *new;\n-\tint ret;\n-\n-\tnew = prepare_creds();\n-\tif (!new)\n-\t\treturn -ENOMEM;\n-\n-\tret = install_session_keyring_to_cred(new, keyring);\n-\tif (ret \u003c 0) {\n-\t\tabort_creds(new);\n-\t\treturn ret;\n-\t}\n-\n-\treturn commit_creds(new);\n+\treturn current_cred() != current_real_cred();\n }\n \n /*\n@@ -602,7 +585,8 @@ bool lookup_user_key_possessed(const struct key *key,\n * to a key or the best found key was a negative key; -EKEYREVOKED or\n * -EKEYEXPIRED if the best found key was revoked or expired; -EACCES if the\n * found key doesn't grant the requested permit or the LSM denied access to it;\n- * or -ENOMEM if a special keyring couldn't be created.\n+ * -ENOMEM if a special keyring couldn't be created; or -EPERM if creating one\n+ * while credentials are overridden.\n *\n * In the case of a successful return, the possession attribute is set on the\n * returned key reference.\n@@ -631,6 +615,11 @@ key_ref_t lookup_user_key(key_serial_t id, unsigned long lflags,\n \t\t\tif (!(lflags \u0026 KEY_LOOKUP_CREATE))\n \t\t\t\tgoto error;\n \n+\t\t\tif (cred_overridden()) {\n+\t\t\t\tkey_ref = ERR_PTR(-EPERM);\n+\t\t\t\tgoto error;\n+\t\t\t}\n+\n \t\t\tret = install_thread_keyring();\n \t\t\tif (ret \u003c 0) {\n \t\t\t\tkey_ref = ERR_PTR(ret);\n@@ -649,6 +638,11 @@ key_ref_t lookup_user_key(key_serial_t id, unsigned long lflags,\n \t\t\tif (!(lflags \u0026 KEY_LOOKUP_CREATE))\n \t\t\t\tgoto error;\n \n+\t\t\tif (cred_overridden()) {\n+\t\t\t\tkey_ref = ERR_PTR(-EPERM);\n+\t\t\t\tgoto error;\n+\t\t\t}\n+\n \t\t\tret = install_process_keyring();\n \t\t\tif (ret \u003c 0) {\n \t\t\t\tkey_ref = ERR_PTR(ret);\n@@ -664,23 +658,34 @@ key_ref_t lookup_user_key(key_serial_t id, unsigned long lflags,\n \n \tcase KEY_SPEC_SESSION_KEYRING:\n \t\tif (!ctx.cred-\u003esession_keyring) {\n-\t\t\t/* always install a session keyring upon access if one\n-\t\t\t * doesn't exist yet */\n \t\t\tret = look_up_user_keyrings(NULL, \u0026user_session);\n \t\t\tif (ret \u003c 0)\n \t\t\t\tgoto error;\n-\t\t\tif (lflags \u0026 KEY_LOOKUP_CREATE)\n+\n+\t\t\tif (lflags \u0026 KEY_LOOKUP_CREATE) {\n+\t\t\t\tkey_put(user_session);\n+\t\t\t\tif (cred_overridden()) {\n+\t\t\t\t\tkey_ref = ERR_PTR(-EPERM);\n+\t\t\t\t\tgoto error;\n+\t\t\t\t}\n+\n \t\t\t\tret = join_session_keyring(NULL);\n-\t\t\telse\n-\t\t\t\tret = install_session_keyring(user_session);\n+\t\t\t\tif (ret \u003c 0)\n+\t\t\t\t\tgoto error;\n+\t\t\t\tgoto reget_creds;\n+\t\t\t}\n \n-\t\t\tkey_put(user_session);\n-\t\t\tif (ret \u003c 0)\n-\t\t\t\tgoto error;\n-\t\t\tgoto reget_creds;\n+\t\t\tkey = user_session;\n+\t\t\tkey_ref = make_key_ref(key, 1);\n+\t\t\tbreak;\n \t\t} else if (test_bit(KEY_FLAG_UID_KEYRING,\n \t\t\t\t \u0026ctx.cred-\u003esession_keyring-\u003eflags) \u0026\u0026\n \t\t\t lflags \u0026 KEY_LOOKUP_CREATE) {\n+\t\t\tif (cred_overridden()) {\n+\t\t\t\tkey_ref = ERR_PTR(-EPERM);\n+\t\t\t\tgoto error;\n+\t\t\t}\n+\n \t\t\tret = join_session_keyring(NULL);\n \t\t\tif (ret \u003c 0)\n \t\t\t\tgoto error;\n"
} |
| 8/2 | 2026/10/21 00:04 | tool | series-patches |
0mArgs:{
"PatchNum": 1
}Results: {
"Output": "Patch [1] [PATCH v2 1/2] KEYS: Return user session keyring on lookup:\n\nA process without a session keyring looking up KEY_SPEC_SESSION_KEYRING\nwithout KEY_LOOKUP_CREATE mutates the credentials. This causes struct creds\ninstances shared with other subsystems to become stale.\n\nAddress this by returning the resolved user session keyring directly\nwithout installing it into credentials.\n\nCc: stable@vger.kernel.org # v5.10+\nSuggested-by: Jann Horn \u003cjannh@google.com\u003e\nCloses: https://lore.kernel.org/keyrings/CAG48ez0XjVSR=M--UBauTm8sJuc+ZbhKzaPa3a6wrSVHyoKevw@mail.gmail.com/\nSigned-off-by: Jarkko Sakkinen \u003cjarkko@kernel.org\u003e\n---\nv2:\n- Added mandatory tags.\n- No code changes.\n---\n Documentation/security/keys/core.rst | 6 ++--\n security/keys/process_keys.c | 44 +++++++---------------------\n 2 files changed, 14 insertions(+), 36 deletions(-)\n\ndiff --git a/Documentation/security/keys/core.rst b/Documentation/security/keys/core.rst\nindex 326b8a973828..c81a3a9fe236 100644\n--- a/Documentation/security/keys/core.rst\n+++ b/Documentation/security/keys/core.rst\n@@ -165,8 +165,10 @@ The key service provides a number of features besides keys:\n When a process changes its real UID, if it used to have no session key, it\n will be subscribed to the default session key for the new UID.\n \n- If a process attempts to access its session key when it doesn't have one,\n- it will be subscribed to the default for its current UID.\n+ If a process attempts to access its session keyring when it doesn't have\n+ one, the default user session keyring for its current UID is returned\n+ without being installed into its credentials. If creation is requested,\n+ an anonymous session keyring is installed.\n \n * Each user has two quotas against which the keys they own are tracked. One\n limits the total number of keys and keyrings, the other limits the total\ndiff --git a/security/keys/process_keys.c b/security/keys/process_keys.c\nindex 1a51a36ce32a..558ffe58b5d6 100644\n--- a/security/keys/process_keys.c\n+++ b/security/keys/process_keys.c\n@@ -346,31 +346,6 @@ int install_session_keyring_to_cred(struct cred *cred, struct key *keyring)\n \treturn 0;\n }\n \n-/*\n- * Install the given keyring as the session keyring of the current task,\n- * replacing the existing one if any. If the given keyring is NULL, then\n- * install a new anonymous session keyring.\n- *\n- * Return: 0 on success; -errno on failure.\n- */\n-static int install_session_keyring(struct key *keyring)\n-{\n-\tstruct cred *new;\n-\tint ret;\n-\n-\tnew = prepare_creds();\n-\tif (!new)\n-\t\treturn -ENOMEM;\n-\n-\tret = install_session_keyring_to_cred(new, keyring);\n-\tif (ret \u003c 0) {\n-\t\tabort_creds(new);\n-\t\treturn ret;\n-\t}\n-\n-\treturn commit_creds(new);\n-}\n-\n /*\n * Handle the fsuid changing.\n */\n@@ -664,20 +639,21 @@ key_ref_t lookup_user_key(key_serial_t id, unsigned long lflags,\n \n \tcase KEY_SPEC_SESSION_KEYRING:\n \t\tif (!ctx.cred-\u003esession_keyring) {\n-\t\t\t/* always install a session keyring upon access if one\n-\t\t\t * doesn't exist yet */\n \t\t\tret = look_up_user_keyrings(NULL, \u0026user_session);\n \t\t\tif (ret \u003c 0)\n \t\t\t\tgoto error;\n-\t\t\tif (lflags \u0026 KEY_LOOKUP_CREATE)\n+\n+\t\t\tif (lflags \u0026 KEY_LOOKUP_CREATE) {\n+\t\t\t\tkey_put(user_session);\n \t\t\t\tret = join_session_keyring(NULL);\n-\t\t\telse\n-\t\t\t\tret = install_session_keyring(user_session);\n+\t\t\t\tif (ret \u003c 0)\n+\t\t\t\t\tgoto error;\n+\t\t\t\tgoto reget_creds;\n+\t\t\t}\n \n-\t\t\tkey_put(user_session);\n-\t\t\tif (ret \u003c 0)\n-\t\t\t\tgoto error;\n-\t\t\tgoto reget_creds;\n+\t\t\tkey = user_session;\n+\t\t\tkey_ref = make_key_ref(key, 1);\n+\t\t\tbreak;\n \t\t} else if (test_bit(KEY_FLAG_UID_KEYRING,\n \t\t\t\t \u0026ctx.cred-\u003esession_keyring-\u003eflags) \u0026\u0026\n \t\t\t lflags \u0026 KEY_LOOKUP_CREATE) {\n-- \n2.47.3"
} |
| 9/2 | 2026/10/21 00:04 | llm | finding-triage-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 9083 output: 15 thoughts: 10 Thoughts: Okay, here's the summary, rendered as if I am the one thinking these thoughts: **Analyzing Patch 2** Alright, I've just been reviewing the documentation on this latest software patch, and I need to dive into the second iteration to get a complete picture. Let's see what specifically changed or was implemented in Patch 2. I have to make sure there are no new vulnerabilities introduced, or if this version is more stable than the previous one, so I'm paying close attention to the details of this release. |
| 10/2 | 2026/10/21 00:04 | tool | series-patches |
0mArgs:{
"PatchNum": 2
}Results: {
"Output": "Patch [2] [PATCH v2 2/2] KEYS: Reject keyring creation with overridden credentials:\n\nIf the current task is running with overridden credentials calling\ncommit_creds() is forbidden and triggers a BUG_ON().\n\nReject keyring creation with -EPERM when credentials are overridden.\n\nCc: stable@vger.kernel.org # v5.10+\nSuggested-by: Jann Horn \u003cjannh@google.com\u003e\nCloses: https://lore.kernel.org/keyrings/CAG48ez0XjVSR=M--UBauTm8sJuc+ZbhKzaPa3a6wrSVHyoKevw@mail.gmail.com/\nSigned-off-by: Jarkko Sakkinen \u003cjarkko@kernel.org\u003e\n---\nv2:\n- Added mandatory tags.\n- No code changes.\n---\n Documentation/security/keys/core.rst | 3 ++-\n security/keys/process_keys.c | 31 +++++++++++++++++++++++++++-\n 2 files changed, 32 insertions(+), 2 deletions(-)\n\ndiff --git a/Documentation/security/keys/core.rst b/Documentation/security/keys/core.rst\nindex c81a3a9fe236..9779ce71df47 100644\n--- a/Documentation/security/keys/core.rst\n+++ b/Documentation/security/keys/core.rst\n@@ -168,7 +168,8 @@ The key service provides a number of features besides keys:\n If a process attempts to access its session keyring when it doesn't have\n one, the default user session keyring for its current UID is returned\n without being installed into its credentials. If creation is requested,\n- an anonymous session keyring is installed.\n+ an anonymous session keyring is installed, failing with -EPERM if\n+ credentials are overridden.\n \n * Each user has two quotas against which the keys they own are tracked. One\n limits the total number of keys and keyrings, the other limits the total\ndiff --git a/security/keys/process_keys.c b/security/keys/process_keys.c\nindex 558ffe58b5d6..ab7f13ccebb1 100644\n--- a/security/keys/process_keys.c\n+++ b/security/keys/process_keys.c\n@@ -346,6 +346,14 @@ int install_session_keyring_to_cred(struct cred *cred, struct key *keyring)\n \treturn 0;\n }\n \n+/*\n+ * Determine whether the current task is running with overridden credentials.\n+ */\n+static bool cred_overridden(void)\n+{\n+\treturn current_cred() != current_real_cred();\n+}\n+\n /*\n * Handle the fsuid changing.\n */\n@@ -577,7 +585,8 @@ bool lookup_user_key_possessed(const struct key *key,\n * to a key or the best found key was a negative key; -EKEYREVOKED or\n * -EKEYEXPIRED if the best found key was revoked or expired; -EACCES if the\n * found key doesn't grant the requested permit or the LSM denied access to it;\n- * or -ENOMEM if a special keyring couldn't be created.\n+ * -ENOMEM if a special keyring couldn't be created; or -EPERM if creating one\n+ * while credentials are overridden.\n *\n * In the case of a successful return, the possession attribute is set on the\n * returned key reference.\n@@ -606,6 +615,11 @@ key_ref_t lookup_user_key(key_serial_t id, unsigned long lflags,\n \t\t\tif (!(lflags \u0026 KEY_LOOKUP_CREATE))\n \t\t\t\tgoto error;\n \n+\t\t\tif (cred_overridden()) {\n+\t\t\t\tkey_ref = ERR_PTR(-EPERM);\n+\t\t\t\tgoto error;\n+\t\t\t}\n+\n \t\t\tret = install_thread_keyring();\n \t\t\tif (ret \u003c 0) {\n \t\t\t\tkey_ref = ERR_PTR(ret);\n@@ -624,6 +638,11 @@ key_ref_t lookup_user_key(key_serial_t id, unsigned long lflags,\n \t\t\tif (!(lflags \u0026 KEY_LOOKUP_CREATE))\n \t\t\t\tgoto error;\n \n+\t\t\tif (cred_overridden()) {\n+\t\t\t\tkey_ref = ERR_PTR(-EPERM);\n+\t\t\t\tgoto error;\n+\t\t\t}\n+\n \t\t\tret = install_process_keyring();\n \t\t\tif (ret \u003c 0) {\n \t\t\t\tkey_ref = ERR_PTR(ret);\n@@ -645,6 +664,11 @@ key_ref_t lookup_user_key(key_serial_t id, unsigned long lflags,\n \n \t\t\tif (lflags \u0026 KEY_LOOKUP_CREATE) {\n \t\t\t\tkey_put(user_session);\n+\t\t\t\tif (cred_overridden()) {\n+\t\t\t\t\tkey_ref = ERR_PTR(-EPERM);\n+\t\t\t\t\tgoto error;\n+\t\t\t\t}\n+\n \t\t\t\tret = join_session_keyring(NULL);\n \t\t\t\tif (ret \u003c 0)\n \t\t\t\t\tgoto error;\n@@ -657,6 +681,11 @@ key_ref_t lookup_user_key(key_serial_t id, unsigned long lflags,\n \t\t} else if (test_bit(KEY_FLAG_UID_KEYRING,\n \t\t\t\t \u0026ctx.cred-\u003esession_keyring-\u003eflags) \u0026\u0026\n \t\t\t lflags \u0026 KEY_LOOKUP_CREATE) {\n+\t\t\tif (cred_overridden()) {\n+\t\t\t\tkey_ref = ERR_PTR(-EPERM);\n+\t\t\t\tgoto error;\n+\t\t\t}\n+\n \t\t\tret = join_session_keyring(NULL);\n \t\t\tif (ret \u003c 0)\n \t\t\t\tgoto error;\n-- \n2.47.3"
} |
| 11/2 | 2026/10/21 00:04 | llm | finding-triage-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 10658 output: 32 thoughts: 53 Thoughts: Here's my analysis of the provided text, as though I'm thinking through the problem myself: **Diving into `keyctl_session_to_parent`** Alright, let's get down to brass tacks. I need to understand `keyctl_session_to_parent`, and my intuition tells me it's located in the heart of the kernel's key management system, specifically in `security/keys/process_keys.c`. Given my understanding of kernel security, the name itself is a pretty big clue – I'm guessing it's about establishing a relationship between a session key and a parent key, likely during process interactions. Time to dig in. I should start by carefully examining the code surrounding that function in `security/keys/process_keys.c`. I need to see how it's called, what data structures it operates on, and what its role is within the broader key hierarchy mechanisms of the kernel. This is where the real nuance is. I want to build a solid mental model of its execution path and its security implications. |
| 12/2 | 2026/10/21 00:04 | tool | grepper |
0mArgs:{
"Expression": "keyctl_session_to_parent",
"PathPrefixes": [
"security/keys"
]
}Results: {
"Output": "security/keys/compat.c=17=COMPAT_SYSCALL_DEFINE5(keyctl, u32, option,\n--\nsecurity/keys/compat.c-77-\tcase KEYCTL_SESSION_TO_PARENT:\nsecurity/keys/compat.c:78:\t\treturn keyctl_session_to_parent();\nsecurity/keys/compat.c-79-\n--\nsecurity/keys/internal.h=259=extern long keyctl_get_security(key_serial_t keyid, char __user *buffer,\nsecurity/keys/internal.h-260-\t\t\t\tsize_t buflen);\nsecurity/keys/internal.h:261:extern long keyctl_session_to_parent(void);\nsecurity/keys/internal.h-262-extern long keyctl_reject_key(key_serial_t, unsigned, unsigned, key_serial_t);\n--\nsecurity/keys/keyctl.c=1575=long keyctl_get_security(key_serial_t keyid,\n--\nsecurity/keys/keyctl.c-1639- */\nsecurity/keys/keyctl.c:1640:long keyctl_session_to_parent(void)\nsecurity/keys/keyctl.c-1641-{\n--\nsecurity/keys/keyctl.c=1888=SYSCALL_DEFINE5(keyctl, int, option, unsigned long, arg2, unsigned long, arg3,\n--\nsecurity/keys/keyctl.c-1969-\tcase KEYCTL_SESSION_TO_PARENT:\nsecurity/keys/keyctl.c:1970:\t\treturn keyctl_session_to_parent();\nsecurity/keys/keyctl.c-1971-\n"
} |
| 13/2 | 2026/10/21 00:04 | llm | finding-triage-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 11399 output: 37 thoughts: 46 Thoughts: Here's my summary, as though I'm thinking through this process: **Diving into keyctl_session_to_parent** Right, `keyctl_session_to_parent`... I seem to recall that function from kernel key management. Now where is it? Ah, yes, a quick check tells me it resides in `security/keys/keyctl.c`. Good, that narrows down the search considerably. Let's get that file open. I'll take a look around line 1640, give or take, that's usually a good starting point for functions in that sort of file. I'll need to refresh my memory on the surrounding code, context is key in understanding these lower-level functions. Let's see what we find. |
| 14/2 | 2026/10/21 00:04 | tool | read-file |
0mArgs:{
"File": "security/keys/keyctl.c",
"FirstLine": 1635,
"LineCount": 80
}Results: {
"Contents": "1635:\t *\n1636:\t * The keyring will be emplaced on the parent when it next resumes userspace.\n1637:\t *\n1638:\t * If successful, 0 will be returned.\n1639:\t */\n1640:\tlong keyctl_session_to_parent(void)\n1641:\t{\n1642:\t\tstruct task_struct *me, *parent;\n1643:\t\tconst struct cred *mycred, *pcred;\n1644:\t\tstruct callback_head *newwork, *oldwork;\n1645:\t\tkey_ref_t keyring_r;\n1646:\t\tstruct cred *cred;\n1647:\t\tint ret;\n1648:\t\n1649:\t\tkeyring_r = lookup_user_key(KEY_SPEC_SESSION_KEYRING, 0, KEY_NEED_LINK);\n1650:\t\tif (IS_ERR(keyring_r))\n1651:\t\t\treturn PTR_ERR(keyring_r);\n1652:\t\n1653:\t\tret = -ENOMEM;\n1654:\t\n1655:\t\t/* our parent is going to need a new cred struct, a new tgcred struct\n1656:\t\t * and new security data, so we allocate them here to prevent ENOMEM in\n1657:\t\t * our parent */\n1658:\t\tcred = cred_alloc_blank();\n1659:\t\tif (!cred)\n1660:\t\t\tgoto error_keyring;\n1661:\t\tnewwork = \u0026cred-\u003ercu;\n1662:\t\n1663:\t\tcred-\u003esession_keyring = key_ref_to_ptr(keyring_r);\n1664:\t\tkeyring_r = NULL;\n1665:\t\tinit_task_work(newwork, key_change_session_keyring);\n1666:\t\n1667:\t\tme = current;\n1668:\t\trcu_read_lock();\n1669:\t\twrite_lock_irq(\u0026tasklist_lock);\n1670:\t\n1671:\t\tret = -EPERM;\n1672:\t\toldwork = NULL;\n1673:\t\tparent = rcu_dereference_protected(me-\u003ereal_parent,\n1674:\t\t\t\t\t\t lockdep_is_held(\u0026tasklist_lock));\n1675:\t\n1676:\t\t/* the parent mustn't be init and mustn't be a kernel thread */\n1677:\t\tif (parent-\u003epid \u003c= 1 || !parent-\u003emm)\n1678:\t\t\tgoto unlock;\n1679:\t\n1680:\t\t/* the parent must be single threaded */\n1681:\t\tif (!thread_group_empty(parent))\n1682:\t\t\tgoto unlock;\n1683:\t\n1684:\t\t/* the parent and the child must have different session keyrings or\n1685:\t\t * there's no point */\n1686:\t\tmycred = current_cred();\n1687:\t\tpcred = __task_cred(parent);\n1688:\t\tif (mycred == pcred ||\n1689:\t\t mycred-\u003esession_keyring == pcred-\u003esession_keyring) {\n1690:\t\t\tret = 0;\n1691:\t\t\tgoto unlock;\n1692:\t\t}\n1693:\t\n1694:\t\t/* the parent must have the same effective ownership and mustn't be\n1695:\t\t * SUID/SGID */\n1696:\t\tif (!uid_eq(pcred-\u003euid,\t mycred-\u003eeuid) ||\n1697:\t\t !uid_eq(pcred-\u003eeuid, mycred-\u003eeuid) ||\n1698:\t\t !uid_eq(pcred-\u003esuid, mycred-\u003eeuid) ||\n1699:\t\t !gid_eq(pcred-\u003egid,\t mycred-\u003eegid) ||\n1700:\t\t !gid_eq(pcred-\u003eegid, mycred-\u003eegid) ||\n1701:\t\t !gid_eq(pcred-\u003esgid, mycred-\u003eegid))\n1702:\t\t\tgoto unlock;\n1703:\t\n1704:\t\t/* the keyrings must have the same UID */\n1705:\t\tif ((pcred-\u003esession_keyring \u0026\u0026\n1706:\t\t !uid_eq(pcred-\u003esession_keyring-\u003euid, mycred-\u003eeuid)) ||\n1707:\t\t !uid_eq(mycred-\u003esession_keyring-\u003euid, mycred-\u003eeuid))\n1708:\t\t\tgoto unlock;\n1709:\t\n1710:\t\t/* cancel an already pending keyring replacement */\n1711:\t\toldwork = task_work_cancel_func(parent, key_change_session_keyring);\n1712:\t\n1713:\t\t/* the replacement session keyring is applied just prior to userspace\n1714:\t\t * restarting */\n"
} |
| 15/2 | 2026/10/21 00:04 | llm | finding-triage-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 12922 output: 31 thoughts: 1328 Thoughts: **The Null Pointer Dereference in Keyctl_session_to_parent: A Deep Dive** Okay, let's break this down. Line 1707 is the key. Specifically, the bit `!uid_eq(mycred->session_keyring->uid, mycred->euid)`. My initial thought was, "Wait a minute, *does* `mycred->session_keyring` even exist here?" Let's rewind. Before a certain patch, when `keyctl_session_to_parent` calls `lookup_user_key` with `KEY_SPEC_SESSION_KEYRING` and without `KEY_LOOKUP_CREATE`, if the current process *didn't* have a session keyring, `lookup_user_key` would install the user session keyring into the current process's credentials using `install_session_keyring`. This meant that after that call, `mycred = current_cred()` was *guaranteed* to have a non-NULL `mycred->session_keyring`. The author of this code clearly assumed this behavior. Now, after Patch 1, things are different. The patch notes specifically state that a process *without* a session keyring, looking up `KEY_SPEC_SESSION_KEYRING` *without* `KEY_LOOKUP_CREATE`, now *doesn't* mutate the credentials. Instead, it just returns the resolved user session keyring. In other words, `lookup_user_key` *doesn't* install the keyring. So, `keyctl_session_to_parent` calls `lookup_user_key`. The flags are zero. Before, `lookup_user_key` installed the keyring, so `mycred->session_keyring` was guaranteed to be non-NULL. Now, the patch prevents the installation. Therefore, `mycred = current_cred()` *still* has `mycred->session_keyring == NULL`. The critical problem is that the code *still* blindly dereferences `mycred->session_keyring->uid` at line 1707 without checking if `mycred->session_keyring` is NULL. This is a classic null pointer dereference! And it's caused by a change in behavior introduced by the patch. The check for `pcred->session_keyring` uses the safe idiom `(pcred->session_keyring && ...)`. However, `mycred` doesn't have a similar check before dereferencing `mycred->session_keyring->uid`. The crash report confirms this: "KASAN: null-ptr-deref..." That's precisely what we're seeing! Let's quickly verify the offset of `uid` in `struct key`. |
| 16/2 | 2026/10/21 00:05 | tool | grepper |
0mArgs:{
"Expression": "struct key \\{",
"PathPrefixes": [
"include/linux/key.h"
]
}Results: {
"Output": "include/linux/key.h=185=enum key_state {\n--\ninclude/linux/key.h-197- */\ninclude/linux/key.h:198:struct key {\ninclude/linux/key.h-199-\trefcount_t\t\tusage;\t\t/* number of references */\n"
} |
| 17/2 | 2026/10/21 00:05 | llm | finding-triage-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 14966 output: 35 thoughts: 20 Thoughts: Okay, let's dive into this. I'm going to take a look at the code. **Diving into `include/linux/key.h` - Line 198** Right, so I'm thinking, let's jump directly into `include/linux/key.h` around line 198. My intuition tells me that the key definitions are structured in a way that provides a solid foundation, which should allow me to quickly identify any potential performance problems or concurrency issues. I'm keen to get straight to that area of the code and see exactly how they handle the data structure involved in key management. Hopefully, the surrounding context will quickly indicate the level of abstraction they are using. I want to build a quick mental picture of the key structure and how it relates to other kernel components. |
| 18/2 | 2026/10/21 00:05 | tool | read-file |
0mArgs:{
"File": "include/linux/key.h",
"FirstLine": 198,
"LineCount": 60
}Results: {
"Contents": " 198:\tstruct key {\n 199:\t\trefcount_t\t\tusage;\t\t/* number of references */\n 200:\t\tkey_serial_t\t\tserial;\t\t/* key serial number */\n 201:\t\tunion {\n 202:\t\t\tstruct list_head graveyard_link;\n 203:\t\t\tstruct rb_node\tserial_node;\n 204:\t\t};\n 205:\t#ifdef CONFIG_KEY_NOTIFICATIONS\n 206:\t\tstruct watch_list\t*watchers;\t/* Entities watching this key for changes */\n 207:\t#endif\n 208:\t\tstruct rw_semaphore\tsem;\t\t/* change vs change sem */\n 209:\t\tstruct key_user\t\t*user;\t\t/* owner of this key */\n 210:\t\tvoid\t\t\t*security;\t/* security data for this key */\n 211:\t\tunion {\n 212:\t\t\ttime64_t\texpiry;\t\t/* time at which key expires (or 0) */\n 213:\t\t\ttime64_t\trevoked_at;\t/* time at which key was revoked */\n 214:\t\t};\n 215:\t\ttime64_t\t\tlast_used_at;\t/* last time used for LRU keyring discard */\n 216:\t\tkuid_t\t\t\tuid;\n 217:\t\tkgid_t\t\t\tgid;\n 218:\t\tkey_perm_t\t\tperm;\t\t/* access permissions */\n 219:\t\tunsigned short\t\tquotalen;\t/* length added to quota */\n 220:\t\tunsigned short\t\tdatalen;\t/* payload data length\n 221:\t\t\t\t\t\t\t * - may not match RCU dereferenced payload\n 222:\t\t\t\t\t\t\t * - payload should contain own length\n 223:\t\t\t\t\t\t\t */\n 224:\t\tshort\t\t\tstate;\t\t/* Key state (+) or rejection error (-) */\n 225:\t\n 226:\t#ifdef KEY_DEBUGGING\n 227:\t\tunsigned\t\tmagic;\n 228:\t#define KEY_DEBUG_MAGIC\t\t0x18273645u\n 229:\t#endif\n 230:\t\n 231:\t\tunsigned long\t\tflags;\t\t/* status flags (change with bitops) */\n 232:\t#define KEY_FLAG_DEAD\t\t0\t/* set if key type has been deleted */\n 233:\t#define KEY_FLAG_REVOKED\t1\t/* set if key had been revoked */\n 234:\t#define KEY_FLAG_IN_QUOTA\t2\t/* set if key consumes quota */\n 235:\t#define KEY_FLAG_USER_CONSTRUCT\t3\t/* set if key is being constructed in userspace */\n 236:\t#define KEY_FLAG_ROOT_CAN_CLEAR\t4\t/* set if key can be cleared by root without permission */\n 237:\t#define KEY_FLAG_INVALIDATED\t5\t/* set if key has been invalidated */\n 238:\t#define KEY_FLAG_BUILTIN\t6\t/* set if key is built in to the kernel */\n 239:\t#define KEY_FLAG_ROOT_CAN_INVAL\t7\t/* set if key can be invalidated by root without permission */\n 240:\t#define KEY_FLAG_KEEP\t\t8\t/* set if key should not be removed */\n 241:\t#define KEY_FLAG_UID_KEYRING\t9\t/* set if key is a user or user session keyring */\n 242:\t#define KEY_FLAG_USER_ALIVE\t10\t/* set if final put has not happened on key yet */\n 243:\t\n 244:\t\t/* the key type and key description string\n 245:\t\t * - the desc is used to match a key against search criteria\n 246:\t\t * - it should be a printable string\n 247:\t\t * - eg: for krb5 AFS, this might be \"afs@REDHAT.COM\"\n 248:\t\t */\n 249:\t\tunion {\n 250:\t\t\tstruct keyring_index_key index_key;\n 251:\t\t\tstruct {\n 252:\t\t\t\tunsigned long\thash;\n 253:\t\t\t\tunsigned long\tlen_desc;\n 254:\t\t\t\tstruct key_type\t*type;\t\t/* type of key */\n 255:\t\t\t\tstruct key_tag\t*domain_tag;\t/* Domain of operation */\n 256:\t\t\t\tchar\t\t*description;\n 257:\t\t\t\tu64\t\ttype_id;\n"
} |
| 19/2 | 2026/10/21 00:05 | llm | finding-triage-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 16314 output: 38 thoughts: 2959 Thoughts: **Analysis of the Kernel NULL Pointer Dereference Bug** Okay, so the crash report points directly to a null pointer dereference, specifically at offset 0xd8, which, after carefully dissecting the `struct key`, I've confirmed corresponds to the `kuid_t uid` member. That's a crucial bit of information. The assembly code highlights the problematic instruction, `mov (%rdx), %ebp`, where `rdx` is the derived offset that has become zero (NULL). Now, let's trace this back to the `keyctl_session_to_parent` function. This function's logic involves transferring a session keyring to a parent process. Crucially, I'm noting the initial call to `lookup_user_key` with `KEY_SPEC_SESSION_KEYRING`. This function is pivotal. Before a specific patch (Patch 1), `lookup_user_key` *guaranteed* that a session keyring was installed in the credentials if one didn't already exist. The comments and documentation are explicit about this behavior. In other words, `mycred->session_keyring` could *never* be NULL previously. However, Patch 1 intentionally changed this behavior. Instead of installing the session keyring, it now *returns* the resolved keyring without changing the current credentials. This was a deliberate change to avoid credential staleness in other subsystems. I am confident of this because of the commit message itself. The implications are now clear. If `keyctl_session_to_parent` is called by a process without a session keyring (and Patch 1 is present), the following happens: 1. `lookup_user_key` *succeeds* in finding a session keyring. 2. However, this doesn't populate `current->cred->session_keyring`. 3. Later, the code checks the validity and UID of the *uninitialized* `mycred->session_keyring->uid`. This is a crucial check. However, because it's NULL, the dereference occurs. 4. The crash ensues. The critical bug is that the code in `keyctl_session_to_parent` assumes `mycred->session_keyring` is always non-NULL when it can be. Therefore, the fix must consider that `mycred->session_keyring` can now be NULL. I must examine the code to see if there are other similar assumptions, but `keyctl_session_to_parent` seems to be the primary point of failure. Let's check the git blame on the offending line (1707) in `keyctl_session_to_parent` and see who changed it, and why. I'll need to develop a patch to fix this NULL pointer dereference. |
| 20/2 | 2026/10/21 00:05 | tool | git-blame |
0mArgs:{
"End": 1715,
"File": "security/keys/keyctl.c",
"Start": 1700
}Results: {
"Output": "9a56c2db49e73 1700) \t !gid_eq(pcred-\u003eegid, mycred-\u003eegid) ||\n9a56c2db49e73 1701) \t !gid_eq(pcred-\u003esgid, mycred-\u003eegid))\n413cd3d9abeae 1702) \t\tgoto unlock;\nee18d64c1f632 1703) \nee18d64c1f632 1704) \t/* the keyrings must have the same UID */\n3a50597de8635 1705) \tif ((pcred-\u003esession_keyring \u0026\u0026\n2a74dbb9a86e8 1706) \t !uid_eq(pcred-\u003esession_keyring-\u003euid, mycred-\u003eeuid)) ||\n2a74dbb9a86e8 1707) \t !uid_eq(mycred-\u003esession_keyring-\u003euid, mycred-\u003eeuid))\n413cd3d9abeae 1708) \t\tgoto unlock;\nee18d64c1f632 1709) \n413cd3d9abeae 1710) \t/* cancel an already pending keyring replacement */\n68cbd415dd4b9 1711) \toldwork = task_work_cancel_func(parent, key_change_session_keyring);\nee18d64c1f632 1712) \nee18d64c1f632 1713) \t/* the replacement session keyring is applied just prior to userspace\nee18d64c1f632 1714) \t * restarting */\n91989c707884e 1715) \tret = task_work_add(parent, newwork, TWA_RESUME);\n"
} |
| 21/2 | 2026/10/21 00:05 | llm | finding-triage-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 20412 output: 27 thoughts: 36 Thoughts: Okay, here's my take on those commits, from my perspective: **Investigating Recent Code Changes** Alright, let's dive into this. I need to get a clear picture of what's been tweaked recently. I'm going to pull up the details for commit `3a50597de8635` and also `2a74dbb9a86e8` using `git show`. Knowing the commit hashes is a good starting point, but I need to see the actual diffs, the commit messages, and any relevant metadata. This will give me the immediate context I require. |
| 22/2 | 2026/10/21 00:06 | tool | git-show |
0mArgs:{
"Commit": "3a50597de8635"
}Results: {
"Output": "commit 3a50597de8635cd05133bd12c95681c82fe7b878\nAuthor: David Howells \u003cdhowells@redhat.com\u003e\nDate: Tue Oct 2 19:24:29 2012 +0100\n\n KEYS: Make the session and process keyrings per-thread\n \n Make the session keyring per-thread rather than per-process, but still\n inherited from the parent thread to solve a problem with PAM and gdm.\n \n The problem is that join_session_keyring() will reject attempts to change the\n session keyring of a multithreaded program but gdm is now multithreaded before\n it gets to the point of starting PAM and running pam_keyinit to create the\n session keyring. See:\n \n https://bugs.freedesktop.org/show_bug.cgi?id=49211\n \n The reason that join_session_keyring() will only change the session keyring\n under a single-threaded environment is that it's hard to alter the other\n thread's credentials to effect the change in a multi-threaded program. The\n problems are such as:\n \n (1) How to prevent two threads both running join_session_keyring() from\n racing.\n \n (2) Another thread's credentials may not be modified directly by this process.\n \n (3) The number of threads is uncertain whilst we're not holding the\n appropriate spinlock, making preallocation slightly tricky.\n \n (4) We could use TIF_NOTIFY_RESUME and key_replace_session_keyring() to get\n another thread to replace its keyring, but that means preallocating for\n each thread.\n \n A reasonable way around this is to make the session keyring per-thread rather\n than per-process and just document that if you want a common session keyring,\n you must get it before you spawn any threads - which is the current situation\n anyway.\n \n Whilst we're at it, we can the process keyring behave in the same way. This\n means we can clean up some of the ickyness in the creds code.\n \n Basically, after this patch, the session, process and thread keyrings are about\n inheritance rules only and not about sharing changes of keyring.\n \n Reported-by: Mantas M. \u003cgrawity@gmail.com\u003e\n Signed-off-by: David Howells \u003cdhowells@redhat.com\u003e\n Tested-by: Ray Strode \u003crstrode@redhat.com\u003e\n\ndiff --git a/include/linux/cred.h b/include/linux/cred.h\nindex ebbed2ce66379..0142aacb70b70 100644\n--- a/include/linux/cred.h\n+++ b/include/linux/cred.h\n@@ -76,21 +76,6 @@ extern int groups_search(const struct group_info *, kgid_t);\n extern int in_group_p(kgid_t);\n extern int in_egroup_p(kgid_t);\n \n-/*\n- * The common credentials for a thread group\n- * - shared by CLONE_THREAD\n- */\n-#ifdef CONFIG_KEYS\n-struct thread_group_cred {\n-\tatomic_t\tusage;\n-\tpid_t\t\ttgid;\t\t\t/* thread group process ID */\n-\tspinlock_t\tlock;\n-\tstruct key __rcu *session_keyring;\t/* keyring inherited over fork */\n-\tstruct key\t*process_keyring;\t/* keyring private to this process */\n-\tstruct rcu_head\trcu;\t\t\t/* RCU deletion hook */\n-};\n-#endif\n-\n /*\n * The security context of a task\n *\n@@ -139,6 +124,8 @@ struct cred {\n #ifdef CONFIG_KEYS\n \tunsigned char\tjit_keyring;\t/* default keyring to attach requested\n \t\t\t\t\t * keys to */\n+\tstruct key __rcu *session_keyring; /* keyring inherited over fork */\n+\tstruct key\t*process_keyring; /* keyring private to this process */\n \tstruct key\t*thread_keyring; /* keyring private to this thread */\n \tstruct key\t*request_key_auth; /* assumed request_key authority */\n \tstruct thread_group_cred *tgcred; /* thread-group shared credentials */\ndiff --git a/kernel/cred.c b/kernel/cred.c\nindex de728ac50d821..3f7ad1ec2ae43 100644\n--- a/kernel/cred.c\n+++ b/kernel/cred.c\n@@ -29,17 +29,6 @@\n \n static struct kmem_cache *cred_jar;\n \n-/*\n- * The common credentials for the initial task's thread group\n- */\n-#ifdef CONFIG_KEYS\n-static struct thread_group_cred init_tgcred = {\n-\t.usage\t= ATOMIC_INIT(2),\n-\t.tgid\t= 0,\n-\t.lock\t= __SPIN_LOCK_UNLOCKED(init_cred.tgcred.lock),\n-};\n-#endif\n-\n /*\n * The initial credentials for the initial task\n */\n@@ -65,9 +54,6 @@ struct cred init_cred = {\n \t.user\t\t\t= INIT_USER,\n \t.user_ns\t\t= \u0026init_user_ns,\n \t.group_info\t\t= \u0026init_groups,\n-#ifdef CONFIG_KEYS\n-\t.tgcred\t\t\t= \u0026init_tgcred,\n-#endif\n };\n \n static inline void set_cred_subscribers(struct cred *cred, int n)\n@@ -95,36 +81,6 @@ static inline void alter_cred_subscribers(const struct cred *_cred, int n)\n #endif\n }\n \n-/*\n- * Dispose of the shared task group credentials\n- */\n-#ifdef CONFIG_KEYS\n-static void release_tgcred_rcu(struct rcu_head *rcu)\n-{\n-\tstruct thread_group_cred *tgcred =\n-\t\tcontainer_of(rcu, struct thread_group_cred, rcu);\n-\n-\tBUG_ON(atomic_read(\u0026tgcred-\u003eusage) != 0);\n-\n-\tkey_put(tgcred-\u003esession_keyring);\n-\tkey_put(tgcred-\u003eprocess_keyring);\n-\tkfree(tgcred);\n-}\n-#endif\n-\n-/*\n- * Release a set of thread group credentials.\n- */\n-static void release_tgcred(struct cred *cred)\n-{\n-#ifdef CONFIG_KEYS\n-\tstruct thread_group_cred *tgcred = cred-\u003etgcred;\n-\n-\tif (atomic_dec_and_test(\u0026tgcred-\u003eusage))\n-\t\tcall_rcu(\u0026tgcred-\u003ercu, release_tgcred_rcu);\n-#endif\n-}\n-\n /*\n * The RCU callback to actually dispose of a set of credentials\n */\n@@ -150,9 +106,10 @@ static void put_cred_rcu(struct rcu_head *rcu)\n #endif\n \n \tsecurity_cred_free(cred);\n+\tkey_put(cred-\u003esession_keyring);\n+\tkey_put(cred-\u003eprocess_keyring);\n \tkey_put(cred-\u003ethread_keyring);\n \tkey_put(cred-\u003erequest_key_auth);\n-\trelease_tgcred(cred);\n \tif (cred-\u003egroup_info)\n \t\tput_group_info(cred-\u003egroup_info);\n \tfree_uid(cred-\u003euser);\n@@ -246,15 +203,6 @@ struct cred *cred_alloc_blank(void)\n \tif (!new)\n \t\treturn NULL;\n \n-#ifdef CONFIG_KEYS\n-\tnew-\u003etgcred = kzalloc(sizeof(*new-\u003etgcred), GFP_KERNEL);\n-\tif (!new-\u003etgcred) {\n-\t\tkmem_cache_free(cred_jar, new);\n-\t\treturn NULL;\n-\t}\n-\tatomic_set(\u0026new-\u003etgcred-\u003eusage, 1);\n-#endif\n-\n \tatomic_set(\u0026new-\u003eusage, 1);\n #ifdef CONFIG_DEBUG_CREDENTIALS\n \tnew-\u003emagic = CRED_MAGIC;\n@@ -308,9 +256,10 @@ struct cred *prepare_creds(void)\n \tget_user_ns(new-\u003euser_ns);\n \n #ifdef CONFIG_KEYS\n+\tkey_get(new-\u003esession_keyring);\n+\tkey_get(new-\u003eprocess_keyring);\n \tkey_get(new-\u003ethread_keyring);\n \tkey_get(new-\u003erequest_key_auth);\n-\tatomic_inc(\u0026new-\u003etgcred-\u003eusage);\n #endif\n \n #ifdef CONFIG_SECURITY\n@@ -334,39 +283,20 @@ EXPORT_SYMBOL(prepare_creds);\n */\n struct cred *prepare_exec_creds(void)\n {\n-\tstruct thread_group_cred *tgcred = NULL;\n \tstruct cred *new;\n \n-#ifdef CONFIG_KEYS\n-\ttgcred = kmalloc(sizeof(*tgcred), GFP_KERNEL);\n-\tif (!tgcred)\n-\t\treturn NULL;\n-#endif\n-\n \tnew = prepare_creds();\n-\tif (!new) {\n-\t\tkfree(tgcred);\n+\tif (!new)\n \t\treturn new;\n-\t}\n \n #ifdef CONFIG_KEYS\n \t/* newly exec'd tasks don't get a thread keyring */\n \tkey_put(new-\u003ethread_keyring);\n \tnew-\u003ethread_keyring = NULL;\n \n-\t/* create a new per-thread-group creds for all this set of threads to\n-\t * share */\n-\tmemcpy(tgcred, new-\u003etgcred, sizeof(struct thread_group_cred));\n-\n-\tatomic_set(\u0026tgcred-\u003eusage, 1);\n-\tspin_lock_init(\u0026tgcred-\u003elock);\n-\n \t/* inherit the session keyring; new process keyring */\n-\tkey_get(tgcred-\u003esession_keyring);\n-\ttgcred-\u003eprocess_keyring = NULL;\n-\n-\trelease_tgcred(new);\n-\tnew-\u003etgcred = tgcred;\n+\tkey_put(new-\u003eprocess_keyring);\n+\tnew-\u003eprocess_keyring = NULL;\n #endif\n \n \treturn new;\n@@ -383,9 +313,6 @@ struct cred *prepare_exec_creds(void)\n */\n int copy_creds(struct task_struct *p, unsigned long clone_flags)\n {\n-#ifdef CONFIG_KEYS\n-\tstruct thread_group_cred *tgcred;\n-#endif\n \tstruct cred *new;\n \tint ret;\n \n@@ -425,22 +352,12 @@ int copy_creds(struct task_struct *p, unsigned long clone_flags)\n \t\t\tinstall_thread_keyring_to_cred(new);\n \t}\n \n-\t/* we share the process and session keyrings between all the threads in\n-\t * a process - this is slightly icky as we violate COW credentials a\n-\t * bit */\n+\t/* The process keyring is only shared between the threads in a process;\n+\t * anything outside of those threads doesn't inherit.\n+\t */\n \tif (!(clone_flags \u0026 CLONE_THREAD)) {\n-\t\ttgcred = kmalloc(sizeof(*tgcred), GFP_KERNEL);\n-\t\tif (!tgcred) {\n-\t\t\tret = -ENOMEM;\n-\t\t\tgoto error_put;\n-\t\t}\n-\t\tatomic_set(\u0026tgcred-\u003eusage, 1);\n-\t\tspin_lock_init(\u0026tgcred-\u003elock);\n-\t\ttgcred-\u003eprocess_keyring = NULL;\n-\t\ttgcred-\u003esession_keyring = key_get(new-\u003etgcred-\u003esession_keyring);\n-\n-\t\trelease_tgcred(new);\n-\t\tnew-\u003etgcred = tgcred;\n+\t\tkey_put(new-\u003eprocess_keyring);\n+\t\tnew-\u003eprocess_keyring = NULL;\n \t}\n #endif\n \n@@ -643,9 +560,6 @@ void __init cred_init(void)\n */\n struct cred *prepare_kernel_cred(struct task_struct *daemon)\n {\n-#ifdef CONFIG_KEYS\n-\tstruct thread_group_cred *tgcred;\n-#endif\n \tconst struct cred *old;\n \tstruct cred *new;\n \n@@ -653,14 +567,6 @@ struct cred *prepare_kernel_cred(struct task_struct *daemon)\n \tif (!new)\n \t\treturn NULL;\n \n-#ifdef CONFIG_KEYS\n-\ttgcred = kmalloc(sizeof(*tgcred), GFP_KERNEL);\n-\tif (!tgcred) {\n-\t\tkmem_cache_free(cred_jar, new);\n-\t\treturn NULL;\n-\t}\n-#endif\n-\n \tkdebug(\"prepare_kernel_cred() alloc %p\", new);\n \n \tif (daemon)\n@@ -678,13 +584,10 @@ struct cred *prepare_kernel_cred(struct task_struct *daemon)\n \tget_group_info(new-\u003egroup_info);\n \n #ifdef CONFIG_KEYS\n-\tatomic_set(\u0026tgcred-\u003eusage, 1);\n-\tspin_lock_init(\u0026tgcred-\u003elock);\n-\ttgcred-\u003eprocess_keyring = NULL;\n-\ttgcred-\u003esession_keyring = NULL;\n-\tnew-\u003etgcred = tgcred;\n-\tnew-\u003erequest_key_auth = NULL;\n+\tnew-\u003esession_keyring = NULL;\n+\tnew-\u003eprocess_keyring = NULL;\n \tnew-\u003ethread_keyring = NULL;\n+\tnew-\u003erequest_key_auth = NULL;\n \tnew-\u003ejit_keyring = KEY_REQKEY_DEFL_THREAD_KEYRING;\n #endif\n \ndiff --git a/security/keys/keyctl.c b/security/keys/keyctl.c\nindex a0d373f76815c..65b38417c2113 100644\n--- a/security/keys/keyctl.c\n+++ b/security/keys/keyctl.c\n@@ -1475,7 +1475,8 @@ long keyctl_session_to_parent(void)\n \t\tgoto error_keyring;\n \tnewwork = \u0026cred-\u003ercu;\n \n-\tcred-\u003etgcred-\u003esession_keyring = key_ref_to_ptr(keyring_r);\n+\tcred-\u003esession_keyring = key_ref_to_ptr(keyring_r);\n+\tkeyring_r = NULL;\n \tinit_task_work(newwork, key_change_session_keyring);\n \n \tme = current;\n@@ -1500,7 +1501,7 @@ long keyctl_session_to_parent(void)\n \tmycred = current_cred();\n \tpcred = __task_cred(parent);\n \tif (mycred == pcred ||\n-\t mycred-\u003etgcred-\u003esession_keyring == pcred-\u003etgcred-\u003esession_keyring) {\n+\t mycred-\u003esession_keyring == pcred-\u003esession_keyring) {\n \t\tret = 0;\n \t\tgoto unlock;\n \t}\n@@ -1516,9 +1517,9 @@ long keyctl_session_to_parent(void)\n \t\tgoto unlock;\n \n \t/* the keyrings must have the same UID */\n-\tif ((pcred-\u003etgcred-\u003esession_keyring \u0026\u0026\n-\t pcred-\u003etgcred-\u003esession_keyring-\u003euid != mycred-\u003eeuid) ||\n-\t mycred-\u003etgcred-\u003esession_keyring-\u003euid != mycred-\u003eeuid)\n+\tif ((pcred-\u003esession_keyring \u0026\u0026\n+\t pcred-\u003esession_keyring-\u003euid != mycred-\u003eeuid) ||\n+\t mycred-\u003esession_keyring-\u003euid != mycred-\u003eeuid)\n \t\tgoto unlock;\n \n \t/* cancel an already pending keyring replacement */\ndiff --git a/security/keys/process_keys.c b/security/keys/process_keys.c\nindex 178b8c3b130a4..9de5dc5982764 100644\n--- a/security/keys/process_keys.c\n+++ b/security/keys/process_keys.c\n@@ -169,9 +169,8 @@ static int install_thread_keyring(void)\n int install_process_keyring_to_cred(struct cred *new)\n {\n \tstruct key *keyring;\n-\tint ret;\n \n-\tif (new-\u003etgcred-\u003eprocess_keyring)\n+\tif (new-\u003eprocess_keyring)\n \t\treturn -EEXIST;\n \n \tkeyring = keyring_alloc(\"_pid\", new-\u003euid, new-\u003egid,\n@@ -179,17 +178,8 @@ int install_process_keyring_to_cred(struct cred *new)\n \tif (IS_ERR(keyring))\n \t\treturn PTR_ERR(keyring);\n \n-\tspin_lock_irq(\u0026new-\u003etgcred-\u003elock);\n-\tif (!new-\u003etgcred-\u003eprocess_keyring) {\n-\t\tnew-\u003etgcred-\u003eprocess_keyring = keyring;\n-\t\tkeyring = NULL;\n-\t\tret = 0;\n-\t} else {\n-\t\tret = -EEXIST;\n-\t}\n-\tspin_unlock_irq(\u0026new-\u003etgcred-\u003elock);\n-\tkey_put(keyring);\n-\treturn ret;\n+\tnew-\u003eprocess_keyring = keyring;\n+\treturn 0;\n }\n \n /*\n@@ -230,7 +220,7 @@ int install_session_keyring_to_cred(struct cred *cred, struct key *keyring)\n \t/* create an empty session keyring */\n \tif (!keyring) {\n \t\tflags = KEY_ALLOC_QUOTA_OVERRUN;\n-\t\tif (cred-\u003etgcred-\u003esession_keyring)\n+\t\tif (cred-\u003esession_keyring)\n \t\t\tflags = KEY_ALLOC_IN_QUOTA;\n \n \t\tkeyring = keyring_alloc(\"_ses\", cred-\u003euid, cred-\u003egid,\n@@ -242,17 +232,11 @@ int install_session_keyring_to_cred(struct cred *cred, struct key *keyring)\n \t}\n \n \t/* install the keyring */\n-\tspin_lock_irq(\u0026cred-\u003etgcred-\u003elock);\n-\told = cred-\u003etgcred-\u003esession_keyring;\n-\trcu_assign_pointer(cred-\u003etgcred-\u003esession_keyring, keyring);\n-\tspin_unlock_irq(\u0026cred-\u003etgcred-\u003elock);\n-\n-\t/* we're using RCU on the pointer, but there's no point synchronising\n-\t * on it if it didn't previously point to anything */\n-\tif (old) {\n-\t\tsynchronize_rcu();\n+\told = cred-\u003esession_keyring;\n+\trcu_assign_pointer(cred-\u003esession_keyring, keyring);\n+\n+\tif (old)\n \t\tkey_put(old);\n-\t}\n \n \treturn 0;\n }\n@@ -367,9 +351,9 @@ key_ref_t search_my_process_keyrings(struct key_type *type,\n \t}\n \n \t/* search the process keyring second */\n-\tif (cred-\u003etgcred-\u003eprocess_keyring) {\n+\tif (cred-\u003eprocess_keyring) {\n \t\tkey_ref = keyring_search_aux(\n-\t\t\tmake_key_ref(cred-\u003etgcred-\u003eprocess_keyring, 1),\n+\t\t\tmake_key_ref(cred-\u003eprocess_keyring, 1),\n \t\t\tcred, type, description, match, no_state_check);\n \t\tif (!IS_ERR(key_ref))\n \t\t\tgoto found;\n@@ -388,12 +372,10 @@ key_ref_t search_my_process_keyrings(struct key_type *type,\n \t}\n \n \t/* search the session keyring */\n-\tif (cred-\u003etgcred-\u003esession_keyring) {\n+\tif (cred-\u003esession_keyring) {\n \t\trcu_read_lock();\n \t\tkey_ref = keyring_search_aux(\n-\t\t\tmake_key_ref(rcu_dereference(\n-\t\t\t\t\t cred-\u003etgcred-\u003esession_keyring),\n-\t\t\t\t 1),\n+\t\t\tmake_key_ref(rcu_dereference(cred-\u003esession_keyring), 1),\n \t\t\tcred, type, description, match, no_state_check);\n \t\trcu_read_unlock();\n \n@@ -563,7 +545,7 @@ key_ref_t lookup_user_key(key_serial_t id, unsigned long lflags,\n \t\tbreak;\n \n \tcase KEY_SPEC_PROCESS_KEYRING:\n-\t\tif (!cred-\u003etgcred-\u003eprocess_keyring) {\n+\t\tif (!cred-\u003eprocess_keyring) {\n \t\t\tif (!(lflags \u0026 KEY_LOOKUP_CREATE))\n \t\t\t\tgoto error;\n \n@@ -575,13 +557,13 @@ key_ref_t lookup_user_key(key_serial_t id, unsigned long lflags,\n \t\t\tgoto reget_creds;\n \t\t}\n \n-\t\tkey = cred-\u003etgcred-\u003eprocess_keyring;\n+\t\tkey = cred-\u003eprocess_keyring;\n \t\tatomic_inc(\u0026key-\u003eusage);\n \t\tkey_ref = make_key_ref(key, 1);\n \t\tbreak;\n \n \tcase KEY_SPEC_SESSION_KEYRING:\n-\t\tif (!cred-\u003etgcred-\u003esession_keyring) {\n+\t\tif (!cred-\u003esession_keyring) {\n \t\t\t/* always install a session keyring upon access if one\n \t\t\t * doesn't exist yet */\n \t\t\tret = install_user_keyrings();\n@@ -596,7 +578,7 @@ key_ref_t lookup_user_key(key_serial_t id, unsigned long lflags,\n \t\t\tif (ret \u003c 0)\n \t\t\t\tgoto error;\n \t\t\tgoto reget_creds;\n-\t\t} else if (cred-\u003etgcred-\u003esession_keyring ==\n+\t\t} else if (cred-\u003esession_keyring ==\n \t\t\t cred-\u003euser-\u003esession_keyring \u0026\u0026\n \t\t\t lflags \u0026 KEY_LOOKUP_CREATE) {\n \t\t\tret = join_session_keyring(NULL);\n@@ -606,7 +588,7 @@ key_ref_t lookup_user_key(key_serial_t id, unsigned long lflags,\n \t\t}\n \n \t\trcu_read_lock();\n-\t\tkey = rcu_dereference(cred-\u003etgcred-\u003esession_keyring);\n+\t\tkey = rcu_dereference(cred-\u003esession_keyring);\n \t\tatomic_inc(\u0026key-\u003eusage);\n \t\trcu_read_unlock();\n \t\tkey_ref = make_key_ref(key, 1);\n@@ -766,12 +748,6 @@ long join_session_keyring(const char *name)\n \tstruct key *keyring;\n \tlong ret, serial;\n \n-\t/* only permit this if there's a single thread in the thread group -\n-\t * this avoids us having to adjust the creds on all threads and risking\n-\t * ENOMEM */\n-\tif (!current_is_single_threaded())\n-\t\treturn -EMLINK;\n-\n \tnew = prepare_creds();\n \tif (!new)\n \t\treturn -ENOMEM;\n@@ -783,7 +759,7 @@ long join_session_keyring(const char *name)\n \t\tif (ret \u003c 0)\n \t\t\tgoto error;\n \n-\t\tserial = new-\u003etgcred-\u003esession_keyring-\u003eserial;\n+\t\tserial = new-\u003esession_keyring-\u003eserial;\n \t\tret = commit_creds(new);\n \t\tif (ret == 0)\n \t\t\tret = serial;\n@@ -806,6 +782,9 @@ long join_session_keyring(const char *name)\n \t} else if (IS_ERR(keyring)) {\n \t\tret = PTR_ERR(keyring);\n \t\tgoto error2;\n+\t} else if (keyring == new-\u003esession_keyring) {\n+\t\tret = 0;\n+\t\tgoto error2;\n \t}\n \n \t/* we've got a keyring - now to install it */\n@@ -862,8 +841,7 @@ void key_change_session_keyring(struct callback_head *twork)\n \n \tnew-\u003ejit_keyring\t= old-\u003ejit_keyring;\n \tnew-\u003ethread_keyring\t= key_get(old-\u003ethread_keyring);\n-\tnew-\u003etgcred-\u003etgid\t= old-\u003etgcred-\u003etgid;\n-\tnew-\u003etgcred-\u003eprocess_keyring = key_get(old-\u003etgcred-\u003eprocess_keyring);\n+\tnew-\u003eprocess_keyring\t= key_get(old-\u003eprocess_keyring);\n \n \tsecurity_transfer_creds(new, old);\n \ndiff --git a/security/keys/request_key.c b/security/keys/request_key.c\nindex 000e750175202..275c4f9e4b8c0 100644\n--- a/security/keys/request_key.c\n+++ b/security/keys/request_key.c\n@@ -150,12 +150,12 @@ static int call_sbin_request_key(struct key_construction *cons,\n \t\tcred-\u003ethread_keyring ? cred-\u003ethread_keyring-\u003eserial : 0);\n \n \tprkey = 0;\n-\tif (cred-\u003etgcred-\u003eprocess_keyring)\n-\t\tprkey = cred-\u003etgcred-\u003eprocess_keyring-\u003eserial;\n+\tif (cred-\u003eprocess_keyring)\n+\t\tprkey = cred-\u003eprocess_keyring-\u003eserial;\n \tsprintf(keyring_str[1], \"%d\", prkey);\n \n \trcu_read_lock();\n-\tsession = rcu_dereference(cred-\u003etgcred-\u003esession_keyring);\n+\tsession = rcu_dereference(cred-\u003esession_keyring);\n \tif (!session)\n \t\tsession = cred-\u003euser-\u003esession_keyring;\n \tsskey = session-\u003eserial;\n@@ -297,14 +297,14 @@ static void construct_get_dest_keyring(struct key **_dest_keyring)\n \t\t\t\tbreak;\n \n \t\tcase KEY_REQKEY_DEFL_PROCESS_KEYRING:\n-\t\t\tdest_keyring = key_get(cred-\u003etgcred-\u003eprocess_keyring);\n+\t\t\tdest_keyring = key_get(cred-\u003eprocess_keyring);\n \t\t\tif (dest_keyring)\n \t\t\t\tbreak;\n \n \t\tcase KEY_REQKEY_DEFL_SESSION_KEYRING:\n \t\t\trcu_read_lock();\n \t\t\tdest_keyring = key_get(\n-\t\t\t\trcu_dereference(cred-\u003etgcred-\u003esession_keyring));\n+\t\t\t\trcu_dereference(cred-\u003esession_keyring));\n \t\t\trcu_read_unlock();\n \n \t\t\tif (dest_keyring)\n"
} |
| 23/2 | 2026/10/21 00:06 | llm | finding-triage-evaluator |
2mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 27332 output: 430 thoughts: 2163 Thoughts: **Analysis of the Session Keyring Vulnerability** Okay, so here's the breakdown of this issue, from my perspective. Before the patch, the `lookup_user_key` function, specifically when dealing with `KEY_SPEC_SESSION_KEYRING`, had this clever behavior. If a session keyring didn't exist for the current credentials, it would *always* install one when accessed. This meant that whenever `keyctl_session_to_parent` called `lookup_user_key` with a flag of zero, it was *guaranteed* that the current credential's `session_keyring` would be populated. This ensured that subsequent code, in `keyctl_session_to_parent`, could safely assume the existence of a session keyring. Now, the troublesome patch came along. It changed the logic of the `lookup_user_key` function. Specifically, if the `KEY_LOOKUP_CREATE` flag wasn't set – and `keyctl_session_to_parent` wasn't setting it – and the current credentials *didn't* have a session keyring, the function would now *return* the user session *without* actually installing it into the current credentials! This means, `ctx.cred->session_keyring` remains NULL. The problem arises in `keyctl_session_to_parent`. It calls `lookup_user_key`, gets a *possible* session keyring (which *could* be NULL after the patch), and then goes on to operate under the *assumption* that `current_cred()->session_keyring` is a valid pointer. This is where the null pointer dereference happens. `mycred->session_keyring` is now NULL. Then, during security checks, the code attempts to dereference `mycred->session_keyring->uid`. Boom! KASAN catches the dereference of a null pointer, and the system crashes. So, in essence, the patch introduced a subtle change in the behavior of `lookup_user_key`, making it conditional on installing the session keyring. Because of this, the subsequent code in `keyctl_session_to_parent`, which *assumed* the keyring was installed, now reads from null memory. Absolutely. This *is* a clear-cut case of the patch introducing the vulnerability. The pre-patch code *guaranteed* the presence of the session keyring if it was accessed. The post-patch code made it conditional. The dereference in `keyctl_session_to_parent` was not safeguarded to prevent this case. |
| 24/2 | 2026/10/21 00:08 | tool | set-results |
0mArgs:{
"Introduced": true,
"Reasoning": "The crash is a NULL pointer dereference (KASAN null-ptr-deref at offset 0xd8, corresponding to the `uid` field of `struct key`) in `keyctl_session_to_parent()`:\n```c\nif ((pcred-\u003esession_keyring \u0026\u0026\n !uid_eq(pcred-\u003esession_keyring-\u003euid, mycred-\u003eeuid)) ||\n !uid_eq(mycred-\u003esession_keyring-\u003euid, mycred-\u003eeuid))\n goto unlock;\n```\n\nPrior to patch 1 (\"KEYS: Return user session keyring on lookup\"), when a process without a session keyring looked up `KEY_SPEC_SESSION_KEYRING` without `KEY_LOOKUP_CREATE`, `lookup_user_key()` invoked `install_session_keyring(user_session)` to install the user session keyring into the calling task's credentials (`ctx.cred-\u003esession_keyring`). As a result, in `keyctl_session_to_parent()`, `mycred = current_cred()` was guaranteed to have a non-NULL `mycred-\u003esession_keyring`.\n\nPatch 1 modified `lookup_user_key()` to return the user session keyring reference directly without installing it into the caller's credentials when `KEY_LOOKUP_CREATE` is not specified. In `keyctl_session_to_parent()`, `lookup_user_key(KEY_SPEC_SESSION_KEYRING, 0, KEY_NEED_LINK)` is called with `lflags = 0`. Consequently, `mycred-\u003esession_keyring` remains NULL. Later in `keyctl_session_to_parent()`, `mycred-\u003esession_keyring-\u003euid` is unconditionally dereferenced at line 1707, triggering the null-pointer dereference GPF.\n\nTherefore, the crash was directly introduced by the tested patch series."
}Results: {
"Introduced": true,
"Reasoning": "The crash is a NULL pointer dereference (KASAN null-ptr-deref at offset 0xd8, corresponding to the `uid` field of `struct key`) in `keyctl_session_to_parent()`:\n```c\nif ((pcred-\u003esession_keyring \u0026\u0026\n !uid_eq(pcred-\u003esession_keyring-\u003euid, mycred-\u003eeuid)) ||\n !uid_eq(mycred-\u003esession_keyring-\u003euid, mycred-\u003eeuid))\n goto unlock;\n```\n\nPrior to patch 1 (\"KEYS: Return user session keyring on lookup\"), when a process without a session keyring looked up `KEY_SPEC_SESSION_KEYRING` without `KEY_LOOKUP_CREATE`, `lookup_user_key()` invoked `install_session_keyring(user_session)` to install the user session keyring into the calling task's credentials (`ctx.cred-\u003esession_keyring`). As a result, in `keyctl_session_to_parent()`, `mycred = current_cred()` was guaranteed to have a non-NULL `mycred-\u003esession_keyring`.\n\nPatch 1 modified `lookup_user_key()` to return the user session keyring reference directly without installing it into the caller's credentials when `KEY_LOOKUP_CREATE` is not specified. In `keyctl_session_to_parent()`, `lookup_user_key(KEY_SPEC_SESSION_KEYRING, 0, KEY_NEED_LINK)` is called with `lflags = 0`. Consequently, `mycred-\u003esession_keyring` remains NULL. Later in `keyctl_session_to_parent()`, `mycred-\u003esession_keyring-\u003euid` is unconditionally dereferenced at line 1707, triggering the null-pointer dereference GPF.\n\nTherefore, the crash was directly introduced by the tested patch series."
} |
| Total Calls | Total Tokens | Avg Tokens | Total Duration (Seconds) | Avg Duration (Seconds) |
|---|
| Total Calls | Total Duration (Seconds) | Avg Duration (Seconds) |
|---|