Skip to content

Shared-memory shm_open fds are never closed after mmap (ft_shm_create / ft_shm_really_init) #552

Description

@yayong3

Summary

On the default path (shared memory enabled, FAKETIME_DISABLE_SHM unset), libfaketime opens a POSIX shm object, mmaps it, and then leaves the file descriptor open.

POSIX mmap does not consume the fd. After a successful map the descriptor can — and should — be closed; the mapping remains. Here the fd is a local (shm_fdN in ft_shm_create, ticks_shm_fd in ft_shm_really_init) and is discarded without close.

A process that goes through create-then-attach therefore leaks two descriptors pointing at /dev/shm/faketime_shm_<pid>. The same pattern exists in the faketime wrapper (src/faketime.c), which also shm_opens, mmaps, munmaps, and never closes shm_fd.

Not a crash. It is a real leak on the default init path; long-lived processes with a tight LimitNOFILE feel it as one more step toward EMFILE.

Present on 0.9.12 (bbb6c08).

Code

Create path — fd opened at :524, mapped at :536, unmapped at :561, function returns with the fd still open:

/* src/libfaketime.c — ft_shm_create */
if (-1 == (shm_fdN = shm_open(shm_name, O_CREAT|O_EXCL|O_RDWR, S_IWUSR|S_IRUSR)))
  … exitif (MAP_FAILED == (ft_sharedN = mmap(..., shm_fdN, 0)))
  … exit …
…
munmap(ft_sharedN, sizeof(struct ft_shared_s));
… ft_sem_close(&semN); …
/* shm_fdN is never closed */

Attach path — second open, same object, same omission:

/* ft_shm_really_init :757 */
if (-1 == (ticks_shm_fd = shm_open(shm_name, O_CREAT|O_RDWR, S_IWUSR|S_IRUSR)))
  … exitif (MAP_FAILED == (ft_shared = mmap(..., ticks_shm_fd, 0)))
  … exit/* ticks_shm_fd is never closed */

ft_cleanup() munmaps ft_shared and closes the semaphore; it does not close a shm fd, and none is stored.

The wrapper copies the create path (src/faketime.c:282–344) and also skips close(shm_fd).

Reproduce

make -C src
gcc -O0 -o poc poc.c
FAKETIME=+0 LD_PRELOAD=./src/libfaketime.so.1 ./poc
#define _GNU_SOURCE
#include <dirent.h>
#include <stdio.h>
#include <string.h>
#include <time.h>
#include <unistd.h>

int main(void)
{
  struct timespec ts;
  DIR *dir;
  struct dirent *entry;
  int found = 0;

  if (clock_gettime(CLOCK_REALTIME, &ts) != 0)
    return 2;
  dir = opendir("/proc/self/fd");
  if (!dir)
    return 3;
  while ((entry = readdir(dir)) != NULL) {
    char path[64], target[512];
    ssize_t n;
    if (entry->d_name[0] == '.')
      continue;
    snprintf(path, sizeof path, "/proc/self/fd/%s", entry->d_name);
    n = readlink(path, target, sizeof target - 1);
    if (n < 0)
      continue;
    target[n] = '\0';
    if (strstr(target, "faketime_shm_") != NULL) {
      printf("fd=%s target=%s\n", entry->d_name, target);
      found++;
    }
  }
  closedir(dir);
  printf("faketime_shm_fd_count=%d\n", found);
  return 0;
}

Actual output:

fd=4 target=/dev/shm/faketime_shm_<pid>
fd=6 target=/dev/shm/faketime_shm_<pid>
faketime_shm_fd_count=2

With FAKETIME_DISABLE_SHM=1 the count is 0.

Suggested fix

Close the shm fd immediately after a successful mmap (and on the mmap/ftruncate error paths). The mapping does not need the fd afterwards:

--- a/src/libfaketime.c
+++ b/src/libfaketime.c
@@ -536,8 +536,9 @@ static void ft_shm_create(void) {
   if (MAP_FAILED == (ft_sharedN = mmap(NULL, sizeof(struct ft_shared_s), PROT_READ|PROT_WRITE,
                      MAP_SHARED, shm_fdN, 0)))
   {
     perror("libfaketime: In ft_shm_create(), mmap failed");
+    close(shm_fdN);
     exit(EXIT_FAILURE);
   }
+  close(shm_fdN);
@@ -763,8 +763,9 @@ static void ft_shm_really_init (void)
     if (MAP_FAILED == (ft_shared = mmap(NULL, sizeof(struct ft_shared_s), PROT_READ|PROT_WRITE,
             MAP_SHARED, ticks_shm_fd, 0)))
     {
       perror("libfaketime: In ft_shm_init(), mmap failed");
+      close(ticks_shm_fd);
       exit(1);
     }
+    close(ticks_shm_fd);

The same close(shm_fd) belongs in src/faketime.c after its mmap.

Also close shm_fdN / shm_fd on the ftruncate failure paths, which currently exit with the fd still open.

Environment

  • wolfcw/libfaketime 0.9.12 (bbb6c08)
  • Linux x86_64, stock make, shared memory left enabled

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions