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)))
… exit …
if (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)))
… exit …
if (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
Summary
On the default path (shared memory enabled,
FAKETIME_DISABLE_SHMunset), libfaketime opens a POSIX shm object,mmaps it, and then leaves the file descriptor open.POSIX
mmapdoes not consume the fd. After a successful map the descriptor can — and should — beclosed; the mapping remains. Here the fd is a local (shm_fdNinft_shm_create,ticks_shm_fdinft_shm_really_init) and is discarded withoutclose.A process that goes through create-then-attach therefore leaks two descriptors pointing at
/dev/shm/faketime_shm_<pid>. The same pattern exists in thefaketimewrapper (src/faketime.c), which alsoshm_opens,mmaps,munmaps, and neverclosesshm_fd.Not a crash. It is a real leak on the default init path; long-lived processes with a tight
LimitNOFILEfeel it as one more step towardEMFILE.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:Attach path — second open, same object, same omission:
ft_cleanup()munmapsft_sharedand 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 skipsclose(shm_fd).Reproduce
Actual output:
With
FAKETIME_DISABLE_SHM=1the count is 0.Suggested fix
Close the shm fd immediately after a successful
mmap(and on themmap/ftruncateerror paths). The mapping does not need the fd afterwards:The same
close(shm_fd)belongs insrc/faketime.cafter itsmmap.Also close
shm_fdN/shm_fdon theftruncatefailure paths, which currentlyexitwith the fd still open.Environment
wolfcw/libfaketime0.9.12 (bbb6c08)make, shared memory left enabled