| 0/0 |
2026/06/23 14:39 |
flow |
assessment-kcsan |
25m
Results: map[Benign:true Explanation:The KCSAN report identifies a data race between `copy_creds()` and
`prepare_creds()` on a `struct cred` object.
### Race Summary
The race occurs when one task (Task 9782) is creating a new task (e.g., via
`clone()`) and sharing the current credentials, while another task (Task 9783,
likely a sibling thread) is preparing new credentials (e.g., via `capset()`).
Both tasks access the same shared `struct cred` object.
* **Task 9782 (Write):** Calls `copy_creds()`, which calls `get_cred_many()`.
This function increments the reference count (`usage`) and sets `non_rcu = 0` on
the shared credential structure to ensure it is freed via RCU now that it has
multiple owners.
* **Task 9783 (Read):** Calls `prepare_creds()`, which performs a `memcpy()`
of the entire `current->cred` structure into a newly allocated `struct cred`.
### Synchronization Context
In a multi-threaded process, threads share the same `struct cred` object. While
`struct cred` is mostly immutable, the `usage` (refcount) and `non_rcu`
(RCU-freeing flag) fields are modified in-place when new references are taken
(e.g., during `fork()` or `get_current_cred()`). There is no global lock
protecting these fields across all threads; instead, they rely on atomic
operations for `usage` and the fact that `non_rcu` transitions are safe.
### Final Classification: **BENIGN**
### Reasoning
The race is structurally tolerant to stale or torn reads for the following
reasons:
1. **Immediate Overwrite:** In `prepare_creds()`, immediately after the
`memcpy(new, old, sizeof(struct cred))`, the code explicitly initializes the
racing fields in the new structure:
```c
new->non_rcu = 0;
atomic_long_set(&new->usage, 1);
```
Even if the `memcpy` reads a torn or stale value for `non_rcu` or `usage`
from the `old` structure, these values are never used; they are overwritten
before the `new` credential becomes visible or is used for any logic.
2. **Safe State Transition:** The `non_rcu` field only ever transitions from
`1` to `0` (never back to `1` while shared). This transition indicates that the
credential structure, which was previously used only synchronously, now has
multiple references and must be freed via RCU. A concurrent reader seeing either
`0` or `1` is safe because:
* If it sees `1` and copies it, it immediately corrects it to `0`.
* The source structure eventually settles at `0`, ensuring correct RCU
freeing behavior when the last reference is dropped.
3. **Immutability of Other Fields:** All other fields in `struct cred` (UIDs,
GIDs, capabilities, etc.) are immutable once the credential structure is
installed and shared. Therefore, the `memcpy` is guaranteed to see consistent
values for all logically significant fields.
### Recommended Annotations
To suppress the KCSAN warning, the `memcpy` in `prepare_creds()` could be
wrapped in a `data_race()` annotation, or the `non_rcu` assignment in
`get_cred_many()` could be marked. However, the current behavior is logically
sound.
```c
/* kernel/cred.c */
struct cred *prepare_creds(void)
{
...
old = task->cred;
data_race(memcpy(new, old, sizeof(struct cred))); // Mark as known race
new->non_rcu = 0;
atomic_long_set(&new->usage, 1);
...
}
```]
|
| 1/1 |
2026/06/23 14:39 |
action |
kernel-checkouter |
1m
Results: map[KernelSrc:/app/workdir/cache/src/a570badb39da92d4cefbc0ec66a35468d16367e9]
|
| 2/1 |
2026/06/23 14:40 |
action |
kernel-builder |
11m
Results: map[KernelObj:/app/workdir/cache/build/ab7d18b2b98ec32f72c77ac25bab09554f77836c]
|
| 3/1 |
2026/06/23 14:52 |
action |
codesearch-prepare |
9m
Results: map[Index:codesearch-index]
|
| 4/1 |
2026/06/23 15:01 |
agent |
expert |
3m
Model:
Results:
Instruction: # KCSAN Data Race Severity Analysis Protocol
You are an expert Linux kernel concurrency engineer analyzing a Kernel
Concurrency Sanitizer (KCSAN) data race report to determine if it is
**BENIGN** or **HARMFUL**.
## 1. CLASSIFICATIONS
### **BENIGN (Truly Benign)**
The logic is sound and structurally tolerant to compiler optimizations or
stale/torn reads.
- **Diagnostics/Stats:** Reads used only for `/proc`, `/sys`, counters, or
`pr_info`.
- **Heuristic Hints:** A "hint" flag where an old value only causes a
slightly delayed update or a sub-optimal but safe fast-path.
- **Single-Writer Flag Updates:** A single writer updating flags where the
concurrent read is a simple bitwise check (e.g., `flags & MASK`). These are
historically tolerated, assuming neither "Fused Accesses" nor "Ordering
Violations" are relevant in this context.
- **Marked Reloads:** A load feeding into a `cmpxchg()` loop or checked
against a later `READ_ONCE()` reload.
- **Safe Overwrites:** Writing the same value already present.
### **HARMFUL (Logic Bug or Marking Required)**
The race causes incorrect behavior due to a synchronization failure or
because missing annotations allow the compiler to break the algorithm.
**Marking Required for Correctness:**
The algorithm is logically sound but requires annotations (`READ_ONCE()`,
`WRITE_ONCE()`, `smp_load_acquire()`, `smp_store_release()`, etc.) to be safe.
- **Fused Accesses:** The compiler might merge accesses or hoist a load out
of a loop, breaking polling/wait loops (livelocks).
- **Torn Accesses:** A large access (e.g., 64-bit on 32-bit arch) might be
split into multiple non-atomic accesses. Note that `READ_ONCE()` does **not**
guarantee atomicity for 64-bit variables on 32-bit architectures.
- **Ordering Violations:** The race breaks a "happens-before" relationship
(requires primitives with implied or explicit memory barriers).
**Logic Bugs:**
A fundamental synchronization failure. Marking accesses will **not** fix it;
the logic itself must change.
- **Pointers/Lifecycle:** The racing variable is a pointer being dereferenced
or a refcount governing object lifecycle (Use-After-Free risk).
- **Control Flow:** The variable guards a critical section, memory allocation,
or hardware command.
- **Bitfields:** Concurrent writes to different bits in the same word.
Compilers often use non-atomic read-modify-write sequences, meaning a
write to `bit_A` can "clobber" a concurrent write to `bit_B`. However,
do not blindly assume all bitfield accesses are harmful; you must prove
that a concurrent write actually clobbers another in a way that breaks
logic.
- **Complex Structures:** Races on shared lists, trees, or hashmaps.
- **Lossy Updates:** Concurrent plain RMW operations (e.g., `var++`) on
non-diagnostic variables where every increment must be preserved.
- **State Machines:** Races allowing a state machine to bypass transitions
or enter an invalid state.
- **Adjacent Unsynchronized Operations:** Consider races happening at the
same time. For example, if both threads execute `struct->has_elements = true;
list_add(node, &struct->list);`, the race on `has_elements` implies an
adjacent race on `list_head`, which is HARMFUL.
## 2. RESEARCH & ANALYSIS WORKFLOW
1. **Locate the Race:** Find the exact variables and functions in the stack
traces. Do not speculate about hypothetical compiler
behaviors or theoretical dangers (e.g., dismissing something as
"fundamentally unsafe") without tracing the actual data flow to a crash.
2. **Contextualize:** Identify held locks, RCU sections, or interrupt
contexts. Explain why the race is possible (e.g., "Thread A holds `lock`,
but Thread B is a lockless reader").
3. **Data Flow:** Follow the racing variable into its subsequent uses. If
the reader sees a stale or torn value, what branch is taken? Does it lead
to a pointer dereference or `BUG_ON()`? **You must prove harm via this
data flow.**
4. **Failure Sequencing (if HARMFUL):** Construct a two-column execution trace
demonstrating the failure.
*Example format:*
```text
CPU0 CPU1
function_a()
// lockless read
ptr = global_ptr
<PREEMPT>
function_b()
lock(&my_lock)
global_ptr = NULL
unlock(&my_lock)
kfree(ptr)
if (ptr)
*ptr = 1; // -> Use-After-Free!
```
## 3. OUTPUT FORMAT
- **Race Summary:** `[Function A]` vs `[Function B]` on `[Variable/Field]`.
- **Synchronization context:** List held locks and explain the lack of mutual
exclusion.
- **Final Classification:** **BENIGN** or **HARMFUL**.
**If BENIGN:**
- **Reasoning:** Briefly explain why the race is structurally tolerant to stale
or torn reads.
- **Recommended Annotations:** Suggest appropriate annotations (e.g.,
`READ_ONCE()`, `data_race()`).
**If HARMFUL:**
- **Failure Reasoning:** Explain and provide a two-column interleaving showing
exactly how the race leads to a failure such as corruption or kernel crash.
- **Recommended Fix (ONLY if fix is trivial):** Suggest a structural fix (e.g.,
"Hold `mapping->i_pages` lock", "Convert to `atomic_t`") or required memory
ordering annotations (e.g., "Wrap in `READ_ONCE()`", "Use
`smp_load_acquire()`").
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 data race 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: The KCSAN report identifies a data race between `copy_creds()` and `prepare_creds()` on a `struct cred` object.
### Race Summary
The race occurs when one task (Task 9782) is creating a new task (e.g., via `clone()`) and sharing the current credentials, while another task (Task 9783, likely a sibling thread) is preparing new credentials (e.g., via `capset()`). Both tasks access the same shared `struct cred` object.
* **Task 9782 (Write):** Calls `copy_creds()`, which calls `get_cred_many()`. This function increments the reference count (`usage`) and sets `non_rcu = 0` on the shared credential structure to ensure it is freed via RCU now that it has multiple owners.
* **Task 9783 (Read):** Calls `prepare_creds()`, which performs a `memcpy()` of the entire `current->cred` structure into a newly allocated `struct cred`.
### Synchronization Context
In a multi-threaded process, threads share the same `struct cred` object. While `struct cred` is mostly immutable, the `usage` (refcount) and `non_rcu` (RCU-freeing flag) fields are modified in-place when new references are taken (e.g., during `fork()` or `get_current_cred()`). There is no global lock protecting these fields across all threads; instead, they rely on atomic operations for `usage` and the fact that `non_rcu` transitions are safe.
### Final Classification: **BENIGN**
### Reasoning
The race is structurally tolerant to stale or torn reads for the following reasons:
1. **Immediate Overwrite:** In `prepare_creds()`, immediately after the `memcpy(new, old, sizeof(struct cred))`, the code explicitly initializes the racing fields in the new structure:
```c
new->non_rcu = 0;
atomic_long_set(&new->usage, 1);
```
Even if the `memcpy` reads a torn or stale value for `non_rcu` or `usage` from the `old` structure, these values are never used; they are overwritten before the `new` credential becomes visible or is used for any logic.
2. **Safe State Transition:** The `non_rcu` field only ever transitions from `1` to `0` (never back to `1` while shared). This transition indicates that the credential structure, which was previously used only synchronously, now has multiple references and must be freed via RCU. A concurrent reader seeing either `0` or `1` is safe because:
* If it sees `1` and copies it, it immediately corrects it to `0`.
* The source structure eventually settles at `0`, ensuring correct RCU freeing behavior when the last reference is dropped.
3. **Immutability of Other Fields:** All other fields in `struct cred` (UIDs, GIDs, capabilities, etc.) are immutable once the credential structure is installed and shared. Therefore, the `memcpy` is guaranteed to see consistent values for all logically significant fields.
### Recommended Annotations
To suppress the KCSAN warning, the `memcpy` in `prepare_creds()` could be wrapped in a `data_race()` annotation, or the `non_rcu` assignment in `get_cred_many()` could be marked. However, the current behavior is logically sound.
```c
/* kernel/cred.c */
struct cred *prepare_creds(void)
{
...
old = task->cred;
data_race(memcpy(new, old, sizeof(struct cred))); // Mark as known race
new->non_rcu = 0;
atomic_long_set(&new->usage, 1);
...
}
```
|
| 5/2 |
2026/06/23 15:01 |
llm |
expert |
0m
Model:
Tokens:
input: 5811
output: 34
thoughts: 377
Thoughts: **Analyzing a Data Race in the Credential Handling Code**
Okay, here's what the KCSAN report is showing me. There's a data race, a classic concurrent access problem, between `copy_creds` (the write operation) and `prepare_creds` (the read operation). This requires some digging, but it's not unusual.
First, I need to understand the specifics. The write side is `copy_creds` at offset `0x78` in `kernel/cred.c`, specifically at line 278. It looks like `get_cred_many` is involved, probably inlining. The write targets an address, `0xffff888103fc6b28`, and writes 4 bytes.
The read side is `prepare_creds`, also in `kernel/cred.c`, at line 192, with an offset of `0x71`. The read accesses `0xffff888103fc6a80` and reads 184 bytes.
Wait, I need to check something... are these addresses actually overlapping? Let's subtract them: `0xffff888103fc6b28 - 0xffff888103fc6a80 = 0xA8` in hex, which is 168 in decimal. Okay, that means the write address is *within* the range of the read, since `168 < 184`. That's important. It means the write could be clobbering data the read is accessing.
Okay, so I have the key locations: `kernel/cred.c:278` for `copy_creds` and `kernel/cred.c:192` for `prepare_creds`. Time to dig into the code itself. I'll start there.
|
| 6/2 |
2026/06/23 15:01 |
tool |
read-file |
0m
Args: map[File:kernel/cred.c FirstLine:180 LineCount:120]
Results: map[Contents: 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
229: */
230: struct cred *prepare_exec_creds(void)
231: {
232: struct cred *new;
233:
234: new = prepare_creds();
235: if (!new)
236: return new;
237:
238: #ifdef CONFIG_KEYS
239: /* newly exec'd tasks don't get a thread keyring */
240: key_put(new->thread_keyring);
241: new->thread_keyring = NULL;
242:
243: /* inherit the session keyring; new process keyring */
244: key_put(new->process_keyring);
245: new->process_keyring = NULL;
246: #endif
247:
248: new->suid = new->fsuid = new->euid;
249: new->sgid = new->fsgid = new->egid;
250:
251: return new;
252: }
253:
254: /*
255: * Copy credentials for the new process created by fork()
256: *
257: * We share if we can, but under some circumstances we have to generate a new
258: * set.
259: *
260: * The new process gets the current process's subjective credentials as its
261: * objective and subjective credentials
262: */
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})",
]
|
| 7/2 |
2026/06/23 15:01 |
llm |
expert |
0m
Model:
Tokens:
input: 7948
output: 35
thoughts: 148
Thoughts: **Analyzing Credential Copying in the Kernel**
Okay, so I'm looking at how credentials get copied around in the kernel, specifically focusing on some `struct cred` manipulation. My initial interest is in understanding the read and write operations that are taking place and to confirm whether they are operating as I'd expect.
First, I see in `prepare_creds`, line 192, a `memcpy`. This is a straightforward 184-byte read of the `struct cred` data from the `task->cred` structure. Nothing particularly complex here. This is the source for the copy.
Then, moving on to `copy_creds`, line 278 has the write. It's a write to `p->real_cred`. The call to `get_cred_many` does the write. This is the target for the copy. I'll need to confirm if `get_cred_many` is what I think it is, or if there is something else at play. I know that `get_cred_many` is inlined, so it's probably doing something fairly fundamental. I'm going to quickly check the definition in `include/linux/cred.h` to make sure.
I'll check the definition of `get_cred_many` to confirm my understanding of the operation.
|
| 8/2 |
2026/06/23 15:01 |
tool |
read-file |
0m
Args: map[File:include/linux/cred.h FirstLine:215 LineCount:15]
Results: map[Contents: 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
]
|
| 9/2 |
2026/06/23 15:01 |
llm |
expert |
0m
Model:
Tokens:
input: 8647
output: 19
thoughts: 1054
Thoughts: **Analyzing the Credential Concurrency Issue**
Okay, let's break this down. KCSAN reported a write-read race condition involving `cred` structures. The write happens in `get_cred_many` at line 222, specifically to `nonconst_cred->non_rcu = 0;`. The read is occurring in `prepare_creds` at line 192, where it does a `memcpy` from the `task->cred` structure.
The core of the problem seems to stem from how credentials are handled during process creation, particularly with thread creation using `CLONE_THREAD`. `get_cred_many` is being called to update the credentials of a *new* task (`p`) in `copy_creds` which is called from `copy_process`. The new task `p` inherits the current task's (`current`) credentials through `dup_task_struct`. This means initially, `p->cred` is the *same* as `current->cred`.
Inside `copy_creds`, if the `CLONE_THREAD` flag is set (which is used in thread creation), it calls `get_cred_many`. This function then increments the usage count of the shared credentials and crucially sets `non_rcu` to zero.
The other thread calling `prepare_creds` (task 9783 in the report) is likely in a sibling task or in the same process, and is calling `memcpy` to copy `current->cred`. Since `get_cred_many` and `prepare_creds` can occur concurrently, it causes a race condition: `prepare_creds` attempts to read `current->cred` while `get_cred_many` is modifying `non_rcu`.
So, the scenario is as follows: a new thread is being created, using the existing task's credentials via `CLONE_THREAD`. The new thread's `p->cred` initially points to the same `cred` structure as the parent task (`current->cred`). Then `get_cred_many` is called on the shared credential, modifying `non_rcu`. Meanwhile, the parent (or another sibling) thread, in the process of a `capset` operation, potentially calls `prepare_creds` which performs the `memcpy`.
The key question now is whether setting `non_rcu` to 0 is a genuine issue. The `non_rcu` field, as I understand it, signals whether a `cred` structure can be freed safely through RCU mechanisms. If `non_rcu` is 0, it indicates that RCU-safe freeing *should* be used.
I need to confirm if concurrent access to and modifications of `cred` structures, and especially of `non_rcu`, cause problems. Specifically, `memcpy` reads need to be synchronized in some way with the modifications done by `get_cred_many`.
|
| 10/2 |
2026/06/23 15:02 |
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/23 15:02 |
llm |
expert |
0m
Model:
Tokens:
input: 10903
output: 35
thoughts: 67
Thoughts: **Investigating a Missing Field**
Okay, so I'm looking at this `codesearch-struct-layout` output, and I'm not seeing the `non_rcu` field. That's strange, I thought it should be there. Hmm, could it be part of a union that I overlooked, or did I simply miss it in the initial scan? Hold on... I think I've spotted `jit_keyring` at offset 704. Time to double-check the relevant header file and revisit the struct's definition. Let's see what the source says about the presence of `non_rcu`. This feels like one of those situations where the layout output and the declaration don't immediately align, and a closer look at the actual code is required to understand the discrepancy.
|
| 12/2 |
2026/06/23 15:02 |
tool |
read-file |
0m
Args: map[File:include/linux/cred.h FirstLine:100 LineCount:60]
Results: map[Contents: 100: * (2) The subjective context. These details are used when the task is acting
101: * upon another object, be that a file, a task, a key or whatever.
102: *
103: * Note that some members of this structure belong to both categories - the
104: * LSM security pointer for instance.
105: *
106: * A task has two security pointers. task->real_cred points to the objective
107: * context that defines that task's actual details. The objective part of this
108: * context is used whenever that task is acted upon.
109: *
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);
]
|
| 13/2 |
2026/06/23 15:02 |
llm |
expert |
0m
Model:
Tokens:
input: 12327
output: 21
thoughts: 272
Thoughts: **Analyzing the Credential Structure and Potential Race Condition**
Alright, let's break this down. I'm looking at this `cred` structure, specifically how `non_rcu` is intertwined with `rcu_head` via a union. Hmm, a union. That's immediately interesting because it means they share memory. Line 148 has a `non_rcu` integer, and on the next line `rcu` is an `rcu_head`. It's a choice between the two at any given moment.
Okay, let's see how it's used. In `get_cred_many`, on line 222, I see a write to `non_rcu`: `nonconst_cred->non_rcu = 0;`. Clear enough, we're setting it to zero there.
Then, I see a read, a `memcpy` from `old` to `new` within `prepare_creds` (line 192). This `memcpy` copies the entire `struct cred`, which, crucially, includes `non_rcu`.
So, the race condition I see is between `copy_creds`, which sets `non_rcu` to zero, and `prepare_creds`, which is reading it. `prepare_creds` is doing a complete copy so it will read whatever the current value is. I can see a potential race here.
But wait a minute... `non_rcu` is *usually* zero, right? I'd imagine that `copy_creds` is the *default* and that this is the common scenario, so in most cases, it would read zero. So, when is it *not* zero? I need to find out where `non_rcu` is actually set to *1*. That's the real question. I need to track down where this value is modified to truly understand the race and its impact. I'll need to search the code base for where `non_rcu` is actually set to 1.
|
| 14/2 |
2026/06/23 15:02 |
tool |
grepper |
0m
Args: map[Expression:non_rcu = 1]
Results: map[Output:fs/open.c=414=static const struct cred *access_override_creds(void)
--
fs/open.c-458- */
fs/open.c:459: override_cred->non_rcu = 1;
fs/open.c-460- return override_creds(override_cred);
]
|
| 15/2 |
2026/06/23 15:02 |
llm |
expert |
0m
Model:
Tokens:
input: 13157
output: 33
thoughts: 11
Thoughts: Okay, here's my take on those thoughts, tailored for an expert audience, presented in the first person:
**Diving into `fs/open.c`**
Alright, time to get to the core of this. Let's start with `fs/open.c`. Given the likely issue I'm investigating, it makes absolute sense to begin here. `open()` is such a fundamental entry point for file interactions, and the implementation details are often where subtle bugs, performance bottlenecks, or security vulnerabilities reside. I'm expecting to find the standard stuff - path resolution, permission checks, inode allocation/retrieval, file descriptor management, perhaps some interaction with the VFS layer. But I need to be sharp and look for any interesting optimizations, potential race conditions related to locking, and any non-obvious interactions with specific filesystems. I'll be particularly interested in how it handles flags, error conditions, and the interaction with things like the process's file descriptor table. The devil is always in the details, so I need to pay close attention to the flow and logic that handles various edge cases, such as symlinks, character devices, and shared memory segments.
|
| 16/2 |
2026/06/23 15:02 |
tool |
read-file |
0m
Args: map[File:fs/open.c FirstLine:440 LineCount:30]
Results: map[Contents: 440:
441: /*
442: * The new set of credentials can *only* be used in
443: * task-synchronous circumstances, and does not need
444: * RCU freeing, unless somebody then takes a separate
445: * reference to it.
446: *
447: * NOTE! This is _only_ true because this credential
448: * is used purely for override_creds() that installs
449: * it as the subjective cred. Other threads will be
450: * accessing ->real_cred, not the subjective cred.
451: *
452: * If somebody _does_ make a copy of this (using the
453: * 'get_current_cred()' function), that will clear the
454: * non_rcu field, because now that other user may be
455: * expecting RCU freeing. But normal thread-synchronous
456: * cred accesses will keep things non-racy to avoid RCU
457: * freeing.
458: */
459: override_cred->non_rcu = 1;
460: return override_creds(override_cred);
461: }
462:
463: static int do_faccessat(int dfd, const char __user *filename, int mode, int flags)
464: {
465: struct path path;
466: struct inode *inode;
467: int res;
468: unsigned int lookup_flags = LOOKUP_FOLLOW;
469: const struct cred *old_cred = NULL;
]
|
| 17/2 |
2026/06/23 15:02 |
llm |
expert |
0m
Model:
Tokens:
input: 13913
output: 33
thoughts: 2590
Thoughts: **Analyzing Potential Data Races in Credential Management**
Okay, so I'm diving into this `non_rcu` optimization in the credential structure (`struct cred`) and how it interacts with RCU-based freeing. The basic idea is that if a credential isn't being shared (used synchronously only), `non_rcu` is set to 1, and RCU isn't involved in its freeing. But, as soon as someone *does* take a reference (like a new thread in the same process), `non_rcu` must be zeroed to indicate sharing and the need for RCU protection.
I'm focusing on `get_cred_many` and `copy_creds` because those seem like the key players here. `get_cred_many` is straightforward: it zeros `non_rcu` and increments the usage count. `copy_creds` is called during process/thread creation, and here's where things get interesting. Specifically, the line `p->real_cred = get_cred_many(p->cred, 2)` when the thread uses the `CLONE_THREAD` flag. This essentially means the credentials will be shared among threads. So, `get_cred_many` will be modifying `non_rcu` and `usage` in a shared `struct cred`.
The race condition I see is between `get_cred_many` and `prepare_creds`. `prepare_creds` does a `memcpy` from an existing `struct cred` (potentially `current->cred` if cloning a thread) to a new one. If one thread is calling `prepare_creds` to prepare a new set of credentials, and another is calling `get_cred_many` to take a reference to a shared credential (e.g., a thread in the same process with `CLONE_THREAD`), there's a data race on `non_rcu`. The report from KCSAN also suggests this is a 4-byte write racing with a multi-byte read.
However, the analysis suggests this particular race is benign. The important observation is that `prepare_creds` immediately sets `new->non_rcu = 0` *after* the `memcpy`. So, even if `memcpy` reads a stale value of `non_rcu`, it gets overwritten. Same for `usage`: even if the copy reads a bad value, `atomic_long_set` takes care of it. The `memcpy` reads `old->usage`, and then `prepare_creds` overwrites `new->usage` to `1`.
Digging deeper, I need to consider all possible writers to `struct cred` fields. Other than `usage` and `non_rcu`, which are always modified in-place, the `struct cred` is generally immutable. In particular, `cap_permitted`, `cap_effective`, and other capability-related fields, are written when you're creating a new set of credentials via `prepare_creds`, modifying them, and then installing them with `commit_creds`. Critically, between `prepare_creds` and `commit_creds`, the new `struct cred` is private to the thread. The `memcpy` happens between the call to `prepare_creds`, and the `new` struct creds are modified locally. After `commit_creds` and using `rcu_assign_pointer` is called, the changes are visible.
The core race happens because one thread calls `copy_creds` to take a reference and modifies `non_rcu` and `usage` using `get_cred_many`, and another thread calls `prepare_creds` to prepare a new set of credentials based on the current one. Since `prepare_creds` always overwrites `new->non_rcu = 0;` and then `new->usage = 1`, and the fact that `current->cred` is always valid for the running task, and is only modified by the thread itself, that potential race on `non_rcu` is not actually harmful.
|
| 18/2 |
2026/06/23 15:02 |
tool |
read-file |
0m
Args: map[File:kernel/cred.c FirstLine:420 LineCount:60]
Results: map[Contents: 420: if (new->user_ns != old->user_ns)
421: switch_cred_namespaces(old, new);
422:
423: /* send notifications */
424: if (!uid_eq(new->uid, old->uid) ||
425: !uid_eq(new->euid, old->euid) ||
426: !uid_eq(new->suid, old->suid) ||
427: !uid_eq(new->fsuid, old->fsuid))
428: proc_id_connector(task, PROC_EVENT_UID);
429:
430: if (!gid_eq(new->gid, old->gid) ||
431: !gid_eq(new->egid, old->egid) ||
432: !gid_eq(new->sgid, old->sgid) ||
433: !gid_eq(new->fsgid, old->fsgid))
434: proc_id_connector(task, PROC_EVENT_GID);
435:
436: /* release the old obj and subj refs both */
437: put_cred_many(old, 2);
438: return 0;
439: }
440: EXPORT_SYMBOL(commit_creds);
441:
442: /**
443: * abort_creds - Discard a set of credentials and unlock the current task
444: * @new: The credentials that were going to be applied
445: *
446: * Discard a set of credentials that were under construction and unlock the
447: * current task.
448: */
449: void abort_creds(struct cred *new)
450: {
451: kdebug("abort_creds(%p{%ld})", new,
452: atomic_long_read(&new->usage));
453:
454: BUG_ON(atomic_long_read(&new->usage) < 1);
455: put_cred(new);
456: }
457: EXPORT_SYMBOL(abort_creds);
458:
459: /**
460: * cred_fscmp - Compare two credentials with respect to filesystem access.
461: * @a: The first credential
462: * @b: The second credential
463: *
464: * cred_cmp() will return zero if both credentials have the same
465: * fsuid, fsgid, and supplementary groups. That is, if they will both
466: * provide the same access to files based on mode/uid/gid.
467: * If the credentials are different, then either -1 or 1 will
468: * be returned depending on whether @a comes before or after @b
469: * respectively in an arbitrary, but stable, ordering of credentials.
470: *
471: * Return: -1, 0, or 1 depending on comparison
472: */
473: int cred_fscmp(const struct cred *a, const struct cred *b)
474: {
475: struct group_info *ga, *gb;
476: int g;
477:
478: if (a == b)
479: return 0;
]
|
| 19/2 |
2026/06/23 15:02 |
llm |
expert |
0m
Model:
Tokens:
input: 18380
output: 33
thoughts: 13
Thoughts: Here's my attempt at summarizing the thought process, tailored for an expert audience:
**Tracing the Origins of `commit_creds`**
Okay, so I need to understand where `commit_creds` is initially called. I'm thinking, right off the bat, that this is going to be crucial for understanding how credentials are being established and propagated within the system. I have to grasp the very moment they're being cemented, to better understand their lifecycle. I'll need to pinpoint the entry point—where the trust is first established. This will probably provide insights into what contexts those credentials are created in, as well. Where's the start? I’ll need to understand the function's arguments too, of course - but first, the *where*.
|
| 20/2 |
2026/06/23 15:02 |
tool |
read-file |
0m
Args: map[File:kernel/cred.c FirstLine:380 LineCount:40]
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: */
400: smp_wmb();
401: }
402:
403: /* alter the thread keyring */
404: if (!uid_eq(new->fsuid, old->fsuid))
405: key_fsuid_changed(new);
406: if (!gid_eq(new->fsgid, old->fsgid))
407: key_fsgid_changed(new);
408:
409: /* do it
410: * RLIMIT_NPROC limits on user->processes have already been checked
411: * in set_user().
412: */
413: if (new->user != old->user || new->user_ns != old->user_ns)
414: inc_rlimit_ucounts(new->ucounts, UCOUNT_RLIMIT_NPROC, 1);
415:
416: rcu_assign_pointer(task->real_cred, new);
417: rcu_assign_pointer(task->cred, new);
418: if (new->user != old->user || new->user_ns != old->user_ns)
419: dec_rlimit_ucounts(old->ucounts, UCOUNT_RLIMIT_NPROC, 1);
]
|
| 21/2 |
2026/06/23 15:02 |
llm |
expert |
0m
Model:
Tokens:
input: 19333
output: 35
thoughts: 1312
Thoughts: **Analyzing the Credential Race Condition**
Okay, so I'm looking at this `commit_creds` function, and it seems like it *only* updates the credentials for the *current* task. That means other threads within the same process aren't immediately seeing the change. Wait, that doesn't sound right; if they're threads, they're supposed to share credentials, right? If one updates, they should all be updated.
Right, in Linux, threads, specifically those created with `CLONE_THREAD`, share the same `signal_struct`, but each has its own `task_struct`. The `task_struct` contains pointers, `cred` and `real_cred`. When a thread is cloned, `copy_creds` uses `get_cred_many` to take a reference to the parent's `cred` and assigns it to both the child's `real_cred` and `cred`, thus sharing the same `struct cred`.
Now, if a thread calls `capset`, it calls `prepare_creds` which *creates* a new `struct cred` object. Then, `commit_creds` updates *its own* `task->cred` and `task->real_cred`. This would mean threads could have different credentials, which is possible in Linux, though not typical. POSIX says they should share credentials, so glibc does signal all threads to call `setuid()` to address this.
So, let's get back to the race condition. We have Task 9782 being created (fork/clone) and Task 9783 being an existing thread in the same process. They're both accessing the same `struct cred` object.
Task 9782, during `copy_creds`, is using `get_cred_many(p->cred, 2)` which then executes `nonconst_cred->non_rcu = 0;`. Meanwhile, Task 9783, within `prepare_creds`, is doing a `memcpy(new, old, sizeof(struct cred))`. This is exactly what KCSAN flagged.
Is it harmful?
As I analyzed before:
1. `non_rcu` is overwritten in `new` by `prepare_creds` right after `memcpy`.
2. `usage` is overwritten in `new` by `prepare_creds` right after `memcpy`.
3. No other fields are modified in-place in `old`.
So, the new cred should be correctly initialized regardless of the race.
Wait, what about the old cred? `get_cred_many` is modifying `old->non_rcu`. If `old->non_rcu` was 1, it becomes 0. That's necessary because there are now more references. The write `nonconst_cred->non_rcu = 0` is atomic. The read in `memcpy` might see 0 or 1. If it sees 1, `new->non_rcu` becomes 1, but then it's immediately set to 0. If it sees 0, `new->non_rcu` becomes 0, and then it's set to 0 again. In both cases, `new->non_rcu` is 0.
What about other threads? Other threads pointing to `old` will eventually see `old->non_rcu == 0`. It's a transition from 1 to 0 which is safe.
Then there is that `access_override_creds` which sets `non_rcu = 1` on a cred that is about to be installed as subjective cred. `fs/open.c` calls `override_creds`, where `non_rcu = 1` is set on a cred that is only pointed to by `current->cred`, and not `current->real_cred`.
If another thread calls `get_current_cred()`, it will clear `non_rcu`. Now let's check `get_current_cred`.
|
| 22/2 |
2026/06/23 15:02 |
tool |
read-file |
0m
Args: map[File:include/linux/cred.h FirstLine:245 LineCount:15]
Results: map[Contents: 245: return NULL;
246: if (!atomic_long_inc_not_zero(&nonconst_cred->usage))
247: return NULL;
248: nonconst_cred->non_rcu = 0;
249: return cred;
250: }
251:
252: /**
253: * put_cred_many - Release a reference to a set of credentials
254: * @_cred: The credentials to release
255: * @nr: Number of references to release
256: *
257: * Release a reference to a set of credentials, deleting them when the last ref
258: * is released. If %NULL is passed, nothing is done.
259: *
]
|
| 23/2 |
2026/06/23 15:02 |
llm |
expert |
0m
Model:
Tokens:
input: 21778
output: 19
thoughts: 45
Thoughts: Okay, here's my interpretation of that thought process, from an expert's perspective:
**Missing Function Call Inspection**
Hmm, hold on a second. I was expecting to find the `get_current_cred` function call right here, but that's not what I see. It looks like we're using `get_cred_many`, plus something else… I'm going to have to do a quick search to figure out where the `get_current_cred` function is actually being invoked. It is probably important for context, so let me do a search now.
|
| 24/2 |
2026/06/23 15:02 |
tool |
grepper |
0m
Args: map[Expression:get_current_cred]
Results: map[Output:Documentation/security/credentials.rst=340=process's current set of credentials::
Documentation/security/credentials.rst-341-
Documentation/security/credentials.rst:342: const struct cred *get_current_cred(void);
Documentation/security/credentials.rst-343-
--
Documentation/translations/zh_CN/security/credentials.rst=254=const指针上操作,因此不需要进行类型转换,但需要临时放弃const限定,以便能够修改
--
Documentation/translations/zh_CN/security/credentials.rst-289-
Documentation/translations/zh_CN/security/credentials.rst:290: const struct cred *get_current_cred(void);
Documentation/translations/zh_CN/security/credentials.rst-291-
--
drivers/misc/vmw_vmci/vmci_host.c=296=static int vmci_host_do_init_context(struct vmci_host_dev *vmci_host_dev,
--
drivers/misc/vmw_vmci/vmci_host.c-322-
drivers/misc/vmw_vmci/vmci_host.c:323: cred = get_current_cred();
drivers/misc/vmw_vmci/vmci_host.c-324- vmci_host_dev->context = vmci_ctx_create(init_block.cid,
--
drivers/usb/core/devio.c=1035=static int usbdev_open(struct inode *inode, struct file *file)
--
drivers/usb/core/devio.c-1072- ps->disc_pid = get_pid(task_pid(current));
drivers/usb/core/devio.c:1073: ps->cred = get_current_cred();
drivers/usb/core/devio.c-1074- smp_wmb();
--
drivers/usb/core/devio.c=1619=static int proc_do_submiturb(struct usb_dev_state *ps, struct usbdevfs_urb *uurb,
--
drivers/usb/core/devio.c-1932- as->pid = get_pid(task_pid(current));
drivers/usb/core/devio.c:1933: as->cred = get_current_cred();
drivers/usb/core/devio.c-1934- snoop_urb(ps->dev, as->userurb, as->urb->pipe,
--
fs/ceph/mds_client.c=1209=static void __register_request(struct ceph_mds_client *mdsc,
--
fs/ceph/mds_client.c-1231-
fs/ceph/mds_client.c:1232: req->r_cred = get_current_cred();
fs/ceph/mds_client.c-1233- if (!req->r_mnt_idmap)
--
fs/ceph/mds_client.c=5852=int ceph_mds_check_access(struct ceph_mds_client *mdsc, char *tpath, int mask)
fs/ceph/mds_client.c-5853-{
fs/ceph/mds_client.c:5854: const struct cred *cred = get_current_cred();
fs/ceph/mds_client.c-5855- u32 caller_uid = from_kuid(&init_user_ns, cred->fsuid);
--
fs/fs_context.c=258=static struct fs_context *alloc_fs_context(struct file_system_type *fs_type,
--
fs/fs_context.c-274- fc->fs_type = get_filesystem(fs_type);
fs/fs_context.c:275: fc->cred = get_current_cred();
fs/fs_context.c-276- fc->net_ns = get_net(current->nsproxy->net_ns);
--
fs/fuse/backing.c=83=int fuse_backing_open(struct fuse_conn *fc, struct fuse_backing_map *map)
--
fs/fuse/backing.c-121- fb->file = file;
fs/fuse/backing.c:122: fb->cred = get_current_cred();
fs/fuse/backing.c-123- refcount_set(&fb->count, 1);
--
fs/nfs/inode.c=1215=struct nfs_open_context *alloc_nfs_open_context(struct dentry *dentry,
--
fs/nfs/inode.c-1228- else
fs/nfs/inode.c:1229: ctx->cred = get_current_cred();
fs/nfs/inode.c-1230- rcu_assign_pointer(ctx->ll_cred, NULL);
--
fs/nfs/unlink.c=172=nfs_async_unlink(struct dentry *dentry, const struct qstr *name)
--
fs/nfs/unlink.c-185-
fs/nfs/unlink.c:186: data->cred = get_current_cred();
fs/nfs/unlink.c-187- data->res.dir_attr = &data->dir_attr;
--
fs/nfs/unlink.c=339=nfs_async_rename(struct inode *old_dir, struct inode *new_dir,
--
fs/nfs/unlink.c-362-
fs/nfs/unlink.c:363: data->cred = get_current_cred();
fs/nfs/unlink.c-364-
--
fs/nfsd/filecache.c=211=nfsd_file_alloc(struct net *net, struct inode *inode, unsigned char need,
--
fs/nfsd/filecache.c-224- nf->nf_file = NULL;
fs/nfsd/filecache.c:225: nf->nf_cred = get_current_cred();
fs/nfsd/filecache.c-226- nf->nf_net = net;
--
fs/nfsd/filecache.c=1354=nfsd_file_acquire_local(struct net *net, struct svc_cred *cred,
--
fs/nfsd/filecache.c-1362- */
fs/nfsd/filecache.c:1363: const struct cred *save_cred = get_current_cred();
fs/nfsd/filecache.c-1364- __be32 beres;
--
fs/open.c=414=static const struct cred *access_override_creds(void)
--
fs/open.c-452- * If somebody _does_ make a copy of this (using the
fs/open.c:453: * 'get_current_cred()' function), that will clear the
fs/open.c-454- * non_rcu field, because now that other user may be
--
include/linux/cred.h=291=DEFINE_FREE(put_cred, struct cred *, if (!IS_ERR_OR_NULL(_T)) put_cred(_T))
--
include/linux/cred.h-324-/**
include/linux/cred.h:325: * get_current_cred - Get the current task's subjective credentials
include/linux/cred.h-326- *
--
include/linux/cred.h-330- */
include/linux/cred.h:331:#define get_current_cred() \
include/linux/cred.h-332- (get_cred(current_cred()))
--
io_uring/io_uring.c=359=static void io_prep_async_work(struct io_kiocb *req)
--
io_uring/io_uring.c-364- req->flags |= REQ_F_CREDS;
io_uring/io_uring.c:365: req->creds = get_current_cred();
io_uring/io_uring.c-366- }
--
io_uring/register.c=90=static int io_register_personality(struct io_ring_ctx *ctx)
--
io_uring/register.c-95-
io_uring/register.c:96: creds = get_current_cred();
io_uring/register.c-97-
--
io_uring/sqpoll.c=436=__cold int io_sq_offload_create(struct io_ring_ctx *ctx,
--
io_uring/sqpoll.c-465-
io_uring/sqpoll.c:466: ctx->sq_creds = get_current_cred();
io_uring/sqpoll.c-467- ctx->sq_data = sqd;
--
net/bluetooth/af_bluetooth.c=143=struct sock *bt_sock_alloc(struct net *net, struct socket *sock,
--
net/bluetooth/af_bluetooth.c-164- sk->sk_peer_pid = get_pid(task_tgid(current));
net/bluetooth/af_bluetooth.c:165: sk->sk_peer_cred = get_current_cred();
net/bluetooth/af_bluetooth.c-166- spin_unlock(&sk->sk_peer_lock);
--
net/unix/af_unix.c=744=static inline int prepare_peercred(struct unix_peercred *peercred)
--
net/unix/af_unix.c-752- peercred->peer_pid = get_pid(pid);
net/unix/af_unix.c:753: peercred->peer_cred = get_current_cred();
net/unix/af_unix.c-754- }
--
net/vmw_vsock/af_vsock.c=908=static struct sock *__vsock_create(struct net *net,
--
net/vmw_vsock/af_vsock.c-962- vsk->trusted = ns_capable_noaudit(&init_user_ns, CAP_NET_ADMIN);
net/vmw_vsock/af_vsock.c:963: vsk->owner = get_current_cred();
net/vmw_vsock/af_vsock.c-964- vsk->connect_timeout = VSOCK_DEFAULT_CONNECT_TIMEOUT;
--
security/apparmor/domain.c=1223=int aa_change_hat(const char *hats[], int count, u64 token, int flags)
--
security/apparmor/domain.c-1233- /* released below */
security/apparmor/domain.c:1234: subj_cred = get_current_cred();
security/apparmor/domain.c-1235- label = aa_get_newest_cred_label(subj_cred);
--
security/apparmor/domain.c=1390=int aa_change_profile(const char *fqname, int flags)
--
security/apparmor/domain.c-1398- struct aa_task_ctx *ctx = task_ctx(current);
security/apparmor/domain.c:1399: const struct cred *subj_cred = get_current_cred();
security/apparmor/domain.c-1400- int error = 0;
--
security/keys/process_keys.c=611=key_ref_t lookup_user_key(key_serial_t id, unsigned long lflags,
--
security/keys/process_keys.c-625-try_again:
security/keys/process_keys.c:626: ctx.cred = get_current_cred();
security/keys/process_keys.c-627- key_ref = ERR_PTR(-ENOKEY);
--
security/keys/request_key.c=118=static int call_sbin_request_key(struct key *authkey, void *aux)
--
security/keys/request_key.c-138-
security/keys/request_key.c:139: cred = get_current_cred();
security/keys/request_key.c-140- keyring = keyring_alloc(desc, cred->fsuid, cred->fsgid, cred,
]
|
| 25/2 |
2026/06/23 15:02 |
llm |
expert |
0m
Model:
Tokens:
input: 25070
output: 35
thoughts: 169
Thoughts: **Analyzing the Credential Retrieval Mechanism**
Okay, so `get_current_cred()` is essentially a macro, and it's defined pretty simply: `#define get_current_cred() (get_cred(current_cred()))`. Right, that makes sense. It's using `get_cred` on the currently running process's credentials, probably fetched via `current_cred()`. I remember seeing `get_cred` defined somewhere else, likely within `include/linux/cred.h`. Let me double-check that definition to confirm my mental model.
Ah, here it is: `static inline const struct cred *get_cred(const struct cred *cred)`. Looks like it accepts a constant pointer to a `cred` structure. The function itself converts it to a non-const pointer, which is standard practice in Linux to allow modifications if needed (though it returns the const version). The null check is there, which is good defensive programming, then it increments the usage count atomically. The use of `atomic_long_inc_not_zero` is a good indicator of memory safety, and ensuring multiple users of the credential don't affect access. Finally, it sets `non_rcu` to 0. It's returning the original `cred`, making the pointer const again for the calling function. So it looks like it's managing reference counting to prevent premature freeing of the credential structure. I'm satisfied; the design seems robust.
|
| 26/2 |
2026/06/23 15:03 |
tool |
read-file |
0m
Args: map[File:include/linux/cred.h FirstLine:230 LineCount:20]
Results: map[Contents: 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: }
240:
241: static inline const struct cred *get_cred_rcu(const struct cred *cred)
242: {
243: struct cred *nonconst_cred = (struct cred *) cred;
244: if (!cred)
245: return NULL;
246: if (!atomic_long_inc_not_zero(&nonconst_cred->usage))
247: return NULL;
248: nonconst_cred->non_rcu = 0;
249: return cred;
]
|
| 27/2 |
2026/06/23 15:03 |
llm |
expert |
0m
Model:
Tokens:
input: 25893
output: 33
thoughts: 877
Thoughts: **Analyzing Potential Race Conditions in `struct cred` Handling**
Okay, let's break this down systematically. I see that `get_cred` ultimately calls `get_cred_many(cred, 1)`. And within `get_cred_many`, the code explicitly sets `non_rcu = 0`. So, every time a reference is taken to a potentially shared `struct cred`, this flag is cleared.
The core of the issue boils down to a potential race between `get_cred_many` writing `non_rcu = 0` and the `memcpy` operation in `prepare_creds`, which reads the entire structure. The question becomes, could there be a "torn" read of the other fields? Thinking this through, `get_cred_many` only touches `non_rcu` and `usage`. `non_rcu` is an `int`, and `usage` is an `atomic_long_t`. On the architectures I'm familiar with, writes to both of these types are atomic. The KCSAN report confirms this with its observation of a 4-byte write to `non_rcu`.
Even if the `memcpy` in `prepare_creds` reads a partially updated `non_rcu` or `usage`, `prepare_creds` immediately overwrites both fields *after* the `memcpy` is completed. However, there's a crucial question about compiler reordering. Let's look at the relevant code snippet:
```c
192: memcpy(new, old, sizeof(struct cred));
193:
194: new->non_rcu = 0;
195: atomic_long_set(&new->usage, 1);
```
The worry is the compiler might reorder the `memcpy` and the assignments that follow. But, `new` is a local pointer to freshly allocated memory. So the compiler shouldn't be able to reorder these because all write to the same memory location (`new->...`). The `memcpy` fills the entire block, and then `new->non_rcu = 0` only modifies part of it. The compiler *must* guarantee that the assignment happens after `memcpy` has finished writing to that particular location. So, the value in `new` should be safe from partial updates.
Okay, let's expand the scope. Are there any other fields potentially racing? Are there any other writers to the `old` cred? In the `copy_creds` function, the `CLONE_THREAD` flag determines whether we share or create a new cred. If `CLONE_THREAD` *isn't* set, no sharing, no problem.
Consider the case where two threads are calling `get_cred` on the same `old` cred. Both threads will write `non_rcu = 0` and increment `usage`. This is safe because writing 0 to a variable already holding 0 or 1 is benign.
Now, what if one thread is calling `prepare_creds` while another calls `commit_creds`? `commit_creds` doesn't modify the *contents* of the old cred, it only decrements the refcount. `put_cred_many(old, 2)` might free the cred if the refcount drops to 0. But crucially, `prepare_creds` is called by a thread that *already* holds a reference to `old` (through `current->cred`). Therefore, `old` cannot be freed while `prepare_creds` is running.
However, what if another thread calls `commit_creds`? It decrements the refcount of the cred it *used* to have. If it was sharing the cred with the thread calling `prepare_creds`, the refcount must have been at least 2. In which case, `put_cred_many` won't free it.
So, the only concurrent writers to the *contents* of a shared `struct cred` are `get_cred_many` (and its variations). And they only modify `non_rcu` and `usage`. But, wait, I need to be thorough. I should perform a code search to identify any other in-place modifications to a `struct cred` that aren't within `prepare_creds` or `cred_alloc_blank`. Gotta check for every potential writer to ensure the analysis is complete.
|
| 28/2 |
2026/06/23 15:03 |
tool |
grepper |
0m
Args: map[Expression:cred->[a-zA-Z0-9_]+ = 0?1?;]
Results: map[Output:fs/open.c=414=static const struct cred *access_override_creds(void)
--
fs/open.c-458- */
fs/open.c:459: override_cred->non_rcu = 1;
fs/open.c-460- return override_creds(override_cred);
--
include/linux/cred.h=217=static inline const struct cred *get_cred_many(const struct cred *cred, int nr)
--
include/linux/cred.h-221- return cred;
include/linux/cred.h:222: nonconst_cred->non_rcu = 0;
include/linux/cred.h-223- atomic_long_add(nr, &nonconst_cred->usage);
--
include/linux/cred.h=241=static inline const struct cred *get_cred_rcu(const struct cred *cred)
--
include/linux/cred.h-247- return NULL;
include/linux/cred.h:248: nonconst_cred->non_rcu = 0;
include/linux/cred.h-249- return cred;
--
net/sunrpc/auth.c=592=rpcauth_init_cred(struct rpc_cred *cred, const struct auth_cred *acred,
--
net/sunrpc/auth.c-598- cred->cr_auth = auth;
net/sunrpc/auth.c:599: cred->cr_flags = 0;
net/sunrpc/auth.c-600- cred->cr_ops = ops;
]
|
| 29/2 |
2026/06/23 15:03 |
llm |
expert |
0m
Model:
Tokens:
input: 28177
output: 35
thoughts: 171
Thoughts: **Analyzing Credential Structure Modification**
Okay, so I'm digging into this `net/sunrpc/auth.c` code, and I need to be precise. The structure we're dealing with here is `struct rpc_cred`, not the generic `struct cred`. That's important to keep straight.
So, it looks like `non_rcu` and `usage` are the only fields being modified in place directly. That's a good starting point for narrowing down the analysis.
But hold on... `security_prepare_creds` just popped into my head. Let me see where it's called. Right! It's called in `prepare_creds` *after* the `memcpy` operation. And it *does* modify `new->security`. That's a key point.
Now, my experience tells me that LSM security blobs within a `struct cred` are usually considered immutable once they're set. I need to figure out if there is any other place where `old->security` might be altered after the initial setup.
I should check the `security_prepare_creds` function in detail now to get a better grasp on what's actually happening.
|
| 30/2 |
2026/06/23 15:03 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:kernel/cred.c Name:security_prepare_creds]
Results: map[SourceCode:2854: /**
2855: * security_prepare_creds() - Prepare a new set of credentials
2856: * @new: new credentials
2857: * @old: original credentials
2858: * @gfp: gfp flags
2859: *
2860: * Prepare a new set of credentials by copying the data from the old set.
2861: *
2862: * Return: Returns 0 on success, negative values on failure.
2863: */
2864: int security_prepare_creds(struct cred *new, const struct cred *old, gfp_t gfp)
2865: {
2866: int rc = lsm_cred_alloc(new, gfp);
2867:
2868: if (rc)
2869: return rc;
2870:
2871: rc = call_int_hook(cred_prepare, new, old, gfp);
2872: if (unlikely(rc))
2873: security_cred_free(new);
2874: return rc;
2875: }
SourceFile:security/security.c]
|
| 31/2 |
2026/06/23 15:03 |
llm |
expert |
0m
Model:
Tokens:
input: 28977
output: 24
thoughts: 723
Thoughts: **Analyzing a Credential Structure Race Condition**
Okay, so I'm looking at this KCSAN report. It flags a race condition, and my initial thought is, "let's break this down." The code sequence involves `lsm_cred_alloc`, then some `cred_prepare` hooks. The hooks typically copy data, safely, from the old credentials' `security` to the new ones, since the old security structure is immutable.
The core of the problem, according to the report, is the `non_rcu` field. It seems `copy_creds` (via `get_cred_many`) writes `non_rcu = 0`, and `prepare_creds` reads it using `memcpy`. My first reaction is, "Wait, that's almost certainly benign." `prepare_creds` immediately overwrites the copied value with 0 anyway. So even if there's a transient read, the end state is the same.
But I'm an expert, so I'm not just going to dismiss it that quickly. I have to think about potential gotchas. Is there any way `memcpy` itself could be problematic? What if the compiler reorders loads and stores within the `memcpy`? But still, the final assignment to 0 should overwrite any intermediate values. The value assigned to `non_rcu` is explicitly zero.
Then I start thinking, "What other fields could be affected by this `memcpy`?" The `memcpy` covers the entire `struct cred`, so I need to make sure nothing else is being unexpectedly modified. I need to make sure that the `get_cred_many` function only writes to `non_rcu` and `usage`. I think that is the case, but I have to verify it.
My next step is to examine the structure layout more closely. Let me check the sizes and offsets. I need to see if `non_rcu` and `usage` are adjacent in memory. The layout is crucial. Okay, `usage` is an `atomic_long_t` (8 bytes), and `non_rcu` is an `int` (4 bytes). Now, I need to see where they sit in the `struct cred`. Ah, `non_rcu` is in a union with `rcu_head`. They are not necessarily adjacent because of `__randomize_layout`, that complicates things. But even if they were next to each other, they are separate fields.
Let me analyze the KCSAN report details. The report says it is a `write to 0xffff888103fc6b28 of 4 bytes` and a `read to 0xffff888103fc6a80 of 184 bytes`. Okay, the write is to offset 168 (0xA8) and the struct size is 184. The usage is at offset 0. So, they are far apart in the struct. I can see the write and read are operating on different locations.
Now, I remember `non_rcu` is in a union. So I need to make sure `memcpy` can correctly read from the structure. If `memcpy` reads `rcu_head` while `non_rcu` is being written to, it's the same thing. Either way, it will correctly represent the intended operation.
Now, my mind shifts to other potential concurrent writers. I checked the code and I don't see any other in-place modifiers of `struct cred` in that area.
Wait, I should check `set_cred_ucounts`! And I'm going to scan the entire `kernel/cred.c` file again just to be sure. I'll search to ensure there isn't another function that I've missed.
|
| 32/2 |
2026/06/23 15:03 |
tool |
codesearch-file-index |
0m
Args: map[SourceFile:kernel/cred.c]
Results: map[Entities:[map[Kind:function Name:__put_cred] map[Kind:function Name:abort_creds] map[Kind:function Name:commit_creds] map[Kind:function Name:copy_creds] map[Kind:function Name:cred_alloc_blank] map[Kind:function Name:cred_cap_issubset] map[Kind:function Name:cred_fscmp] map[Kind:function Name:cred_init] map[Kind:function Name:exit_creds] map[Kind:function Name:get_task_cred] map[Kind:function Name:prepare_creds] map[Kind:function Name:prepare_exec_creds] map[Kind:function Name:prepare_kernel_cred] map[Kind:function Name:put_cred_rcu] map[Kind:function Name:set_create_files_as] map[Kind:function Name:set_cred_ucounts] map[Kind:function Name:set_security_override] map[Kind:global_variable Name:__UNIQUE_ID_addressable___put_cred_806] map[Kind:global_variable Name:__UNIQUE_ID_addressable_abort_creds_827] map[Kind:global_variable Name:__UNIQUE_ID_addressable_commit_creds_825] map[Kind:global_variable Name:__UNIQUE_ID_addressable_cred_fscmp_828] map[Kind:global_variable Name:__UNIQUE_ID_addressable_get_task_cred_810] map[Kind:global_variable Name:__UNIQUE_ID_addressable_prepare_creds_812] map[Kind:global_variable Name:__UNIQUE_ID_addressable_prepare_kernel_cred_831] map[Kind:global_variable Name:__UNIQUE_ID_addressable_set_create_files_as_833] map[Kind:global_variable Name:__UNIQUE_ID_addressable_set_security_override_832] map[Kind:global_variable Name:__gendwarfksyms_ptr___put_cred] map[Kind:global_variable Name:__gendwarfksyms_ptr_abort_creds] map[Kind:global_variable Name:__gendwarfksyms_ptr_commit_creds] map[Kind:global_variable Name:__gendwarfksyms_ptr_cred_fscmp] map[Kind:global_variable Name:__gendwarfksyms_ptr_get_task_cred] map[Kind:global_variable Name:__gendwarfksyms_ptr_prepare_creds] map[Kind:global_variable Name:__gendwarfksyms_ptr_prepare_kernel_cred] map[Kind:global_variable Name:__gendwarfksyms_ptr_set_create_files_as] map[Kind:global_variable Name:__gendwarfksyms_ptr_set_security_override]]]
|
| 33/2 |
2026/06/23 15:03 |
llm |
expert |
0m
Model:
Tokens:
input: 31330
output: 36
thoughts: 13
Thoughts: Okay, here's my attempt at summarizing those thoughts from an expert's perspective, using a first-person perspective:
**Diving into `set_cred_ucounts`**
Alright, time to dig into this `set_cred_ucounts` function. I need to understand its role. What exactly is it doing? I'm assuming it's related to setting user credentials and tracking associated resource limits or usage counts. I want to quickly refresh my memory on the critical areas: is it touching user ID mappings, permission sets, security attributes... or am I missing something key? I need to be sure before moving further. Any potential security implications need a hard look. Let's see if this function is behaving correctly.
|
| 34/2 |
2026/06/23 15:03 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:kernel/cred.c Name:set_cred_ucounts]
Results: map[SourceCode: 513: int set_cred_ucounts(struct cred *new)
514: {
515: struct ucounts *new_ucounts, *old_ucounts = new->ucounts;
516:
517: /*
518: * This optimization is needed because alloc_ucounts() uses locks
519: * for table lookups.
520: */
521: if (old_ucounts->ns == new->user_ns && uid_eq(old_ucounts->uid, new->uid))
522: return 0;
523:
524: if (!(new_ucounts = alloc_ucounts(new->user_ns, new->uid)))
525: return -EAGAIN;
526:
527: new->ucounts = new_ucounts;
528: put_ucounts(old_ucounts);
529:
530: return 0;
531: }
SourceFile:kernel/cred.c]
|
| 35/2 |
2026/06/23 15:03 |
llm |
expert |
0m
Model:
Tokens:
input: 31850
output: 36
thoughts: 42
Thoughts: Okay, here's my take on those thoughts, framed as my own:
**Analyzing Credential Creation**
Alright, I'm looking at this instantiation with `new`. That immediately tells me this is a *new* credential being set up, not a modification of an existing one. That's good; it simplifies things. No worries about accidentally corrupting a shared credential. Always a relief to avoid that complexity.
Now, I'm wondering, how does this interact with `set_create_files_as`? I need to consider that call as this is setting up a cred.
|
| 36/2 |
2026/06/23 15:03 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:kernel/cred.c Name:set_create_files_as]
Results: map[SourceCode: 624: /**
625: * set_create_files_as - Set the LSM file create context in a set of credentials
626: * @new: The credentials to alter
627: * @inode: The inode to take the context from
628: *
629: * Change the LSM file creation context in a set of credentials to be the same
630: * as the object context of the specified inode, so that the new inodes have
631: * the same MAC context as that inode.
632: */
633: int set_create_files_as(struct cred *new, struct inode *inode)
634: {
635: if (!uid_valid(inode->i_uid) || !gid_valid(inode->i_gid))
636: return -EINVAL;
637: new->fsuid = inode->i_uid;
638: new->fsgid = inode->i_gid;
639: return security_kernel_create_files_as(new, inode);
640: }
SourceFile:kernel/cred.c]
|
| 37/2 |
2026/06/23 15:03 |
llm |
expert |
0m
Model:
Tokens:
input: 32372
output: 18
thoughts: 3093
Thoughts: **Benign Data Race in Credential Copying**
Okay, so I've been digging into this kernel code, specifically around credential management, and I think I've got a handle on this race condition. It all boils down to how credentials are shared and copied during operations like `fork` and `capset`.
My initial hunch that only `non_rcu` and `usage` are modified in-place on shared credentials is, I believe, correct. The core of the issue is in how `copy_creds` (triggered during `fork` or clone) and `prepare_creds` (used in `capset`) interact when they're working with the same credential structure. Essentially, a child process is forking off a parent, but then a sibling thread uses `prepare_creds` on the same structure.
From what I can tell, `copy_creds` is called by the parent process after a task is duplicated via `dup_task_struct`. This is all pretty standard, but the key is that it *copies* the credentials using `memcpy`. Meanwhile, `prepare_creds`, likely in a separate thread within the same process, is concurrently reading and modifying the same credential structure. It's reading the "old" structure and then performing a `memcpy` to the "new" structure.
The stack traces confirm my suspicions: `copy_creds` modifies while `prepare_creds` reads.
Now, the "race" arises because `memcpy` copies the whole `struct cred`, which includes the `non_rcu` and `usage` fields. And here's where it gets interesting: the values being read by `memcpy` for these fields are *immediately* overwritten in the target struct.
Specifically: `get_cred_many` sets `non_rcu` to 0. `prepare_creds` copies over the struct, and *then* sets `non_rcu` to 0 and `usage` to 1. So, even if there's a race, the writes in `prepare_creds` dominate.
So, while the C standard would technically flag this as undefined behavior, the kernel allows it because it's a *benign* data race. The writes to the destination overwrite the potentially stale values.
I considered whether the `non_rcu` field being zeroed out has other implications. If it were one, it indicates that it's a "override" credential that must become zero, so it can be safely freed via RCU. This transition is expected. The race just means `prepare_creds` might see 1 or 0 during the `memcpy` but overwrites it to 0.
I scrutinized the `memcpy` implementation. If the compiler were to optimize the read, it might be an issue. But the value is immediately overwritten.
I examined `usage`, too. I see the same is true there. While `memcpy` might read a torn value, `prepare_creds` overwrites this value immediately after.
Then I considered other fields. But only `non_rcu` and `usage` are modified in-place, at least as far as I can see.
So, I am confident it's benign. The key is the immediate overwrite of these two fields after the copy.
I re-checked `put_cred_many`. No in-place modifications happen there except for the refcount, which is handled atomically.
Then I reviewed the implications of `non_rcu`. The code paths that use `non_rcu` all treat a value of zero (which is what we set) in the same way.
I looked at the potential impact of `__randomize_layout`. Even if fields are randomized, the code treats them as distinct struct fields.
I also considered other writers. `commit_creds` calls `put_cred_many` but it would not be modifying the shared cred at the same time as `prepare_creds`.
And `atomic_long_add` and `atomic_long_sub_and_test` are used in the refcount. The `memcpy` might see a torn value, but `usage` is immediately overwritten.
I checked for other reports of the race in the kernel.
Now, the potential for a "fix" is something I thought about as well. I could use `WRITE_ONCE(nonconst_cred->non_rcu, 0)` and `data_race(memcpy(...))`. But I am convinced it's benign.
I validated `non_rcu` and `jit_keyring` and whether they were packed in a struct. They're separate.
The KCSAN report shows the write is 4 bytes. This is exactly the `int`.
So, it's a benign data race. The writes
|
| 38/2 |
2026/06/23 15:03 |
tool |
grepper |
0m
Args:
Results: map[Output:fs/open.c=414=static const struct cred *access_override_creds(void)
--
fs/open.c-453- * 'get_current_cred()' function), that will clear the
fs/open.c:454: * non_rcu field, because now that other user may be
fs/open.c-455- * expecting RCU freeing. But normal thread-synchronous
--
fs/open.c-458- */
fs/open.c:459: override_cred->non_rcu = 1;
fs/open.c-460- return override_creds(override_cred);
--
include/linux/cred.h=115=struct cred {
--
include/linux/cred.h-147- union {
include/linux/cred.h:148: int non_rcu; /* Can we skip RCU deletion? */
include/linux/cred.h-149- struct rcu_head rcu; /* RCU deletion hook */
--
include/linux/cred.h=217=static inline const struct cred *get_cred_many(const struct cred *cred, int nr)
--
include/linux/cred.h-221- return cred;
include/linux/cred.h:222: nonconst_cred->non_rcu = 0;
include/linux/cred.h-223- atomic_long_add(nr, &nonconst_cred->usage);
--
include/linux/cred.h=241=static inline const struct cred *get_cred_rcu(const struct cred *cred)
--
include/linux/cred.h-247- return NULL;
include/linux/cred.h:248: nonconst_cred->non_rcu = 0;
include/linux/cred.h-249- return cred;
--
kernel/cred.c=71=void __put_cred(struct cred *cred)
--
kernel/cred.c-79-
kernel/cred.c:80: if (cred->non_rcu)
kernel/cred.c-81- put_cred_rcu(&cred->rcu);
--
kernel/cred.c=179=struct cred *prepare_creds(void)
--
kernel/cred.c-193-
kernel/cred.c:194: new->non_rcu = 0;
kernel/cred.c-195- atomic_long_set(&new->usage, 1);
--
kernel/cred.c=559=struct cred *prepare_kernel_cred(struct task_struct *daemon)
--
kernel/cred.c-575- *new = *old;
kernel/cred.c:576: new->non_rcu = 0;
kernel/cred.c-577- atomic_long_set(&new->usage, 1);
--
tools/testing/selftests/bpf/prog_tests/rcu_read_lock.c=15=static void test_success(void)
--
tools/testing/selftests/bpf/prog_tests/rcu_read_lock.c-31- bpf_program__set_autoload(skel->progs.nested_rcu_region, true);
tools/testing/selftests/bpf/prog_tests/rcu_read_lock.c:32: bpf_program__set_autoload(skel->progs.task_trusted_non_rcuptr, true);
tools/testing/selftests/bpf/prog_tests/rcu_read_lock.c-33- bpf_program__set_autoload(skel->progs.rcu_read_lock_subprog, true);
--
tools/testing/selftests/bpf/progs/rcu_read_lock.c=331=SEC("?fentry.s/" SYS_PREFIX "sys_getpgid")
tools/testing/selftests/bpf/progs/rcu_read_lock.c:332:int task_trusted_non_rcuptr(void *ctx)
tools/testing/selftests/bpf/progs/rcu_read_lock.c-333-{
]
|
| 39/2 |
2026/06/23 15:03 |
llm |
expert |
0m
Model:
Tokens:
input: 37570
output: 32
thoughts: 85
Thoughts: Okay, let me break this down.
**Diving into Credential Handling**
Alright, I'm digging into this kernel code, and I see something interesting in `kernel/cred.c` at line 80: the function `__put_cred`. And right away, there's that `if (cred->non_rcu)` check. *Hmm, interesting. That implies there are potentially different credential release paths, depending on whether it's RCU-protected or not.*
But then, the really sharp bit catches my eye: `put_cred_rcu(&cred->rcu)`. Now hold on... `put_cred_rcu`? *Why am I calling an RCU-specific function in this context? Let's take another pass through this `__put_cred` and see if the broader context clarifies things.*
|
| 40/2 |
2026/06/23 15:03 |
tool |
read-file |
0m
Args: map[File:kernel/cred.c FirstLine:60 LineCount:40]
Results: map[Contents: 60: put_ucounts(cred->ucounts);
61: put_user_ns(cred->user_ns);
62: kmem_cache_free(cred_jar, cred);
63: }
64:
65: /**
66: * __put_cred - Destroy a set of credentials
67: * @cred: The record to release
68: *
69: * Destroy a set of credentials on which no references remain.
70: */
71: void __put_cred(struct cred *cred)
72: {
73: kdebug("__put_cred(%p{%ld})", cred,
74: atomic_long_read(&cred->usage));
75:
76: BUG_ON(atomic_long_read(&cred->usage) != 0);
77: BUG_ON(cred == current->cred);
78: BUG_ON(cred == current->real_cred);
79:
80: if (cred->non_rcu)
81: put_cred_rcu(&cred->rcu);
82: else
83: call_rcu(&cred->rcu, put_cred_rcu);
84: }
85: EXPORT_SYMBOL(__put_cred);
86:
87: /*
88: * Clean up a task's credentials when it exits
89: */
90: void exit_creds(struct task_struct *tsk)
91: {
92: struct cred *real_cred, *cred;
93:
94: kdebug("exit_creds(%u,%p,%p,{%ld})", tsk->pid, tsk->real_cred, tsk->cred,
95: atomic_long_read(&tsk->cred->usage));
96:
97: real_cred = (struct cred *) tsk->real_cred;
98: tsk->real_cred = NULL;
99:
]
|
| 41/2 |
2026/06/23 15:03 |
llm |
expert |
0m
Model:
Tokens:
input: 38427
output: 24
thoughts: 1208
Thoughts: **Analyzing the Credential Copying Race Condition**
Okay, so I'm diving into this `copy_creds` and `prepare_creds` interaction. My initial thought is that `put_cred_rcu` is the key function that handles the actual freeing of the credentials. If `non_rcu` is true, it's a direct call, otherwise, `call_rcu` is used, which makes `non_rcu` an optimization to bypass `call_rcu` when it is safe. So, `non_rcu` essentially tells us whether to immediately free or defer via RCU.
The core of the issue seems to be a potential data race with `memcpy` due to the `non_rcu` variable. I've checked the usage, and it's also overwritten. However, after careful analysis, I've confirmed that the race condition on `non_rcu` during `memcpy` is benign. Here's why: first, the destination's `new->non_rcu` is immediately set to 0. Second, the source `old->non_rcu` is being changed from 1 to 0, which is a safe transition; it just means that later calls to `put_cred` will use RCU.
I also needed to ensure that the `memcpy` operation couldn't be interrupted, leading to the `old` credential being freed prematurely. I've considered that the caller of `prepare_creds` holds a reference to `old` via `current->cred`, so it can't be freed. Further, I had to ensure that `current->cred` is stable and not changed by another thread. `commit_creds` only modifies the credentials for the current task, and the task running `prepare_creds` can't be concurrently in `commit_creds`. I also considered `ptrace` and `de_thread`, but these don't typically modify credentials while a task is in kernel space. Thus, `old` is stable.
The race is purely between the write to `non_rcu` in `copy_creds` and the read of `non_rcu` in `prepare_creds`, which is benign.
Let's look at a concrete scenario: I'm thinking of Task 9782 (parent) doing `clone3` and Task 9783 (sibling thread) doing `capset`, both sharing the same `cred` object. The parent calls `copy_creds`, which leads to `get_cred_many` setting `non_rcu = 0`. Then the sibling calls `prepare_creds`, which contains the `memcpy` operation that reads `non_rcu`.
This is exactly what's happening and it's benign. The key is that the transition of `non_rcu` from 1 to 0 is safe because the target of `memcpy` is immediately overwritten with 0.
I need to confirm if `prepare_creds` has other side effects. It calls functions like `get_group_info`, `get_uid`, and `get_user_ns` and others. All these increment refcounts of other objects. These objects are also pointed to by `old`. Since `old` is stable, those pointers are stable as well. The important thing is that these refcounts are incremented on the *new* credential object, which makes this safe. For example, `new->group_info` is copied by `memcpy` and its refcount is incremented as expected.
I also double-checked that `get_cred_many` is called with `nr=2`. This is because both `p->real_cred` and `p->cred` are set to the same object. `copy_creds` sets `p->real_cred = get_cred_many(p->cred, 2)` and subsequently sets `p->cred = p->real_cred`. So, that's fine.
In essence, the race is benign because `non_rcu` is safely transitioned from 1 to 0 and other fields are properly managed. So, the data race is between `copy_creds` writing `non_rcu = 0` to the shared `struct cred` and `prepare_creds` reading the same `struct cred` through `memcpy`. This is safe because the destination's `non_rcu` is overwritten, `usage` is overwritten. The transition from `non_rcu` 1 to 0 is safe.
I will recommend annotating the `memcpy` in `prepare_creds` with `data_race()`. This should be sufficient. I'll now check if there are
|
| 42/2 |
2026/06/23 15:04 |
tool |
git-log |
0m
Args: map[MessageRegexps:[KCSAN cred]]
Results:
|
| 43/2 |
2026/06/23 15:04 |
llm |
expert |
0m
Model:
Tokens:
input: 40697
output: 23
thoughts: 26
Thoughts: Okay, here's my take on those thoughts, tailored for an expert audience, presented in the first person:
**Investigating Credential and Data Race Issues**
Alright, the initial query for commits containing *both* "KCSAN" and "cred" turned up empty. That's a bit surprising, but not entirely unexpected given the potential for how these issues might be described and resolved. It's a reminder that search terms need to be precise, but also that we have to be flexible in our approach.
My immediate thought is to broaden the scope slightly. The absence of a direct link to KCSAN doesn't mean the issue isn't related or was discovered through some form of detection. So, let's try a different tack. I'll switch gears and try searching for commits that mention only "cred" in conjunction with "data race". This will allow me to capture a wider pool of commits related to credential exposure and data races. My expectation is the second query will return a broader, more informative set of results. It will be interesting to assess the intersection of those two concerns, as it may reveal a wider array of interactions in the code.
|
| 44/2 |
2026/06/23 15:04 |
tool |
git-log |
0m
Args: map[MessageRegexps:[cred data race]]
Results: map[Output:614e40faf5ae tools/memory-model: Provide exact SRCU semantics
8843e06f67b1 ring-buffer: Handle race between rb_move_tail and rb_check_pages
]
|
| 45/2 |
2026/06/23 15:04 |
llm |
expert |
0m
Model:
Tokens:
input: 41055
output: 21
thoughts: 17
Thoughts: Okay, here's my summary, in the first person, as requested, focusing on my thought process:
**Diving into Credential Handling**
Alright, that initial thought isn't particularly insightful, so I'll move on. Time to look into the meat of this issue, and where the kernel manages credentials. Specifically, `kernel/cred.c` is where I'd expect to find the relevant code. Given my expertise, I already know the core functions in that area, and my next step is to examine its commit history. I'm going to look at the relevant modifications there. It's time to sift through the changes to see how credentials are created, modified, and used within the kernel.
|
| 46/2 |
2026/06/23 15:04 |
tool |
git-log |
0m
Args: map[PathPrefix:kernel/cred.c]
Results: map[Output:6b1c66c9cca9 exec_state: relocate dumpable information
5547598e59d7 cred: remove unused set_security_override_from_ctx()
40314c2818b7 cred: make init_cred static
3a18f809184b ns: add active reference count
edd3cb05c00a copy_process: pass clone_flags as u64 across calltree
a51a1d6bcaa3 cred: remove old {override,revert}_creds() helpers
49dffdfde462 cred: Add a light version of override/revert_creds()
edc6670233a3 cred: Use KMEM_CACHE() instead of kmem_cache_create()
ae1914174a63 cred: get rid of CONFIG_DEBUG_CREDENTIALS
f8fa5d769259 cred: switch to using atomic_long_t
]
|
| 47/2 |
2026/06/23 15:04 |
llm |
expert |
0m
Model:
Tokens:
input: 41491
output: 34
thoughts: 69
Thoughts: Okay, let's see...
**Analyzing the `non_rcu` Flag**
Hmm, I haven't been looking at anything data race related lately. Okay, wait... should I double-check if this `non_rcu` flag is new? Sometimes things sneak in. Alright, a quick scan... nope, seems like it's been around for a bit.
Ah, right, now I remember. `non_rcu` was actually introduced to improve the optimization of `override_creds`. That rings a bell.
Just to be sure, I'll do a quick check to see exactly when that commit went in. It'll give me a more concrete timeline and any other context around the change.
|
| 48/2 |
2026/06/23 15:05 |
tool |
git-log |
0m
Args: map[CodeRegexp:non_rcu PathPrefix:include/linux/cred.h]
Results: map[Output:d7852fbd0f04 access: avoid the RCU grace period for the temporary subjective credentials
]
|
| 49/2 |
2026/06/23 15:05 |
llm |
expert |
0m
Model:
Tokens:
input: 41786
output: 26
thoughts: 30
Thoughts: Okay, here's my interpretation of that.
**Analyzing a Specific Commit**
Right, so I'm thinking back to commit `d7852fbd0f04` from 2021. That's a significant marker in time. I need to refresh my memory on what was happening then. Let's pull up the details and see what was changed. I need to understand the context. What were the driving forces at that time, which features or bug fixes were in focus. It's time to dive in and get reacquainted with the code.
|
| 50/2 |
2026/06/23 15:05 |
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);
]
|
| 51/2 |
2026/06/23 15:05 |
llm |
expert |
0m
Model:
Tokens:
input: 44424
output: 15
thoughts: 1040
Thoughts: **Benign Race Condition in Credential Handling**
Okay, so I've been digging into this potential race condition, and the commit message and code are definitely confirming my initial analysis. The core issue is the interaction between `get_cred` (or `get_cred_many` as it's now called) and `prepare_creds`. Specifically, the concern is around the `non_rcu` field within the `cred` struct.
My understanding is this: `get_cred_many` *clears* `non_rcu`. The other side of the potential issue is `prepare_creds`, where the `cred` struct is copied. The key is, `prepare_creds` *also* explicitly clears `non_rcu` in the *copy* that it creates. It's a double write of 0.
So, the race is *between* the clearing in `get_cred_many` and the copy-then-clear in `prepare_creds`. Since `prepare_creds` overwrites `non_rcu` anyway, there's no actual data corruption even if there's a stale read. The race is benign.
I've also confirmed that the `usage` field, which is incremented in `get_cred_many`, is set to 1 in `prepare_creds`.
The provided trace shows the two operations:
```
CPU0 CPU1
copy_creds()
get_cred_many(p->cred, 2)
nonconst_cred->non_rcu = 0; // write
prepare_creds()
old = task->cred;
memcpy(new, old, sizeof(struct cred)); // read
new->non_rcu = 0; // overwrite
```
In the end the value of `new->non_rcu` is always 0, and the value of `old->non_rcu` eventually is 0.
The only other thing I was thinking about was layout randomization with `__randomize_layout`. If the struct's layout is randomized, then the offset of `non_rcu` might vary between kernels. But KCSAN is running on a specific kernel image. Therefore, the offset is constant.
I've checked the race summary; it nails the important points. It says there's a race, but the new struct is correctly initialized so this is fine. The code explicitly sets the values.
And yes, the race is on `non_rcu` and `usage` in a shared `struct cred`. Because `prepare_creds` does a complete copy, then explicitly sets these, the values are correct.
I was being a little overly cautious, and the suggestion to wrap with `data_race()` to suppress the KCSAN warning is something I would consider, but that might hide future problems.
**Additional Consideration:** I did want to check for any other fields that might be adjacent in memory, where a wider store could cause unintended modification. The `jit_keyring` field, being an `unsigned char` and therefore only 1 byte, adjacent to the `non_rcu` which is an `int`, does come to mind. But, `non_rcu` is an `int`, and should always use a 32-bit store. So, it's not going to clobber other adjacent fields. Even if there *was* an issue here, it would be a bug in `get_cred_many`, and not the race between the two functions.
**Conclusion:** The race is benign.
|
| 52/2 |
2026/06/23 15:05 |
tool |
set-results |
0m
Args:
Results:
|
| 53/2 |
2026/06/23 15:05 |
llm |
expert |
0m
Model:
Tokens:
input: 46269
output: 861
thoughts: 111
Thoughts: **My Analysis Concluded: A Benign Race Condition**
Alright, the deep dive is done. I've pinpointed the race condition, meticulously outlined the mechanisms behind it, and most importantly, I've confirmed that it's harmless. Here's the gist of it:
We're looking at a race between the `copy_creds` function, specifically through its dependency on `get_cred_many`, and the `prepare_creds` function, which utilizes a `memcpy` operation.
The key fields at the heart of this interaction are `non_rcu` and `usage`. These are the specific areas of memory we're concerned about, where one thread might be reading or modifying while another is also doing the same.
The crucial point is that both of these fields are immediately overwritten in the *destination* of the `memcpy` operation. This makes the potential conflict short-lived.
I've carefully examined the `non_rcu` transition in the source, and that part is perfectly safe. Furthermore, no other fields undergo any problematic in-place modifications. The whole thing looks okay. Case closed.
|
| 54/1 |
2026/06/23 15:05 |
action |
format-explanation |
0m
Results: map[Explanation:The KCSAN report identifies a data race between `copy_creds()` and
`prepare_creds()` on a `struct cred` object.
### Race Summary
The race occurs when one task (Task 9782) is creating a new task (e.g., via
`clone()`) and sharing the current credentials, while another task (Task 9783,
likely a sibling thread) is preparing new credentials (e.g., via `capset()`).
Both tasks access the same shared `struct cred` object.
* **Task 9782 (Write):** Calls `copy_creds()`, which calls `get_cred_many()`.
This function increments the reference count (`usage`) and sets `non_rcu = 0` on
the shared credential structure to ensure it is freed via RCU now that it has
multiple owners.
* **Task 9783 (Read):** Calls `prepare_creds()`, which performs a `memcpy()`
of the entire `current->cred` structure into a newly allocated `struct cred`.
### Synchronization Context
In a multi-threaded process, threads share the same `struct cred` object. While
`struct cred` is mostly immutable, the `usage` (refcount) and `non_rcu`
(RCU-freeing flag) fields are modified in-place when new references are taken
(e.g., during `fork()` or `get_current_cred()`). There is no global lock
protecting these fields across all threads; instead, they rely on atomic
operations for `usage` and the fact that `non_rcu` transitions are safe.
### Final Classification: **BENIGN**
### Reasoning
The race is structurally tolerant to stale or torn reads for the following
reasons:
1. **Immediate Overwrite:** In `prepare_creds()`, immediately after the
`memcpy(new, old, sizeof(struct cred))`, the code explicitly initializes the
racing fields in the new structure:
```c
new->non_rcu = 0;
atomic_long_set(&new->usage, 1);
```
Even if the `memcpy` reads a torn or stale value for `non_rcu` or `usage`
from the `old` structure, these values are never used; they are overwritten
before the `new` credential becomes visible or is used for any logic.
2. **Safe State Transition:** The `non_rcu` field only ever transitions from
`1` to `0` (never back to `1` while shared). This transition indicates that the
credential structure, which was previously used only synchronously, now has
multiple references and must be freed via RCU. A concurrent reader seeing either
`0` or `1` is safe because:
* If it sees `1` and copies it, it immediately corrects it to `0`.
* The source structure eventually settles at `0`, ensuring correct RCU
freeing behavior when the last reference is dropped.
3. **Immutability of Other Fields:** All other fields in `struct cred` (UIDs,
GIDs, capabilities, etc.) are immutable once the credential structure is
installed and shared. Therefore, the `memcpy` is guaranteed to see consistent
values for all logically significant fields.
### Recommended Annotations
To suppress the KCSAN warning, the `memcpy` in `prepare_creds()` could be
wrapped in a `data_race()` annotation, or the `non_rcu` assignment in
`get_cred_many()` could be marked. However, the current behavior is logically
sound.
```c
/* kernel/cred.c */
struct cred *prepare_creds(void)
{
...
old = task->cred;
data_race(memcpy(new, old, sizeof(struct cred))); // Mark as known race
new->non_rcu = 0;
atomic_long_set(&new->usage, 1);
...
}
```]
|