I found that below codes makes heap leak if I check it with tcmalloc heap checker with draconian mode but the leak is not found with LSan
(I assume that internal allocation in glibc is suppressed in LSan)
#include <string.h>
#include <netdb.h>
int foo() {
struct addrinfo hints, *res;
memset(&hints, 0, sizeof hints);
getaddrinfo("www.example.com", 0, &hints, &res);
freeaddrinfo(res);
}
int main() {
foo();
}
I checked a bit more and found that getaddrinfo() uses scratch buffer in glibc internally
and suspect that those scratch buffer makes memory leaks
(even though it isn't harmful)
But sadly there isn't full explanation
and only says that "scratch buffer is variable-sized buffers with on-stack default allocation";;
What scratch buffer exactly do though?
you can refer glibc/include/scratch_buffer.h here
Internally, all the NSS interfaces (of which
getaddrinfois one) look likegethostbyname_r:The caller supplies a buffer for the result data via
buf, ofbuflenbytes. If it turns out that this buffer is not sufficient in size, the function fails with anERANGEerror. The caller is expected to grow the buffer (reallocating it in some way) and call the function against, with the other parameters the same. This repeats until the buffer is large enough and the function succeeds (or the function fails for some other reason). It's a longer story how we ended up with this strange interface, but it's the interfaces we have today.getaddrinfolooks differently, but the internal backing implementations are very similar to the publicgethostbyname_rfunction.Because the retry-with-a-larger-buffer idiom is so common throughout the NSS code,
struct scratch_bufferwas introduced. (Previously, there was a fairly eclectic mix of fixed buffer sizes,alloca,allocawithmallocfallback, and so on.)struct scratch_buffercombines a fixed-size on-stack buffer which is used for the first NSS call. If that fails withERANGE,scratch_buffer_growis called, which switches to a heap buffer, and on subsequent calls, allocates a larger heap buffer.scratch_buffer_freedeallocates the heap buffer if there is one.In your example, the leaks that
tcmallocreports are not related to the scratch buffers. (We have had certainly such bugs ingetaddrinfo, particularly on obscure error paths, but the current code should be mostly okay.) Link order is also not a problem because evidently,tcmallocis active, otherwise you would not get any leak reports.The reason why you see leaks with
tcmalloc(but not with other tools such as valgrind) is thattcmallocdoes not call the magic__libc_freeresfunction, which was specifically added for heap checkers. Normally, when the process terminates, glibc does not deallocate all internal allocations because the kernel will release that memory anyway. Most subsystems register there allocations in some way with__libc_freeres. In thegetaddrinfoexample, I see the following still-allocated resources:/etc/resolv.conf(system DNS configuration)./etc/nsswitch.conf(NSS configuration).dlopencalls (for loading the NSS services modules.getaddrinfo.You can see these allocations easily if you run your example under valgrind, using a command like this one:
The key part is
--run-libc-freeres=no, which instructs valgrind not to call__libc_freeres, which it does by default. If you omit this parameter, valgrind will not report any memory leaks.