Introduction: the full name of this protection mechanism is Linux Kernel Heap Quarantine - slab_ Quartine is mainly used for kernel UAF utilization.
UAF utilization: heap spray is used to control the victim object. The principle is that kmalloc() can always allocate the recently released heap block.
SLAB_QUARANTINE principle: first, put the heap block to be released into the isolation queue temporarily and wait for it to be released, so that the heap block cannot be obtained immediately during heap injection; Second, take quarantine randomization Isolation randomization mechanism: when the isolation area increases, batch is randomly selected (batch is used for heap isolation to store objects) to randomly release half of the objects, so that the number of times to obtain vulnerability objects is uncertain (to avoid deterministic heap injection, but if the injection times are very large, this mechanism may be bypassed); The third is setting CONFIG_SLAB_QUARANTINE Option, call init_on_free to empty the memory block to avoid that there is still payload in the heap block placed in the isolation area.
1. Test
Test source code: the author developed two lkdtm programs for testing. The source code is in here.
1-1. Test I
Content: the program name is lkdtm_HEAP_SPRAY, simulate the heap spray process, first from kmem_ The cache allocates and releases an object, and then allocates 400000 similar objects to check whether they are allocated to the newly released object.
#define SPRAY_LENGTH 400000 ... addr = kmem_cache_alloc(spray_cache, GFP_KERNEL); ... kmem_cache_free(spray_cache, addr); pr_info("Allocated and freed spray_cache object %p of size %d\n", addr, SPRAY_ITEM_SIZE); ... pr_info("Original heap spraying: allocate %d objects of size %d...\n", SPRAY_LENGTH, SPRAY_ITEM_SIZE); for (i = 0; i < SPRAY_LENGTH; i++) { spray_addrs[i] = kmem_cache_alloc(spray_cache, GFP_KERNEL); ... if (spray_addrs[i] == addr) { pr_info("FAIL: attempt %lu: freed object is reallocated\n", i); break; } } if (i == SPRAY_LENGTH) pr_info("OK: original heap spraying hasn't succeeded\n");
If config is turned off_ SLAB_ Quantine, it is successfully allocated to the heap block just released:
# echo HEAP_SPRAY > /sys/kernel/debug/provoke-crash/DIRECT lkdtm: Performing direct entry HEAP_SPRAY lkdtm: Allocated and freed spray_cache object 000000002b5b3ad4 of size 333 lkdtm: Original heap spraying: allocate 400000 objects of size 333... lkdtm: FAIL: attempt 0: freed object is reallocated
If config is enabled_ SLAB_ Quota, the allocation fails:
# echo HEAP_SPRAY > /sys/kernel/debug/provoke-crash/DIRECT lkdtm: Performing direct entry HEAP_SPRAY lkdtm: Allocated and freed spray_cache object 000000009909e777 of size 333 lkdtm: Original heap spraying: allocate 400000 objects of size 333... lkdtm: OK: original heap spraying hasn't succeeded
1-2. Test II
Content: the size of the isolation area must be large enough. The program name is lkdtm_PUSH_THROUGH_QUARANTINE, start with kmem_cache allocates and releases an object, and then allocates and releases 400000 times - kmem_cache_alloc()+kmem_cache_free().
addr = kmem_cache_alloc(spray_cache, GFP_KERNEL); ... kmem_cache_free(spray_cache, addr); pr_info("Allocated and freed spray_cache object %p of size %d\n", addr, SPRAY_ITEM_SIZE); pr_info("Push through quarantine: allocate and free %d objects of size %d...\n", SPRAY_LENGTH, SPRAY_ITEM_SIZE); for (i = 0; i < SPRAY_LENGTH; i++) { push_addr = kmem_cache_alloc(spray_cache, GFP_KERNEL); ... kmem_cache_free(spray_cache, push_addr); if (push_addr == addr) { pr_info("Target object is reallocated at attempt %lu\n", i); break; } } if (i == SPRAY_LENGTH) { pr_info("Target object is NOT reallocated in %d attempts\n", SPRAY_LENGTH); }
Put the object into the heap isolation area, wait for it to return to freelist, and then reallocate it. It can be seen that the allocation times required to cover the vulnerability object each time are very similar, which is very beneficial to UAF utilization. So the author developed quarantine randomization
# echo PUSH_THROUGH_QUARANTINE > /sys/kernel/debug/provoke-crash/ lkdtm: Performing direct entry PUSH_THROUGH_QUARANTINE lkdtm: Allocated and freed spray_cache object 000000008fdb15c3 of size 333 lkdtm: Push through quarantine: allocate and free 400000 objects of size 333... lkdtm: Target object is reallocated at attempt 182994 # echo PUSH_THROUGH_QUARANTINE > /sys/kernel/debug/provoke-crash/ lkdtm: Performing direct entry PUSH_THROUGH_QUARANTINE lkdtm: Allocated and freed spray_cache object 000000004e223cbe of size 333 lkdtm: Push through quarantine: allocate and free 400000 objects of size 333... lkdtm: Target object is reallocated at attempt 186830 # echo PUSH_THROUGH_QUARANTINE > /sys/kernel/debug/provoke-crash/ lkdtm: Performing direct entry PUSH_THROUGH_QUARANTINE lkdtm: Allocated and freed spray_cache object 000000007663a058 of size 333 lkdtm: Push through quarantine: allocate and free 400000 objects of size 333... lkdtm: Target object is reallocated at attempt 182010
2. quarantine randomization Isolation randomization
Principle: heap isolation uses batch to store objects. When the isolation area increases, batch is randomly selected to release half of the objects. As can be seen below, the number of times the vulnerability object is allocated again is uncertain.
lkdtm: Target object is reallocated at attempt 107884 lkdtm: Target object is reallocated at attempt 265641 lkdtm: Target object is reallocated at attempt 100030 lkdtm: Target object is NOT reallocated in 400000 attempts lkdtm: Target object is reallocated at attempt 204731 lkdtm: Target object is reallocated at attempt 359333 lkdtm: Target object is reallocated at attempt 289349 lkdtm: Target object is reallocated at attempt 119893 lkdtm: Target object is reallocated at attempt 225202 lkdtm: Target object is reallocated at attempt 87343
Problem: randomization alone cannot prevent the attacker. The object in the isolation zone may have stored the attacker's payload. How to empty the contents of the heap before putting it into the quarantine?
Solution: the reset code init already exists in the kernel_ on_ Free, the author developed CONFIG_SLAB_QUARANTINE , the authors found that init_on_free didn't clear in time, so he submitted it patch To fix this problem. The author also submitted additional patch To facilitate debugging and analysis and help understand the isolation mechanism. The output examples are as follows:
quarantine: PUT 508992 to tail batch 123, whole sz 65118872, batch sz 508854 quarantine: whole sz exceed max by 494552, REDUCE head batch 0 by 415392, leave 396304 quarantine: data level in batches: 0 - 77% 1 - 108% 2 - 83% 3 - 21% ... 125 - 75% 126 - 12% 127 - 108% quarantine: whole sz exceed max by 79160, REDUCE head batch 12 by 14160, leave 17608 quarantine: whole sz exceed max by 65000, REDUCE head batch 75 by 218328, leave 195232 quarantine: PUT 508992 to tail batch 124, whole sz 64979984, batch sz 508854 ...
3. Performance
Network throughput:
init_on_free=on relative to init_on_free=off brings 28% performance loss.
CONFIG_ SLAB_ Quartine is better than init_on_free=on to reduce the performance loss by 2%