Bug 192098

Summary: DEBUG_MEMGUARD and INVARIANTS duplicate "double-free" detection logic
Product: Base System Reporter: Enji Cooper <ngie>
Component: kernAssignee: freebsd-bugs (Nobody) <bugs>
Status: Closed Works As Intended    
Severity: Affects Some People CC: emaste, markj
Priority: ---    
Version: CURRENT   
Hardware: Any   
OS: Any   

Description Enji Cooper freebsd_committer freebsd_triage 2014-07-24 18:04:19 UTC
The code in sys/vm/memguard.c and sys/vm/uma_dbg.c both attempt to detect double-frees, but when I have both DEBUG_MEMGUARD and INVARIANTS compiled in and try to run my "bad_memory" kld, a double-free trips the panic in uma_dbg.c, not memguard.c:

memguard.c:

252 static u_long *
253 v2sizep(vm_offset_t va)
254 {
255         vm_paddr_t pa;
256         struct vm_page *p;
257
258         pa = pmap_kextract(va);
259         if (pa == 0)
260                 panic("MemGuard detected double-free of %p", (void *)va);
261         p = PHYS_TO_VM_PAGE(pa);
262         KASSERT(p->wire_count != 0 && p->queue == PQ_NONE,
263             ("MEMGUARD: Expected wired page %p in vtomgfifo!", p));
264         return (&p->plinks.memguard.p);
265 }
266
267 static u_long *
268 v2sizev(vm_offset_t va)
269 {
270         vm_paddr_t pa;
271         struct vm_page *p;
272
273         pa = pmap_kextract(va);
274         if (pa == 0)
275                 panic("MemGuard detected double-free of %p", (void *)va);
276         p = PHYS_TO_VM_PAGE(pa);
277         KASSERT(p->wire_count != 0 && p->queue == PQ_NONE,
278             ("MEMGUARD: Expected wired page %p in vtomgfifo!", p));
279         return (&p->plinks.memguard.v);
280 }

uma_dbg.c:

282
283         if (!BIT_ISSET(SLAB_SETSIZE, freei, &slab->us_debugfree))
284                 panic("Duplicate free of %p from zone %p(%s) slab %p(%d)\n",
285                     item, zone, zone->uz_name, slab, freei);

Unread portion of the kernel message buffer:
Running callback for double_free
panic: Duplicate free of 0xfffff80003d1cf40 from zone 0xfffff800bfee4000(16) slab 0xfffff80003d1cf90(244)

cpuid = 0
KDB: stack backtrace:
db_trace_self_wrapper() at db_trace_self_wrapper+0x2b/frame 0xfffffe030c759530
kdb_backtrace() at kdb_backtrace+0x39/frame 0xfffffe030c7595e0
vpanic() at vpanic+0x126/frame 0xfffffe030c759620
panic() at panic+0x43/frame 0xfffffe030c759680
uma_dbg_free() at uma_dbg_free+0x101/frame 0xfffffe030c7596b0
uma_zfree_arg() at uma_zfree_arg+0xf6/frame 0xfffffe030c759730
free() at free+0xad/frame 0xfffffe030c759760
sysctl_test_bad_memory_operation() at sysctl_test_bad_memory_operation+0x88/frame 0xfffffe030c7597a0
sysctl_root_handler_locked() at sysctl_root_handler_locked+0x68/frame 0xfffffe030c7597e0
sysctl_root() at sysctl_root+0x18e/frame 0xfffffe030c759830
userland_sysctl() at userland_sysctl+0x192/frame 0xfffffe030c7598d0
sys___sysctl() at sys___sysctl+0x74/frame 0xfffffe030c759980
amd64_syscall() at amd64_syscall+0x2b3/frame 0xfffffe030c759ab0
Xfast_syscall() at Xfast_syscall+0xfb/frame 0xfffffe030c759ab0
--- syscall (202, FreeBSD ELF64, sys___sysctl), rip = 0x80094491a, rsp = 0x7fffffffda18, rbp = 0x7fffffffda50 ---
KDB: enter: panic

Reading symbols from /boot/kernel/aio.ko.symbols...done.
Loaded symbols for /boot/kernel/aio.ko.symbols
Reading symbols from /boot/kernel/bad_memory.ko...done.
Loaded symbols for /boot/kernel/bad_memory.ko
#0  doadump (textdump=60780544) at pcpu.h:219
219     pcpu.h: No such file or directory.
        in pcpu.h
(kgdb) #0  doadump (textdump=60780544) at pcpu.h:219
#1  0xffffffff80348655 in db_fncall (dummy1=<value optimized out>,
    dummy2=<value optimized out>, dummy3=<value optimized out>,
    dummy4=<value optimized out>) at /usr/src/sys/ddb/db_command.c:578
#2  0xffffffff8034833d in db_command (cmd_table=0x0)
    at /usr/src/sys/ddb/db_command.c:449
#3  0xffffffff803480b4 in db_command_loop ()
    at /usr/src/sys/ddb/db_command.c:502
#4  0xffffffff8034ab30 in db_trap (type=<value optimized out>, code=0)
    at /usr/src/sys/ddb/db_main.c:231
#5  0xffffffff8094cae9 in kdb_trap (type=3, code=0, tf=<value optimized out>)
    at /usr/src/sys/kern/subr_kdb.c:654
#6  0xffffffff80d34b42 in trap (frame=0xfffffe030c759510)
    at /usr/src/sys/amd64/amd64/trap.c:541
#7  0xffffffff80d16c89 in skiphook ()
    at /usr/src/sys/amd64/amd64/exception.S:245
#8  0xffffffff815c5e20 in cnputs_mtx ()
#9  0x0000000000000080 in ?? ()
#10 0xfffffe030c7594c0 in ?? ()
#11 0x0000000000000080 in ?? ()
#12 0x0000000000000000 in ?? ()
Current language:  auto; currently minimal

The checks in memguard.c should be guarded by `#if !defined(INVARIANTS) ... #endif' to eliminate unnecessary overhead double-checking this condition.

Reproduction steps:

Run the following commands as root:

- git clone https://github.com/yaneurabeya/scratch
- cd scratch/testing/tools/bad_memory
- for s in obj depend all install; do make $s || break; done
- kldload bad_memory
- sysctl test.bad_memory_operation=double_free
Comment 1 Mark Johnston freebsd_committer freebsd_triage 2021-04-01 16:04:06 UTC
memguard targets specific malloc types and zones, whereas UMA does general use-after-free and double free checking for all zones (with some constraints).  Compiling memguard into a kernel does not enable it, you have to point it at a particular zone (which the repro steps don't do).