I preloaded libumem and enabled umem debugging. This made it very easy to find the culprit:
rm@turin:~$ LD_PRELOAD=libumem.so UMEM_DEBUG=default man -M /tmp/test/share/man -w
Abort (core dumped)
rm@turin:~$ mdb core
Loading modules: [ libumem.so.1 libc.so.1 ld.so.1 ]
> $C
08039ef8 libc.so.1`_lwp_kill+0x15(1, 6, 0, 1, fefa3000, fef9127d)
08039f18 libc.so.1`raise+0x2b(6)
08039f38 libumem.so.1`umem_do_abort+0x53()
08039f48 libumem.so.1`__umem_assert_failed(fef9127d, fef914fc, 889df34)
08039f88 libumem.so.1`process_free+0x74(889df34, 0, 8039fa8)
08039fc8 libumem.so.1`realloc+0x3d(889df34, 100)
0803a018 libc.so.1`getdelim+0x16e(803a064, 803a068, a, 80690c0)
0803a038 libc.so.1`getline+0x33(803a064, 803a068, 80690c0)
0803a088 process_page+0x67(8874ee8, 803a10c, 0, 80524d4)
0803a0d8 process_section+0xf7(803a10c)
0803a528 mwpath+0xd4(886ef10, 0, 0, 803a610, 803a610, 8878dc8)
0803a548 do_makewhatis+0x5e(886ef40, 0, 803a5a8, 80565e2, fed90548, 0)
0803a5a8 main+0x496(803a5ac, fef035c8, 803a5e8, 805286b)
0803a5e8 _start_crt+0x9a(4, 803a610, f7ee301f, 0, 0, 0)
0803a604 _start+0x1a(4, 803a6f4, 803a6f8, 803a6fb, 803a70f, 0)
> ::umem_status
Status: ready and active
Concurrency: 16
Logs: (inactive)
Message buffer:
realloc(889df34): invalid or corrupted buffer
stack trace:
libumem.so.1'umem_err_recoverable+0x37
libumem.so.1'process_free+0x74
libumem.so.1'realloc+0x3d
libc.so.1'getdelim+0x16e
libc.so.1'getline+0x33
man'process_page+0x67
man'process_section+0xf7
man'mwpath+0xd4
man'do_makewhatis+0x5e
man'main+0x496
man'_start_crt+0x9a
man'_start+0x1a
> 803a10c/s
0x803a10c: /tmp/test/share/man/man3
> 889df34::whatis
889df34 is 889df28+c, allocated from umem_alloc_160:
ADDR BUFADDR TIMESTAMP THREAD
CACHE LASTLOG CONTENTS
889eeb0 889df28 3aafd83ba063 1
884e010 0 0
libumem.so.1`umem_cache_alloc_debug+0x216
libumem.so.1`umem_cache_alloc+0x1f5
libumem.so.1`umem_alloc+0x5c
libumem.so.1`umem_malloc+0x25
libumem.so.1`realloc+0xa2
libc.so.1`getdelim+0x4d
libc.so.1`getline+0x33
process_page+0x67
process_section+0xf7
mwpath+0xd4
do_makewhatis+0x5e
main+0x496
_start_crt+0x9a
_start+0x1a
Note how the buffer that getline/getdlim tried to free is offset at +c
from an expected address. The getline
function expects to operate by freeing the same string. Critically this means that while the contents of the string that is realloced can be changed, the actual pointer itself should not move. Unfortunately, if we look at the case of it trying to handle the .IX
macro it proceeds to increment the line
pointer that is fed into getdelim
breaking things. The attached manual happens to use this in a few places:
grep IX *
.de IX
. de IX
.IX Title "Font::TTF::AATutils 3"
.IX Subsection "($classes, $states) = AAT_read_subtable($fh, $baseOffset, $subtableStart, $limits)"
.IX Subsection "$length = AAT_write_state_table($fh, $classes, $states, $numExtraTables, $packEntry)"
.IX Subsection "($classes, $states, $entries) = AAT_read_state_table($fh, $numActionWords)"
.IX Subsection "($format, $lookup) = AAT_read_lookup($fh, $valueSize, $length, $default)"
.IX Subsection "AAT_write_lookup($fh, $format, $lookup, $valueSize, $default)"
.IX Header "AUTHOR"
.IX Header "LICENSING"
illumos doesn't normally use this macro in its old sgml->man converted pages and doesn't in mandoc. The fiix here is to simply use a temporary variable to track these modifications of the starting address.