skip to Main Content

The following code is an excerpt of my C programm to connect to a server:

#define _POSIX_C_SOURCE 200809L

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netdb.h>
#include <unistd.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdbool.h>
#include <sys/wait.h>
#include <sys/ipc.h>
#include <sys/shm.h>

#define PORTNUMBER 443
#define HOSTNAME "www.google.com"

int main() {
  struct addrinfo hints, * res, * matchingIP;
  char addrstr[99];
  char port[20];
  snprintf(port, sizeof(port), "%d", PORTNUMBER); // PORTNUMBER as char*
  int sock;
  memset( & hints, 0, sizeof hints);
  hints.ai_family = AF_INET;
  hints.ai_socktype = SOCK_STREAM;
  hints.ai_protocol = 0;
  hints.ai_flags = AI_CANONNAME;

  int status = getaddrinfo(HOSTNAME, port, & hints, & matchingIP); // Memory Leak occurs here?
  if (status < 0) {
    perror("No IP address found.");
  }

  for (res = matchingIP; res != NULL; res = res -> ai_next) {
    sock = socket(res -> ai_family, res -> ai_socktype, res -> ai_protocol);
    if (sock < 0) {
      perror("No socket created.");
      continue;
    }

    if (connect(sock, res -> ai_addr, res -> ai_addrlen) < 0) {
      perror("Client not connected.");
      continue;
    }
    break;
  }

  if (res == NULL) {
    perror("Client could not connect.");
    return EXIT_FAILURE;
  }

  inet_ntop(res -> ai_family, & ((struct sockaddr_in * ) res -> ai_addr) -> sin_addr, addrstr, sizeof addrstr);
  printf("IPv4 address: %s (%s)n", addrstr, res -> ai_canonname);
  freeaddrinfo(matchingIP);
}

Now when I run valgrind, it shows there is "still reachable" memory, I’ve narrowed it down to the getaddrinfo call (the leak does not appear before). Here is the full valgrind log:

==566543== HEAP SUMMARY:
==566543==     in use at exit: 3,509 bytes in 8 blocks
==566543==   total heap usage: 112 allocs, 104 frees, 112,649 bytes allocated
==566543== 
==566543== 38 bytes in 1 blocks are still reachable in loss record 1 of 7
==566543==    at 0x483B7F3: malloc (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so)
==566543==    by 0x401F5DE: strdup (strdup.c:42)
==566543==    by 0x4019A91: _dl_load_cache_lookup (dl-cache.c:338)
==566543==    by 0x400A989: _dl_map_object (dl-load.c:2102)
==566543==    by 0x400F514: openaux (dl-deps.c:64)
==566543==    by 0x4A17B47: _dl_catch_exception (dl-error-skeleton.c:208)
==566543==    by 0x400F962: _dl_map_object_deps (dl-deps.c:248)
==566543==    by 0x4015DAF: dl_open_worker (dl-open.c:571)
==566543==    by 0x4A17B47: _dl_catch_exception (dl-error-skeleton.c:208)
==566543==    by 0x4015609: _dl_open (dl-open.c:837)
==566543==    by 0x4A16AE0: do_dlopen (dl-libc.c:96)
==566543==    by 0x4A17B47: _dl_catch_exception (dl-error-skeleton.c:208)
==566543== 
==566543== 38 bytes in 1 blocks are still reachable in loss record 2 of 7
==566543==    at 0x483B7F3: malloc (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so)
==566543==    by 0x400D5B7: _dl_new_object (dl-object.c:196)
==566543==    by 0x4006E96: _dl_map_object_from_fd (dl-load.c:997)
==566543==    by 0x400A61A: _dl_map_object (dl-load.c:2236)
==566543==    by 0x400F514: openaux (dl-deps.c:64)
==566543==    by 0x4A17B47: _dl_catch_exception (dl-error-skeleton.c:208)
==566543==    by 0x400F962: _dl_map_object_deps (dl-deps.c:248)
==566543==    by 0x4015DAF: dl_open_worker (dl-open.c:571)
==566543==    by 0x4A17B47: _dl_catch_exception (dl-error-skeleton.c:208)
==566543==    by 0x4015609: _dl_open (dl-open.c:837)
==566543==    by 0x4A16AE0: do_dlopen (dl-libc.c:96)
==566543==    by 0x4A17B47: _dl_catch_exception (dl-error-skeleton.c:208)
==566543== 
==566543== 45 bytes in 1 blocks are still reachable in loss record 3 of 7
==566543==    at 0x483B7F3: malloc (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so)
==566543==    by 0x401F5DE: strdup (strdup.c:42)
==566543==    by 0x4019A91: _dl_load_cache_lookup (dl-cache.c:338)
==566543==    by 0x400A989: _dl_map_object (dl-load.c:2102)
==566543==    by 0x4015D46: dl_open_worker (dl-open.c:513)
==566543==    by 0x4A17B47: _dl_catch_exception (dl-error-skeleton.c:208)
==566543==    by 0x4015609: _dl_open (dl-open.c:837)
==566543==    by 0x4A16AE0: do_dlopen (dl-libc.c:96)
==566543==    by 0x4A17B47: _dl_catch_exception (dl-error-skeleton.c:208)
==566543==    by 0x4A17C12: _dl_catch_error (dl-error-skeleton.c:227)
==566543==    by 0x4A16C14: dlerror_run (dl-libc.c:46)
==566543==    by 0x4A16C14: __libc_dlopen_mode (dl-libc.c:195)
==566543==    by 0x49FA8CB: nss_load_library (nsswitch.c:359)
==566543== 
==566543== 45 bytes in 1 blocks are still reachable in loss record 4 of 7
==566543==    at 0x483B7F3: malloc (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so)
==566543==    by 0x400D5B7: _dl_new_object (dl-object.c:196)
==566543==    by 0x4006E96: _dl_map_object_from_fd (dl-load.c:997)
==566543==    by 0x400A61A: _dl_map_object (dl-load.c:2236)
==566543==    by 0x4015D46: dl_open_worker (dl-open.c:513)
==566543==    by 0x4A17B47: _dl_catch_exception (dl-error-skeleton.c:208)
==566543==    by 0x4015609: _dl_open (dl-open.c:837)
==566543==    by 0x4A16AE0: do_dlopen (dl-libc.c:96)
==566543==    by 0x4A17B47: _dl_catch_exception (dl-error-skeleton.c:208)
==566543==    by 0x4A17C12: _dl_catch_error (dl-error-skeleton.c:227)
==566543==    by 0x4A16C14: dlerror_run (dl-libc.c:46)
==566543==    by 0x4A16C14: __libc_dlopen_mode (dl-libc.c:195)
==566543==    by 0x49FA8CB: nss_load_library (nsswitch.c:359)
==566543== 
==566543== 936 bytes in 2 blocks are still reachable in loss record 5 of 7
==566543==    at 0x483DD99: calloc (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so)
==566543==    by 0x401331C: _dl_check_map_versions (dl-version.c:274)
==566543==    by 0x40160FC: dl_open_worker (dl-open.c:577)
==566543==    by 0x4A17B47: _dl_catch_exception (dl-error-skeleton.c:208)
==566543==    by 0x4015609: _dl_open (dl-open.c:837)
==566543==    by 0x4A16AE0: do_dlopen (dl-libc.c:96)
==566543==    by 0x4A17B47: _dl_catch_exception (dl-error-skeleton.c:208)
==566543==    by 0x4A17C12: _dl_catch_error (dl-error-skeleton.c:227)
==566543==    by 0x4A16C14: dlerror_run (dl-libc.c:46)
==566543==    by 0x4A16C14: __libc_dlopen_mode (dl-libc.c:195)
==566543==    by 0x49FA8CB: nss_load_library (nsswitch.c:359)
==566543==    by 0x49FB178: __nss_lookup_function (nsswitch.c:467)
==566543==    by 0x49BB4BE: gaih_inet.constprop.0 (getaddrinfo.c:800)
==566543== 
==566543== 1,200 bytes in 1 blocks are still reachable in loss record 6 of 7
==566543==    at 0x483DD99: calloc (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so)
==566543==    by 0x400D283: _dl_new_object (dl-object.c:89)
==566543==    by 0x4006E96: _dl_map_object_from_fd (dl-load.c:997)
==566543==    by 0x400A61A: _dl_map_object (dl-load.c:2236)
==566543==    by 0x400F514: openaux (dl-deps.c:64)
==566543==    by 0x4A17B47: _dl_catch_exception (dl-error-skeleton.c:208)
==566543==    by 0x400F962: _dl_map_object_deps (dl-deps.c:248)
==566543==    by 0x4015DAF: dl_open_worker (dl-open.c:571)
==566543==    by 0x4A17B47: _dl_catch_exception (dl-error-skeleton.c:208)
==566543==    by 0x4015609: _dl_open (dl-open.c:837)
==566543==    by 0x4A16AE0: do_dlopen (dl-libc.c:96)
==566543==    by 0x4A17B47: _dl_catch_exception (dl-error-skeleton.c:208)
==566543== 
==566543== 1,207 bytes in 1 blocks are still reachable in loss record 7 of 7
==566543==    at 0x483DD99: calloc (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so)
==566543==    by 0x400D283: _dl_new_object (dl-object.c:89)
==566543==    by 0x4006E96: _dl_map_object_from_fd (dl-load.c:997)
==566543==    by 0x400A61A: _dl_map_object (dl-load.c:2236)
==566543==    by 0x4015D46: dl_open_worker (dl-open.c:513)
==566543==    by 0x4A17B47: _dl_catch_exception (dl-error-skeleton.c:208)
==566543==    by 0x4015609: _dl_open (dl-open.c:837)
==566543==    by 0x4A16AE0: do_dlopen (dl-libc.c:96)
==566543==    by 0x4A17B47: _dl_catch_exception (dl-error-skeleton.c:208)
==566543==    by 0x4A17C12: _dl_catch_error (dl-error-skeleton.c:227)
==566543==    by 0x4A16C14: dlerror_run (dl-libc.c:46)
==566543==    by 0x4A16C14: __libc_dlopen_mode (dl-libc.c:195)
==566543==    by 0x49FA8CB: nss_load_library (nsswitch.c:359)
==566543== 
==566543== LEAK SUMMARY:
==566543==    definitely lost: 0 bytes in 0 blocks
==566543==    indirectly lost: 0 bytes in 0 blocks
==566543==      possibly lost: 0 bytes in 0 blocks
==566543==    still reachable: 3,509 bytes in 8 blocks
==566543==         suppressed: 0 bytes in 0 blocks
==566543== 
==566543== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)

I’ve read online several times that while "still reachable" memory leaks are memory leaks, they are not ones I should worry about. Sadly for me, it is a formal requirement that valgrind reports absolutely no memory leaks, including still reachable. Is there any chance I am able to fix it or is it the fault of the library? If the latter – what are suitable alternatives?
As far as I am are the host runs using IPv6 – perhaps that’s relevant.

Edit: The project is compiled with the -std=c99 flag and defines the posix C source as #define _POSIX_C_SOURCE 200809L. The valgrind flags are valgrind --leak-check=full --show-leak-kinds=all --trace-children=yes --log-file=log.txt --track-origins=yes -s and I’m developing on Ubuntu 20.04.6 LTS

Another edit: Minimal reproducible example

3

Answers


  1. Is there any chance I am able to fix it or is it the fault of the
    library?

    It’s not really a fault, but as you noted (and was also mentioned in the comments) libc decided it needs to bind to libnss. There are at least a couple of ways to confirm that:

    1. Run your program with LD_DEBUG=bindings (e.g., LD_DEBUG=bindings ./program)
    2. Add a getchar() to pause your program before exiting and examine /proc/<pid>/maps.

    Unfortunatelly, there’s no reliable or portable way to dl_close() that I’m aware of.

    If the latter – what are suitable alternatives?

    The best alternative is to provide valgrind with a suppressions file which will make it ignore specific allocations/leaks/reachable blocks.

    You can generate one specifically for this case by running valgrind as you did but adding the option --gen-suppressions=yes.

    Login or Signup to reply.
  2. My opinion is that this is most likely a GNU libc bug.

    Oh look.

    Valgrind saying it’s a GNU libc bug

    GNU libc bug

    GNU libc includes a cleanup function, __libc_freeres() that gets called by Valgrind upon a clean exit. It sounds like someone added some dynamic loading and there’s missing cleanup of the dynamic loading name cache from __libc_freeres.

    I could add that to the Valgrind default supressions. That would only be the last resort if the GNU libc devs refuse to fix the issue. The above bug has been open for getting on for 2 years so it’s not looking very optimistic.

    Sadly for me, it is a formal requirement that valgrind reports absolutely no memory leaks, including still reachable.

    If the goal there is to improve your software quality that’s a good thing. But if the goal is only to give the appearance of quality and to enable the suits to make powerpoint presentatons with a page showing zero leaks then that’s a bad thing. That’s a slippery slope that leads to huge suppression files that risk hiding genuine issues.

    Having said that, personally I would add a suppression for this.

    Login or Signup to reply.
  3. And going through your options

    valgrind –leak-check=full –show-leak-kinds=all –trace-children=yes –log-file=log.txt –track-origins=yes -s

    Does your executable spawn child processes? If not you don’t need --trace-children=yes. On the other hand if you do spawn and need --trace-children=yes then you should by using the pid in the log file name, such as --log-file=log.%p,txt where %p will be replaced by the PID. If you don’t do that then the child log files will overwrite parent ones.

    I don’t recommend using --track-origins=yes until after you have found errors. It causes a fairly significant slowdown. If you’re OK with that overhead then the advantage is that it will have more info to start with.

    -s if you are really serious then you will check that any suppressions that you have added are still in use and remove them when they are no longer used.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search