Linux Kernel Use-After-Free Exploitation Technique in Linux Kernel 7.0-rc7 Using the slab_sheaf Union State Confusion Technique
by: Antonius
Country: Indonesia
https://www.bluedragonsec.com – https://github.com/bluedragonsecurityBackground
Linux kernel 7.0-rc7 uses the SLUB Sheaves architecture, which replaces the older kmem_cache_cpu structure. In this new architecture, each sheaf is represented by struct slab_sheaf, declared in mm/slub.c around line 404 in Linux kernel 7.0-rc7.
Based on analysis of the Linux kernel 7 rc7 source code, one of the most interesting design characteristics of struct slab_sheaf is the use of a union at the beginning of the structure. This union allows the same memory block to be used for three different purposes depending on the context. The Linux kernel use-after-free exploitation technique using slab_sheaf Union State Confusion is directly related to the ambiguity created by this union.
Anatomy of the Union in slab_sheaf
Union Declaration (mm/slub.c:404)
Below is the complete declaration of the union portion of struct slab_sheaf:
struct slab_sheaf {
union {
struct rcu_head rcu_head; /* used during RCU free */
struct list_head barn_list; /* used while in the barn */
struct {
unsigned int capacity;
bool pfmemalloc;
}; /* used during prefill */
};
struct kmem_cache *cache;
unsigned int size;
int node;
void *objects[];
};This union does not define an extra alias name — the three variants can be accessed directly through their field names (rcu_head, barn_list, capacity, pfmemalloc).
Actual Memory Layout of the Union
Although the three union variants are declared separately, in memory they all occupy exactly the same byte range, starting at offset +0x00 of struct slab_sheaf.

The first row in the table above shows that rcu_head.func and barn_list.next occupy identical offsets (+0x00). Likewise, rcu_head.next and barn_list.prev are both located at +0x08.
This is a direct consequence of how unions work in C: all members of a union share the same starting address. There is no memory separation between them.
Type Definitions of Each Field
Understanding the type of each field is important in order to understand the meaning of that overlap:
/* struct rcu_head (from the kernel RCU infrastructure) */
struct rcu_head {
void (*func)(struct rcu_head *head); /* +0x00: callback fn */
struct rcu_head *next; /* +0x08: RCU linkage */
};
/* struct list_head (standard kernel doubly-linked list) */
struct list_head {
struct list_head *next; /* +0x00: pointer to the next element */
struct list_head *prev; /* +0x08: pointer to the previous element */
};The most critical difference is at offset +0x00: in the rcu_head context, the bytes at this position represent a callback function pointer. In the barn_list context, those very same bytes represent a pointer to the next struct list_head. The same 8-byte memory region is interpreted as two different things depending on which view the code uses.
State Machine of a slab_sheaf
To avoid confusion about this union, we first need to understand the life cycle of a slab_sheaf in kernel 7.0-rc7.
Possible States
At any given time, a slab_sheaf may be in one of the following states:

State Transitions and Union Use
The important transition is the movement between the IN_BARN and RCU_PENDING states. When a sheaf is moved from the barn into the RCU path:
1. The sheaf is taken out of the barn: list_del(&sheaf->barn_list) is called. After this, barn_list.next and barn_list.prev still contain values from when the sheaf was inside the barn doubly-linked list.
2. The sheaf is inserted into pcs->rcu_free: the union still contains the old barn_list values and has not yet been cleared.
3. When the sheaf becomes full, call_rcu(&sheaf->rcu_head, rcu_free_sheaf) is called. At this point, the RCU infrastructure writes its new values into rcu_head.func and rcu_head.next — overwriting barn_list.next and barn_list.prev.
The reverse direction also occurs. In rcu_free_sheaf() (mm/slub.c around line 5789), after the RCU grace period completes, if the barn still has a free slot, the sheaf is placed back into the barn:
/* mm/slub.c:5829 — after the RCU callback finishes */
if (data_race(barn->nr_full) < MAX_FULL_SHEAVES) {
barn_put_full_sheaf(barn, sheaf); /* enters the barn again */
return;
}This means that one slab_sheaf can repeatedly go through the cycle barn → rcu_free sheaf → call_rcu → grace period → barn again. Every transition changes the interpretation of the union from barn_list to rcu_head and back.
The Hanging State: The Core of the slab_sheaf Union State Confusion Technique
This technique revolves around one question: is there a moment when the kernel reads or writes the slab_sheaf union using an interpretation that does not match the sheaf's actual state?
There Is No State Flag
struct slab_sheaf does not contain an explicit field that records which state is currently active. There is no enum state, no BARN_OR_RCU flag. The kernel code determines the correct interpretation only from program-flow context — for example, if the sheaf has just left the barn, then barn_list is assumed to be valid. If the sheaf has just been handed to call_rcu(), then rcu_head is assumed to be valid.
This approach is common in the kernel, which is written in C, and it is not inherently wrong as long as the state machine is executed correctly. However, it creates a dependency on the correctness of the operation order.
The Window Between call_rcu and barn_list Use
Consider the following flow in __kfree_rcu_sheaf() (mm/slub.c around line 5862):
/* Step 1: the sheaf is taken from the spare or the barn */
/* At this point the union contains values that are valid as barn_list */
/* or random values if it was taken from the spare */
/* Step 2: objects are collected into objects[] */
rcu_sheaf->objects[rcu_sheaf->size++] = obj;
/* Step 3: when the sheaf becomes full, call_rcu is invoked */
if (rcu_sheaf->size >= s->sheaf_capacity) {
pcs->rcu_free = NULL;
rcu_sheaf->node = numa_mem_id();
/* call_rcu will overwrite rcu_head.func and rcu_head.next */
call_rcu(&rcu_sheaf->rcu_head, rcu_free_sheaf); /* slub.c:5949 */
}Between Step 1 and Step 3, there is a window in which the union still contains old values that may come from the barn context. If, during that window, some other code reads the union using the rcu_head interpretation (for example, debug or tracing code), the values it sees are actually barn_list.next and barn_list.prev, not valid rcu_head values.
Re-entry into the Barn After RCU
A more interesting theoretical scenario occurs inside rcu_free_sheaf() (mm/slub.c around line 5789 in Linux kernel 7.0-rc7). After the RCU grace period finishes and the callback is invoked:
static void rcu_free_sheaf(struct rcu_head *head)
{
sheaf = container_of(head, struct slab_sheaf, rcu_head);
/* the union now contains rcu_head values (from the RCU infrastructure) */
if (__rcu_free_sheaf_prepare(s, sheaf))
goto flush;
/* CONDITION: if the barn still has a free slot */
if (data_race(barn->nr_full) < MAX_FULL_SHEAVES) {
barn_put_full_sheaf(barn, sheaf);
/* barn_put_full_sheaf will call list_add(&sheaf->barn_list, ...) */
/* list_add WRITES to barn_list.next and barn_list.prev */
/* which are at the SAME OFFSETS as rcu_head.func/next */
return;
}
}When barn_put_full_sheaf() calls list_add(&sheaf->barn_list, …), the operation writes to barn_list.next (offset +0x00) and barn_list.prev (offset +0x08). The values written are pointers to list_head structures inside the barn. Because barn_list and rcu_head share the same offsets, the visible effect is that rcu_head.func (offset +0x00) is overwritten by a barn_list pointer.
So What Does This Mean?
In theory, this ambiguity becomes relevant in the following contexts:
- If there is a mechanism that allows reading a slab_sheaf at the wrong time — for example, via an information leak or a race condition that provides an observation window into the sheaf contents — that reader may interpret the union values differently from what the kernel intends.
- If there is a write primitive that can modify the slab_sheaf union while the sheaf is in the RCU_PENDING state, a value written at offset +0x00 will be interpreted by the RCU infrastructure as a callback function pointer.
- Symmetrically, a modification made while the sheaf is in the IN_BARN state at offset +0x00 will be interpreted by barn code as barn_list.next, which is a pointer to the next element in the barn doubly-linked list.
Full Life Cycle and Transition Points
To understand this exploitation technique more completely, below is a full slab_sheaf life-cycle diagram with the relevant union transition points:
alloc_empty_sheaf()
|
v
[PERCPU_MAIN / PERCPU_SPARE]
union: not actively used
|
| barn_put_full_sheaf() — list_add(&barn_list)
| WRITES: barn_list.next = ptr_barn_head
| barn_list.prev = ptr_barn_head
v
[IN_BARN] <── union active as barn_list (list_head)
barn_list.next = next element in sheaves_full/empty
barn_list.prev = previous element
|
| barn_get_*() — list_del(&barn_list)
| after list_del: barn_list contains LIST_POISON
| or the last list head value
v
[PERCPU_RCU] <── union contains leftover barn_list values
|
| call_rcu(&rcu_head, rcu_free_sheaf)
| RCU infrastructure WRITES:
| rcu_head.func = callback
| rcu_head.next = RCU linkage
v
[RCU_PENDING] <── union active as rcu_head
|
| grace period ends, rcu_free_sheaf() is called
|
+-- if the barn has a free slot:
| barn_put_full_sheaf() is called
| list_add WRITES to barn_list.next/prev
| (overwriting rcu_head.func/next)
| v
| [IN_BARN] — back into the barn
|
+-- if the barn is full:
sheaf_flush_unused() + free_empty_sheaf()
sheaf is freed back to kmallocRequired Conditions
It is important to stress that this technique is not a vulnerability that can be exploited directly from scratch. It is a design property that amplifies the impact when some other bug already exists.
Required conditions:
- Initial access primitive: another vulnerability must already exist — such as a use-after-free, buffer overflow, or race condition — that grants the ability to read or write memory overlapping a slab_sheaf.
- Precise timing: that access must occur while the sheaf is transitioning between IN_BARN and RCU_PENDING, or while rcu_free_sheaf() is returning the sheaf to the barn.
- Knowledge of the sheaf location: the attacker must know the address of the target slab_sheaf in memory. This usually requires a separate information leak.
In other words, this technique is an amplifier, not an initial vector. It turns a limited memory access primitive into something potentially more significant because the manipulated value carries special meaning for kernel infrastructure (the RCU machinery or barn list management).
Mitigation and Defensive Considerations
Existing Approaches
Kernel 7.0-rc7 has several protection layers that limit the practical impact of this ambiguity:
- KASAN (Kernel Address Sanitizer): on debug builds, KASAN detects access to uninitialized or freed memory. This helps detect incorrect transition conditions during testing.
- lockdep: the lock misuse detection framework. Some state transitions are protected by locks that are tracked by lockdep, so access outside the correct locking context can be detected.
- Locking discipline: every barn operation is protected by spin_lock_irqsave, and every percpu operation is protected by local_trylock. This limits the window in which transitional states can be exploited.
What Is Not Addressed
The existing mitigations do not explicitly mark the slab_sheaf state or validate the interpretation of the union. A theoretically stronger approach would be:
- Add an explicit state field to struct slab_sheaf (an enum or bitfield) that is updated atomically during transitions. This would allow runtime validation that the union is being accessed with the correct interpretation.
- Split the union into separate fields with clear lifetimes that do not overlap. Although this would increase the structure size, it would eliminate the ambiguity entirely.
Conclusion
The slab_sheaf Union State Confusion technique is centered on one architectural fact in kernel 7.0-rc7: struct slab_sheaf uses a union to overlay rcu_head and barn_list at the same memory offsets, without an explicit state marker that prevents access using the wrong interpretation.
In theory, this creates a condition in which values that are meaningful in one context (an RCU callback pointer or a barn doubly-linked-list pointer) may be read or written through a different context if another bug already exists that allows access to slab_sheaf contents.