| 0/0 |
2026/06/25 00:00 |
flow |
assessment-security |
13m
Results: map[DenialOfService:false Explanation:Based on the analysis of the provided kernel bug report and the source code,
here is the assessment of the bug:
### Root Cause Analysis
The report indicates a KCSAN-detected data race between `get_cred_many()` and
`prepare_creds()`.
1. **The Write:** Task 9782 is executing `clone3()` with `CLONE_THREAD`, which
calls `copy_creds()`. This function invokes `get_cred_many(p->cred, 2)` on the
shared credentials of the thread group. `get_cred_many()` modifies the
credential object by writing to `cred->non_rcu = 0` and incrementing
`cred->usage`.
2. **The Read:** Concurrently, Task 9783 (another thread in the same thread
group sharing the same `cred` object) is executing `capset()`, which calls
`prepare_creds()`. This function allocates a new credential object and copies
the old shared credentials into it using `memcpy(new, old, sizeof(struct
cred))`.
The data race occurs because `memcpy()` reads the entire `struct cred` (184
bytes), including the `non_rcu` and `usage` fields, at the exact same time
`get_cred_many()` is modifying them.
### Security Impact
This data race is **completely benign** and **not exploitable**.
If we look at the implementation of `prepare_creds()` in `kernel/cred.c`,
immediately after the `memcpy()` operation, the code explicitly overwrites the
exact fields that were involved in the race:
```c
old = task->cred;
memcpy(new, old, sizeof(struct cred));
new->non_rcu = 0;
atomic_long_set(&new->usage, 1);
```
Because `new->non_rcu` and `new->usage` are unconditionally re-initialized right
after the copy, whatever torn or intermediate values `memcpy()` might have read
from the concurrently modified `old` credential object are completely discarded.
They have absolutely no effect on the program's execution.
Furthermore, all other fields in the `struct cred` (like `uid`, `gid`,
`cap_permitted`, etc.) are considered immutable once a credential is committed.
They are never modified concurrently, meaning `memcpy()` safely copies them
without any risk of corruption.
### Conclusion
While the code paths to trigger this race are easily accessible to unprivileged
users (via standard system calls like `clone3` and `capset`), the bug has no
security implications. It cannot lead to memory corruption, privilege
escalation, information leaks, or denial of service. Exploitable:false FilesystemTrigger:false NetworkTrigger:false PeripheralTrigger:false RemoteTrigger:false Unprivileged:true UserNamespace:true VMGuestTrigger:false VMHostTrigger:false]
|
| 1/1 |
2026/06/25 00:00 |
action |
syz-repro-to-c-repro |
0m
Results:
|
| 2/1 |
2026/06/25 00:00 |
action |
kernel-checkouter |
0m
Results: map[KernelSrc:/app/workdir/cache/src/a570badb39da92d4cefbc0ec66a35468d16367e9]
|
| 3/1 |
2026/06/25 00:00 |
action |
kernel-builder |
0m
Results: map[KernelObj:/app/workdir/cache/build/ab7d18b2b98ec32f72c77ac25bab09554f77836c]
|
| 4/1 |
2026/06/25 00:00 |
action |
codesearch-prepare |
0m
Results: map[Index:codesearch-index]
|
| 5/1 |
2026/06/25 00:00 |
agent |
expert |
12m
Model:
Results: map[DenialOfService:false Exploitable:false FilesystemTrigger:false NetworkTrigger:false PeripheralTrigger:false RemoteTrigger:false Unprivileged:true UserNamespace:true VMGuestTrigger:false VMHostTrigger:false]
Instruction: You are an experienced Linux kernel security engineer. Your task is to analyze given kernel bug report
and determine its security impact based on the following dimensions.
Use the provided tools to examine the source code, check for capability checks (e.g., capable(), ns_capable()),
and understand the nature of the bug. Analyze the given kernel build and configuration.
You can check the kernel config by grepping ".config" file; you can check kernel cmdline by grepping
".config" file for "CONFIG_CMDLINE=". Assume sysctl parameters have default values.
But analyze for the corresponding production build w/o debugging tools enabled (like KASAN, KMSAN, UBSAN).
Try different strategies when analyzing the bug:
- think of ways in which the vulnerable code is unreachable
- or the other way around: try to come up with different ideas of how an unprivileged user can reach the bug
If still unsure err on the side of the bug being non-exploitable/not-accessible.
In the final reply, provide a reasoning for your assessment.
Analysis dimensions:
* Exploitable:
Determine if the bug can result in memory corruption, elevated privileges, or an information leak.
Memory safety issues are almost always exploitable (KASAN or UBSAN reports for use-after-free, out-of-bounds;
refcounting issues, corrupted lists, etc). When kernel is crashing on a completely wild pointer access
(e.g. user-space address, or non-canonical address, but not on NULL or address corresponding to KASAN shadow
for NULL address), including both data accesses and control transfers, that also usually implies possibility
of exploitation. Such reports usually say "unable to handle kernel paging request".
Uses of uninitialized values detected by KMSAN may be exploitable b/c attacker frequently can affect uninit
values with spraying techniques. However, for these exploitability depends on how exactly the uninit value
is used in the code, and what it affects.
Information leaks are exploitable on their own and should be classified as such. A bug that copies kernel
memory contents to userspace (e.g. an out-of-bounds read whose result is returned to the caller, or
uninitialized stack/heap bytes written to a user buffer) is exploitable: it can reveal kernel pointer
values and defeat KASLR, expose sensitive data such as cryptographic keys or other processes' memory, and
serves as a necessary building block in most modern kernel privilege-escalation exploit chains. Do not classify
an information leak as non-exploitable solely because it does not directly cause a memory write or control-flow
hijack; the leak itself is the exploit primitive.
Think of what happens after the bug is triggered. Some bugs cause kernel panic and halt execution,
they are harder to exploit. For example, BUG reports halts the kernel. However, WARNING reports don't halt
execution in production builds. Debug bug detection tools (like KASAN, KMSAN, KCSAN, UBSAN) are also not enabled
in production builds, so attacker can freely exploit these bugs w/o being detected by these tools.
If you see an integer overflow, think how the overflowed value used later (if it's used as allocation size,
or an array index). If you see an out-of-bounds read, think if it's followed by an out-of-bounds write as well.
Some KCSAN data-races may be exploitable by skilled attackers as well. Think what data structures got corrupted
as the result of data races and how. However, note that kernel has lots of "benign" data races that don't lead
to any runtime misbehavior at all.
* Denial Of Service:
Determine if the bug can result in denial-of-service. Most bugs can, since they cause system crash,
hangs, deadlocks, or resource leaks. This is mostly applicable to WARNING bugs that won't cause system crash
in production. For these think what will be consequences of the violation of the kernel assumptions flagged
by the WARNING. In some cases the unexpected condition is also properly handled by the normal control flow
(e.g. with "if (WARN_ON(...))"), these won't cause denial-of-service. If the condition is not handled,
then it may or may not cause denial-of-service.
* Accessible From Unprivileged Processes:
Determine if the bug can be reached from a typical (non-root) user process that does NOT have any special capabilities
(like CAP_SYS_ADMIN, CAP_NET_ADMIN, CAP_NET_RAW, CAP_PERFMON) or access to device nodes restricted to root.
Assume that unprivileged_bpf_disabled=1, that is eBPF loading is not accessible. However, cBPF (classical BPF)
is still accessible to non-root processes.
Assume that user namespaces are not accessible, that is, the process cannot get the mentioned capabilities even
within a new user namespace (checked by ns_capable() function in the kernel sources).
* Accessible From User Namespaces:
Determine if the bug can be reached within a user-namespace where the process has all capabilities
(including CAP_SYS_ADMIN, CAP_NET_ADMIN, CAP_NET_RAW, CAP_PERFMON). Such capabilities are checked with ns_capable()
function in the kernel sources.
* VM Guest Trigger:
Determine if the bug can be triggered from the context of a typical KVM guest (e.g., set up by a QEMU VMM).
Consider accesses to standard Linux host paravirtualized features (virtio-blk, virtio-net, etc.),
and handling of VM exits in the KVM code.
* VM Host Trigger in The Confidential Computing Context:
Determine if the bug can be triggered in a confidential computing guest kernel from the context of a KVM host.
Consider access to standard Linux guest paravirtualized features (virtio-blk, virtio-net, etc.).
* Ethernet Network Trigger:
Determine if the bug can be triggered by processing ingress network Ethernet traffic, either directly (network stack)
or via drivers exposed to network data.
* Other Remote Trigger:
Determine if the bug can be triggered by processing remote traffic other than Ethernet (Wifi, Bluetooth, NFC, etc).
* Peripheral Trigger:
Determine if the bug can be triggered via an untrusted peripheral device that can be physically plugged
into a system, such as a USB device or a niche hardware driver handling external hardware inputs.
This is particularly important for mobile and desktop environments where users can plug in unknown devices.
* Malicious Filesystem Trigger:
Determine if the bug can be triggered by the kernel mounting and parsing a malicious filesystem image.
This is highly critical for Desktop and Mobile environments where external media or downloaded images
might be auto-mounted.
Don't make assumptions about the kernel source code (it may be different from what you assume it is).
Extensively use the provided code access tools (codesearch-*, git-*, grepper, etc)
to examine the actual source code, and confirm any assumptions.
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:
The kernel bug report is:
==================================================================
BUG: KCSAN: data-race in copy_creds / prepare_creds
write to 0xffff888103fc6b28 of 4 bytes by task 9782 on cpu 0:
get_cred_many include/linux/cred.h:222 [inline]
copy_creds+0x78/0x340 kernel/cred.c:278
copy_process+0x8b8/0x2350 kernel/fork.c:2148
kernel_clone+0x1c0/0x5f0 kernel/fork.c:2746
__do_sys_clone3 kernel/fork.c:3048 [inline]
__se_sys_clone3+0x1f5/0x230 kernel/fork.c:3027
__x64_sys_clone3+0x31/0x40 kernel/fork.c:3027
x64_sys_call+0x580/0x3020 arch/x86/include/generated/asm/syscalls_64.h:436
do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline]
do_syscall_64+0x136/0x3c0 arch/x86/entry/syscall_64.c:94
entry_SYSCALL_64_after_hwframe+0x77/0x7f
read to 0xffff888103fc6a80 of 184 bytes by task 9783 on cpu 1:
prepare_creds+0x71/0x550 kernel/cred.c:192
__do_sys_capset kernel/capability.c:247 [inline]
__se_sys_capset+0x2da/0x480 kernel/capability.c:216
__x64_sys_capset+0x31/0x40 kernel/capability.c:216
x64_sys_call+0x2aac/0x3020 arch/x86/include/generated/asm/syscalls_64.h:127
do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline]
do_syscall_64+0x136/0x3c0 arch/x86/entry/syscall_64.c:94
entry_SYSCALL_64_after_hwframe+0x77/0x7f
Reported by Kernel Concurrency Sanitizer on:
CPU: 1 UID: 0 PID: 9783 Comm: syz.5.1734 Tainted: G W syzkaller #0 PREEMPT(lazy)
Tainted: [W]=WARN
Hardware name: Google Google Compute Engine/Google Compute Engine, BIOS Google 05/09/2026
==================================================================
Reply: Based on the analysis of the provided kernel bug report and the source code, here is the assessment of the bug:
### Root Cause Analysis
The report indicates a KCSAN-detected data race between `get_cred_many()` and `prepare_creds()`.
1. **The Write:** Task 9782 is executing `clone3()` with `CLONE_THREAD`, which calls `copy_creds()`. This function invokes `get_cred_many(p->cred, 2)` on the shared credentials of the thread group. `get_cred_many()` modifies the credential object by writing to `cred->non_rcu = 0` and incrementing `cred->usage`.
2. **The Read:** Concurrently, Task 9783 (another thread in the same thread group sharing the same `cred` object) is executing `capset()`, which calls `prepare_creds()`. This function allocates a new credential object and copies the old shared credentials into it using `memcpy(new, old, sizeof(struct cred))`.
The data race occurs because `memcpy()` reads the entire `struct cred` (184 bytes), including the `non_rcu` and `usage` fields, at the exact same time `get_cred_many()` is modifying them.
### Security Impact
This data race is **completely benign** and **not exploitable**.
If we look at the implementation of `prepare_creds()` in `kernel/cred.c`, immediately after the `memcpy()` operation, the code explicitly overwrites the exact fields that were involved in the race:
```c
old = task->cred;
memcpy(new, old, sizeof(struct cred));
new->non_rcu = 0;
atomic_long_set(&new->usage, 1);
```
Because `new->non_rcu` and `new->usage` are unconditionally re-initialized right after the copy, whatever torn or intermediate values `memcpy()` might have read from the concurrently modified `old` credential object are completely discarded. They have absolutely no effect on the program's execution.
Furthermore, all other fields in the `struct cred` (like `uid`, `gid`, `cap_permitted`, etc.) are considered immutable once a credential is committed. They are never modified concurrently, meaning `memcpy()` safely copies them without any risk of corruption.
### Conclusion
While the code paths to trigger this race are easily accessible to unprivileged users (via standard system calls like `clone3` and `capset`), the bug has no security implications. It cannot lead to memory corruption, privilege escalation, information leaks, or denial of service.
|
| 6/2 |
2026/06/25 00:00 |
llm |
expert |
0m
Model:
Tokens:
input: 6403
output: 109
thoughts: 208
Thoughts: **Data Race Debugging Thoughts**
Okay, a KCSAN data-race report has popped up, which, as usual, requires some careful dissection. Looks like there's a race between `copy_creds` and `prepare_creds`. The report pinpoints a write to memory address `0xffff888103fc6b28` by task 9782 on CPU 0. That write is 4 bytes and originates in `get_cred_many`, calls `copy_creds`, which in turn is called from `copy_process`. On the other hand, task 9783 on CPU 1 is reading 184 bytes from address `0xffff888103fc6a80` through the path `prepare_creds`, which, as a system call, gets called from `__do_sys_capset`.
Right, `copy_creds` implies a fork or clone operation, and `prepare_creds` is often called when a process modifies its credentials, likely with a `capset` system call to handle capability changes. My immediate thought is, are the credential objects sharing data in a way that is not synchronized? That location seems to be within the credential structure. So, I need to look closely at `kernel/cred.c` and `include/linux/cred.h` to understand the data structures and how `copy_creds` and `prepare_creds` interact. I will examine how these two routines, and the underlying data structures, manage shared credential state. Need to check if proper locking mechanisms are being used to protect critical sections, or if there's any potential for out-of-order execution that could lead to this race condition. I know what to check, so let's start digging.
|
| 7/2 |
2026/06/25 00:00 |
tool |
grepper |
0m
Args: map[Expression:copy_creds PathPrefix:kernel/cred.c]
Results: map[Output:kernel/cred.c=230=struct cred *prepare_exec_creds(void)
--
kernel/cred.c-262- */
kernel/cred.c:263:int copy_creds(struct task_struct *p, u64 clone_flags)
kernel/cred.c-264-{
]
|
| 8/2 |
2026/06/25 00:00 |
tool |
grepper |
0m
Args: map[Expression:prepare_creds PathPrefix:kernel/cred.c]
Results: map[Output:kernel/cred.c=146=struct cred *cred_alloc_blank(void)
--
kernel/cred.c-165-/**
kernel/cred.c:166: * prepare_creds - Prepare a new set of credentials for modification
kernel/cred.c-167- *
--
kernel/cred.c-178- */
kernel/cred.c:179:struct cred *prepare_creds(void)
kernel/cred.c-180-{
--
kernel/cred.c-188-
kernel/cred.c:189: kdebug("prepare_creds() alloc %p", new);
kernel/cred.c-190-
--
kernel/cred.c-214-
kernel/cred.c:215: if (security_prepare_creds(new, old, GFP_KERNEL_ACCOUNT) < 0)
kernel/cred.c-216- goto error;
--
kernel/cred.c-223-}
kernel/cred.c:224:EXPORT_SYMBOL(prepare_creds);
kernel/cred.c-225-
--
kernel/cred.c=230=struct cred *prepare_exec_creds(void)
--
kernel/cred.c-233-
kernel/cred.c:234: new = prepare_creds();
kernel/cred.c-235- if (!new)
--
kernel/cred.c=263=int copy_creds(struct task_struct *p, u64 clone_flags)
--
kernel/cred.c-285-
kernel/cred.c:286: new = prepare_creds();
kernel/cred.c-287- if (!new)
--
kernel/cred.c=559=struct cred *prepare_kernel_cred(struct task_struct *daemon)
--
kernel/cred.c-596-
kernel/cred.c:597: if (security_prepare_creds(new, old, GFP_KERNEL_ACCOUNT) < 0)
kernel/cred.c-598- goto error;
]
|
| 9/2 |
2026/06/25 00:00 |
tool |
grepper |
0m
Args: map[Expression:get_cred_many PathPrefix:include/linux/cred.h]
Results: map[Output:include/linux/cred.h=191=DEFINE_CLASS(override_creds,
--
include/linux/cred.h-201-/**
include/linux/cred.h:202: * get_cred_many - Get references on a set of credentials
include/linux/cred.h-203- * @cred: The credentials to reference
--
include/linux/cred.h-216- */
include/linux/cred.h:217:static inline const struct cred *get_cred_many(const struct cred *cred, int nr)
include/linux/cred.h-218-{
--
include/linux/cred.h=236=static inline const struct cred *get_cred(const struct cred *cred)
include/linux/cred.h-237-{
include/linux/cred.h:238: return get_cred_many(cred, 1);
include/linux/cred.h-239-}
]
|
| 10/2 |
2026/06/25 00:00 |
tool |
codesearch-struct-layout |
0m
Args:
Results: map[Fields:[map[Name:usage OffsetBits:0 SizeBits:64] map[Name:uid OffsetBits:64 SizeBits:32] map[Name:gid OffsetBits:96 SizeBits:32] map[Name:suid OffsetBits:128 SizeBits:32] map[Name:sgid OffsetBits:160 SizeBits:32] map[Name:euid OffsetBits:192 SizeBits:32] map[Name:egid OffsetBits:224 SizeBits:32] map[Name:fsuid OffsetBits:256 SizeBits:32] map[Name:fsgid OffsetBits:288 SizeBits:32] map[Name:securebits OffsetBits:320 SizeBits:32] map[Name:cap_inheritable OffsetBits:384 SizeBits:64] map[Name:cap_permitted OffsetBits:448 SizeBits:64] map[Name:cap_effective OffsetBits:512 SizeBits:64] map[Name:cap_bset OffsetBits:576 SizeBits:64] map[Name:cap_ambient OffsetBits:640 SizeBits:64] map[Name:jit_keyring OffsetBits:704 SizeBits:8] map[Name:session_keyring OffsetBits:768 SizeBits:64] map[Name:process_keyring OffsetBits:832 SizeBits:64] map[Name:thread_keyring OffsetBits:896 SizeBits:64] map[Name:request_key_auth OffsetBits:960 SizeBits:64] map[Name:security OffsetBits:1024 SizeBits:64] map[Name:user OffsetBits:1088 SizeBits:64] map[Name:user_ns OffsetBits:1152 SizeBits:64] map[Name:ucounts OffsetBits:1216 SizeBits:64] map[Name:group_info OffsetBits:1280 SizeBits:64] map[Name: OffsetBits:1344 SizeBits:128]]]
|
| 11/2 |
2026/06/25 00:00 |
llm |
expert |
0m
Model:
Tokens:
input: 8519
output: 101
thoughts: 239
Thoughts: **Analyzing a Memory Operation**
Okay, so I'm looking at these memory operations, and immediately something seems off. There's a write to `0xffff888103fc6b28` of 4 bytes, and a read from `0xffff888103fc6a80` of 184 bytes. The address difference is 0xa8, which is 168 bytes. This means the read goes up to address `0xffff888103fc6b38`. Now, the write is to `0xffff888103fc6b28`, which lines up with the read offset; it's `0xffff888103fc6a80 + 168`. Hmmm... that makes me think `cred` struct is being used here, and `0xffff888103fc6a80` is where that structure starts.
I need to quickly figure out what's going on within that `cred` structure. Let me check the layout, starting with `usage`. That should be at offset 0, and I believe that's 64 bits, or 8 bytes. Let me just quickly confirm, is that `atomic_t` or `refcount_t`? I should probably double-check the `include/linux/cred.h` file to be absolutely sure.
|
| 12/2 |
2026/06/25 00:00 |
tool |
read-file |
0m
Args: map[File:include/linux/cred.h FirstLine:210 LineCount:30]
Results: map[Contents: 210: * pointer is const, this will temporarily discard the const and increment the
211: * usage count. The purpose of this is to attempt to catch at compile time the
212: * accidental alteration of a set of credentials that should be considered
213: * immutable.
214: *
215: * Returns: @cred when the references are acquired, NULL otherwise.
216: */
217: static inline const struct cred *get_cred_many(const struct cred *cred, int nr)
218: {
219: struct cred *nonconst_cred = (struct cred *) cred;
220: if (!cred)
221: return cred;
222: nonconst_cred->non_rcu = 0;
223: atomic_long_add(nr, &nonconst_cred->usage);
224: return cred;
225: }
226:
227: /*
228: * get_cred - Get a reference on a set of credentials
229: * @cred: The credentials to reference
230: *
231: * Get a reference on the specified set of credentials. The caller must
232: * release the reference. If %NULL is passed, it is returned with no action.
233: *
234: * This is used to deal with a committed set of credentials.
235: */
236: static inline const struct cred *get_cred(const struct cred *cred)
237: {
238: return get_cred_many(cred, 1);
239: }
]
|
| 13/2 |
2026/06/25 00:00 |
tool |
read-file |
0m
Args: map[File:kernel/cred.c FirstLine:179 LineCount:50]
Results: map[Contents: 179: struct cred *prepare_creds(void)
180: {
181: struct task_struct *task = current;
182: const struct cred *old;
183: struct cred *new;
184:
185: new = kmem_cache_alloc(cred_jar, GFP_KERNEL);
186: if (!new)
187: return NULL;
188:
189: kdebug("prepare_creds() alloc %p", new);
190:
191: old = task->cred;
192: memcpy(new, old, sizeof(struct cred));
193:
194: new->non_rcu = 0;
195: atomic_long_set(&new->usage, 1);
196: get_group_info(new->group_info);
197: get_uid(new->user);
198: get_user_ns(new->user_ns);
199:
200: #ifdef CONFIG_KEYS
201: key_get(new->session_keyring);
202: key_get(new->process_keyring);
203: key_get(new->thread_keyring);
204: key_get(new->request_key_auth);
205: #endif
206:
207: #ifdef CONFIG_SECURITY
208: new->security = NULL;
209: #endif
210:
211: new->ucounts = get_ucounts(new->ucounts);
212: if (!new->ucounts)
213: goto error;
214:
215: if (security_prepare_creds(new, old, GFP_KERNEL_ACCOUNT) < 0)
216: goto error;
217:
218: return new;
219:
220: error:
221: abort_creds(new);
222: return NULL;
223: }
224: EXPORT_SYMBOL(prepare_creds);
225:
226: /*
227: * Prepare credentials for current to perform an execve()
228: * - The caller must hold ->cred_guard_mutex
]
|
| 14/2 |
2026/06/25 00:00 |
tool |
read-file |
0m
Args: map[File:kernel/cred.c FirstLine:263 LineCount:50]
Results: map[Contents: 263: int copy_creds(struct task_struct *p, u64 clone_flags)
264: {
265: struct cred *new;
266: int ret;
267:
268: #ifdef CONFIG_KEYS_REQUEST_CACHE
269: p->cached_requested_key = NULL;
270: #endif
271:
272: if (
273: #ifdef CONFIG_KEYS
274: !p->cred->thread_keyring &&
275: #endif
276: clone_flags & CLONE_THREAD
277: ) {
278: p->real_cred = get_cred_many(p->cred, 2);
279: kdebug("share_creds(%p{%ld})",
280: p->cred, atomic_long_read(&p->cred->usage));
281: inc_rlimit_ucounts(task_ucounts(p), UCOUNT_RLIMIT_NPROC, 1);
282: get_cred_namespaces(p);
283: return 0;
284: }
285:
286: new = prepare_creds();
287: if (!new)
288: return -ENOMEM;
289:
290: if (clone_flags & CLONE_NEWUSER) {
291: ret = create_user_ns(new);
292: if (ret < 0)
293: goto error_put;
294: ret = set_cred_ucounts(new);
295: if (ret < 0)
296: goto error_put;
297: }
298:
299: #ifdef CONFIG_KEYS
300: /* new threads get their own thread keyrings if their parent already
301: * had one */
302: if (new->thread_keyring) {
303: key_put(new->thread_keyring);
304: new->thread_keyring = NULL;
305: if (clone_flags & CLONE_THREAD)
306: install_thread_keyring_to_cred(new);
307: }
308:
309: /* The process keyring is only shared between the threads in a process;
310: * anything outside of those threads doesn't inherit.
311: */
312: if (!(clone_flags & CLONE_THREAD)) {
]
|
| 15/2 |
2026/06/25 00:00 |
llm |
expert |
0m
Model:
Tokens:
input: 11023
output: 35
thoughts: 160
Thoughts: **Data Race Discovery**
Okay, let's break this down. My internal alarms are going off β potential data race alert! I see Task 9782 on CPU 0 is calling `get_cred_many`, and it looks like it's operating on `p->cred`. Specifically, it's modifying a field, `non_rcu`. Then, almost simultaneously, Task 9783 on CPU 1 is calling `memcpy` to copy `task->cred`.
Hang on a second⦠`get_cred_many` seems to be writing to `non_rcu`, and the other task is reading the same memory region through its copy via `memcpy`. This sounds like a classic read-write conflict! The crucial question now: where is `non_rcu` defined within the `struct cred`? I need to verify its location and how it's being used. Time to check `include/linux/cred.h` immediately. I need the definitive source on how this structure is laid out. I have a strong feeling this is the start of an important investigation.
|
| 16/2 |
2026/06/25 00:00 |
tool |
read-file |
0m
Args: map[File:include/linux/cred.h FirstLine:110 LineCount:50]
Results: map[Contents: 110: * task->cred points to the subjective context that defines the details of how
111: * that task is going to act upon another object. This may be overridden
112: * temporarily to point to another security context, but normally points to the
113: * same context as task->real_cred.
114: */
115: struct cred {
116: atomic_long_t usage;
117: kuid_t uid; /* real UID of the task */
118: kgid_t gid; /* real GID of the task */
119: kuid_t suid; /* saved UID of the task */
120: kgid_t sgid; /* saved GID of the task */
121: kuid_t euid; /* effective UID of the task */
122: kgid_t egid; /* effective GID of the task */
123: kuid_t fsuid; /* UID for VFS ops */
124: kgid_t fsgid; /* GID for VFS ops */
125: unsigned securebits; /* SUID-less security management */
126: kernel_cap_t cap_inheritable; /* caps our children can inherit */
127: kernel_cap_t cap_permitted; /* caps we're permitted */
128: kernel_cap_t cap_effective; /* caps we can actually use */
129: kernel_cap_t cap_bset; /* capability bounding set */
130: kernel_cap_t cap_ambient; /* Ambient capability set */
131: #ifdef CONFIG_KEYS
132: unsigned char jit_keyring; /* default keyring to attach requested
133: * keys to */
134: struct key *session_keyring; /* keyring inherited over fork */
135: struct key *process_keyring; /* keyring private to this process */
136: struct key *thread_keyring; /* keyring private to this thread */
137: struct key *request_key_auth; /* assumed request_key authority */
138: #endif
139: #ifdef CONFIG_SECURITY
140: void *security; /* LSM security */
141: #endif
142: struct user_struct *user; /* real user ID subscription */
143: struct user_namespace *user_ns; /* user_ns the caps and keyrings are relative to. */
144: struct ucounts *ucounts;
145: struct group_info *group_info; /* supplementary groups for euid/fsgid */
146: /* RCU deletion */
147: union {
148: int non_rcu; /* Can we skip RCU deletion? */
149: struct rcu_head rcu; /* RCU deletion hook */
150: };
151: } __randomize_layout;
152:
153: extern void __put_cred(struct cred *);
154: extern void exit_creds(struct task_struct *);
155: extern int copy_creds(struct task_struct *, u64);
156: extern const struct cred *get_task_cred(struct task_struct *);
157: extern struct cred *cred_alloc_blank(void);
158: extern struct cred *prepare_creds(void);
159: extern struct cred *prepare_exec_creds(void);
]
|
| 17/2 |
2026/06/25 00:00 |
llm |
expert |
0m
Model:
Tokens:
input: 12423
output: 34
thoughts: 571
Thoughts: **Analyzing Credential Copying in Linux Kernel**
Okay, so I'm looking at this memory access pattern and trying to figure out what's going on with these `cred` structs. I see `non_rcu` is at the very end of the `cred` struct, in a union with `rcu_head`. Running `codesearch-struct-layout` I confirmed the size is 168 bytes before the last field, which is 16 bytes for `rcu_head`. So `non_rcu` is indeed at an offset of 168 bytes within the struct.
The code shows a write operation targeting `0xffff888103fc6b28` which is 4 bytes. Simultaneously, there's a read at `0xffff888103fc6a80` for 184 bytes. Now, `0xffff888103fc6a80 + 168 = 0xffff888103fc6b28`, which means the write is precisely targeting `non_rcu`. Excellent.
Now, let's look at how the credentials are handled in the kernel. `get_cred_many` seems important. It takes a `cred` pointer and sets `non_rcu` to 0. It also increments the usage count. Simple enough.
But `prepare_creds` is where the core logic exists. It allocates a new `cred` struct and copies the contents of an existing `cred` struct using `memcpy(new, old, sizeof(struct cred))`. That `old` pointer is coming from `task->cred`.
Then, I see `copy_creds` uses `get_cred_many`. `copy_creds` calls `get_cred_many(p->cred, 2)` when a new task `p` is created, and the credentials are copied from the parent. That's interesting, so `p->cred` is initially inherited from the parent.
This seems to point to a standard copy-on-write credential inheritance scheme. `copy_creds` is called from `copy_process`, which makes the flow of control clear. I need to look into `copy_process` in `kernel/fork.c` to confirm my understanding of the new task initialization. This should illuminate exactly what happens with the `cred` structure inheritance in the fork system call.
|
| 18/2 |
2026/06/25 00:01 |
tool |
read-file |
0m
Args: map[File:kernel/fork.c FirstLine:2130 LineCount:50]
Results: map[Contents:2130: if (args->name)
2131: strscpy_pad(p->comm, args->name, sizeof(p->comm));
2132:
2133: p->set_child_tid = (clone_flags & CLONE_CHILD_SETTID) ? args->child_tid : NULL;
2134: /*
2135: * TID is cleared in mm_release() when the task exits
2136: */
2137: p->clear_child_tid = (clone_flags & CLONE_CHILD_CLEARTID) ? args->child_tid : NULL;
2138:
2139: ftrace_graph_init_task(p);
2140:
2141: rt_mutex_init_task(p);
2142: raw_spin_lock_init(&p->blocked_lock);
2143:
2144: lockdep_assert_irqs_enabled();
2145: #ifdef CONFIG_PROVE_LOCKING
2146: DEBUG_LOCKS_WARN_ON(!p->softirqs_enabled);
2147: #endif
2148: retval = copy_creds(p, clone_flags);
2149: if (retval < 0)
2150: goto bad_fork_free;
2151:
2152: retval = -EAGAIN;
2153: if (is_rlimit_overlimit(task_ucounts(p), UCOUNT_RLIMIT_NPROC, rlimit(RLIMIT_NPROC))) {
2154: if (p->real_cred->user != INIT_USER &&
2155: !capable(CAP_SYS_RESOURCE) && !capable(CAP_SYS_ADMIN))
2156: goto bad_fork_cleanup_count;
2157: }
2158: current->flags &= ~PF_NPROC_EXCEEDED;
2159:
2160: /*
2161: * If multiple threads are within copy_process(), then this check
2162: * triggers too late. This doesn't hurt, the check is only there
2163: * to stop root fork bombs.
2164: */
2165: retval = -EAGAIN;
2166: if (data_race(nr_threads >= max_threads))
2167: goto bad_fork_cleanup_count;
2168:
2169: delayacct_tsk_init(p); /* Must remain after dup_task_struct() */
2170: p->flags &= ~(PF_SUPERPRIV | PF_WQ_WORKER | PF_IDLE | PF_NO_SETAFFINITY);
2171: p->flags |= PF_FORKNOEXEC;
2172: INIT_LIST_HEAD(&p->children);
2173: INIT_LIST_HEAD(&p->sibling);
2174: rcu_copy_process(p);
2175: p->vfork_done = NULL;
2176: spin_lock_init(&p->alloc_lock);
2177:
2178: init_sigpending(&p->pending);
2179:
]
|
| 19/2 |
2026/06/25 00:01 |
llm |
expert |
0m
Model:
Tokens:
input: 14409
output: 26
thoughts: 349
Thoughts: **Race Condition Detected in Credential Cloning**
Okay, let me think this through. The `dup_task_struct` function is called *before* `copy_creds`. Right, so `dup_task_struct` essentially clones the parent's `task_struct`, and that means `p->cred` is initially a pointer to the *parent's* `cred` structure. That makes sense.
Now, let's look at `copy_creds`. I see that within that function, there's a specific block of code:
```c
if (
#ifdef CONFIG_KEYS
!p->cred->thread_keyring &&
#endif
clone_flags & CLONE_THREAD
) {
p->real_cred = get_cred_many(p->cred, 2);
```
Here, *that* `p->cred` is still the parent's credential structure. It then calls `get_cred_many` on it. And that's where things get interesting because `get_cred_many` *modifies* `p->cred->non_rcu`. Specifically, `nonconst_cred->non_rcu = 0;`.
But wait a minute... *That* `p->cred` is *shared* with the parent! If the parent is concurrently calling `prepare_creds`, it's going to read `task->cred`, which is the *same* `cred` object. It uses `memcpy(new, old, sizeof(struct cred))` to make a copy. That means `memcpy` is reading `old->non_rcu` *while* `get_cred_many` is writing `nonconst_cred->non_rcu = 0`. Boom, that's a classic race condition!
Essentially, `get_cred_many` is modifying `non_rcu` on a potentially shared credential structure. I need to understand why `get_cred_many` touches `non_rcu` in the first place and see if there is any reason why this is supposed to be safe. Time to check the git log for `get_cred_many` and `non_rcu` and get a better understanding.
|
| 20/2 |
2026/06/25 00:01 |
tool |
git-log |
10m
Error:
Args: map[CodeRegexp:non_rcu Count:10]
Results:
|
| 21/2 |
2026/06/25 00:11 |
llm |
expert |
0m
Model:
Tokens:
input: 15299
output: 76
thoughts: 22
Thoughts: Here's a summary of my thought process, focusing on the specified files, crafted as if they were my own reflections:
**Initial Investigation into Credential Management in the Linux Kernel**
Okay, my first port of call is going to be `include/linux/cred.h` and `kernel/cred.c`. That's where I need to focus my attention. I need to understand the fundamental data structures and the primary functions that deal with credentials within the kernel. I'm assuming that the header file (`cred.h`) will define the core `cred` structure, the attributes it holds (like user ID, group ID, capabilities, etc.), and possibly some inline functions for access or manipulation. The corresponding C file (`cred.c`) will then provide the implementation details for all of this, including the creation, destruction, copying, and modification of credentials. I'm especially interested in how these are integrated with the security framework. I expect to see references to other security modules or hooks in both files. Let's see what we've got.
|
| 22/2 |
2026/06/25 00:11 |
tool |
git-log |
0m
Args: map[CodeRegexp:non_rcu Count:10 PathPrefix:include/linux/cred.h]
Results: map[Output:d7852fbd0f04 access: avoid the RCU grace period for the temporary subjective credentials
]
|
| 23/2 |
2026/06/25 00:11 |
tool |
git-log |
0m
Args: map[CodeRegexp:non_rcu Count:10 PathPrefix:kernel/cred.c]
Results: map[Output:a51a1d6bcaa3 cred: remove old {override,revert}_creds() helpers
d7852fbd0f04 access: avoid the RCU grace period for the temporary subjective credentials
]
|
| 24/2 |
2026/06/25 00:11 |
llm |
expert |
0m
Model:
Tokens:
input: 15716
output: 26
thoughts: 18
Thoughts: Okay, here's my take on those thoughts, tailored for an expert audience:
**Analyzing Commit d7852fbd0f04**
Alright, time to dig into commit `d7852fbd0f04`. I want to get a solid understanding of what was changed, the implications, and if there are any immediate red flags. First, I'll pull the diff and scan for the high-level scope: are we talking a localized fix, a feature addition, or a more systemic architectural change? My gut tells me this one needs a deeper dive, given the commit hash. I'm looking for the "why" behind the changes, not just the "what". Were there any related issues or pull requests? The commit message should be telling, but I've learned to never trust it implicitly; I'll cross-reference with any associated ticketing system.
I'll be specifically looking for potential impact on performance, security, or existing integrations. Any dependencies altered? Does this touch any critical paths or frequently accessed data structures? I need to analyze any subtle shifts in logic or algorithm choices. Code style and adherence to established best practices are important, but in a production environment, performance is more paramount. I'm also keeping an eye out for any obvious race conditions or concurrent access issues, since those are always fun to debug later. Overall, I am looking for ways to assess the potential ripple effects and if this commit adheres to the long-term architectural vision. I will use this assessment to determine the priority of any follow-up actions.
|
| 25/2 |
2026/06/25 00:11 |
tool |
git-show |
0m
Args:
Results: map[Output:commit d7852fbd0f0423937fa287a598bfde188bb68c22
Author: Linus Torvalds <torvalds@linux-foundation.org>
Date: Thu Jul 11 09:54:40 2019 -0700
access: avoid the RCU grace period for the temporary subjective credentials
It turns out that 'access()' (and 'faccessat()') can cause a lot of RCU
work because it installs a temporary credential that gets allocated and
freed for each system call.
The allocation and freeing overhead is mostly benign, but because
credentials can be accessed under the RCU read lock, the freeing
involves a RCU grace period.
Which is not a huge deal normally, but if you have a lot of access()
calls, this causes a fair amount of seconday damage: instead of having a
nice alloc/free patterns that hits in hot per-CPU slab caches, you have
all those delayed free's, and on big machines with hundreds of cores,
the RCU overhead can end up being enormous.
But it turns out that all of this is entirely unnecessary. Exactly
because access() only installs the credential as the thread-local
subjective credential, the temporary cred pointer doesn't actually need
to be RCU free'd at all. Once we're done using it, we can just free it
synchronously and avoid all the RCU overhead.
So add a 'non_rcu' flag to 'struct cred', which can be set by users that
know they only use it in non-RCU context (there are other potential
users for this). We can make it a union with the rcu freeing list head
that we need for the RCU case, so this doesn't need any extra storage.
Note that this also makes 'get_current_cred()' clear the new non_rcu
flag, in case we have filesystems that take a long-term reference to the
cred and then expect the RCU delayed freeing afterwards. It's not
entirely clear that this is required, but it makes for clear semantics:
the subjective cred remains non-RCU as long as you only access it
synchronously using the thread-local accessors, but you _can_ use it as
a generic cred if you want to.
It is possible that we should just remove the whole RCU markings for
->cred entirely. Only ->real_cred is really supposed to be accessed
through RCU, and the long-term cred copies that nfs uses might want to
explicitly re-enable RCU freeing if required, rather than have
get_current_cred() do it implicitly.
But this is a "minimal semantic changes" change for the immediate
problem.
Acked-by: Peter Zijlstra (Intel) <peterz@infradead.org>
Acked-by: Eric Dumazet <edumazet@google.com>
Acked-by: Paul E. McKenney <paulmck@linux.ibm.com>
Cc: Oleg Nesterov <oleg@redhat.com>
Cc: Jan Glauber <jglauber@marvell.com>
Cc: Jiri Kosina <jikos@kernel.org>
Cc: Jayachandran Chandrasekharan Nair <jnair@marvell.com>
Cc: Greg KH <greg@kroah.com>
Cc: Kees Cook <keescook@chromium.org>
Cc: David Howells <dhowells@redhat.com>
Cc: Miklos Szeredi <miklos@szeredi.hu>
Cc: Al Viro <viro@zeniv.linux.org.uk>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
diff --git a/fs/open.c b/fs/open.c
index b5b80469b93d..a59abe3c669a 100644
--- a/fs/open.c
+++ b/fs/open.c
@@ -374,6 +374,25 @@ long do_faccessat(int dfd, const char __user *filename, int mode)
override_cred->cap_permitted;
}
+ /*
+ * The new set of credentials can *only* be used in
+ * task-synchronous circumstances, and does not need
+ * RCU freeing, unless somebody then takes a separate
+ * reference to it.
+ *
+ * NOTE! This is _only_ true because this credential
+ * is used purely for override_creds() that installs
+ * it as the subjective cred. Other threads will be
+ * accessing ->real_cred, not the subjective cred.
+ *
+ * If somebody _does_ make a copy of this (using the
+ * 'get_current_cred()' function), that will clear the
+ * non_rcu field, because now that other user may be
+ * expecting RCU freeing. But normal thread-synchronous
+ * cred accesses will keep things non-RCY.
+ */
+ override_cred->non_rcu = 1;
+
old_cred = override_creds(override_cred);
retry:
res = user_path_at(dfd, filename, lookup_flags, &path);
diff --git a/include/linux/cred.h b/include/linux/cred.h
index 7eb43a038330..f7a30e0099be 100644
--- a/include/linux/cred.h
+++ b/include/linux/cred.h
@@ -145,7 +145,11 @@ struct cred {
struct user_struct *user; /* real user ID subscription */
struct user_namespace *user_ns; /* user_ns the caps and keyrings are relative to. */
struct group_info *group_info; /* supplementary groups for euid/fsgid */
- struct rcu_head rcu; /* RCU deletion hook */
+ /* RCU deletion */
+ union {
+ int non_rcu; /* Can we skip RCU deletion? */
+ struct rcu_head rcu; /* RCU deletion hook */
+ };
} __randomize_layout;
extern void __put_cred(struct cred *);
@@ -246,6 +250,7 @@ static inline const struct cred *get_cred(const struct cred *cred)
if (!cred)
return cred;
validate_creds(cred);
+ nonconst_cred->non_rcu = 0;
return get_new_cred(nonconst_cred);
}
@@ -257,6 +262,7 @@ static inline const struct cred *get_cred_rcu(const struct cred *cred)
if (!atomic_inc_not_zero(&nonconst_cred->usage))
return NULL;
validate_creds(cred);
+ nonconst_cred->non_rcu = 0;
return cred;
}
diff --git a/kernel/cred.c b/kernel/cred.c
index c73a87a4df13..153ae369e024 100644
--- a/kernel/cred.c
+++ b/kernel/cred.c
@@ -144,7 +144,10 @@ void __put_cred(struct cred *cred)
BUG_ON(cred == current->cred);
BUG_ON(cred == current->real_cred);
- call_rcu(&cred->rcu, put_cred_rcu);
+ if (cred->non_rcu)
+ put_cred_rcu(&cred->rcu);
+ else
+ call_rcu(&cred->rcu, put_cred_rcu);
}
EXPORT_SYMBOL(__put_cred);
@@ -256,6 +259,7 @@ struct cred *prepare_creds(void)
old = task->cred;
memcpy(new, old, sizeof(struct cred));
+ new->non_rcu = 0;
atomic_set(&new->usage, 1);
set_cred_subscribers(new, 0);
get_group_info(new->group_info);
@@ -535,7 +539,19 @@ const struct cred *override_creds(const struct cred *new)
validate_creds(old);
validate_creds(new);
- get_cred(new);
+
+ /*
+ * NOTE! This uses 'get_new_cred()' rather than 'get_cred()'.
+ *
+ * That means that we do not clear the 'non_rcu' flag, since
+ * we are only installing the cred into the thread-synchronous
+ * '->cred' pointer, not the '->real_cred' pointer that is
+ * visible to other threads under RCU.
+ *
+ * Also note that we did validate_creds() manually, not depending
+ * on the validation in 'get_cred()'.
+ */
+ get_new_cred((struct cred *)new);
alter_cred_subscribers(new, 1);
rcu_assign_pointer(current->cred, new);
alter_cred_subscribers(old, -1);
@@ -672,6 +688,7 @@ struct cred *prepare_kernel_cred(struct task_struct *daemon)
validate_creds(old);
*new = *old;
+ new->non_rcu = 0;
atomic_set(&new->usage, 1);
set_cred_subscribers(new, 0);
get_uid(new->user);
]
|
| 26/2 |
2026/06/25 00:11 |
llm |
expert |
0m
Model:
Tokens:
input: 18545
output: 35
thoughts: 2516
Thoughts: **Benign Data Race in Credential Handling**
Okay, so I'm looking at this `get_cred_many` function, which used to be `get_cred`. It clears `non_rcu`, likely because it's taking a reference to the credentials that might be used outside the current thread context. But here's the kicker: `copy_creds` does this: `p->real_cred = get_cred_many(p->cred, 2);`. This modifies the `non_rcu` field of `p->cred`, which *is* the parent's credential object.
Now, if the parent's also calling `prepare_creds` concurrently, `prepare_creds` reads `task->cred` (which, again, is the same `cred` object) using `memcpy`. So `memcpy` is reading `old->non_rcu` while `get_cred_many` is writing to `nonconst_cred->non_rcu = 0`. Uh oh, potential race condition!
But hold on, `prepare_creds` *immediately* sets `new->non_rcu = 0` right after that `memcpy`! So even if `memcpy` picks up a "torn" or intermediate value for `old->non_rcu`, it's instantly overwritten. The race is on `old->non_rcu`. `memcpy` reads `non_rcu` as part of copying the whole `struct cred`. `get_cred_many` is writing to it. The important thing is that `prepare_creds` overwrites `new->non_rcu = 0` right after, so the value read by `memcpy` for `non_rcu` doesn't matter, and the compiler knows it.
Wait, the question is: is this exploitable?
The write's `nonconst_cred->non_rcu = 0`. The read is `memcpy`. The read value is immediately discarded because `new->non_rcu = 0`. It's a classic benign data race! Could the compiler optimize away the write because it knows? It doesn't. If the compiler does, it would elide the read of `old->non_rcu`, and that causes no race. But KCSAN caught it, specifically on `non_rcu` at offset 168 (184 bytes total).
`memcpy` reads the entire `struct cred`, which is 184 bytes. And KCSAN flags any concurrent write to this region as a data race with the `memcpy` read. This concurrent write is `nonconst_cred->non_rcu = 0` (4 bytes) at offset 168. So it's a true data race at the machine level, but the read value of `non_rcu` has no effect on execution.
`get_cred_many` also writes to `usage`, which is an atomic variable at offset 0. `memcpy` also reads `usage`. But `new->usage` is overwritten right after: `atomic_long_set(&new->usage, 1);`.
So neither `non_rcu` nor `usage` read by `memcpy` matters. I have to double check `get_cred_many`. `p->cred` won't be freed because the parent is currently forking.
I see now, the read is `prepare_creds`, called by `__do_sys_capset` of task 9783. But `copy_process` calls `clone3` which is called by 9782. 9782 and 9783 share the same `cred`. Threads in the same process do share the same `cred` object!
When a thread is created with `CLONE_THREAD`, the same `cred` is kept, so threads in the same process share the same `cred` object!
Yes, if the parent's also calling `capset` which calls `prepare_creds` at the same time, we have a race.
Task 9783 is allocating a new `cred`, copies shared `cred` into it, modifies, and then calls `commit_creds` which sets the task cred. The race is just on `non_rcu` and `usage`. Both are overwritten immediately. So completely benign. I need to make sure I did not forget any other changes by `get_cred_many`. I did not.
|
| 27/2 |
2026/06/25 00:12 |
tool |
read-file |
0m
Args: map[File:include/linux/cred.h FirstLine:217 LineCount:10]
Results: map[Contents: 217: static inline const struct cred *get_cred_many(const struct cred *cred, int nr)
218: {
219: struct cred *nonconst_cred = (struct cred *) cred;
220: if (!cred)
221: return cred;
222: nonconst_cred->non_rcu = 0;
223: atomic_long_add(nr, &nonconst_cred->usage);
224: return cred;
225: }
226:
]
|
| 28/2 |
2026/06/25 00:12 |
llm |
expert |
0m
Model:
Tokens:
input: 22224
output: 33
thoughts: 28
Thoughts: Okay, here's my interpretation of those thoughts, presented as a personal thought process, as if I were a seasoned expert in the field:
**Internal Audit of Variable Dependencies**
Alright, so the focus is clearly narrowed down. Good. It seems like the modifications are *only* touching `non_rcu` and `usage`. That's a crucial simplification. We're effectively saying we're *reasonably* certain these are the exclusive culprits in whatever problem we're tracking down. We need to be rigorously double-checking this assumption, because this can become a very costly misunderstanding if wrong. Now that we have that focus, let's drill down and confirm that our current understanding is accurate. Now, let's step through and analyze `prepare_creds`. This is where we're probably seeing the action.
|
| 29/2 |
2026/06/25 00:12 |
tool |
read-file |
0m
Args: map[File:kernel/cred.c FirstLine:185 LineCount:20]
Results: map[Contents: 185: new = kmem_cache_alloc(cred_jar, GFP_KERNEL);
186: if (!new)
187: return NULL;
188:
189: kdebug("prepare_creds() alloc %p", new);
190:
191: old = task->cred;
192: memcpy(new, old, sizeof(struct cred));
193:
194: new->non_rcu = 0;
195: atomic_long_set(&new->usage, 1);
196: get_group_info(new->group_info);
197: get_uid(new->user);
198: get_user_ns(new->user_ns);
199:
200: #ifdef CONFIG_KEYS
201: key_get(new->session_keyring);
202: key_get(new->process_keyring);
203: key_get(new->thread_keyring);
204: key_get(new->request_key_auth);
]
|
| 30/2 |
2026/06/25 00:12 |
llm |
expert |
0m
Model:
Tokens:
input: 22772
output: 33
thoughts: 1068
Thoughts: **Analyzing Potential Concurrency Issues with Credential Copying**
Okay, so I'm thinking about this `memcpy` in `prepare_creds` and how it interacts with concurrent access to the credential structure (`cred`). The code immediately sets `new->non_rcu = 0` and increments `new->usage` after the `memcpy`, so any values read by `memcpy` for these specific fields are essentially overwritten. That's good, but are there other locations where we have concurrent accesses that could cause issues?
The real question is, are there any places where `get_cred_many` or `get_cred` are called at the same time as `memcpy`? If so, could these concurrent writes create race conditions. A reader of a `cred` structure could race with `get_cred` writing to `non_rcu`. I remember `override_creds` uses `get_new_cred`, but it doesn't clear `non_rcu`. On the other hand, `get_cred` *does* clear `non_rcu`. If a thread accesses its own credentials (e.g., reading `cred->uid`), it's a normal read, but if another thread calls `get_cred` on that `cred`, it writes to `non_rcu`.
Since `non_rcu` is in a union with `rcu_head` and is at the end of the `struct cred` structure, and `uid` is earlier (offset 8), they are on different cache lines, so there is no false sharing that would cause a data race on `uid`. `non_rcu` is at offset 168, and `uid` is at offset 8. Definitely different cache lines.
However, a new issue occurs to me: what if `memcpy` reads in 8-byte chunks, and the write to `non_rcu` triggers a torn read of the adjacent fields? `group_info` is at offset 160 (8 bytes) and `non_rcu` is at offset 168 (4 bytes). If `memcpy` were to read 8 bytes at offset 168, it would read `non_rcu` and 4 bytes of padding. It doesn't overlap with `group_info`. Even if it did, a concurrent write only affects the bytes of `non_rcu`, so the integrity of `group_info` during the copy is maintained.
Now, could the concurrent modification of `non_rcu` cause some other problem? What if the `old` credential is a temporary cred created by `override_creds`? `override_creds` sets `non_rcu = 1`. If `prepare_creds` is called while `override_creds` is active, `old` is the temporary cred. `memcpy` copies `non_rcu = 1`, but `prepare_creds` overwrites it with 0. If another thread calls `get_cred` on the temporary cred, it sets `non_rcu = 0`. However, temporary credentials are thread-local and other threads shouldn't be accessing them. Or should they? `override_creds` only changes the task's `cred`, not `real_cred`. Other threads usually access `real_cred`, for example via `get_task_cred`.
Okay, `get_task_cred` uses RCU. Let's look at the implementation:
```c
const struct cred *get_task_cred(struct task_struct *task)
{
const struct cred *cred;
rcu_read_lock();
do {
cred = rcu_dereference(task->real_cred);
BUG_ON(!cred);
} while (!atomic_long_inc_not_zero(&((struct cred *)cred)->usage));
rcu_read_unlock();
return cred;
}
```
Wait, `get_task_cred` uses `atomic_long_inc_not_zero`, so it does NOT clear `non_rcu`. It just increments the usage count. `get_task_cred` does not call `get_cred`. But what about `get_cred_rcu`?
```c
static inline const struct cred *get_cred_rcu(const struct cred *cred)
{
struct cred *nonconst_cred = (struct cred *) cred;
if (!cred)
return NULL;
if (!atomic_long_inc_not_zero(&nonconst_cred->usage))
return NULL;
validate_creds(cred);
nonconst_cred->non_rcu = 0;
return
|
| 31/2 |
2026/06/25 00:12 |
tool |
read-file |
0m
Args: map[File:kernel/cred.c FirstLine:380 LineCount:20]
Results: map[Contents: 380:
381: /* dumpability changes */
382: if (!uid_eq(old->euid, new->euid) ||
383: !gid_eq(old->egid, new->egid) ||
384: !uid_eq(old->fsuid, new->fsuid) ||
385: !gid_eq(old->fsgid, new->fsgid) ||
386: !cred_cap_issubset(old, new)) {
387: /* mm-less tasks share init_task's exec_state */
388: if (task->mm)
389: task_exec_state_set_dumpable(suid_dumpable);
390: task->pdeath_signal = 0;
391: /*
392: * If a task drops privileges and becomes nondumpable,
393: * the dumpability change must become visible before
394: * the credential change; otherwise, a __ptrace_may_access()
395: * racing with this change may be able to attach to a task it
396: * shouldn't be able to attach to (as if the task had dropped
397: * privileges without becoming nondumpable).
398: * Pairs with a read barrier in __ptrace_may_access().
399: */
]
|
| 32/2 |
2026/06/25 00:12 |
llm |
expert |
0m
Model:
Tokens:
input: 25283
output: 30
thoughts: 12
Thoughts: Here's my take on those thoughts:
**Analyzing System Calls**
Okay, so I'm seeing something here that makes me think we need to dive deeper. My gut says to start with `get_task_cred`. I'm going to run a `grep` on that function. That'll be the starting point to understand what's happening. I want to see how that function is being called and where it's used. This is a common tactic when you're working with system calls; you follow the breadcrumbs. Knowing what modules utilize `get_task_cred` and where that happens could illuminate the current situation and the surrounding code. Then I can figure out what is really going on.
|
| 33/2 |
2026/06/25 00:12 |
tool |
grepper |
0m
Args: map[Expression:get_task_cred PathPrefix:kernel/cred.c]
Results: map[Output:kernel/cred.c=90=void exit_creds(struct task_struct *tsk)
--
kernel/cred.c-116-/**
kernel/cred.c:117: * get_task_cred - Get another task's objective credentials
kernel/cred.c-118- * @task: The task to query
--
kernel/cred.c-125- */
kernel/cred.c:126:const struct cred *get_task_cred(struct task_struct *task)
kernel/cred.c-127-{
--
kernel/cred.c-139-}
kernel/cred.c:140:EXPORT_SYMBOL(get_task_cred);
kernel/cred.c-141-
--
kernel/cred.c=559=struct cred *prepare_kernel_cred(struct task_struct *daemon)
--
kernel/cred.c-572-
kernel/cred.c:573: old = get_task_cred(daemon);
kernel/cred.c-574-
]
|
| 34/2 |
2026/06/25 00:12 |
llm |
expert |
0m
Model:
Tokens:
input: 25741
output: 33
thoughts: 19
Thoughts: Here's my take on the thought process, as if I were the one thinking it:
**Diving into Credential Handling**
Alright, time to dig into `get_task_cred` in `kernel/cred.c`. I need to refresh my understanding of how the kernel retrieves and manages task credentials. I'm expecting to see the core logic for how a process's security context β UID, GID, capabilities, etc. β is established and accessed. I'm probably going to find how the cred structure is allocated, copied, and potentially shared between processes or threads. Understanding this is key to grasping security boundaries and how privilege escalation might be prevented, or potentially exploited. I'm looking for the nitty-gritty details; how the kernel handles changes to those credentials during things like `setuid`, `setgid`, or capability changes. Any caching mechanisms for performance will be of interest as well, and if there are any specific locking strategies employed to ensure the integrity of the credentials in a multi-threaded context. Let's see what gems are in there.
|
| 35/2 |
2026/06/25 00:12 |
tool |
read-file |
0m
Args: map[File:kernel/cred.c FirstLine:126 LineCount:15]
Results: map[Contents: 126: const struct cred *get_task_cred(struct task_struct *task)
127: {
128: const struct cred *cred;
129:
130: rcu_read_lock();
131:
132: do {
133: cred = __task_cred((task));
134: BUG_ON(!cred);
135: } while (!get_cred_rcu(cred));
136:
137: rcu_read_unlock();
138: return cred;
139: }
140: EXPORT_SYMBOL(get_task_cred);
]
|
| 36/2 |
2026/06/25 00:12 |
llm |
expert |
0m
Model:
Tokens:
input: 26217
output: 30
thoughts: 2550
Thoughts: **Benign Data Race in Credential Handling**
Okay, so I'm looking at this KCSAN report, and it looks like there's a data race involving `get_cred_many`, `prepare_creds`, and a `cred` structure. Specifically, it's flagging a potential issue with `non_rcu` and `usage` fields within the `cred` struct.
My first thought is, this seems like it could be a false positive. We're talking about `get_cred_rcu(cred)` or `get_cred_many` writing to `non_rcu = 0`, while `prepare_creds` is doing a `memcpy` from an old cred to a new one. `prepare_creds` immediately sets `new->non_rcu = 0` after the `memcpy`, so the value read by the `memcpy` is immediately overwritten. The compiler *shouldn't* optimize out that `new->non_rcu = 0`, because `old->non_rcu` is an external object whose value is not guaranteed to be zero at compile time.
Alright, the same applies to `usage` where `prepare_creds` writes an atomic long set immediately after reading the old value. That read value is ignored as well.
So I think the race on `non_rcu` is benign and has no effect on execution. The compiler can't eliminate the overwrite so this looks pretty safe.
Now, let's consider the integrity of the data. Could writes to `non_rcu` tear into other fields, leading to corruption? `non_rcu` is an integer, aligned to a 4-byte boundary, and stores are atomic, so it can't overwrite adjacent fields.
Could there be an interrupt? I see this code is preemptible. If preemption occurs *during* `prepare_creds`, `new` is a new cred object that's not accessible to other threads, so that shouldn't matter.
What about concurrent frees? `old` is `task->cred`. And from what I see the context here is `current->cred`. Can that be freed concurrently? No, `current` holds a reference to its own credentials. It's not freed until exit or `commit_creds`, and the `prepare_creds` call guarantees this.
Aha, okay, the KCSAN report is telling me that there are two tasks here. One that's calling `copy_creds` and `get_cred_many`, and the other calling `prepare_creds` and `memcpy`. Task 9782 is calling `clone3`, and Task 9783 is calling `capset`. So this means that they're both working on the same cred. Task 9782's `p->cred` is the same as Task 9783's `current->cred` if they're threads in the same process. With `CLONE_THREAD`, the same cred pointer is shared. So the race is on that shared cred object. Now, it's clear.
So the data race is real, but still benign! The bug is in `memcpy` reading the whole struct while another thread writes to `non_rcu`, but the read value is immediately discarded.
Let's check if there are other issues. What about `get_cred_many` being called on a `cred` that has `non_rcu = 1`? If that's the case, then this sets to 0 meaning it will be RCU freed instead of synchronously. That's safe, although it's slower. But this isn't an issue because shared creds aren't temporary subjective creds (only those have non_rcu = 1). If a thread calls `override_creds` and then `clone3` it sets `non_rcu = 0` on the temporary cred. And in that case, the commit is doing exactly what it's supposed to do. So the behavior here is correct.
So the data race is benign. KCSAN caught it, but it's only a problem because `memcpy` reads the entire structure, including `non_rcu`, and then it's overwritten.
Can we exploit this somehow? No, `new->non_rcu` is immediately overwritten with 0. Can the compiler optimize this out? No, KCSAN instrumented `memcpy` call so that issue doesn't matter. There's no exploitability.
Any other fields? `usage` is also written by `get_cred_many` via atomic add, and then also immediately overwritten in prepare_creds so it's fine.
Anything else? `get
|
| 37/2 |
2026/06/25 00:13 |
tool |
grepper |
0m
Args: map[Expression:memcpy.*sizeof.*cred PathPrefix:kernel/cred.c]
Results: map[Output:kernel/cred.c=179=struct cred *prepare_creds(void)
--
kernel/cred.c-191- old = task->cred;
kernel/cred.c:192: memcpy(new, old, sizeof(struct cred));
kernel/cred.c-193-
]
|
| 38/2 |
2026/06/25 00:13 |
llm |
expert |
0m
Model:
Tokens:
input: 29909
output: 33
thoughts: 21
Thoughts: Here's my summary, as if I were thinking these thoughts:
**Credential Preparation Conundrum**
Okay, I'm focusing on the credential preparation steps here. Specifically, I'm thinking about the `prepare_creds` function. That seems to be the crucial spot where the credentials are being constructed or modified. But... wait a minute, shouldn't I also be considering `prepare_kernel_cred`? Is there some interplay between those two that I'm overlooking? I need to make sure I understand the relationship between how user credentials and kernel credentials are handled during this process. They seem related and it would be foolish to ignore the second function. I need to figure out if my hypothesis requires me to inspect both to fully understand the credential preparation flow.
|
| 39/2 |
2026/06/25 00:13 |
tool |
read-file |
0m
Args: map[File:kernel/cred.c FirstLine:559 LineCount:20]
Results: map[Contents: 559: struct cred *prepare_kernel_cred(struct task_struct *daemon)
560: {
561: const struct cred *old;
562: struct cred *new;
563:
564: if (WARN_ON_ONCE(!daemon))
565: return NULL;
566:
567: new = kmem_cache_alloc(cred_jar, GFP_KERNEL);
568: if (!new)
569: return NULL;
570:
571: kdebug("prepare_kernel_cred() alloc %p", new);
572:
573: old = get_task_cred(daemon);
574:
575: *new = *old;
576: new->non_rcu = 0;
577: atomic_long_set(&new->usage, 1);
578: get_uid(new->user);
]
|
| 40/2 |
2026/06/25 00:13 |
llm |
expert |
0m
Model:
Tokens:
input: 30404
output: 66
thoughts: 1527
Thoughts: **Benign Data Race Analysis**
Okay, so I've been digging into this KCSAN report. It highlights a data race, a race condition between `get_cred_many` (via `copy_creds` in `clone3`) and `prepare_creds` (via `capset`). Both functions involve shared `cred` structures. `get_cred_many` is writing to `cred->non_rcu` and `cred->usage` while `prepare_creds` is doing a `memcpy` to copy the entire `cred` structure. It looks like `memcpy` is reading those very values, causing the race.
My immediate thought was, "Denial of Service?" But, hold on. This read value is *immediately* discarded. `prepare_creds` explicitly overwrites `non_rcu` with `0` and sets `usage` to 1 right after the `memcpy`. That's the key. So, whatever garbage was read during that race is irrelevant. It's overwritten. This also confirms a pattern observed in the code. I then looked to see if other fields were at risk. The other fields are immutable post-credential creation and are thus not a risk.
Could this be exploited? Nope. No memory corruption, no control flow hijack, no information leak, nothing. It's a benign artifact, a consequence of KCSAN's instrumentation, the use of `memcpy` and concurrent modification. The compiler won't reorder the assignment of `new->non_rcu = 0` before the `memcpy` because they both write to the same memory location, which forces the compiler to preserve the order of the writes.
"Accessible From Unprivileged Processes?" Yep, the trigger is indeed accessible to unprivileged users, specifically via `clone3` and `capset`, and user namespaces. However, the data race has no *functional* impact. Therefore, since it can be triggered, I'll set the appropriate parameters for that.
Let me double-check. There's no possible branch or other weirdness in the code flow. `memcpy` happens, then the overwrite *always* happens, in both `prepare_creds` and `prepare_kernel_cred`. The compiler will not reorder it.
Could this be some word-tearing issue where the concurrent write corrupts an adjacent field? Nah. I checked the offsets and data types. `non_rcu` is an `int`, and the concurrent write only modifies those four bytes. The adjacent `group_info` pointer is not touched, whether on a 32-bit or 64-bit architecture.
What about that `union` with `rcu_head`? That only comes into play when freeing the credentials. It's not used when they are shared and alive, so no problem there either.
So, the verdict is in. Completely benign data race. The KCSAN report is accurate in identifying the race, but the race itself is harmless.
|
| 41/2 |
2026/06/25 00:13 |
tool |
set-results |
0m
Args: map[DenialOfService:false Exploitable:false FilesystemTrigger:false NetworkTrigger:false PeripheralTrigger:false RemoteTrigger:false Unprivileged:true UserNamespace:true VMGuestTrigger:false VMHostTrigger:false]
Results: map[DenialOfService:false Exploitable:false FilesystemTrigger:false NetworkTrigger:false PeripheralTrigger:false RemoteTrigger:false Unprivileged:true UserNamespace:true VMGuestTrigger:false VMHostTrigger:false]
|
| 42/2 |
2026/06/25 00:13 |
llm |
expert |
0m
Model:
Tokens:
input: 32695
output: 575
thoughts: 307
Thoughts: **My Assessment of the KCSAN Data Race Report**
Okay, the results are in. Now it's time to craft the final response. My analysis of this KCSAN data race report is as follows: it hinges on a concurrency issue between the `get_cred_many()` and `prepare_creds()` functions. Specifically, `get_cred_many()` is writing to the `cred->non_rcu` and `cred->usage` fields of a credential structure, and `prepare_creds()` is reading the *entire* `cred` struct using a `memcpy`.
This race materializes when two threads within the same process, sharing the identical credential object, simultaneously trigger system calls that call these two routines, such as `clone3` with the `CLONE_THREAD` flag, or `capset`.
Here's the critical point: the data race, while present, is completely benign. The reason? Right after the potentially problematic `memcpy()`, `prepare_creds()` explicitly overwrites the potentially stale values it just read! Both `new->non_rcu` and `new->usage` are immediately reset to safe values: `new->non_rcu = 0;` and `atomic_long_set(&new->usage, 1);`. Thus, any values read during the `memcpy()` for these specific fields are essentially discarded and have zero impact on program execution.
Since all other fields in the `cred` struct are immutable after being committed, no concurrency concerns arise. Therefore, there's no way to influence any other data by means of this race. I would add that, the bug is accessible by unprivileged users, but, despite this, it has no security ramifications. It's neither exploitable nor does it introduce a denial-of-service vulnerability.
|
| 43/1 |
2026/06/25 00:13 |
action |
format-explanation |
0m
Results: map[Explanation:Based on the analysis of the provided kernel bug report and the source code,
here is the assessment of the bug:
### Root Cause Analysis
The report indicates a KCSAN-detected data race between `get_cred_many()` and
`prepare_creds()`.
1. **The Write:** Task 9782 is executing `clone3()` with `CLONE_THREAD`, which
calls `copy_creds()`. This function invokes `get_cred_many(p->cred, 2)` on the
shared credentials of the thread group. `get_cred_many()` modifies the
credential object by writing to `cred->non_rcu = 0` and incrementing
`cred->usage`.
2. **The Read:** Concurrently, Task 9783 (another thread in the same thread
group sharing the same `cred` object) is executing `capset()`, which calls
`prepare_creds()`. This function allocates a new credential object and copies
the old shared credentials into it using `memcpy(new, old, sizeof(struct
cred))`.
The data race occurs because `memcpy()` reads the entire `struct cred` (184
bytes), including the `non_rcu` and `usage` fields, at the exact same time
`get_cred_many()` is modifying them.
### Security Impact
This data race is **completely benign** and **not exploitable**.
If we look at the implementation of `prepare_creds()` in `kernel/cred.c`,
immediately after the `memcpy()` operation, the code explicitly overwrites the
exact fields that were involved in the race:
```c
old = task->cred;
memcpy(new, old, sizeof(struct cred));
new->non_rcu = 0;
atomic_long_set(&new->usage, 1);
```
Because `new->non_rcu` and `new->usage` are unconditionally re-initialized right
after the copy, whatever torn or intermediate values `memcpy()` might have read
from the concurrently modified `old` credential object are completely discarded.
They have absolutely no effect on the program's execution.
Furthermore, all other fields in the `struct cred` (like `uid`, `gid`,
`cap_permitted`, etc.) are considered immutable once a credential is committed.
They are never modified concurrently, meaning `memcpy()` safely copies them
without any risk of corruption.
### Conclusion
While the code paths to trigger this race are easily accessible to unprivileged
users (via standard system calls like `clone3` and `capset`), the bug has no
security implications. It cannot lead to memory corruption, privilege
escalation, information leaks, or denial of service.]
|