On 2/27/20 19:28, Alexander Popov wrote: > On 19.02.2020 16:43, zerons wrote: >> This patch does work for cve-2017-2636 case, it is barely impossible to win the >> race. My concern is based on an assumption: we do have a double kfree() bug and >> we can win the race. > > Yes, I agree that the double-free check in CONFIG_SLAB_FREELIST_HARDENED can be > bypassed in some cases by winning the race and inserting kmalloc() between kfree(). > > But I *don't* agree that this double-free check can help the attacker. > > Without this check in CONFIG_SLAB_FREELIST_HARDENED, double-free exploitation is > always easier, since the attacker has no need to race at all. In the write-up > about CVE-2017-2636 exploit [1] I showed how to do heap spray *after* > double-free (kfree-kfree-kmalloc-kmalloc). > I thought the freelist obfuscation[1] and prefecth next pointer[2] may block this method(kfree-kfree-kmalloc-kmalloc), and the prefetch_freepointer() should've stopped the 2nd kmalloc(). Today, I did some tests on Ubuntu 18.04 with kernel 5.3.18, without your patch. Here is the code. It writes something to modprobe_path for debugging. After the 2nd kmalloc() return, ptr0 == ptr1 is true, which means the attacker could have two objects point to same memory area. Although the system now is quite fragile: the next kmalloc() would trigger do_general_protection() since the c->freelist is something like 0x4141414141414141, the attacker still can win. ======================================================================= #define TARGET_SIZE 0x1000 static int __init test_init(void) { char *ptr0, *ptr1, *ptr2; char *path_addr; size_t l = 0; path_addr = (char *)kallsyms_lookup_name("modprobe_path"); while (1) { if (!*(path_addr+l)) break; l++; } l += 8 - (l%8); ptr0 = kmalloc(TARGET_SIZE, GFP_KERNEL); *(unsigned long *)(path_addr+l) = (unsigned long)ptr0; kfree(ptr0); *(unsigned long *)(path_addr+l+8) = *(unsigned long *)ptr0; *(unsigned long *)(path_addr+l+0x10) = (unsigned long)ptr0; kfree(ptr0); *(unsigned long *)(path_addr+l+0x18) = *(unsigned long *)ptr0; ptr0 = kmalloc(TARGET_SIZE, GFP_KERNEL); *(unsigned long *)(path_addr+l+0x20) = (unsigned long)ptr0; *(unsigned long *)(path_addr+l+0x28) = *(unsigned long *)ptr0; ptr1 = kmalloc(TARGET_SIZE, GFP_KERNEL); *(unsigned long *)(path_addr+l+0x30) = (unsigned long)ptr1; *(unsigned long *)(path_addr+l+0x38) = *(unsigned long *)ptr0; #if 0 ptr2 = kmalloc(TARGET_SIZE, GFP_KERNEL); *(unsigned long *)(path_addr+l+0x40) = (unsigned long)ptr2; *(unsigned long *)(path_addr+l+0x48) = *(unsigned long *)ptr0; #endif return 0; } ========================================================================== [1]: https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=2482ddec [2]: https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=0ad9500e1