Technical experience sharing, welcome to follow and provide guidance.
In the previous article, we detailed the layout of KASAN shadow memory. This article analyzes the implementation principles of KASAN under slab.
kmem_cache Layout
For the slab layout with KASAN enabled, the structure has undergone some changes, as shown below:
struct kmem_cache {
......
#ifdef CONFIG_KASAN
struct kasan_cache kasan_info;
#endif
......
};
struct kasan_cache {
int alloc_meta_offset;
int free_meta_offset;
bool is_kmalloc;
};
The above structure members are reserved for debugging information when debugging the slab. Next, we will analyze the code calls during slab creation.
kmem_cache_create
kmem_cache_create_usercopy
create_cache
__kmem_cache_create
kmem_cache_open
calculate_sizes
kasan_cache_create
__kasan_cache_create
We focus on the slab creation of kmalloc, which is created at boot time. The code calls are as follows:
start_kernel
mm_init
kmem_cache_init
create_kmalloc_caches
create_boot_cache
__kmem_cache_create
kmem_cache_open
calculate_sizes
kasan_cache_create
__kasan_cache_create
We focus on the key function<span>__kasan_cache_create</span>, whose source code is as follows:
void __kasan_cache_create(struct kmem_cache *cache, unsigned int *size,
slab_flags_t *flags)
{
unsigned int ok_size;
unsigned int optimal_size;
*flags |= SLAB_KASAN;
if (!kasan_stack_collection_enabled())
return;
ok_size = *size;
/* Add alloc meta into redzone. */
cache->kasan_info.alloc_meta_offset = *size;
*size += sizeof(struct kasan_alloc_meta);
if (*size > KMALLOC_MAX_SIZE) {
cache->kasan_info.alloc_meta_offset = 0;
*size = ok_size;
/* Continue, since free meta might still fit. */
}
optimal_size = cache->object_size + optimal_redzone(cache->object_size);
/* Limit it with KMALLOC_MAX_SIZE (relevant for SLAB only). */
if (optimal_size > KMALLOC_MAX_SIZE)
optimal_size = KMALLOC_MAX_SIZE;
/* Use optimal size if the size with added metas is not large enough. */
if (*size < optimal_size)
*size = optimal_size;
}
It can be seen that three main modifications were made:
- flags
- kasan_info
- size
To verify, we can confirm in crash as follows:
crash> kmem -S kmalloc-128
CACHE OBJSIZE ALLOCATED TOTAL SLABS SSIZE NAME
ffffff8007003c80 128 140707 141056 4408 8k kmalloc-128
crash> struct kmem_cache.name ffffff8007003c80
name = 0xffffffd00c59e708 "kmalloc-128"
crash> struct kmem_cache ffffff8007003c80
struct kmem_cache {
cpu_slab = 0xffffffd00ca37070,
flags = 1207959552,
min_partial = 5,
size = 256,
object_size = 128,
reciprocal_size = {
m = 1,
sh1 = 1'\001',
sh2 = 7'\a'
},
offset = 64,
cpu_partial = 13,
oo = {
x = 65568
},
max = {
x = 65568
},
min = {
x = 16
},
allocflags = 262144,
refcount = 1,
ctor = 0x0,
inuse = 128,
align = 128,
red_left_pad = 0,
name = 0xffffffd00c59e708 "kmalloc-128",
list = {
next = 0xffffff8007003e68,
prev = 0xffffff8007003b68
},
kobj = {
name = 0xffffffd00c59e708 "kmalloc-128",
entry = {
next = 0xffffff8007003e80,
prev = 0xffffff8007003b80
},
parent = 0xffffff800a200228,
kset = 0xffffff800a200200,
ktype = 0xffffffd00ddee9a8 ,
sd = 0xffffff800a2799c0,
kref = {
refcount = {
refs = {
counter = 1
}
}
},
state_initialized = 1,
state_in_sysfs = 1,
state_add_uevent_sent = 0,
state_remove_uevent_sent = 0,
uevent_suppress = 0
},
kasan_info = {
alloc_meta_offset = 128,
free_meta_offset = 0,
is_kmalloc = true
},
useroffset = 0,
usersize = 128,
node = {0xffffff8007000f80}
}
Extracting the data we are concerned about, the explanation is as follows:
- size: 256
<span>cache->object_size + optimal_redzone(cache->object_size);</span>obtained - alloc_meta_offset:
<span>cache->kasan_info.alloc_meta_offset = *size;</span>obtained - is_kmalloc:
<span>cache->kasan_info.is_kmalloc = true;</span>obtained - flags:
<span>*flags |= SLAB_KASAN;</span>obtained, where 1207959552 is<span>48000000</span>, and SLAB_KASAN is<span>8000000</span>
kmalloc Poison
To understand the poisoning steps of kmalloc, we can first analyze the code flow of kmalloc as follows:
kmalloc
__kmalloc
kasan_kmalloc
____kasan_kmalloc
slab_alloc
slab_alloc_node
__slab_alloc
___slab_alloc
new_slab_objects
new_slab
allocate_slab
kasan_poison_slab
If a new slab needs to be added, the calling flow is as follows:
Here,<span>____kasan_kmalloc</span> mainly serves to poison the memory of the shadow area. Similarly, allocate_slab calls kasan_poison_slab to poison, which is relatively simple. We can look at the implementation of<span>____kasan_kmalloc</span>:
static inline void *____kasan_kmalloc(struct kmem_cache *cache,
const void *object, size_t size, gfp_t flags)
{
unsigned long redzone_start;
unsigned long redzone_end;
if (gfpflags_allow_blocking(flags))
kasan_quarantine_reduce();
if (unlikely(object == NULL))
return NULL;
if (is_kfence_address(kasan_reset_tag(object)))
return (void *)object;
/*
* The object has already been unpoisoned by kasan_slab_alloc() for
* kmalloc() or by kasan_krealloc() for krealloc().
*/
/*
* The redzone has byte-level precision for the generic mode.
* Partially poison the last object granule to cover the unaligned
* part of the redzone.
*/
if (IS_ENABLED(CONFIG_KASAN_GENERIC))
kasan_poison_last_granule((void *)object, size);
/* Poison the aligned part of the redzone. */
redzone_start = round_up((unsigned long)(object + size),
KASAN_GRANULE_SIZE);
redzone_end = round_up((unsigned long)(object + cache->object_size),
KASAN_GRANULE_SIZE);
kasan_poison((void *)redzone_start, redzone_end - redzone_start,
KASAN_KMALLOC_REDZONE, false);
/*
* Save alloc info (if possible) for kmalloc() allocations.
* This also rewrites the alloc info when called from kasan_krealloc().
*/
if (kasan_stack_collection_enabled())
set_alloc_info(cache, (void *)object, flags, true);
/* Keep the tag that was set by kasan_slab_alloc(). */
return (void *)object;
}
This function focuses on two points:
- kasan_poison_last_granule: poisons the shadow area that is less than 8 bytes.
- kasan_poison: poisons other areas with the red zone 0xfc.
Based on my test code “KASAN(2) – Self-Test Module” logs, we focus on the shadow area printed under the kmalloc_oob_right function, as follows:
[ 10.441646] Memory state around the buggy address:
[ 10.441684] ffffff8011758000: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
[ 10.441723] ffffff8011758080: fcfcfcfcfcfcfcfcfcfcfcfcfcfcfcfc
[ 10.441760] >ffffff8011758100: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 03
[ 10.441792] ^
[ 10.441829] ffffff8011758180: fcfcfcfcfcfcfcfcfcfcfcfcfcfcfcfc
[ 10.441866] ffffff8011758200: fcfcfcfcfcfcfcfcfcfcfcfcfcfcfcfc
It can be seen that here 0xfc and 0x3 are the poisoned values written by the above code.
In summary, every slab cache allocation is poisoned by default, and each allocated object has already been unpoisoned in advance. Each time kmalloc is called, it will be poisoned again based on the actual situation. This effectively checks for out-of-bounds (oob) issues. We see that the value of fc at address ffffff8011758080 is the poison set for us when allocating the slab, and kmalloc just re-applies the poison when the memory is actually allocated.
kfree Poison
For the kfree page, it mainly detects use-after-free (uaf) issues, currently based on one path of kernel free for code analysis, as follows:
kfree
slab_free
slab_free_freelist_hook
slab_free_hook
kasan_slab_free
__kasan_slab_free
____kasan_slab_free
<span>____kasan_slab_free</span> code is as follows:
static inline bool ____kasan_slab_free(struct kmem_cache *cache, void *object,
unsigned long ip, bool quarantine, bool init)
{
u8 tag;
void *tagged_object;
tag = get_tag(object);
tagged_object = object;
object = kasan_reset_tag(object);
if (is_kfence_address(object))
return false;
if (unlikely(nearest_obj(cache, virt_to_head_page(object), object) !=
object)) {
kasan_report_invalid_free(tagged_object, ip);
return true;
}
/* RCU slabs could be legally used after free within the RCU period */
if (unlikely(cache->flags & SLAB_TYPESAFE_BY_RCU))
return false;
if (!kasan_byte_accessible(tagged_object)) {
kasan_report_invalid_free(tagged_object, ip);
return true;
}
kasan_poison(object, round_up(cache->object_size, KASAN_GRANULE_SIZE),
KASAN_KMALLOC_FREE, init);
if ((IS_ENABLED(CONFIG_KASAN_GENERIC) && !quarantine))
return false;
if (kasan_stack_collection_enabled())
kasan_set_free_info(cache, object, tag);
return kasan_quarantine_put(cache, object);
}
The main logic is as follows:
- Ignore kfence
- Invalid free not in the same object (KASAN(3) – Test Analysis has examples)
- Within the RCU grace period (KASAN(3) – Test Analysis has examples)
- Pointer not within kernel range
- Poison the object with KASAN_KMALLOC_FREE
- If stack tracing is supported, poison with KASAN_KMALLOC_FREETRACK
- Create a quarantine list, delaying the call to ___cache_free to release memory
At this point, we can see the values of the shadow area as follows:
[ 10.444125] Memory state around the buggy address:
[ 10.444162] ffffff8011758080: fcfcfcfcfcfcfcfcfcfcfcfcfcfcfcfc
[ 10.444199] ffffff8011758100: fa fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb
[ 10.444237] ffffff8011758180: fcfcfcfcfcfcfcfcfcfcfcfcfcfcfcfc
[ 10.444269] ^
[ 10.444306] ffffff8011758200: 00 07 fcfcfcfcfcfcfcfcfcfcfcfcfcfc
At the position ffffff8011758100, we see that fa and fb are set, which are the poisoned values for the free area set by this code.
Conclusion
This article analyzes how KASAN poisons in the kmalloc and kfree functions by reading the code. It can be understood that in the slab allocator, KASAN poisons all required objects, allowing for proper instruction instrumentation to correctly determine whether the current code has out-of-bounds (oob) and use-after-free (uaf) errors. For instruction instrumentation, please refer to “KASAN(4) – INLINE Instrumentation Analysis” and “KASAN(5) – OUTLINE Instrumentation Analysis”.
Reference Documents
This article has detailed references to the following documents. If you need to understand the original meaning, you can check the following link:
http://www.wowotech.net/memory_management/424.html