| 0/0 |
2026/08/08 18:52 |
flow |
assessment-kcsan |
21m
Results: map[Benign:true Explanation:### Race Summary
`wg_index_hashtable_insert` vs `wg_index_hashtable_lookup` on `entry->index` (in
`drivers/net/wireguard/peerlookup.c`).
---
### Synchronization Context
* **Writer (`wg_index_hashtable_insert`):** Holds `handshake->lock` (as a write
lock) and temporarily holds `table->lock` to remove the entry from the hashtable
via `hlist_del_init_rcu()`. However, it writes the new random index to
`entry->index` *after* releasing `table->lock` and without waiting for an RCU
grace period.
* **Reader (`wg_index_hashtable_lookup`):** Holds only `rcu_read_lock_bh()`
(lockless reader). It traverses the bucket corresponding to the old index and
reads `iter_entry->index` to check if it matches the requested index.
* **Lack of Mutual Exclusion:** Because the writer does not wait for an RCU
grace period after deleting the entry, a concurrent reader can still traverse
the list and access `entry` while the writer is updating `entry->index`.
---
### Final Classification: **BENIGN**
#### Reasoning:
1. **No Tearing:** `entry->index` is a 32-bit aligned, 32-bit integer
(`__le32`). On all modern architectures supported by the Linux kernel, 32-bit
aligned reads and writes are atomic, so torn reads/writes are physically
impossible.
2. **No Logic Bug:**
- **Stale Read:** If the reader reads the old index, the comparison
`iter_entry->index == index` evaluates to true, and the lookup succeeds. This
is perfectly valid under RCU, as a concurrent reader is allowed to find an
entry that is currently being deleted.
- **New Read:** If the reader reads the new index, the comparison
`iter_entry->index == index` evaluates to false, and the lookup fails. This
is also perfectly valid because the entry has already been deleted from the
hashtable, so the lookup for the old index *should* fail.
- **Cryptographic Safety:** Even in the theoretically impossible case of a
false positive match (e.g., if a torn read or a collision happens to match
the target index), the cryptographic checks in WireGuard (such as MAC and
state checks) will safely drop any mismatched packets, preventing any
security or functional issues.
3. **No Compiler Optimization Issues:**
- The writer of `entry->index` is serialized by `handshake->lock` (held as a
write lock), so there is only one writer at any time.
- The reader reads `iter_entry->index` exactly once in the comparison, and
does not use it again.
- Therefore, compiler optimizations like reloading or hoisting cannot cause
any incorrect behavior.
---
### Recommended Annotations
To satisfy KCSAN and prevent any potential compiler optimizations, the accesses
to `entry->index` should be annotated with `READ_ONCE()` and `WRITE_ONCE()`:
In `drivers/net/wireguard/peerlookup.c`:
* In `wg_index_hashtable_insert()`:
```c
WRITE_ONCE(entry->index, (__force __le32)get_random_u32());
```
* In `wg_index_hashtable_lookup()`:
```c
if (READ_ONCE(iter_entry->index) == index) {
```
* In `wg_index_hashtable_replace()`:
```c
WRITE_ONCE(new->index, READ_ONCE(old->index));
```]
|
| 1/1 |
2026/08/08 18:52 |
action |
kernel-checkouter |
0m
Results: map[KernelSrc:/app/workdir/cache/src/9d4bc8dbb37da7464722132cca9f0756f97b7cb8]
|
| 2/1 |
2026/08/08 18:52 |
action |
kernel-builder |
9m
Results: map[KernelObj:/app/workdir/cache/build/daf06c518be77aaf508b7d3ce7afaa2d3f743455]
|
| 3/1 |
2026/08/08 19:02 |
action |
codesearch-prepare |
7m
Results: map[Index:codesearch-index]
|
| 4/1 |
2026/08/08 19:09 |
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 wg_index_hashtable_insert / wg_index_hashtable_lookup
write to 0xffff88812d05077c of 4 bytes by task 3375 on cpu 1:
wg_index_hashtable_insert+0xda/0x2a0 drivers/net/wireguard/peerlookup.c:132
wg_noise_handshake_create_response+0x652/0x700 drivers/net/wireguard/noise.c:713
wg_packet_send_handshake_response+0x6d/0x120 drivers/net/wireguard/send.c:94
wg_receive_handshake_packet drivers/net/wireguard/receive.c:154 [inline]
wg_packet_handshake_receive_worker+0x37d/0x570 drivers/net/wireguard/receive.c:213
process_one_work kernel/workqueue.c:3322 [inline]
process_scheduled_works+0x4d4/0x9a0 kernel/workqueue.c:3405
worker_thread+0x569/0x750 kernel/workqueue.c:3486
kthread+0x221/0x270 kernel/kthread.c:436
ret_from_fork+0x146/0x330 arch/x86/kernel/process.c:158
ret_from_fork_asm+0x1a/0x30 arch/x86/entry/entry_64.S:245
read to 0xffff88812d05077c of 4 bytes by task 3410 on cpu 0:
wg_index_hashtable_lookup+0x5b/0x120 drivers/net/wireguard/peerlookup.c:211
wg_noise_handshake_consume_response+0x128/0x8f0 drivers/net/wireguard/noise.c:747
wg_receive_handshake_packet drivers/net/wireguard/receive.c:166 [inline]
wg_packet_handshake_receive_worker+0x38b/0x570 drivers/net/wireguard/receive.c:213
process_one_work kernel/workqueue.c:3322 [inline]
process_scheduled_works+0x4d4/0x9a0 kernel/workqueue.c:3405
worker_thread+0x569/0x750 kernel/workqueue.c:3486
kthread+0x221/0x270 kernel/kthread.c:436
ret_from_fork+0x146/0x330 arch/x86/kernel/process.c:158
ret_from_fork_asm+0x1a/0x30 arch/x86/entry/entry_64.S:245
value changed: 0xabf9bbc1 -> 0x33c4f176
Reported by Kernel Concurrency Sanitizer on:
CPU: 0 UID: 0 PID: 3410 Comm: kworker/0:4 Not tainted syzkaller #0 PREEMPT(lazy)
Hardware name: Google Google Compute Engine/Google Compute Engine, BIOS Google 07/24/2026
Workqueue: wg-kex-wg2 wg_packet_handshake_receive_worker
==================================================================
Reply: ### Race Summary
`wg_index_hashtable_insert` vs `wg_index_hashtable_lookup` on `entry->index` (in `drivers/net/wireguard/peerlookup.c`).
---
### Synchronization Context
* **Writer (`wg_index_hashtable_insert`):** Holds `handshake->lock` (as a write lock) and temporarily holds `table->lock` to remove the entry from the hashtable via `hlist_del_init_rcu()`. However, it writes the new random index to `entry->index` *after* releasing `table->lock` and without waiting for an RCU grace period.
* **Reader (`wg_index_hashtable_lookup`):** Holds only `rcu_read_lock_bh()` (lockless reader). It traverses the bucket corresponding to the old index and reads `iter_entry->index` to check if it matches the requested index.
* **Lack of Mutual Exclusion:** Because the writer does not wait for an RCU grace period after deleting the entry, a concurrent reader can still traverse the list and access `entry` while the writer is updating `entry->index`.
---
### Final Classification: **BENIGN**
#### Reasoning:
1. **No Tearing:** `entry->index` is a 32-bit aligned, 32-bit integer (`__le32`). On all modern architectures supported by the Linux kernel, 32-bit aligned reads and writes are atomic, so torn reads/writes are physically impossible.
2. **No Logic Bug:**
- **Stale Read:** If the reader reads the old index, the comparison `iter_entry->index == index` evaluates to true, and the lookup succeeds. This is perfectly valid under RCU, as a concurrent reader is allowed to find an entry that is currently being deleted.
- **New Read:** If the reader reads the new index, the comparison `iter_entry->index == index` evaluates to false, and the lookup fails. This is also perfectly valid because the entry has already been deleted from the hashtable, so the lookup for the old index *should* fail.
- **Cryptographic Safety:** Even in the theoretically impossible case of a false positive match (e.g., if a torn read or a collision happens to match the target index), the cryptographic checks in WireGuard (such as MAC and state checks) will safely drop any mismatched packets, preventing any security or functional issues.
3. **No Compiler Optimization Issues:**
- The writer of `entry->index` is serialized by `handshake->lock` (held as a write lock), so there is only one writer at any time.
- The reader reads `iter_entry->index` exactly once in the comparison, and does not use it again.
- Therefore, compiler optimizations like reloading or hoisting cannot cause any incorrect behavior.
---
### Recommended Annotations
To satisfy KCSAN and prevent any potential compiler optimizations, the accesses to `entry->index` should be annotated with `READ_ONCE()` and `WRITE_ONCE()`:
In `drivers/net/wireguard/peerlookup.c`:
* In `wg_index_hashtable_insert()`:
```c
WRITE_ONCE(entry->index, (__force __le32)get_random_u32());
```
* In `wg_index_hashtable_lookup()`:
```c
if (READ_ONCE(iter_entry->index) == index) {
```
* In `wg_index_hashtable_replace()`:
```c
WRITE_ONCE(new->index, READ_ONCE(old->index));
```
|
| 5/2 |
2026/08/08 19:09 |
llm |
expert |
0m
Model:
Tokens:
input: 6007
output: 86
thoughts: 163
Thoughts: **Analyzing a KCSAN Data Race in the WireGuard Peer Lookup Code**
Okay, so the user wants me to dive into a KCSAN data race report, specifically in the Linux kernel. It looks like the issue is with WireGuard, which is interesting. The report pinpoints a race between `wg_index_hashtable_insert` (a write operation) and `wg_index_hashtable_lookup` (a read operation) on a 4-byte variable. The value is changing from `0xabf9bbc1` to `0x33c4f176`, which gives me a place to start. The file in question is `drivers/net/wireguard/peerlookup.c`, and that's going to be key.
My initial approach will be to get my bearings in the code. I'll need to understand the context of these two functions. I'll use `codesearch-definition-source` or `read-file` to grab the source code from `drivers/net/wireguard/peerlookup.c`. I'll start by searching the code for both `wg_index_hashtable_insert` and `wg_index_hashtable_lookup` functions to get a high-level picture of how they are interacting. Seeing their definitions, callers, and any associated locks or synchronization primitives will be crucial. From there, I can analyze the path of execution to understand how this 4-byte variable is accessed and how the race is possible. This should help me form a hypothesis about the root cause of this KCSAN report.
|
| 6/2 |
2026/08/08 19:09 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/net/wireguard/peerlookup.c Name:wg_index_hashtable_insert]
Results: map[SourceCode: 119: __le32 wg_index_hashtable_insert(struct index_hashtable *table,
120: struct index_hashtable_entry *entry)
121: {
122: struct index_hashtable_entry *existing_entry;
123:
124: spin_lock_bh(&table->lock);
125: hlist_del_init_rcu(&entry->index_hash);
126: spin_unlock_bh(&table->lock);
127:
128: rcu_read_lock_bh();
129:
130: search_unused_slot:
131: /* First we try to find an unused slot, randomly, while unlocked. */
132: entry->index = (__force __le32)get_random_u32();
133: hlist_for_each_entry_rcu_bh(existing_entry,
134: index_bucket(table, entry->index),
135: index_hash) {
136: if (existing_entry->index == entry->index)
137: /* If it's already in use, we continue searching. */
138: goto search_unused_slot;
139: }
140:
141: /* Once we've found an unused slot, we lock it, and then double-check
142: * that nobody else stole it from us.
143: */
144: spin_lock_bh(&table->lock);
145: hlist_for_each_entry_rcu_bh(existing_entry,
146: index_bucket(table, entry->index),
147: index_hash) {
148: if (existing_entry->index == entry->index) {
149: spin_unlock_bh(&table->lock);
150: /* If it was stolen, we start over. */
151: goto search_unused_slot;
152: }
153: }
154: /* Otherwise, we know we have it exclusively (since we're locked),
155: * so we insert.
156: */
157: hlist_add_head_rcu(&entry->index_hash,
158: index_bucket(table, entry->index));
159: spin_unlock_bh(&table->lock);
160:
161: rcu_read_unlock_bh();
162:
163: return entry->index;
164: }
SourceFile:drivers/net/wireguard/peerlookup.c]
|
| 7/2 |
2026/08/08 19:09 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/net/wireguard/peerlookup.c Name:wg_index_hashtable_lookup]
Results: map[SourceCode: 200: /* Returns a strong reference to a entry->peer */
201: struct index_hashtable_entry *
202: wg_index_hashtable_lookup(struct index_hashtable *table,
203: const enum index_hashtable_type type_mask,
204: const __le32 index, struct wg_peer **peer)
205: {
206: struct index_hashtable_entry *iter_entry, *entry = NULL;
207:
208: rcu_read_lock_bh();
209: hlist_for_each_entry_rcu_bh(iter_entry, index_bucket(table, index),
210: index_hash) {
211: if (iter_entry->index == index) {
212: if (likely(iter_entry->type & type_mask))
213: entry = iter_entry;
214: break;
215: }
216: }
217: if (likely(entry)) {
218: entry->peer = wg_peer_get_maybe_zero(entry->peer);
219: if (likely(entry->peer))
220: *peer = entry->peer;
221: else
222: entry = NULL;
223: }
224: rcu_read_unlock_bh();
225: return entry;
226: }
SourceFile:drivers/net/wireguard/peerlookup.c]
|
| 8/2 |
2026/08/08 19:09 |
llm |
expert |
0m
Model:
Tokens:
input: 7776
output: 41
thoughts: 1332
Thoughts: **Data Race Discovered in `wg_index_hashtable_insert` and `wg_index_hashtable_lookup`**
Okay, let's break this down. In the `wg_index_hashtable_insert` function, I see an RCU-related issue. Specifically, after `hlist_del_init_rcu` is called to remove an entry from the hash table (line 125), a new random index is immediately written to `entry->index` (line 132). The critical point is that this write happens *before* any RCU grace period.
My concern is that if another thread is concurrently calling `wg_index_hashtable_lookup` using the *old* index, it could still be traversing the hash table, as it's under `rcu_read_lock_bh()`. `hlist_del_init_rcu` just *initializes* the removal; it doesn't guarantee immediate visibility change for RCU readers. So, the lookup thread might still encounter the `entry` in the RCU-protected list or in the bucket itself if the deletion happens just as the lookup dereferences the pointer.
The `wg_index_hashtable_lookup` function does a lockless lookup. It reads `iter_entry->index` at line 211, and *that* read is now racing with the write of `entry->index` in `wg_index_hashtable_insert` on a different CPU.
Let's trace it carefully. Thread A, in insert, removes the entry from the lock-protected list and immediately assigns a new random index to the entry. Thread B, in lookup, might be traversing the list, and it can read the index. The values are therefore not synchronized.
If Thread B reads the *new* value (or a torn value), it will likely skip the entry since the old index will not match. In this case, the lookup will fail, and this is *okay*, because a lockless lookup is permitted to fail if a concurrent deletion happens.
If Thread B sees the old value, then it will successfully locate the element, as expected.
However, the problem is potentially more subtle due to the possibility of compiler optimizations. Since there's no `READ_ONCE` or `WRITE_ONCE` on `entry->index`, the compiler *could* reorder or optimize the load or store. I'm imagining a situation where the compiler could load `iter_entry->index` multiple times within the lookup, resulting in inconsistent results.
Further, given `entry->index` is a 32-bit value, on standard architectures, reads and writes are atomic, so it is unlikely that tearing will occur, although it is possible in certain situations.
So, in short, there's a clear data race here. I need to investigate the struct definition of `index_hashtable_entry` to see if `entry->index` is written or read anywhere else, which might compound the problem. The core issue is the unsynchronized access to `entry->index` during concurrent insertion and lookup, before an RCU grace period.
|
| 9/2 |
2026/08/08 19:10 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/net/wireguard/peerlookup.c Name:index_hashtable_entry]
Results: map[SourceCode: 44: struct index_hashtable_entry {
45: struct wg_peer *peer;
46: struct hlist_node index_hash;
47: enum index_hashtable_type type;
48: __le32 index;
49: };
SourceFile:drivers/net/wireguard/peerlookup.h]
|
| 10/2 |
2026/08/08 19:10 |
llm |
expert |
0m
Model:
Tokens:
input: 9928
output: 36
thoughts: 32
Thoughts: Okay, let's dive into this. Here's how I'd approach this task, as if these were my internal thoughts:
**Analyzing `index` Usage in `drivers/net/wireguard/peerlookup.c`**
Alright, time to get my hands dirty. I need to understand how the `index` variable is being utilized within `drivers/net/wireguard/peerlookup.c`. I want to see *every* instance of it. I'm thinking this is probably a crucial piece of the puzzle within that peer lookup functionality. Understanding its purpose and how it's being manipulated is key to grasping the low-level implementation. I'll start with a straightforward search for all appearances of that variable name. Hopefully, this will reveal patterns and relationships that will start to tell me a story about its role in the code's operation. I'm ready to dig in and decipher its significance.
|
| 11/2 |
2026/08/08 19:10 |
tool |
grepper |
0m
Args: map[Expression:\bindex\b PathPrefix:drivers/net/wireguard/peerlookup.c]
Results: map[Output:drivers/net/wireguard/peerlookup.c=73=static struct hlist_head *index_bucket(struct index_hashtable *table,
drivers/net/wireguard/peerlookup.c:74: const __le32 index)
drivers/net/wireguard/peerlookup.c-75-{
--
drivers/net/wireguard/peerlookup.c-78- */
drivers/net/wireguard/peerlookup.c:79: return &table->hashtable[(__force u32)index &
drivers/net/wireguard/peerlookup.c-80- (HASH_SIZE(table->hashtable) - 1)];
--
drivers/net/wireguard/peerlookup.c=119=__le32 wg_index_hashtable_insert(struct index_hashtable *table,
--
drivers/net/wireguard/peerlookup.c-131- /* First we try to find an unused slot, randomly, while unlocked. */
drivers/net/wireguard/peerlookup.c:132: entry->index = (__force __le32)get_random_u32();
drivers/net/wireguard/peerlookup.c-133- hlist_for_each_entry_rcu_bh(existing_entry,
drivers/net/wireguard/peerlookup.c:134: index_bucket(table, entry->index),
drivers/net/wireguard/peerlookup.c-135- index_hash) {
drivers/net/wireguard/peerlookup.c:136: if (existing_entry->index == entry->index)
drivers/net/wireguard/peerlookup.c-137- /* If it's already in use, we continue searching. */
--
drivers/net/wireguard/peerlookup.c-145- hlist_for_each_entry_rcu_bh(existing_entry,
drivers/net/wireguard/peerlookup.c:146: index_bucket(table, entry->index),
drivers/net/wireguard/peerlookup.c-147- index_hash) {
drivers/net/wireguard/peerlookup.c:148: if (existing_entry->index == entry->index) {
drivers/net/wireguard/peerlookup.c-149- spin_unlock_bh(&table->lock);
--
drivers/net/wireguard/peerlookup.c-157- hlist_add_head_rcu(&entry->index_hash,
drivers/net/wireguard/peerlookup.c:158: index_bucket(table, entry->index));
drivers/net/wireguard/peerlookup.c-159- spin_unlock_bh(&table->lock);
--
drivers/net/wireguard/peerlookup.c-162-
drivers/net/wireguard/peerlookup.c:163: return entry->index;
drivers/net/wireguard/peerlookup.c-164-}
--
drivers/net/wireguard/peerlookup.c=166=bool wg_index_hashtable_replace(struct index_hashtable *table,
--
drivers/net/wireguard/peerlookup.c-176-
drivers/net/wireguard/peerlookup.c:177: new->index = old->index;
drivers/net/wireguard/peerlookup.c-178- hlist_replace_rcu(&old->index_hash, &new->index_hash);
--
drivers/net/wireguard/peerlookup.c=202=wg_index_hashtable_lookup(struct index_hashtable *table,
drivers/net/wireguard/peerlookup.c-203- const enum index_hashtable_type type_mask,
drivers/net/wireguard/peerlookup.c:204: const __le32 index, struct wg_peer **peer)
drivers/net/wireguard/peerlookup.c-205-{
--
drivers/net/wireguard/peerlookup.c-208- rcu_read_lock_bh();
drivers/net/wireguard/peerlookup.c:209: hlist_for_each_entry_rcu_bh(iter_entry, index_bucket(table, index),
drivers/net/wireguard/peerlookup.c-210- index_hash) {
drivers/net/wireguard/peerlookup.c:211: if (iter_entry->index == index) {
drivers/net/wireguard/peerlookup.c-212- if (likely(iter_entry->type & type_mask))
]
|
| 12/2 |
2026/08/08 19:10 |
llm |
expert |
0m
Model:
Tokens:
input: 11327
output: 43
thoughts: 252
Thoughts: **Investigating WireGuard's Index Hashtable Implementation**
Okay, let's dive into this. I need to understand how WireGuard manages its indexed data structures. My initial thought is to check if there are any other files within the `drivers/net/wireguard` directory that might be using `index_hashtable_entry` or just `index` on their own. That's a reasonable first step.
Hold on, before I get carried away, let me examine `wg_index_hashtable_replace`. That function is probably more relevant.
```c
bool wg_index_hashtable_replace(struct index_hashtable *table,
struct index_hashtable_entry *old,
struct index_hashtable_entry *new)
{
bool ret = false;
spin_lock_bh(&table->lock);
if (!hlist_unhashed(&old->index_hash)) {
new->index = old->index;
hlist_replace_rcu(&old->index_hash, &new->index_hash);
hlist_init_node(&old->index_hash);
ret = true;
}
spin_unlock_bh(&table->lock);
return ret;
}
```
Now, that's interesting. I need to get a broader understanding of how the `index_hashtable` is used within WireGuard itself. The function `wg_index_hashtable_insert` is likely a good place to start, so let's look for all the functions that call it.
|
| 13/2 |
2026/08/08 19:10 |
tool |
codesearch-find-references |
0m
Args: map[ContextFile:drivers/net/wireguard/peerlookup.c Name:wg_index_hashtable_insert]
Results: map[References:[map[ReferenceKind:calls ReferencingEntityKind:function ReferencingEntityName:wg_noise_handshake_create_initiation SourceFile:drivers/net/wireguard/noise.c SourceLine:569] map[ReferenceKind:calls ReferencingEntityKind:function ReferencingEntityName:wg_noise_handshake_create_response SourceFile:drivers/net/wireguard/noise.c SourceLine:713]] TruncatedOutput:false]
|
| 14/2 |
2026/08/08 19:10 |
llm |
expert |
0m
Model:
Tokens:
input: 12101
output: 44
thoughts: 73
Thoughts: Here's my analysis of this code snippet:
**Deep Dive into WireGuard Handshake Initialization**
Okay, so I'm looking at the WireGuard handshake process and immediately see a connection between the initiation and response creation, specifically through the use of `wg_index_hashtable_insert`. Both `wg_noise_handshake_create_initiation` and `wg_noise_handshake_create_response` are leveraging this insertion function. My immediate instinct is to focus on the response creation, `wg_noise_handshake_create_response`, to gain a deeper understanding. To do so, I'll turn my attention to `drivers/net/wireguard/noise.c` where I know the implementation for that particular function lives. This seems like a promising entry point to understand how these handshakes are being built. I'm keen to see how the response interacts with the hash table.
|
| 15/2 |
2026/08/08 19:10 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/net/wireguard/noise.c Name:wg_noise_handshake_create_response]
Results: map[SourceCode: 667: bool wg_noise_handshake_create_response(struct message_handshake_response *dst,
668: struct noise_handshake *handshake)
669: {
670: u8 key[NOISE_SYMMETRIC_KEY_LEN];
671: bool ret = false;
672:
673: /* We need to wait for crng _before_ taking any locks, since
674: * curve25519_generate_secret uses get_random_bytes_wait.
675: */
676: wait_for_random_bytes();
677:
678: down_read(&handshake->static_identity->lock);
679: down_write(&handshake->lock);
680:
681: if (handshake->state != HANDSHAKE_CONSUMED_INITIATION)
682: goto out;
683:
684: dst->header.type = cpu_to_le32(MESSAGE_HANDSHAKE_RESPONSE);
685: dst->receiver_index = handshake->remote_index;
686:
687: /* e */
688: curve25519_generate_secret(handshake->ephemeral_private);
689: if (!curve25519_generate_public(dst->unencrypted_ephemeral,
690: handshake->ephemeral_private))
691: goto out;
692: message_ephemeral(dst->unencrypted_ephemeral,
693: dst->unencrypted_ephemeral, handshake->chaining_key,
694: handshake->hash);
695:
696: /* ee */
697: if (!mix_dh(handshake->chaining_key, NULL, handshake->ephemeral_private,
698: handshake->remote_ephemeral))
699: goto out;
700:
701: /* se */
702: if (!mix_dh(handshake->chaining_key, NULL, handshake->ephemeral_private,
703: handshake->remote_static))
704: goto out;
705:
706: /* psk */
707: mix_psk(handshake->chaining_key, handshake->hash, key,
708: handshake->preshared_key);
709:
710: /* {} */
711: message_encrypt(dst->encrypted_nothing, NULL, 0, key, handshake->hash);
712:
713: dst->sender_index = wg_index_hashtable_insert(
714: handshake->entry.peer->device->index_hashtable,
715: &handshake->entry);
716:
717: handshake->state = HANDSHAKE_CREATED_RESPONSE;
718: ret = true;
719:
720: out:
721: up_write(&handshake->lock);
722: up_read(&handshake->static_identity->lock);
723: memzero_explicit(key, NOISE_SYMMETRIC_KEY_LEN);
724: return ret;
725: }
SourceFile:drivers/net/wireguard/noise.c]
|
| 16/2 |
2026/08/08 19:10 |
llm |
expert |
0m
Model:
Tokens:
input: 13349
output: 44
thoughts: 32
Thoughts: Here's my attempt at summarizing the thought process, tailored for an expert audience:
**Analyzing wg_noise_handshake_consume_response**
Okay, before diving too deep, I need to get a clear picture of how this handshake response is processed. Let's zoom in on `wg_noise_handshake_consume_response` within `drivers/net/wireguard/noise.c`. I'm going to look closely at the function's logic. I need to refresh my understanding of how the Noise protocol is implemented in WireGuard, specifically how the handshake information is consumed and validated. I'll probably be thinking about things like:
* **Key derivation**: How are the keys derived from the received response and the existing state? Are we seeing potential issues with forward secrecy?
* **Nonce handling**: This is crucial. How are the nonces handled to prevent replay attacks and maintain security? Any potential off-by-one errors or misuse of the counter?
* **Integrity checks**: What integrity checks are in place to ensure the response hasn't been tampered with? Are they robust enough, or are there known weaknesses?
* **State transitions**: How does this function update the WireGuard peer's state based on the response? I'll watch for any potential race conditions or inconsistent state updates.
* **Error handling**: How does it handle invalid responses or errors during key derivation? Is the error handling secure, or is it leaking information?
* **Memory Management**: Are we seeing any uninitialized memory read-based vulnerabilities, particularly around the message buffers?
|
| 17/2 |
2026/08/08 19:10 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/net/wireguard/noise.c Name:wg_noise_handshake_consume_response]
Results: map[SourceCode: 727: struct wg_peer *
728: wg_noise_handshake_consume_response(struct message_handshake_response *src,
729: struct wg_device *wg)
730: {
731: enum noise_handshake_state state = HANDSHAKE_ZEROED;
732: struct wg_peer *peer = NULL, *ret_peer = NULL;
733: struct noise_handshake *handshake;
734: u8 key[NOISE_SYMMETRIC_KEY_LEN];
735: u8 hash[NOISE_HASH_LEN];
736: u8 chaining_key[NOISE_HASH_LEN];
737: u8 e[NOISE_PUBLIC_KEY_LEN];
738: u8 ephemeral_private[NOISE_PUBLIC_KEY_LEN];
739: u8 static_private[NOISE_PUBLIC_KEY_LEN];
740: u8 preshared_key[NOISE_SYMMETRIC_KEY_LEN];
741:
742: down_read(&wg->static_identity.lock);
743:
744: if (unlikely(!wg->static_identity.has_identity))
745: goto out;
746:
747: handshake = (struct noise_handshake *)wg_index_hashtable_lookup(
748: wg->index_hashtable, INDEX_HASHTABLE_HANDSHAKE,
749: src->receiver_index, &peer);
750: if (unlikely(!handshake))
751: goto out;
752:
753: down_read(&handshake->lock);
754: state = handshake->state;
755: memcpy(hash, handshake->hash, NOISE_HASH_LEN);
756: memcpy(chaining_key, handshake->chaining_key, NOISE_HASH_LEN);
757: memcpy(ephemeral_private, handshake->ephemeral_private,
758: NOISE_PUBLIC_KEY_LEN);
759: memcpy(preshared_key, handshake->preshared_key,
760: NOISE_SYMMETRIC_KEY_LEN);
761: up_read(&handshake->lock);
762:
763: if (state != HANDSHAKE_CREATED_INITIATION)
764: goto fail;
765:
766: /* e */
767: message_ephemeral(e, src->unencrypted_ephemeral, chaining_key, hash);
768:
769: /* ee */
770: if (!mix_dh(chaining_key, NULL, ephemeral_private, e))
771: goto fail;
772:
773: /* se */
774: if (!mix_dh(chaining_key, NULL, wg->static_identity.static_private, e))
775: goto fail;
776:
777: /* psk */
778: mix_psk(chaining_key, hash, key, preshared_key);
779:
780: /* {} */
781: if (!message_decrypt(NULL, src->encrypted_nothing,
782: sizeof(src->encrypted_nothing), key, hash))
783: goto fail;
784:
785: /* Success! Copy everything to peer */
786: down_write(&handshake->lock);
787: /* It's important to check that the state is still the same, while we
788: * have an exclusive lock.
789: */
790: if (handshake->state != state) {
791: up_write(&handshake->lock);
792: goto fail;
793: }
794: memcpy(handshake->remote_ephemeral, e, NOISE_PUBLIC_KEY_LEN);
795: memcpy(handshake->hash, hash, NOISE_HASH_LEN);
796: memcpy(handshake->chaining_key, chaining_key, NOISE_HASH_LEN);
797: handshake->remote_index = src->sender_index;
798: handshake->state = HANDSHAKE_CONSUMED_RESPONSE;
799: up_write(&handshake->lock);
800: ret_peer = peer;
801: goto out;
802:
803: fail:
804: wg_peer_put(peer);
805: out:
806: memzero_explicit(key, NOISE_SYMMETRIC_KEY_LEN);
807: memzero_explicit(hash, NOISE_HASH_LEN);
808: memzero_explicit(chaining_key, NOISE_HASH_LEN);
809: memzero_explicit(ephemeral_private, NOISE_PUBLIC_KEY_LEN);
810: memzero_explicit(static_private, NOISE_PUBLIC_KEY_LEN);
811: memzero_explicit(preshared_key, NOISE_SYMMETRIC_KEY_LEN);
812: up_read(&wg->static_identity.lock);
813: return ret_peer;
814: }
SourceFile:drivers/net/wireguard/noise.c]
|
| 18/2 |
2026/08/08 19:10 |
llm |
expert |
0m
Model:
Tokens:
input: 15250
output: 45
thoughts: 513
Thoughts: **Analyzing the Race Condition in `wg_index_hashtable_insert`**
Okay, let's break down this race condition. I see we're in `wg_index_hashtable_insert`. Line 124 shows a `spin_lock_bh` being acquired, followed by a `hlist_del_init_rcu` operation on line 125, and then the lock is released on line 126. Then, an `rcu_read_lock_bh` is acquired on line 128. That's immediately a red flag. The context of `entry` is key here. I need to understand what `entry` represents.
Aha, `entry` is a member of `struct noise_handshake`, specifically `handshake->entry`. So, we're dealing with the insertion and potential re-insertion of a handshake object into the index hashtable. The function is re-inserting it. Why? It suggests that this `entry` is being re-used or a new index is being generated. That's a good place to start.
Now, if `wg_index_hashtable_insert` is called, `handshake->entry` could already be in the table. Perhaps it's from an earlier handshake step, or maybe it was inserted when the initiation handshake was created or consumed. That's the crux of the problem. We need to trace where it's being added and when it's being removed.
Okay, let's step through the code. When a handshake initiation is created using `wg_noise_handshake_create_initiation`, `handshake->entry` *is* inserted into the index hashtable. That's confirmed.
So, when we receive or send a response, we might insert it again? Let's check `wg_noise_handshake_create_response`. Yes, the code is inserting `handshake->entry` there. `dst->sender_index = wg_index_hashtable_insert(...)` confirms it.
So before `wg_noise_handshake_create_response` is called, the handshake state is `HANDSHAKE_CONSUMED_INITIATION`. The key is: when an initiation is *consumed*, is the entry deleted? We need to look at `wg_noise_handshake_consume_initiation` to figure that out. If it is *not* deleted in `wg_noise_handshake_consume_initiation`, then this re-insertion in `wg_noise_handshake_create_response` could be the problem. And if not deleted, then it's a re-insertion *after* we've consumed it.
The race could then be around the del/insert operation. We have to be very careful around that. The RCU lock is concerning. So, we'll need to check if the RCU lock is used correctly given the possible removal and reinsertion of the same hash table entry.
|
| 19/2 |
2026/08/08 19:10 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/net/wireguard/noise.c Name:wg_noise_handshake_consume_initiation]
Results: map[SourceCode: 583: struct wg_peer *
584: wg_noise_handshake_consume_initiation(struct message_handshake_initiation *src,
585: struct wg_device *wg)
586: {
587: struct wg_peer *peer = NULL, *ret_peer = NULL;
588: struct noise_handshake *handshake;
589: bool replay_attack, flood_attack;
590: u8 key[NOISE_SYMMETRIC_KEY_LEN];
591: u8 chaining_key[NOISE_HASH_LEN];
592: u8 hash[NOISE_HASH_LEN];
593: u8 s[NOISE_PUBLIC_KEY_LEN];
594: u8 e[NOISE_PUBLIC_KEY_LEN];
595: u8 t[NOISE_TIMESTAMP_LEN];
596: u64 initiation_consumption;
597:
598: down_read(&wg->static_identity.lock);
599: if (unlikely(!wg->static_identity.has_identity))
600: goto out;
601:
602: handshake_init(chaining_key, hash, wg->static_identity.static_public);
603:
604: /* e */
605: message_ephemeral(e, src->unencrypted_ephemeral, chaining_key, hash);
606:
607: /* es */
608: if (!mix_dh(chaining_key, key, wg->static_identity.static_private, e))
609: goto out;
610:
611: /* s */
612: if (!message_decrypt(s, src->encrypted_static,
613: sizeof(src->encrypted_static), key, hash))
614: goto out;
615:
616: /* Lookup which peer we're actually talking to */
617: peer = wg_pubkey_hashtable_lookup(wg->peer_hashtable, s);
618: if (!peer)
619: goto out;
620: handshake = &peer->handshake;
621:
622: /* ss */
623: if (!mix_precomputed_dh(chaining_key, key,
624: handshake->precomputed_static_static))
625: goto out;
626:
627: /* {t} */
628: if (!message_decrypt(t, src->encrypted_timestamp,
629: sizeof(src->encrypted_timestamp), key, hash))
630: goto out;
631:
632: down_read(&handshake->lock);
633: replay_attack = memcmp(t, handshake->latest_timestamp,
634: NOISE_TIMESTAMP_LEN) <= 0;
635: flood_attack = (s64)handshake->last_initiation_consumption +
636: NSEC_PER_SEC / INITIATIONS_PER_SECOND >
637: (s64)ktime_get_coarse_boottime_ns();
638: up_read(&handshake->lock);
639: if (replay_attack || flood_attack)
640: goto out;
641:
642: /* Success! Copy everything to peer */
643: down_write(&handshake->lock);
644: memcpy(handshake->remote_ephemeral, e, NOISE_PUBLIC_KEY_LEN);
645: if (memcmp(t, handshake->latest_timestamp, NOISE_TIMESTAMP_LEN) > 0)
646: memcpy(handshake->latest_timestamp, t, NOISE_TIMESTAMP_LEN);
647: memcpy(handshake->hash, hash, NOISE_HASH_LEN);
648: memcpy(handshake->chaining_key, chaining_key, NOISE_HASH_LEN);
649: handshake->remote_index = src->sender_index;
650: initiation_consumption = ktime_get_coarse_boottime_ns();
651: if ((s64)(handshake->last_initiation_consumption - initiation_consumption) < 0)
652: handshake->last_initiation_consumption = initiation_consumption;
653: handshake->state = HANDSHAKE_CONSUMED_INITIATION;
654: up_write(&handshake->lock);
655: ret_peer = peer;
656:
657: out:
658: memzero_explicit(key, NOISE_SYMMETRIC_KEY_LEN);
659: memzero_explicit(hash, NOISE_HASH_LEN);
660: memzero_explicit(chaining_key, NOISE_HASH_LEN);
661: up_read(&wg->static_identity.lock);
662: if (!ret_peer)
663: wg_peer_put(peer);
664: return ret_peer;
665: }
SourceFile:drivers/net/wireguard/noise.c]
|
| 20/2 |
2026/08/08 19:10 |
llm |
expert |
0m
Model:
Tokens:
input: 17886
output: 31
thoughts: 105
Thoughts: **Digging into the WireGuard Handshake Initialization**
Okay, so I'm looking at `wg_noise_handshake_consume_initiation` and something's bugging me. I see that we're *not* inserting the handshake directly into the index hashtable during initialization. That's unusual. Where does the hashtable entry *actually* come from then?
Right, let's think this through. `handshake->entry` is defined as a `struct index_hashtable_entry`, so the entry exists somewhere, we just need to understand when it gets populated and into the hashtable.
Okay, my next instinct is to track down all the instances of where `handshake->entry` is used. Or, since that's a struct and potentially being used via a pointer, let's broaden the search to look for the address of it too, so `&peer->handshake.entry` or anything like that. That should point me right to where this entry is being created and attached to the handshake. That's the missing piece!
|
| 21/2 |
2026/08/08 19:10 |
tool |
grepper |
0m
Args: map[Expression:handshake\.entry PathPrefix:drivers/net/wireguard/]
Results: map[Output:drivers/net/wireguard/peer.c=200=static void kref_release(struct kref *refcount)
--
drivers/net/wireguard/peer.c-211- wg_index_hashtable_remove(peer->device->index_hashtable,
drivers/net/wireguard/peer.c:212: &peer->handshake.entry);
drivers/net/wireguard/peer.c-213-
]
|
| 22/2 |
2026/08/08 19:10 |
llm |
expert |
0m
Model:
Tokens:
input: 18370
output: 32
thoughts: 43
Thoughts: Okay, let's see. My first thought is: **Hunting for Hashtable Operations in the WireGuard Driver**.
I need to dive into the WireGuard code, specifically within the `drivers/net/wireguard/` directory. My aim is to understand how they are managing their index lookups. To get a handle on it quickly, I'm going to search for function calls that specifically deal with index operations on a hashtable. The key functions to look for are `index_hashtable_insert`, `index_hashtable_remove`, and `index_hashtable_replace`. These function names, or ones very similar, are likely the low-level building blocks for managing the index data structure. I'm hoping to quickly identify the insertion, deletion, and possible replacement mechanisms they're using, which will offer a strong clue as to the overall efficiency and memory management strategy of their indexing within the WireGuard implementation. I'll focus my search here first.
|
| 23/2 |
2026/08/08 19:10 |
tool |
grepper |
0m
Args: map[Expression:index_hashtable_ PathPrefix:drivers/net/wireguard/]
Results: map[Output:drivers/net/wireguard/cookie.c=198=void wg_cookie_message_consume(struct message_handshake_cookie *src,
--
drivers/net/wireguard/cookie.c-204-
drivers/net/wireguard/cookie.c:205: if (unlikely(!wg_index_hashtable_lookup(wg->index_hashtable,
drivers/net/wireguard/cookie.c-206- INDEX_HASHTABLE_HANDSHAKE |
--
drivers/net/wireguard/device.c=310=static int wg_newlink(struct net_device *dev,
--
drivers/net/wireguard/device.c-330-
drivers/net/wireguard/device.c:331: wg->index_hashtable = wg_index_hashtable_alloc();
drivers/net/wireguard/device.c-332- if (!wg->index_hashtable)
--
drivers/net/wireguard/noise.c=88=void wg_noise_handshake_clear(struct noise_handshake *handshake)
--
drivers/net/wireguard/noise.c-90- down_write(&handshake->lock);
drivers/net/wireguard/noise.c:91: wg_index_hashtable_remove(
drivers/net/wireguard/noise.c-92- handshake->entry.peer->device->index_hashtable,
--
drivers/net/wireguard/noise.c=117=static void keypair_free_kref(struct kref *kref)
--
drivers/net/wireguard/noise.c-125- keypair->entry.peer->internal_id);
drivers/net/wireguard/noise.c:126: wg_index_hashtable_remove(keypair->entry.peer->device->index_hashtable,
drivers/net/wireguard/noise.c-127- &keypair->entry);
--
drivers/net/wireguard/noise.c=131=void wg_noise_keypair_put(struct noise_keypair *keypair, bool unreference_now)
--
drivers/net/wireguard/noise.c-135- if (unlikely(unreference_now))
drivers/net/wireguard/noise.c:136: wg_index_hashtable_remove(
drivers/net/wireguard/noise.c-137- keypair->entry.peer->device->index_hashtable,
--
drivers/net/wireguard/noise.c=517=wg_noise_handshake_create_initiation(struct message_handshake_initiation *dst,
--
drivers/net/wireguard/noise.c-568-
drivers/net/wireguard/noise.c:569: dst->sender_index = wg_index_hashtable_insert(
drivers/net/wireguard/noise.c-570- handshake->entry.peer->device->index_hashtable,
--
drivers/net/wireguard/noise.c=667=bool wg_noise_handshake_create_response(struct message_handshake_response *dst,
--
drivers/net/wireguard/noise.c-712-
drivers/net/wireguard/noise.c:713: dst->sender_index = wg_index_hashtable_insert(
drivers/net/wireguard/noise.c-714- handshake->entry.peer->device->index_hashtable,
--
drivers/net/wireguard/noise.c=728=wg_noise_handshake_consume_response(struct message_handshake_response *src,
--
drivers/net/wireguard/noise.c-746-
drivers/net/wireguard/noise.c:747: handshake = (struct noise_handshake *)wg_index_hashtable_lookup(
drivers/net/wireguard/noise.c-748- wg->index_hashtable, INDEX_HASHTABLE_HANDSHAKE,
--
drivers/net/wireguard/noise.c=816=bool wg_noise_handshake_begin_session(struct noise_handshake *handshake,
--
drivers/net/wireguard/noise.c-849- handshake->entry.peer->internal_id);
drivers/net/wireguard/noise.c:850: ret = wg_index_hashtable_replace(
drivers/net/wireguard/noise.c-851- handshake->entry.peer->device->index_hashtable,
--
drivers/net/wireguard/noise.h=30=struct noise_keypair {
drivers/net/wireguard/noise.h:31: struct index_hashtable_entry entry;
drivers/net/wireguard/noise.h-32- struct noise_symmetric_key sending;
--
drivers/net/wireguard/noise.h=65=struct noise_handshake {
drivers/net/wireguard/noise.h:66: struct index_hashtable_entry entry;
drivers/net/wireguard/noise.h-67-
--
drivers/net/wireguard/peer.c=94=static void peer_remove_after_dead(struct wg_peer *peer)
--
drivers/net/wireguard/peer.c-138- * allowedips; we won't have new references in (2) eventually, because
drivers/net/wireguard/peer.c:139: * wg_index_hashtable_lookup will always return NULL, since we removed
drivers/net/wireguard/peer.c-140- * all existing keypairs and no more can be created; we won't have new
--
drivers/net/wireguard/peer.c=200=static void kref_release(struct kref *refcount)
--
drivers/net/wireguard/peer.c-210- */
drivers/net/wireguard/peer.c:211: wg_index_hashtable_remove(peer->device->index_hashtable,
drivers/net/wireguard/peer.c-212- &peer->handshake.entry);
--
drivers/net/wireguard/peerlookup.c=73=static struct hlist_head *index_bucket(struct index_hashtable *table,
--
drivers/net/wireguard/peerlookup.c-82-
drivers/net/wireguard/peerlookup.c:83:struct index_hashtable *wg_index_hashtable_alloc(void)
drivers/net/wireguard/peerlookup.c-84-{
--
drivers/net/wireguard/peerlookup.c-118-
drivers/net/wireguard/peerlookup.c:119:__le32 wg_index_hashtable_insert(struct index_hashtable *table,
drivers/net/wireguard/peerlookup.c:120: struct index_hashtable_entry *entry)
drivers/net/wireguard/peerlookup.c-121-{
drivers/net/wireguard/peerlookup.c:122: struct index_hashtable_entry *existing_entry;
drivers/net/wireguard/peerlookup.c-123-
--
drivers/net/wireguard/peerlookup.c-165-
drivers/net/wireguard/peerlookup.c:166:bool wg_index_hashtable_replace(struct index_hashtable *table,
drivers/net/wireguard/peerlookup.c:167: struct index_hashtable_entry *old,
drivers/net/wireguard/peerlookup.c:168: struct index_hashtable_entry *new)
drivers/net/wireguard/peerlookup.c-169-{
--
drivers/net/wireguard/peerlookup.c-191-
drivers/net/wireguard/peerlookup.c:192:void wg_index_hashtable_remove(struct index_hashtable *table,
drivers/net/wireguard/peerlookup.c:193: struct index_hashtable_entry *entry)
drivers/net/wireguard/peerlookup.c-194-{
--
drivers/net/wireguard/peerlookup.c-200-/* Returns a strong reference to a entry->peer */
drivers/net/wireguard/peerlookup.c:201:struct index_hashtable_entry *
drivers/net/wireguard/peerlookup.c:202:wg_index_hashtable_lookup(struct index_hashtable *table,
drivers/net/wireguard/peerlookup.c:203: const enum index_hashtable_type type_mask,
drivers/net/wireguard/peerlookup.c-204- const __le32 index, struct wg_peer **peer)
drivers/net/wireguard/peerlookup.c-205-{
drivers/net/wireguard/peerlookup.c:206: struct index_hashtable_entry *iter_entry, *entry = NULL;
drivers/net/wireguard/peerlookup.c-207-
--
drivers/net/wireguard/peerlookup.h=33=struct index_hashtable {
--
drivers/net/wireguard/peerlookup.h-38-
drivers/net/wireguard/peerlookup.h:39:enum index_hashtable_type {
drivers/net/wireguard/peerlookup.h-40- INDEX_HASHTABLE_HANDSHAKE = 1U << 0,
--
drivers/net/wireguard/peerlookup.h-43-
drivers/net/wireguard/peerlookup.h:44:struct index_hashtable_entry {
drivers/net/wireguard/peerlookup.h-45- struct wg_peer *peer;
drivers/net/wireguard/peerlookup.h-46- struct hlist_node index_hash;
drivers/net/wireguard/peerlookup.h:47: enum index_hashtable_type type;
drivers/net/wireguard/peerlookup.h-48- __le32 index;
--
drivers/net/wireguard/peerlookup.h-50-
drivers/net/wireguard/peerlookup.h:51:struct index_hashtable *wg_index_hashtable_alloc(void);
drivers/net/wireguard/peerlookup.h:52:__le32 wg_index_hashtable_insert(struct index_hashtable *table,
drivers/net/wireguard/peerlookup.h:53: struct index_hashtable_entry *entry);
drivers/net/wireguard/peerlookup.h:54:bool wg_index_hashtable_replace(struct index_hashtable *table,
drivers/net/wireguard/peerlookup.h:55: struct index_hashtable_entry *old,
drivers/net/wireguard/peerlookup.h:56: struct index_hashtable_entry *new);
drivers/net/wireguard/peerlookup.h:57:void wg_index_hashtable_remove(struct index_hashtable *table,
drivers/net/wireguard/peerlookup.h:58: struct index_hashtable_entry *entry);
drivers/net/wireguard/peerlookup.h:59:struct index_hashtable_entry *
drivers/net/wireguard/peerlookup.h:60:wg_index_hashtable_lookup(struct index_hashtable *table,
drivers/net/wireguard/peerlookup.h:61: const enum index_hashtable_type type_mask,
drivers/net/wireguard/peerlookup.h-62- const __le32 index, struct wg_peer **peer);
--
drivers/net/wireguard/receive.c=509=static void wg_packet_consume_data(struct wg_device *wg, struct sk_buff *skb)
--
drivers/net/wireguard/receive.c-516- PACKET_CB(skb)->keypair =
drivers/net/wireguard/receive.c:517: (struct noise_keypair *)wg_index_hashtable_lookup(
drivers/net/wireguard/receive.c-518- wg->index_hashtable, INDEX_HASHTABLE_KEYPAIR, idx,
]
|
| 24/2 |
2026/08/08 19:10 |
llm |
expert |
0m
Model:
Tokens:
input: 21618
output: 43
thoughts: 21
Thoughts: Here's my attempt at summarizing the thought process, tailored for an expert audience:
**Analyzing the wg_index_hashtable_remove Function**
Alright, let's dive into the `wg_index_hashtable_remove` function. I need to refresh my memory on the specifics of this routine. My goal is to quickly grasp its core functionality β how it handles removal operations within this particular hashtable implementation. I'm especially interested in the edge cases: what happens when a key isn't found, how it deals with collisions, if there's any rehashing involved, and if this function could potentially introduce any subtle concurrency issues in a multithreaded environment. Knowing the locking strategy here is paramount. Specifically, is it a reader-writer lock or a more granular approach. Are there any potential ABA problems? I'm already anticipating the typical considerations for this kind of low-level data structure. I'll be looking closely at the pointer manipulation to ensure they are safe. A close inspection of the memory freeing operations will be required to check for any memory leaks. I'm keen to evaluate its performance characteristics in scenarios with high contention. So, let's get into the code!
|
| 25/2 |
2026/08/08 19:10 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/net/wireguard/peerlookup.c Name:wg_index_hashtable_remove]
Results: map[SourceCode: 192: void wg_index_hashtable_remove(struct index_hashtable *table,
193: struct index_hashtable_entry *entry)
194: {
195: spin_lock_bh(&table->lock);
196: hlist_del_init_rcu(&entry->index_hash);
197: spin_unlock_bh(&table->lock);
198: }
SourceFile:drivers/net/wireguard/peerlookup.c]
|
| 26/2 |
2026/08/08 19:10 |
llm |
expert |
0m
Model:
Tokens:
input: 22081
output: 45
thoughts: 1515
Thoughts: **Analyzing Potential Race Conditions in WireGuard's Index Hashtable**
Okay, so I'm looking at `wg_index_hashtable_remove`, and it's calling `hlist_del_init_rcu` under the table's lock. That makes sense. Now, let's examine `wg_index_hashtable_insert`. The key is at line 125, it calls `hlist_del_init_rcu`, and then on line 132 it assigns a new random index. I see why it first removes the entry β it might already be in the table. But the timing is what grabs my attention.
I'm thinking about concurrent access and RCU. The entry gets removed, and then *immediately*, a new index is written. This means a concurrent reader might have a reference to this entry and is in the middle of traversing the table. The reader might see the entry with either the old *or* the new value for `entry->index`.
Now, is that a problem? Let's break down the possibilities:
* **Reader sees the old index:** The lookup *succeeds*, but returns an entry that's just been deleted. That's fine under RCU; a reader can see an element that's concurrently being deleted. This is the whole point of RCU.
* **Reader sees the new index (or a torn value):** The comparison `iter_entry->index == index` will fail, and the lookup returns NULL (or continues the search). That's okay too, since the entry *is* being deleted, so returning NULL is a perfectly valid outcome.
But wait a minute... what if the reader gets a *torn* value? Now, I need to think about the atomicity of the `__le32` read and write. Are 32-bit reads/writes atomic on most architectures, including 32-bit ones? Yes, usually. But what about compiler optimizations? Could the compiler split the 32-bit write or read?
Could the compiler split the write into two 16-bit writes? Yes, if it's not `WRITE_ONCE`. Could the compiler split the read into two 16-bit reads? Yes, if it's not `READ_ONCE`. What about "Fused Accesses"? Now, what if the compiler decides to reload `iter_entry->index` multiple times? In `wg_index_hashtable_lookup`, the comparison `iter_entry->index == index`, that is not a problem. But, is there any other place `iter_entry->index` is read?
Oh! Right, `wg_index_hashtable_insert` itself. Here's where the new index is written, and then used in a comparison.
```c
hlist_for_each_entry_rcu_bh(existing_entry,
index_bucket(table, entry->index),
index_hash) {
if (existing_entry->index == entry->index)
/* If it's already in use, we continue searching. */
goto search_unused_slot;
}
```
Could the compiler reload `entry->index` *after* the initial write, and if so, could it be concurrently modified? Who *can* modify `entry->index`? Only the thread executing `wg_index_hashtable_insert`, right? It's a per-peer handshake operation, and those are serialized. I need to make sure about that.
Let's check `wg_noise_handshake_create_response`. Ah, yes, `handshake->lock` is held as a *write* lock before the insert.
```c
down_read(&handshake->static_identity->lock);
down_write(&handshake->lock);
...
dst->sender_index = wg_index_hashtable_insert(
handshake->entry.peer->device->index_hashtable,
&handshake->entry);
...
up_write(&handshake->lock);
```
So yes, only one thread can be in `wg_index_hashtable_insert` for this entry. And that is also true for `wg_noise_handshake_create_initiation`. So, I think we are good.
|
| 27/2 |
2026/08/08 19:10 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/net/wireguard/noise.c Name:wg_noise_handshake_create_initiation]
Results: map[SourceCode: 516: bool
517: wg_noise_handshake_create_initiation(struct message_handshake_initiation *dst,
518: struct noise_handshake *handshake)
519: {
520: u8 timestamp[NOISE_TIMESTAMP_LEN];
521: u8 key[NOISE_SYMMETRIC_KEY_LEN];
522: bool ret = false;
523:
524: /* We need to wait for crng _before_ taking any locks, since
525: * curve25519_generate_secret uses get_random_bytes_wait.
526: */
527: wait_for_random_bytes();
528:
529: down_read(&handshake->static_identity->lock);
530: down_write(&handshake->lock);
531:
532: if (unlikely(!handshake->static_identity->has_identity))
533: goto out;
534:
535: dst->header.type = cpu_to_le32(MESSAGE_HANDSHAKE_INITIATION);
536:
537: handshake_init(handshake->chaining_key, handshake->hash,
538: handshake->remote_static);
539:
540: /* e */
541: curve25519_generate_secret(handshake->ephemeral_private);
542: if (!curve25519_generate_public(dst->unencrypted_ephemeral,
543: handshake->ephemeral_private))
544: goto out;
545: message_ephemeral(dst->unencrypted_ephemeral,
546: dst->unencrypted_ephemeral, handshake->chaining_key,
547: handshake->hash);
548:
549: /* es */
550: if (!mix_dh(handshake->chaining_key, key, handshake->ephemeral_private,
551: handshake->remote_static))
552: goto out;
553:
554: /* s */
555: message_encrypt(dst->encrypted_static,
556: handshake->static_identity->static_public,
557: NOISE_PUBLIC_KEY_LEN, key, handshake->hash);
558:
559: /* ss */
560: if (!mix_precomputed_dh(handshake->chaining_key, key,
561: handshake->precomputed_static_static))
562: goto out;
563:
564: /* {t} */
565: tai64n_now(timestamp);
566: message_encrypt(dst->encrypted_timestamp, timestamp,
567: NOISE_TIMESTAMP_LEN, key, handshake->hash);
568:
569: dst->sender_index = wg_index_hashtable_insert(
570: handshake->entry.peer->device->index_hashtable,
571: &handshake->entry);
572:
573: handshake->state = HANDSHAKE_CREATED_INITIATION;
574: ret = true;
575:
576: out:
577: up_write(&handshake->lock);
578: up_read(&handshake->static_identity->lock);
579: memzero_explicit(key, NOISE_SYMMETRIC_KEY_LEN);
580: return ret;
581: }
SourceFile:drivers/net/wireguard/noise.c]
|
| 28/2 |
2026/08/08 19:10 |
llm |
expert |
0m
Model:
Tokens:
input: 25629
output: 44
thoughts: 452
Thoughts: **Data Race Investigation: `wg_index_hashtable`**
Okay, so I'm looking at `wg_noise_handshake_create_initiation`, and it confirms my suspicion; it's holding a write lock on `handshake->lock`. Good, that means only one writer can touch `entry->index` at a time. Now, let's switch gears and examine the reader function, which is `wg_index_hashtable_lookup`. Alright, no explicit locks held there, only `rcu_read_lock_bh()`. That makes it lockless, which is... concerning.
So, the writer's writing `entry->index` without any lock that the reader also holds. Is this a data race? Yup, KCSAN flagged it: a write by task 3375 on CPU 1, and a read by task 3410 on CPU 0 to the same memory location. Now, crucial question: is this BENIGN or HARMFUL?
Let's think. If the reader gets a stale or a fresh value, it will work.
- If it reads the old value, it'll still match `index` and return the entry. Then, the system will delete the entry.
Wait, if the entry was deleted from the hashtable, is it safe to return it?
Yes, because `wg_index_hashtable_lookup` uses `wg_peer_get_maybe_zero(entry->peer)` to get a strong reference.
Wait, the peer's still valid, right? Yes, its lifecycle is controlled by `kref` and RCU. Let's make sure that that's correct, and verify that the `wg_peer_get_maybe_zero` function is indeed properly managing the peer's lifetime.
|
| 29/2 |
2026/08/08 19:10 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/net/wireguard/peerlookup.c Name:wg_peer_get_maybe_zero]
Results: map[SourceCode: 71: struct wg_peer *wg_peer_get_maybe_zero(struct wg_peer *peer)
72: {
73: RCU_LOCKDEP_WARN(!rcu_read_lock_bh_held(),
74: "Taking peer reference without holding the RCU read lock");
75: if (unlikely(!peer || !kref_get_unless_zero(&peer->refcount)))
76: return NULL;
77: return peer;
78: }
SourceFile:drivers/net/wireguard/peer.c]
|
| 30/2 |
2026/08/08 19:10 |
llm |
expert |
0m
Model:
Tokens:
input: 26676
output: 38
thoughts: 1537
Thoughts: **Analyzing Potential Data Races in WireGuard Handshake Lookup**
Okay, so I'm diving into this WireGuard code, specifically how it handles handshake lookups within the index hashtable, and I'm starting to see some interesting interactions. I noticed that `wg_peer_get_maybe_zero` uses `kref_get_unless_zero`. This means that if the peer is being destroyed, it will safely return NULL or 0, preventing any use-after-free scenarios. That seems sound.
But then, the handshake itself is whatβs intriguing. I need to understand what's happening when `wg_index_hashtable_lookup` provides the handshake. It returns a reference to the peer, which is good, and the pointer to the handshake itself, which makes sense because the handshake contains the `entry`.
Now, let's look at `wg_noise_handshake_consume_response`. Here, it calls `wg_index_hashtable_lookup` and attempts to acquire a read lock on `handshake->lock`. But hold on. `wg_index_hashtable_insert`, which is called within `wg_noise_handshake_create_response`, takes a write lock on `handshake->lock`. So, there's potential concurrent access! If a lookup is happening while an insert is happening, the lookup might block on the write lock, which seems safe as the read lock will be acquired after the write lock is released.
The key is the state. After an insert, the state is set to `HANDSHAKE_CREATED_RESPONSE`. When a reader acquires the read lock, it sees this state. But `wg_noise_handshake_consume_response` expects `HANDSHAKE_CREATED_INITIATION`. So the reader will jump to fail which is safe.
Now, let's consider a torn read of `entry->index`. If the read is garbage, the lookup will fail, and it's also safe. So even a torn read does not seem to present an issue.
Then I wondered, could a data race on `entry->index` still cause an issue? Is `entry->index` protected with `READ_ONCE` or `WRITE_ONCE`? No, it's not. That means the compiler is free to optimize, which could lead to torn reads or writes. However, even if there's a torn read, the comparison should still fail or, in the extremely unlikely case of a match, it would be caught by a subsequent state check or cryptographic verification.
Wait, what about that false positive match? Even if the reader finds the wrong handshake, it will check the `handshake->state` or the cryptographic MAC and it will fail! WireGuard is a cryptographic protocol, so the authentication will catch any false matches.
But is it correct to say the data race is benign? According to LKMM and KCSAN, concurrent accesses need synchronization, `READ_ONCE`, or `WRITE_ONCE` or `data_race()`. Letβs classify this. Is this "Heuristic Hints" or "Marked Reloads" or "Safe Overwrites"? Based on the definitions, I'd say this falls under the category of "BENIGN (Truly Benign)" as it's structurally tolerant to stale or torn reads. It's a "Safe Overwrite" because even a wrong lookup will be caught by checks.
Let's search the git history of the Linux kernel to check for discussions.
|
| 31/2 |
2026/08/08 19:11 |
tool |
git-log |
0m
Args: map[CodeRegexp:wg_index_hashtable_insert PathPrefix:drivers/net/wireguard/]
Results: map[Output:e7096c131e51 net: WireGuard secure network tunnel
]
|
| 32/2 |
2026/08/08 19:11 |
llm |
expert |
0m
Model:
Tokens:
input: 29021
output: 74
thoughts: 48
Thoughts: Okay, here's my interpretation of those thoughts, framed as my own:
**Investigating Potential Concurrency Issues**
Alright, before I dive too deep, I need to get a better overview of what's been happening in the codebase related to these areas. Let me start by casting a wider net. I'm going to search through the commit history. I'll use regular expressions to look for any commits that mention things like "wireguard," since that can introduce its own set of concurrency challenges, of course. Then, let's look for commits mentioning terms like "race," which is a clear red flag for concurrency problems, or "KCSAN," since that's a known race detector. I'll also check for occurrences of "READ_ONCE" and "WRITE_ONCE," because those are often used to try to safely access shared memory. And finally, I should probably also look for commits touching the "index_hashtable" or anything associated with it, just to see if there's been recent activity there, since the hashtable itself can be a source of contention. This should give me a good jumping-off point to see if there's been recent activity related to those specific search terms that I can use to explore further.
|
| 33/2 |
2026/08/08 19:11 |
tool |
git-log |
0m
Args: map[MessageRegexps:[wireguard race]]
Results: map[Output:677e6123e3d2 LoongArch: BPF: Disable trampoline for kernel module function trace
af232e7615e4 ipv6: guard ip6_mr_output() with rcu
cdd0b9132d71 ip: Return drop reason if in_dev is NULL in ip_route_input_rcu().
a848c29e3486 NFSv4: Prevent NULL-pointer dereference in nfs42_complete_copies()
49d14b54a527 net: test for not too small csum_start in virtio_net_hdr_to_skb()
7e4196935069 fou: Fix null-ptr-deref in GRO.
d9f49c3106e4 bcachefs: fix field-spanning write warning
381a7d453fa2 wireguard: send: annotate intentional data race in checking empty queue
2fe3d6d2053c wireguard: queueing: annotate intentional data race in cpu round robin
d46401052c2d xfrm6: check ip6_dst_idev() return value in xfrm6_get_saddr()
]
|
| 34/2 |
2026/08/08 19:11 |
tool |
git-log |
0m
Args: map[MessageRegexps:[wireguard KCSAN]]
Results: map[Output:381a7d453fa2 wireguard: send: annotate intentional data race in checking empty queue
2fe3d6d2053c wireguard: queueing: annotate intentional data race in cpu round robin
bba045dc4d99 wireguard: receive: annotate data-race around receiving_counter.counter
93da8d75a665 wireguard: use DEV_STATS_INC()
195374d89368 ipv4: fib: annotate races around nh->nh_saddr_genid and nh->nh_saddr
de5724ca38fd xfrm: fix a data-race in xfrm_lookup_with_ifid()
fce92af1c29d ipv4: annotate data-races around fi->fib_dead
0a9e5794b21e xfrm: annotate data-race around use_time
dcd54265c8bc drop_monitor: fix data-race in dropmon_net_event / trace_napi_poll_hit
]
|
| 35/2 |
2026/08/08 19:11 |
tool |
git-log |
0m
Args: map[MessageRegexps:[wireguard READ_ONCE]]
Results: map[Output:381a7d453fa2 wireguard: send: annotate intentional data race in checking empty queue
bba045dc4d99 wireguard: receive: annotate data-race around receiving_counter.counter
0a9e5794b21e xfrm: annotate data-race around use_time
dcd54265c8bc drop_monitor: fix data-race in dropmon_net_event / trace_napi_poll_hit
]
|
| 36/2 |
2026/08/08 19:11 |
llm |
expert |
0m
Model:
Tokens:
input: 30092
output: 27
thoughts: 24
Thoughts: Here's my attempt at summarizing the thought process, tailored for an expert audience:
**Investigating Peer Lookup in WireGuard**
Okay, so I'm diving into `drivers/net/wireguard/peerlookup.c` again. I need to quickly determine the recent history of this file. Specifically, I'm trying to figure out if there have been any other commits that might be relevant to the current problem I'm working on. My immediate thought is to check for recent modifications in the commit logs, as that's often the quickest way to gain context. Maybe there's a related bug fix or a subtle change in the lookup algorithm itself that I'm overlooking. I'm hoping I can uncover some relevant information that I might have missed in my previous investigations. It's a key file, so a few extra minutes spent going over recent changes is often worth the time.
|
| 37/2 |
2026/08/08 19:11 |
tool |
git-log |
0m
Args: map[PathPrefix:drivers/net/wireguard/peerlookup.c]
Results: map[Output:bf4afc53b77a Convert 'alloc_obj' family to use the new default GFP_KERNEL argument
69050f8d6d07 treewide: Replace kmalloc with kmalloc_obj for non-scalar types
6147f7b1e90f wireguard: peerlookup: take lock before checking hash in replace operation
e7096c131e51 net: WireGuard secure network tunnel
]
|
| 38/2 |
2026/08/08 19:11 |
llm |
expert |
0m
Model:
Tokens:
input: 30446
output: 27
thoughts: 33
Thoughts: Okay, let's dive into this commit, `6147f7b1e90f`, specifically focused on WireGuard's peer lookup: "take lock before checking hash in replace operation." My initial assessment is that this sounds like a potential race condition fix, and I should be able to quickly evaluate its implications. Given my expertise, I'm already anticipating the typical threading concerns in a networking context and how those might impact data integrity in a critical component like a peer lookup table. I need to understand the precise context of the "replace operation" β are we talking about adding, modifying, or removing peer entries? Is the hash table being accessed concurrently by multiple threads? The commit message should hopefully clarify the specifics of the lock and hash check interaction. I'm mentally parsing the title: "take lock *before* checking hash." This immediately suggests the hash check was potentially occurring in a critical section without the necessary protection. So, the question is, what vulnerabilities does this expose and what type of lock is being taken? This is essential for understanding the actual performance and correctness implications. I will use the code as my first port of call.
|
| 39/2 |
2026/08/08 19:11 |
tool |
git-show |
0m
Args:
Results: map[Output:commit 6147f7b1e90ff09bd52afc8b9206a7fcd133daf7
Author: Jason A. Donenfeld <Jason@zx2c4.com>
Date: Wed Sep 9 13:58:15 2020 +0200
wireguard: peerlookup: take lock before checking hash in replace operation
Eric's suggested fix for the previous commit's mentioned race condition
was to simply take the table->lock in wg_index_hashtable_replace(). The
table->lock of the hash table is supposed to protect the bucket heads,
not the entires, but actually, since all the mutator functions are
already taking it, it makes sense to take it too for the test to
hlist_unhashed, as a defense in depth measure, so that it no longer
races with deletions, regardless of what other locks are protecting
individual entries. This is sensible from a performance perspective
because, as Eric pointed out, the case of being unhashed is already the
unlikely case, so this won't add common contention. And comparing
instructions, this basically doesn't make much of a difference other
than pushing and popping %r13, used by the new `bool ret`. More
generally, I like the idea of locking consistency across table mutator
functions, and this might let me rest slightly easier at night.
Suggested-by: Eric Dumazet <edumazet@google.com>
Link: https://lore.kernel.org/wireguard/20200908145911.4090480-1-edumazet@google.com/
Fixes: e7096c131e51 ("net: WireGuard secure network tunnel")
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
diff --git a/drivers/net/wireguard/peerlookup.c b/drivers/net/wireguard/peerlookup.c
index e4deb331476b..f2783aa7a88f 100644
--- a/drivers/net/wireguard/peerlookup.c
+++ b/drivers/net/wireguard/peerlookup.c
@@ -167,9 +167,13 @@ bool wg_index_hashtable_replace(struct index_hashtable *table,
struct index_hashtable_entry *old,
struct index_hashtable_entry *new)
{
- if (unlikely(hlist_unhashed(&old->index_hash)))
- return false;
+ bool ret;
+
spin_lock_bh(&table->lock);
+ ret = !hlist_unhashed(&old->index_hash);
+ if (unlikely(!ret))
+ goto out;
+
new->index = old->index;
hlist_replace_rcu(&old->index_hash, &new->index_hash);
@@ -180,8 +184,9 @@ bool wg_index_hashtable_replace(struct index_hashtable *table,
* simply gets dropped, which isn't terrible.
*/
INIT_HLIST_NODE(&old->index_hash);
+out:
spin_unlock_bh(&table->lock);
- return true;
+ return ret;
}
void wg_index_hashtable_remove(struct index_hashtable *table,
]
|
| 40/2 |
2026/08/08 19:11 |
llm |
expert |
0m
Model:
Tokens:
input: 31628
output: 19
thoughts: 3125
Thoughts: **Benign Data Race Analysis: `wg_index_hashtable_insert` vs. `wg_index_hashtable_lookup`**
Okay, I've got to carefully dissect this data race between insertion and lookup in the WireGuard index hashtable. First off, I'm noting that `wg_index_hashtable_insert` at its core involves removing an entry from the hash table (`hlist_del_init_rcu`) using RCU and then assigning a new index to it. Concurrent lookups, using `rcu_read_lock_bh()`, might be in progress when the removal occurs. The core concern centers on whether these lookups see a partially updated `entry->index` during this process, potentially creating a harmful condition.
First, I looked at the scenario where a reader potentially encounters the entry post-deletion, but before a new random index is written to it. If the reader finds a stale value (the *old* index), the comparison in `wg_index_hashtable_lookup` (`iter_entry->index == index`) evaluates to true and the lookup continues which is perfectly acceptable behavior because the entry could have been removed concurrently.
Now, if a reader encounters the entry after it's being deleted but the insertion has begun the write to `entry->index` and the reader observes the *new* value (or a torn value), the comparison will now almost certainly evaluate to false, which is correct because the lookup should not match a deleted entry. The reader's outcome is again correct for a concurrent lookup.
I also considered a torn read, where a partially updated value is read. If the reader is looking for `index_A` (the old index of the element), and it reads a torn value of `index_B`, that will generate a false match. However, the reader is only looking for a value in `index_A`'s bucket, so it shouldn't even encounter the element. However, in the case of a collision, where the element and the target could be in the same bucket, in theory, the reader could see a torn value that *happens* to match another index, say `index_B`. In this case, the reader thinks it found the item but it didn't! This is theoretically possible, but is this a bug? I believe it is not. Here's why.
I traced the execution in `wg_noise_handshake_consume_response` for a potential return of a mismatched entry. The function dereferences the handshake and copies some keys. Given the *incorrect* keys, the handshake fails! Critically, no memory corruption or crash results; the packet is simply rejected. This is the desired behavior for an invalid packet.
The chance of a torn read is exceedingly small. Because `index` is a 32-bit field, 32-bit aligned reads and writes are atomic on all the architectures the Linux kernel supports. No tearing will occur.
I analyzed whether the compiler could introduce any errors through optimizations. I looked at the code and realized that the compiler reads are straightforward. No complex optimization can cause a problem because there is no chance that any other threads can write to the index in the critical section of `wg_index_hashtable_lookup`.
Finally, after this thorough review, I have concluded that this race condition is BENIGN. The key factors that make this benign include the checks within the loop that will make a mismatch a correct response, atomic access to the variable, and the error checks within the function that can handle an incorrect packet. There are no critical sections in the read that can cause an error or crash.
|
| 41/2 |
2026/08/08 19:12 |
tool |
git-log |
0m
Args: map[MessageRegexps:[peerlookup]]
Results: map[Output:6147f7b1e90f wireguard: peerlookup: take lock before checking hash in replace operation
9179ba31367b wireguard: noise: take lock when removing handshake entry from table
e7096c131e51 net: WireGuard secure network tunnel
]
|
| 42/2 |
2026/08/08 19:12 |
llm |
expert |
0m
Model:
Tokens:
input: 35619
output: 26
thoughts: 33
Thoughts: Here's my take on those thoughts:
**Analyzing a WireGuard Noise Protocol Lock Issue**
Okay, let's dive into this commit, `9179ba31367b wireguard: noise: take lock when removing handshake entry from table`. My first thought is to understand the context. It sounds like we're dealing with a WireGuard implementation, specifically the Noise protocol handshake. The phrase "take lock when removing handshake entry from table" is key. My experience immediately tells me this probably indicates a potential race condition. We're talking about a concurrent data structure β likely a hash table or similar β that stores handshake information. The code appears to be addressing a locking issue when removing an entry from this table. This suggests the removal process isn't properly synchronized. A lock is crucial here to prevent data corruption or inconsistencies if multiple threads or processes try to access and modify the table simultaneously. I need to understand *why* this locking wasn't in place earlier and what the specific risk was before this commit. I'm wondering if this was a theoretical problem identified through static analysis or if it manifested as a real-world bug. Time to dig into the surrounding code and understand the control flow.
|
| 43/2 |
2026/08/08 19:12 |
tool |
git-show |
0m
Args:
Results: map[Output:commit 9179ba31367bcf481c3c79b5f028c94faad9f30a
Author: Jason A. Donenfeld <Jason@zx2c4.com>
Date: Wed Sep 9 13:58:14 2020 +0200
wireguard: noise: take lock when removing handshake entry from table
Eric reported that syzkaller found a race of this variety:
CPU 1 CPU 2
-------------------------------------------|---------------------------------------
wg_index_hashtable_replace(old, ...) |
if (hlist_unhashed(&old->index_hash)) |
| wg_index_hashtable_remove(old)
| hlist_del_init_rcu(&old->index_hash)
| old->index_hash.pprev = NULL
hlist_replace_rcu(&old->index_hash, ...) |
*old->index_hash.pprev |
Syzbot wasn't actually able to reproduce this more than once or create a
reproducer, because the race window between checking "hlist_unhashed" and
calling "hlist_replace_rcu" is just so small. Adding an mdelay(5) or
similar there helps make this demonstrable using this simple script:
#!/bin/bash
set -ex
trap 'kill $pid1; kill $pid2; ip link del wg0; ip link del wg1' EXIT
ip link add wg0 type wireguard
ip link add wg1 type wireguard
wg set wg0 private-key <(wg genkey) listen-port 9999
wg set wg1 private-key <(wg genkey) peer $(wg show wg0 public-key) endpoint 127.0.0.1:9999 persistent-keepalive 1
wg set wg0 peer $(wg show wg1 public-key)
ip link set wg0 up
yes link set wg1 up | ip -force -batch - &
pid1=$!
yes link set wg1 down | ip -force -batch - &
pid2=$!
wait
The fundumental underlying problem is that we permit calls to wg_index_
hashtable_remove(handshake.entry) without requiring the caller to take
the handshake mutex that is intended to protect members of handshake
during mutations. This is consistently the case with calls to wg_index_
hashtable_insert(handshake.entry) and wg_index_hashtable_replace(
handshake.entry), but it's missing from a pertinent callsite of wg_
index_hashtable_remove(handshake.entry). So, this patch makes sure that
mutex is taken.
The original code was a little bit funky though, in the form of:
remove(handshake.entry)
lock(), memzero(handshake.some_members), unlock()
remove(handshake.entry)
The original intention of that double removal pattern outside the lock
appears to be some attempt to prevent insertions that might happen while
locks are dropped during expensive crypto operations, but actually, all
callers of wg_index_hashtable_insert(handshake.entry) take the write
lock and then explicitly check handshake.state, as they should, which
the aforementioned memzero clears, which means an insertion should
already be impossible. And regardless, the original intention was
necessarily racy, since it wasn't guaranteed that something else would
run after the unlock() instead of after the remove(). So, from a
soundness perspective, it seems positive to remove what looks like a
hack at best.
The crash from both syzbot and from the script above is as follows:
general protection fault, probably for non-canonical address 0xdffffc0000000000: 0000 [#1] PREEMPT SMP KASAN
KASAN: null-ptr-deref in range [0x0000000000000000-0x0000000000000007]
CPU: 0 PID: 7395 Comm: kworker/0:3 Not tainted 5.9.0-rc4-syzkaller #0
Hardware name: Google Google Compute Engine/Google Compute Engine, BIOS Google 01/01/2011
Workqueue: wg-kex-wg1 wg_packet_handshake_receive_worker
RIP: 0010:hlist_replace_rcu include/linux/rculist.h:505 [inline]
RIP: 0010:wg_index_hashtable_replace+0x176/0x330 drivers/net/wireguard/peerlookup.c:174
Code: 00 fc ff df 48 89 f9 48 c1 e9 03 80 3c 01 00 0f 85 44 01 00 00 48 b9 00 00 00 00 00 fc ff df 48 8b 45 10 48 89 c6 48 c1 ee 03 <80> 3c 0e 00 0f 85 06 01 00 00 48 85 d2 4c 89 28 74 47 e8 a3 4f b5
RSP: 0018:ffffc90006a97bf8 EFLAGS: 00010246
RAX: 0000000000000000 RBX: ffff888050ffc4f8 RCX: dffffc0000000000
RDX: 0000000000000000 RSI: 0000000000000000 RDI: ffff88808e04e010
RBP: ffff88808e04e000 R08: 0000000000000001 R09: ffff8880543d0000
R10: ffffed100a87a000 R11: 000000000000016e R12: ffff8880543d0000
R13: ffff88808e04e008 R14: ffff888050ffc508 R15: ffff888050ffc500
FS: 0000000000000000(0000) GS:ffff8880ae600000(0000) knlGS:0000000000000000
CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033
CR2: 00000000f5505db0 CR3: 0000000097cf7000 CR4: 00000000001526f0
DR0: 0000000000000000 DR1: 0000000000000000 DR2: 0000000000000000
DR3: 0000000000000000 DR6: 00000000fffe0ff0 DR7: 0000000000000400
Call Trace:
wg_noise_handshake_begin_session+0x752/0xc9a drivers/net/wireguard/noise.c:820
wg_receive_handshake_packet drivers/net/wireguard/receive.c:183 [inline]
wg_packet_handshake_receive_worker+0x33b/0x730 drivers/net/wireguard/receive.c:220
process_one_work+0x94c/0x1670 kernel/workqueue.c:2269
worker_thread+0x64c/0x1120 kernel/workqueue.c:2415
kthread+0x3b5/0x4a0 kernel/kthread.c:292
ret_from_fork+0x1f/0x30 arch/x86/entry/entry_64.S:294
Reported-by: syzbot <syzkaller@googlegroups.com>
Reported-by: Eric Dumazet <edumazet@google.com>
Link: https://lore.kernel.org/wireguard/20200908145911.4090480-1-edumazet@google.com/
Fixes: e7096c131e51 ("net: WireGuard secure network tunnel")
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
diff --git a/drivers/net/wireguard/noise.c b/drivers/net/wireguard/noise.c
index 3dd3b76790d0..c0cfd9b36c0b 100644
--- a/drivers/net/wireguard/noise.c
+++ b/drivers/net/wireguard/noise.c
@@ -87,15 +87,12 @@ static void handshake_zero(struct noise_handshake *handshake)
void wg_noise_handshake_clear(struct noise_handshake *handshake)
{
+ down_write(&handshake->lock);
wg_index_hashtable_remove(
handshake->entry.peer->device->index_hashtable,
&handshake->entry);
- down_write(&handshake->lock);
handshake_zero(handshake);
up_write(&handshake->lock);
- wg_index_hashtable_remove(
- handshake->entry.peer->device->index_hashtable,
- &handshake->entry);
}
static struct noise_keypair *keypair_create(struct wg_peer *peer)
]
|
| 44/2 |
2026/08/08 19:12 |
llm |
expert |
0m
Model:
Tokens:
input: 38577
output: 15
thoughts: 1298
Thoughts: **Analysis of a Data Race in WireGuard's Index Hashtable**
Okay, this is intriguing. KCSAN has flagged a potential data race between `wg_index_hashtable_insert` (writing to `entry->index`) and `wg_index_hashtable_lookup` (reading `iter_entry->index`). I need to confirm if there are any other write accesses to `entry->index`, but let's dive into the core functions first.
Right, `wg_index_hashtable_insert` is assigning `entry->index` to a random 32-bit value. The important thing here is that the write is protected by a spinlock, `table->lock`, before the RCU read side.
And looking at `wg_index_hashtable_lookup`, it's reading `iter_entry->index` within an RCU read critical section. Yes, this confirms the concurrent read/write scenario on `entry->index`. And crucially, it's not using `READ_ONCE` or `WRITE_ONCE`. KCSAN is correct in reporting the race.
But is it actually a *problem*? Let's break this down.
1. **No Tearing:** `entry->index` is a 32-bit integer, and 32-bit reads/writes are atomic on all our supported architectures. So, we're safe from torn reads and writes.
2. **No Logic Bug:** This is key. The concurrency here is designed to be safe. If `lookup` reads the *old* index, it will still match (because of the RCU guarantees). If `lookup` reads the *new* index (or even a nonsensical torn value), the comparison will fail. This is also correct - we're dealing with concurrent deletion and lookup! Even in the off-chance of a false positive match, WireGuard's cryptographic checks will catch any bad packets.
3. **No Compiler Optimization Issues:** The write is protected by a write lock, and the read happens once. The compiler can't do anything that causes issues.
Okay, this is *benign*. We need to be careful with annotations however.
Even though it's benign, we can still satisfy KCSAN by recommending annotations.
So, in `wg_index_hashtable_insert`, we can introduce: `WRITE_ONCE(entry->index, (__force __le32)get_random_u32());`.
And in `wg_index_hashtable_lookup`, let's wrap the read as: `if (READ_ONCE(iter_entry->index) == index)`.
Wait, what about `wg_index_hashtable_replace`?
`WRITE_ONCE(new->index, old->index);` or `new->index = READ_ONCE(old->index);`?
Ah, even though this is under `table->lock`, we *should* wrap it.
Alright, so the core data race has been identified and understood, with solutions at hand. We're clear on how to address the KCSAN warning and also on why the current behaviour is not broken.
We've covered all the required tools - `codesearch-definition-source`, `grepper`, `git-log`, and `git-show`. I have a very clear picture of this race condition.
Time to call `set-results` with `Benign: true`.
|
| 45/2 |
2026/08/08 19:13 |
tool |
set-results |
0m
Args:
Results:
|
| 46/2 |
2026/08/08 19:13 |
llm |
expert |
0m
Model:
Tokens:
input: 40646
output: 807
thoughts: 911
Thoughts: **Analysis of the Race Condition in WireGuard Index Hashtable Operations**
Okay, the `set-results` tool has confirmed the successful execution. Now, time to craft the final response. The analysis has been thorough, so I'll follow the specified format:
* **Race Summary:** It's a concurrency issue between `wg_index_hashtable_insert` and `wg_index_hashtable_lookup` functions, specifically related to the access of `entry->index`.
* **Synchronization Context:**
* **Writer (`wg_index_hashtable_insert`):** Holds `handshake->lock` (as a write lock), which protects the insertion process. The writer also acquires `table->lock` when deleting the entry from the hashtable using `hlist_del_init_rcu()`. Critically, it then updates `entry->index` *after* releasing `table->lock` and without waiting for RCU grace.
* **Reader (`wg_index_hashtable_lookup`):** Operates under `rcu_read_lock_bh()`, which establishes lockless reader behavior. The reader traverses the bucket based on the *old* index and reads `iter_entry->index` to perform a comparison with the search key.
* **Lack of Mutual Exclusion:** The fundamental race occurs because the writer doesn't use RCU synchronization. The writer modifies `entry->index` while it's still potentially reachable by a concurrent reader.
* **Final Classification:** **BENIGN**.
* **Reasoning:** This race is benign for a few key reasons:
1. **No Tearing:** `entry->index` is a 32-bit aligned 32-bit integer (`__le32`). Therefore, on any architecture supported by the Linux kernel, 32-bit reads/writes are atomic, making torn reads/writes physically impossible.
2. **No Logic Bug:**
* **Stale Read:** If the reader finds the old `entry->index`, the comparison will return true, and the lookup finds the entry. This is acceptable in RCU, where concurrent reads of a deleting entry are allowed.
* **New Read:** If the reader picks up the new `entry->index`, the comparison returns false, resulting in a lookup failure. This is also correct, because the entry is already removed.
* **Cryptographic Safety:** In the highly unlikely scenario of a false positive, WireGuard's cryptographic checks will prevent any security or functional problems by dropping mismatched packets.
3. **No Compiler Optimization Issues:**
* The writer to `entry->index` is serialized by `handshake->lock`, which means there is only one writer at any time.
* The reader makes a single read of `iter_entry->index` in the `if` statement, and does not reuse the result.
* Thus, compiler optimizations like hoisting or reloading cannot cause problems.
* **Recommended Annotations:** To satisfy KCSAN and to eliminate any possible surprises from compiler optimizations, let's add `READ_ONCE()` and `WRITE_ONCE()`:
In `drivers/net/wireguard/peerlookup.c`:
* In `wg_index_hashtable_insert()`:
```c
WRITE_ONCE(entry->index, (__force __le32)get_random_u32());
```
* In `wg_index_hashtable_lookup()`:
```c
if (READ_ONCE(iter_entry->index) == index) {
```
* In `wg_index_hashtable_replace()`:
```c
WRITE_ONCE(new->index, READ_ONCE(old->index));
```
This sums it up nicely. I believe it's a solid, well-reasoned explanation, and I'm ready to submit the final report.
|
| 47/1 |
2026/08/08 19:13 |
action |
format-explanation |
0m
Results: map[Explanation:### Race Summary
`wg_index_hashtable_insert` vs `wg_index_hashtable_lookup` on `entry->index` (in
`drivers/net/wireguard/peerlookup.c`).
---
### Synchronization Context
* **Writer (`wg_index_hashtable_insert`):** Holds `handshake->lock` (as a write
lock) and temporarily holds `table->lock` to remove the entry from the hashtable
via `hlist_del_init_rcu()`. However, it writes the new random index to
`entry->index` *after* releasing `table->lock` and without waiting for an RCU
grace period.
* **Reader (`wg_index_hashtable_lookup`):** Holds only `rcu_read_lock_bh()`
(lockless reader). It traverses the bucket corresponding to the old index and
reads `iter_entry->index` to check if it matches the requested index.
* **Lack of Mutual Exclusion:** Because the writer does not wait for an RCU
grace period after deleting the entry, a concurrent reader can still traverse
the list and access `entry` while the writer is updating `entry->index`.
---
### Final Classification: **BENIGN**
#### Reasoning:
1. **No Tearing:** `entry->index` is a 32-bit aligned, 32-bit integer
(`__le32`). On all modern architectures supported by the Linux kernel, 32-bit
aligned reads and writes are atomic, so torn reads/writes are physically
impossible.
2. **No Logic Bug:**
- **Stale Read:** If the reader reads the old index, the comparison
`iter_entry->index == index` evaluates to true, and the lookup succeeds. This
is perfectly valid under RCU, as a concurrent reader is allowed to find an
entry that is currently being deleted.
- **New Read:** If the reader reads the new index, the comparison
`iter_entry->index == index` evaluates to false, and the lookup fails. This
is also perfectly valid because the entry has already been deleted from the
hashtable, so the lookup for the old index *should* fail.
- **Cryptographic Safety:** Even in the theoretically impossible case of a
false positive match (e.g., if a torn read or a collision happens to match
the target index), the cryptographic checks in WireGuard (such as MAC and
state checks) will safely drop any mismatched packets, preventing any
security or functional issues.
3. **No Compiler Optimization Issues:**
- The writer of `entry->index` is serialized by `handshake->lock` (held as a
write lock), so there is only one writer at any time.
- The reader reads `iter_entry->index` exactly once in the comparison, and
does not use it again.
- Therefore, compiler optimizations like reloading or hoisting cannot cause
any incorrect behavior.
---
### Recommended Annotations
To satisfy KCSAN and prevent any potential compiler optimizations, the accesses
to `entry->index` should be annotated with `READ_ONCE()` and `WRITE_ONCE()`:
In `drivers/net/wireguard/peerlookup.c`:
* In `wg_index_hashtable_insert()`:
```c
WRITE_ONCE(entry->index, (__force __le32)get_random_u32());
```
* In `wg_index_hashtable_lookup()`:
```c
if (READ_ONCE(iter_entry->index) == index) {
```
* In `wg_index_hashtable_replace()`:
```c
WRITE_ONCE(new->index, READ_ONCE(old->index));
```]
|