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
getaddrinfo
is one) look likegethostbyname_r
:The caller supplies a buffer for the result data via
buf
, ofbuflen
bytes. If it turns out that this buffer is not sufficient in size, the function fails with anERANGE
error. 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.getaddrinfo
looks differently, but the internal backing implementations are very similar to the publicgethostbyname_r
function.Because the retry-with-a-larger-buffer idiom is so common throughout the NSS code,
struct scratch_buffer
was introduced. (Previously, there was a fairly eclectic mix of fixed buffer sizes,alloca
,alloca
withmalloc
fallback, and so on.)struct scratch_buffer
combines a fixed-size on-stack buffer which is used for the first NSS call. If that fails withERANGE
,scratch_buffer_grow
is called, which switches to a heap buffer, and on subsequent calls, allocates a larger heap buffer.scratch_buffer_free
deallocates the heap buffer if there is one.In your example, the leaks that
tcmalloc
reports 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,tcmalloc
is 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 thattcmalloc
does not call the magic__libc_freeres
function, 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 thegetaddrinfo
example, I see the following still-allocated resources:/etc/resolv.conf
(system DNS configuration)./etc/nsswitch.conf
(NSS configuration).dlopen
calls (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.