Quote from a private correspondence with a Qualys vulnerability researcher (used with permission):> Interesting (great malloc by the way, clean design and implementation > and lots of security checks)!
Additionally, there are a few features that can be switched on runtime that implement even more strict checks, at the cost of performance and/or increased memory usage. You can read more about that in the man page of malloc(3) and my EuroBSDCon 2023 presentation slides or video.
Around 2011 worked on improving the speed of the allocation of small chunks. Working on that, I really got into the mood to return to the old goal of adding a feature that is very useful to developers: memory leak detection. Once I realized I could use a compiler feature to determine the address of the code malloc has been called from, I got on a roll. While the original code worked, it had some drawbacks and for various reasons the leak detection code was never compiled by default until I rewrote it in 2023. OpenBSD 7.4, released in 2023 has leak detection available, switched off by default. In this page I'd like to show how to use this feature.
$ MALLOC_OPTIONS=D ktrace -tu ./a.outThis will produce a file called ktrace.out. The output then can be viewed using kdump To shows the malloc utrace records in in a nice way, use the argument -u malloc:
$ kdump -u mallocI made a little leaky program to illustrate some points.
$ cat -n x.c 1 #include <stdlib.h> 2 #include <stdio.h> 3 4 int 5 main() 6 { 7 void *p; 8 int i; 9 10 for (i = 0; i < 3; i++) 11 printf("%p\n", p = malloc(10240)); 12 free(p); 13 for (i = 0; i < 5; i++) 14 printf("%p\n", malloc(1000)); 15 } $ cc -g x.c $ MALLOC_OPTIONS=D ktrace -tu ./a.out 0x1098469000 0x114c10b000 0x11447c8000 0x1112865800 0x1112865000 0x111283c800 0x1112865400 0x111283c400 [otto@macmini:9]$ kdump -u malloc ******** Start dump a.out ******* M=8 I=1 F=0 U=0 J=1 R=0 X=0 C=0 cache=64 G=0 Leak report: f sum # avg 0x0 4096 4 1024 addr2line -e . 0x0 0xc5e0d0a90 20480 2 10240 addr2line -e ./a.out 0x10a90 0xc5e0d0ad8 1024 1 1024 addr2line -e ./a.out 0x10ad8 0x110bb84d18 65536 1 65536 addr2line -e /usr/lib/libc.so.97.1 0x47d18 ******** End dump a.out ******* $The Leak report shows information for all leaks grouped by the value of f, which is the call site of the function call that allocated memory that was never freed. If the value of f is 0x0, it means no caller data is available, due to the sampling nature of the leak detection. A future release will drop this sampling and have full info for all leaks.
The sum column shows the total number of bytes leaked, the # column shows the number of calls to an allocation that leaked and the avg column shows the average size of the allocations. We can conclude that this program has 5 leaks of size 1024, two leaks of size 10240 and one leak of size 65536. For the smaller allocations the size is rounded up to 1024, while malloc was called wit a size argument of 1000.
$ addr2line -e ./a.out 0x10a90 /home/otto/x.c:11 $ addr2line -e ./a.out 0x10ad8 /home/otto/x.c:14 $This shows that both instances of malloc calls introduced leaks. Fixing the leaks is left as an exercise to the reader.
The third case is a bit different: the leak originates from libc and on my arm64 I get:
$ addr2line -e /usr/lib/libc.so.97.1 0x47d18 BFD: Dwarf Error: found dwarf version '4', this reader only handles version 2 information. ... $This is nasty, the addr2line command in base cannot read the debug info. Luckily there's a workaround (which is needed on e.g. arm64 machines): install the package binutils and use gaddr2line:
$ doas pkg_add binutils $ gaddr2line -e /usr/lib/libc.so.97.1 0x47d18 /usr/src/lib/libc/stdio/makebuf.c:62 $If you look at makebuf.c and the rest of the standard io library you can learn that on this line a buffer used by the stdout stream is allocated. This is strictly speaking a leak, but it is not harmful. When leak hunting we are more interested in leaks that grow with the process doing work.
awk '/addr2line/{ printf($0); cmd=("addr2line -f " $6 " " $7 " " $8 " | tr \\\\n \" \""); printf(" "); system(cmd); print("") } !/addr2line/ { print } 'This script uses system(), use at your own risk.