Replace POSIX semaphore with flock() for cross-arch safety

POSIX named semaphores (sem_t) have architecture-dependent internal
layout in glibc: 32 bytes on 64-bit, 16 bytes on 32-bit. When a
64-bit faketime wrapper creates a semaphore and spawns a 32-bit child,
the child misinterprets the counter and hangs on sem_wait forever.

Extract ft_sem_* abstraction into shared ft_sem.h/ft_sem.c with three
backends: FT_POSIX (existing), FT_SYSV (existing), FT_FLOCK (new
default). The flock backend uses kernel-mediated file locking on
/dev/shm/faketime_lock_<pid>, which is architecture-independent and
auto-releases on process death.

Both libfaketime.so and the faketime wrapper now use the same shared
abstraction, ensuring protocol agreement regardless of backend.
This commit is contained in:
Andrij Abyzov
2026-01-26 14:43:43 +01:00
parent e21bf3017a
commit b687b165b2
5 changed files with 306 additions and 179 deletions

View File

@@ -168,17 +168,20 @@ all: ${LIBS} ${BINS}
libfaketimeMT.o: EXTRA_FLAGS := -DPTHREAD_SINGLETHREADED_TIME libfaketimeMT.o: EXTRA_FLAGS := -DPTHREAD_SINGLETHREADED_TIME
${LIBS_OBJ}: libfaketime.c ft_sem.o: ft_sem.c ft_sem.h
${CC} -o $@ -c ${CFLAGS} ${CPPFLAGS} $<
${LIBS_OBJ}: libfaketime.c ft_sem.h
${CC} -o $@ -c ${CFLAGS} ${CPPFLAGS} ${EXTRA_FLAGS} $< ${CC} -o $@ -c ${CFLAGS} ${CPPFLAGS} ${EXTRA_FLAGS} $<
%.so.${SONAME}: %.o libfaketime.map %.so.${SONAME}: %.o ft_sem.o libfaketime.map
${CC} -o $@ -Wl,-soname,$@ ${LDFLAGS} ${LIB_LDFLAGS} $< ${LDADD} ${CC} -o $@ -Wl,-soname,$@ ${LDFLAGS} ${LIB_LDFLAGS} $< ft_sem.o ${LDADD}
${BINS}: faketime.c ${BINS}: faketime.c ft_sem.o ft_sem.h
${CC} -o $@ ${CFLAGS} ${CPPFLAGS} ${EXTRA_FLAGS} $< ${LDFLAGS} ${BIN_LDFLAGS} ${CC} -o $@ ${CFLAGS} ${CPPFLAGS} ${EXTRA_FLAGS} $< ft_sem.o ${LDFLAGS} ${BIN_LDFLAGS}
clean: clean:
@rm -f ${LIBS_OBJ} ${LIBS} ${BINS} @rm -f ${LIBS_OBJ} ${LIBS} ${BINS} ft_sem.o
distclean: clean distclean: clean
@echo @echo

View File

@@ -44,7 +44,7 @@
#include <sys/types.h> #include <sys/types.h>
#include <sys/wait.h> #include <sys/wait.h>
#include <sys/mman.h> #include <sys/mman.h>
#include <semaphore.h> #include "ft_sem.h"
#include "faketime_common.h" #include "faketime_common.h"
@@ -60,6 +60,7 @@ static const char *date_cmd = "date";
/* semaphore and shared memory names */ /* semaphore and shared memory names */
char sem_name[PATH_BUFSIZE] = {0}, shm_name[PATH_BUFSIZE] = {0}; char sem_name[PATH_BUFSIZE] = {0}, shm_name[PATH_BUFSIZE] = {0};
static ft_sem_t wrapper_sem;
void usage(const char *name) void usage(const char *name)
{ {
@@ -98,9 +99,9 @@ void usage(const char *name)
/** Clean up shared objects */ /** Clean up shared objects */
static void cleanup_shobjs() static void cleanup_shobjs()
{ {
if (-1 == sem_unlink(sem_name)) if (-1 == ft_sem_unlink(&wrapper_sem))
{ {
perror("faketime: sem_unlink"); perror("faketime: ft_sem_unlink");
} }
if (-1 == shm_unlink(shm_name)) if (-1 == shm_unlink(shm_name))
{ {
@@ -255,9 +256,8 @@ int main (int argc, char **argv)
curr_opt++; curr_opt++;
{ {
/* create semaphores and shared memory */ /* create lock and shared memory */
int shm_fd; int shm_fd;
sem_t *sem;
struct ft_shared_s *ft_shared; struct ft_shared_s *ft_shared;
char shared_objs[PATH_BUFSIZE * 2 + 1]; char shared_objs[PATH_BUFSIZE * 2 + 1];
@@ -271,10 +271,10 @@ int main (int argc, char **argv)
snprintf(sem_name, PATH_BUFSIZE -1 ,"/faketime_sem_%ld", (long)getpid()); snprintf(sem_name, PATH_BUFSIZE -1 ,"/faketime_sem_%ld", (long)getpid());
snprintf(shm_name, PATH_BUFSIZE -1 ,"/faketime_shm_%ld", (long)getpid()); snprintf(shm_name, PATH_BUFSIZE -1 ,"/faketime_shm_%ld", (long)getpid());
if (SEM_FAILED == (sem = sem_open(sem_name, O_CREAT|O_EXCL, S_IWUSR|S_IRUSR, 1))) if (-1 == ft_sem_create(sem_name, &wrapper_sem))
{ {
perror("faketime: sem_open"); perror("faketime: ft_sem_create");
fprintf(stderr, "The faketime wrapper only works on platforms that support the sem_open()\nsystem call. However, you may LD_PRELOAD libfaketime without using this wrapper.\n"); fprintf(stderr, "The faketime wrapper failed to create its lock.\nHowever, you may LD_PRELOAD libfaketime without using this wrapper.\n");
exit(EXIT_FAILURE); exit(EXIT_FAILURE);
} }
@@ -282,10 +282,7 @@ int main (int argc, char **argv)
if (-1 == (shm_fd = shm_open(shm_name, O_CREAT|O_EXCL|O_RDWR, S_IWUSR|S_IRUSR))) if (-1 == (shm_fd = shm_open(shm_name, O_CREAT|O_EXCL|O_RDWR, S_IWUSR|S_IRUSR)))
{ {
perror("faketime: shm_open"); perror("faketime: shm_open");
if (-1 == sem_unlink(argv[2])) ft_sem_unlink(&wrapper_sem);
{
perror("faketime: sem_unlink");
}
exit(EXIT_FAILURE); exit(EXIT_FAILURE);
} }
@@ -306,9 +303,9 @@ int main (int argc, char **argv)
exit(EXIT_FAILURE); exit(EXIT_FAILURE);
} }
if (sem_wait(sem) == -1) if (ft_sem_lock(&wrapper_sem) == -1)
{ {
perror("faketime: sem_wait"); perror("faketime: ft_sem_lock");
cleanup_shobjs(); cleanup_shobjs();
exit(EXIT_FAILURE); exit(EXIT_FAILURE);
} }
@@ -334,16 +331,16 @@ int main (int argc, char **argv)
exit(EXIT_FAILURE); exit(EXIT_FAILURE);
} }
if (sem_post(sem) == -1) if (ft_sem_unlock(&wrapper_sem) == -1)
{ {
perror("faketime: semop"); perror("faketime: ft_sem_unlock");
cleanup_shobjs(); cleanup_shobjs();
exit(EXIT_FAILURE); exit(EXIT_FAILURE);
} }
snprintf(shared_objs, sizeof(shared_objs), "%s %s", sem_name, shm_name); snprintf(shared_objs, sizeof(shared_objs), "%s %s", sem_name, shm_name);
setenv("FAKETIME_SHARED", shared_objs, true); setenv("FAKETIME_SHARED", shared_objs, true);
sem_close(sem); ft_sem_close(&wrapper_sem);
} }
{ {

220
src/ft_sem.c Normal file
View File

@@ -0,0 +1,220 @@
/*
* This file is part of libfaketime, version 0.9.12
*
* libfaketime is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License v2 as published by the
* Free Software Foundation.
*
* libfaketime is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License v2 along
* with the libfaketime; if not, write to the Free Software Foundation,
* Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
#include "ft_sem.h"
#if FT_SEMAPHORE_BACKEND == FT_SYSV
#include <sys/sem.h>
#endif
#if FT_SEMAPHORE_BACKEND == FT_FLOCK
#include <sys/file.h>
#endif
/*
* =======================================================================
* Semaphore related functions === SEM
* =======================================================================
*/
#if FT_SEMAPHORE_BACKEND == FT_SYSV
int ft_sem_name2key(char *name)
{
key_t key;
char fullname[256];
snprintf(fullname, sizeof(fullname), "/tmp%s", name);
fullname[sizeof(fullname) - 1] = '\0';
int fd = open(fullname, O_CREAT, S_IRUSR | S_IWUSR);
if (fd < 0)
{
perror("libfaketime: open");
return -1;
}
close(fd);
if (-1 == (key = ftok(fullname, 'F')))
{
perror("libfaketime: ftok");
return -1;
}
return key;
}
#endif
#if FT_SEMAPHORE_BACKEND == FT_FLOCK
static int ft_sem_name_to_path(const char *name, char *path, size_t pathlen)
{
const char *prefix = "/faketime_sem_";
const char *p = strstr(name, prefix);
if (p == NULL)
{
return -1;
}
const char *pid_str = p + strlen(prefix);
snprintf(path, pathlen, "/dev/shm/faketime_lock_%s", pid_str);
path[pathlen - 1] = '\0';
return 0;
}
#endif
int ft_sem_create(char *name, ft_sem_t *ft_sem)
{
#if FT_SEMAPHORE_BACKEND == FT_POSIX
if (SEM_FAILED == (ft_sem->sem = sem_open(name, O_CREAT|O_EXCL, S_IWUSR|S_IRUSR, 1)))
{
return -1;
}
#elif FT_SEMAPHORE_BACKEND == FT_SYSV
key_t key = ft_sem_name2key(name);
int nsems = 1;
ft_sem->semid = semget(key, nsems, IPC_CREAT | IPC_EXCL | S_IWUSR | S_IRUSR);
if (ft_sem->semid >= 0) { /* we got here first */
struct sembuf sb = {
.sem_num = 0,
.sem_op = 1, /* number of resources the semaphore has (when decremented down to 0 the semaphore will block) */
.sem_flg = 0,
};
/* the number of semaphore operation structures (struct sembuf) passed to semop */
int nsops = 1;
/* do a semop() to "unlock" the semaphore */
if (-1 == semop(ft_sem->semid, &sb, nsops)) {
return -1;
}
} else if (errno == EEXIST) { /* someone else got here before us */
return -1;
} else {
return -1;
}
#elif FT_SEMAPHORE_BACKEND == FT_FLOCK
char path[256];
if (ft_sem_name_to_path(name, path, sizeof(path)) < 0)
{
return -1;
}
ft_sem->fd = open(path, O_CREAT|O_EXCL|O_RDWR, S_IWUSR|S_IRUSR);
if (ft_sem->fd < 0)
{
return -1;
}
#endif
strncpy(ft_sem->name, name, sizeof ft_sem->name - 1);
ft_sem->name[sizeof ft_sem->name - 1] = '\0';
return 0;
}
int ft_sem_open(char *name, ft_sem_t *ft_sem)
{
#if FT_SEMAPHORE_BACKEND == FT_POSIX
if (SEM_FAILED == (ft_sem->sem = sem_open(name, 0)))
{
return -1;
}
#elif FT_SEMAPHORE_BACKEND == FT_SYSV
key_t key = ft_sem_name2key(name);
ft_sem->semid = semget(key, 1, S_IWUSR | S_IRUSR);
if (ft_sem->semid < 0) {
return -1;
}
#elif FT_SEMAPHORE_BACKEND == FT_FLOCK
char path[256];
if (ft_sem_name_to_path(name, path, sizeof(path)) < 0)
{
return -1;
}
ft_sem->fd = open(path, O_RDWR);
if (ft_sem->fd < 0)
{
return -1;
}
#endif
strncpy(ft_sem->name, name, sizeof ft_sem->name - 1);
ft_sem->name[sizeof ft_sem->name - 1] = '\0';
return 0;
}
int ft_sem_lock(ft_sem_t *ft_sem)
{
#if FT_SEMAPHORE_BACKEND == FT_POSIX
return sem_wait(ft_sem->sem);
#elif FT_SEMAPHORE_BACKEND == FT_SYSV
struct sembuf sb = {
.sem_num = 0,
.sem_op = -1, /* allocate resource (lock) */
.sem_flg = 0,
};
return semop(ft_sem->semid, &sb, 1);
#elif FT_SEMAPHORE_BACKEND == FT_FLOCK
return flock(ft_sem->fd, LOCK_EX);
#endif
}
int ft_sem_unlock(ft_sem_t *ft_sem)
{
#if FT_SEMAPHORE_BACKEND == FT_POSIX
return sem_post(ft_sem->sem);
#elif FT_SEMAPHORE_BACKEND == FT_SYSV
struct sembuf sb = {
.sem_num = 0,
.sem_op = 1, /* free resource (unlock) */
.sem_flg = 0,
};
return semop(ft_sem->semid, &sb, 1);
#elif FT_SEMAPHORE_BACKEND == FT_FLOCK
return flock(ft_sem->fd, LOCK_UN);
#endif
}
int ft_sem_close(ft_sem_t *ft_sem)
{
#if FT_SEMAPHORE_BACKEND == FT_POSIX
return sem_close(ft_sem->sem);
#elif FT_SEMAPHORE_BACKEND == FT_SYSV
/* NOP -- there's no "close" equivalent */
(void)ft_sem;
return 0;
#elif FT_SEMAPHORE_BACKEND == FT_FLOCK
int ret = close(ft_sem->fd);
ft_sem->fd = -1;
return ret;
#endif
}
int ft_sem_unlink(ft_sem_t *ft_sem)
{
#if FT_SEMAPHORE_BACKEND == FT_POSIX
return sem_unlink(ft_sem->name);
#elif FT_SEMAPHORE_BACKEND == FT_SYSV
return semctl(ft_sem->semid, 0, IPC_RMID);
#elif FT_SEMAPHORE_BACKEND == FT_FLOCK
char path[256];
if (ft_sem_name_to_path(ft_sem->name, path, sizeof(path)) < 0)
{
return -1;
}
return unlink(path);
#endif
}

62
src/ft_sem.h Normal file
View File

@@ -0,0 +1,62 @@
/*
* This file is part of libfaketime, version 0.9.12
*
* libfaketime is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License v2 as published by the
* Free Software Foundation.
*
* libfaketime is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License v2 along
* with the libfaketime; if not, write to the Free Software Foundation,
* Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#ifndef FT_SEM_H
#define FT_SEM_H
/* semaphore backend options */
#define FT_POSIX 1
#define FT_SYSV 2
#define FT_FLOCK 3
/* set default backend */
#ifndef FT_SEMAPHORE_BACKEND
#define FT_SEMAPHORE_BACKEND FT_FLOCK
#endif
/* validate selected backend */
#if FT_SEMAPHORE_BACKEND == FT_POSIX
#elif FT_SEMAPHORE_BACKEND == FT_SYSV
#elif FT_SEMAPHORE_BACKEND == FT_FLOCK
#else
#error "Unknown FT_SEMAPHORE_BACKEND; select between FT_POSIX, FT_SYSV, and FT_FLOCK"
#endif
#if FT_SEMAPHORE_BACKEND == FT_POSIX
#include <semaphore.h>
#endif
typedef struct
{
char name[256];
#if FT_SEMAPHORE_BACKEND == FT_POSIX
sem_t *sem;
#elif FT_SEMAPHORE_BACKEND == FT_SYSV
int semid;
#elif FT_SEMAPHORE_BACKEND == FT_FLOCK
int fd;
#endif
} ft_sem_t;
int ft_sem_create(char *name, ft_sem_t *ft_sem);
int ft_sem_open(char *name, ft_sem_t *ft_sem);
int ft_sem_lock(ft_sem_t *ft_sem);
int ft_sem_unlock(ft_sem_t *ft_sem);
int ft_sem_close(ft_sem_t *ft_sem);
int ft_sem_unlink(ft_sem_t *ft_sem);
#endif /* FT_SEM_H */

View File

@@ -51,7 +51,6 @@
#include <string.h> #include <string.h>
#include <semaphore.h> #include <semaphore.h>
#include <sys/mman.h> #include <sys/mman.h>
#include <sys/sem.h>
#include <sys/stat.h> #include <sys/stat.h>
#include <sys/types.h> #include <sys/types.h>
#include <netinet/in.h> #include <netinet/in.h>
@@ -75,6 +74,7 @@
#include "time_ops.h" #include "time_ops.h"
#include "faketime_common.h" #include "faketime_common.h"
#include "ft_sem.h"
#if defined PTHREAD_SINGLETHREADED_TIME && defined FAKE_STATELESS #if defined PTHREAD_SINGLETHREADED_TIME && defined FAKE_STATELESS
#undef PTHREAD_SINGLETHREADED_TIME #undef PTHREAD_SINGLETHREADED_TIME
@@ -165,20 +165,6 @@ struct utimbuf {
#endif #endif
#endif #endif
/* semaphore backend options */
#define FT_POSIX 1
#define FT_SYSV 2
/* set default backend */
#ifndef FT_SEMAPHORE_BACKEND
#define FT_SEMAPHORE_BACKEND FT_POSIX
#endif
/* validate selected backend */
#if FT_SEMAPHORE_BACKEND == FT_POSIX
#elif FT_SEMAPHORE_BACKEND == FT_SYSV
#else
#error "Unknown FT_SEMAPHORE_BACKEND; select between FT_POSIX and FT_SYSV"
#endif
#ifdef FAKE_RANDOM #ifdef FAKE_RANDOM
#include <sys/random.h> #include <sys/random.h>
#endif #endif
@@ -352,15 +338,6 @@ bool str_array_contains(const char *haystack, const char *needle);
void *ft_dlvsym(void *handle, const char *symbol, const char *version, const char *full_name, char *ignore_list, bool should_debug_dlsym); void *ft_dlvsym(void *handle, const char *symbol, const char *version, const char *full_name, char *ignore_list, bool should_debug_dlsym);
typedef struct {
char name[256];
#if FT_SEMAPHORE_BACKEND == FT_POSIX
sem_t *sem;
#elif FT_SEMAPHORE_BACKEND == FT_SYSV
int semid;
#endif
} ft_sem_t;
/** Semaphore protecting shared data */ /** Semaphore protecting shared data */
static ft_sem_t shared_sem; static ft_sem_t shared_sem;
static bool shared_sem_initialized = false; static bool shared_sem_initialized = false;
@@ -450,138 +427,6 @@ static bool parse_config_file = true;
static void ft_cleanup (void) __attribute__ ((destructor)); static void ft_cleanup (void) __attribute__ ((destructor));
static void ftpl_init (void) __attribute__ ((constructor)); static void ftpl_init (void) __attribute__ ((constructor));
/*
* =======================================================================
* Semaphore related functions === SEM
* =======================================================================
*/
#if FT_SEMAPHORE_BACKEND == FT_SYSV
int ft_sem_name2key(char *name)
{
key_t key;
char fullname[256];
snprintf(fullname, sizeof(fullname), "/tmp%s", name);
fullname[sizeof(fullname) - 1] = '\0';
int fd = open(fullname, O_CREAT, S_IRUSR | S_IWUSR);
if (fd < 0)
{
perror("libfaketime: open");
return -1;
}
close(fd);
if (-1 == (key = ftok(fullname, 'F')))
{
perror("libfaketime: ftok");
return -1;
}
return key;
}
#endif
int ft_sem_create(char *name, ft_sem_t *ft_sem)
{
#if FT_SEMAPHORE_BACKEND == FT_POSIX
if (SEM_FAILED == (ft_sem->sem = sem_open(name, O_CREAT|O_EXCL, S_IWUSR|S_IRUSR, 1)))
{
return -1;
}
#elif FT_SEMAPHORE_BACKEND == FT_SYSV
key_t key = ft_sem_name2key(name);
int nsems = 1;
ft_sem->semid = semget(key, nsems, IPC_CREAT | IPC_EXCL | S_IWUSR | S_IRUSR);
if (ft_sem->semid >= 0) { /* we got here first */
struct sembuf sb = {
.sem_num = 0,
.sem_op = 1, /* number of resources the semaphore has (when decremented down to 0 the semaphore will block) */
.sem_flg = 0,
};
/* the number of semaphore operation structures (struct sembuf) passed to semop */
int nsops = 1;
/* do a semop() to "unlock" the semaphore */
if (-1 == semop(ft_sem->semid, &sb, nsops)) {
return -1;
}
} else if (errno == EEXIST) { /* someone else got here before us */
return -1;
} else {
return -1;
}
#endif
strncpy(ft_sem->name, name, sizeof ft_sem->name - 1);
ft_sem->name[sizeof ft_sem->name - 1] = '\0';
return 0;
}
int ft_sem_open(char *name, ft_sem_t *ft_sem)
{
#if FT_SEMAPHORE_BACKEND == FT_POSIX
if (SEM_FAILED == (ft_sem->sem = sem_open(name, 0)))
{
return -1;
}
#elif FT_SEMAPHORE_BACKEND == FT_SYSV
key_t key = ft_sem_name2key(name);
ft_sem->semid = semget(key, 1, S_IWUSR | S_IRUSR);
if (ft_sem->semid < 0) {
return -1;
}
#endif
strncpy(ft_sem->name, name, sizeof ft_sem->name - 1);
ft_sem->name[sizeof ft_sem->name - 1] = '\0';
return 0;
}
int ft_sem_lock(ft_sem_t *ft_sem)
{
#if FT_SEMAPHORE_BACKEND == FT_POSIX
return sem_wait(ft_sem->sem);
#elif FT_SEMAPHORE_BACKEND == FT_SYSV
struct sembuf sb = {
.sem_num = 0,
.sem_op = -1, /* allocate resource (lock) */
.sem_flg = 0,
};
return semop(ft_sem->semid, &sb, 1);
#endif
}
int ft_sem_unlock(ft_sem_t *ft_sem)
{
#if FT_SEMAPHORE_BACKEND == FT_POSIX
return sem_post(ft_sem->sem);
#elif FT_SEMAPHORE_BACKEND == FT_SYSV
struct sembuf sb = {
.sem_num = 0,
.sem_op = 1, /* free resource (unlock) */
.sem_flg = 0,
};
return semop(ft_sem->semid, &sb, 1);
#endif
}
int ft_sem_close(ft_sem_t *ft_sem)
{
#if FT_SEMAPHORE_BACKEND == FT_POSIX
return sem_close(ft_sem->sem);
#elif FT_SEMAPHORE_BACKEND == FT_SYSV
/* NOP -- there's no "close" equivalent */
(void)ft_sem;
return 0;
#endif
}
int ft_sem_unlink(ft_sem_t *ft_sem)
{
#if FT_SEMAPHORE_BACKEND == FT_POSIX
return sem_unlink(ft_sem->name);
#elif FT_SEMAPHORE_BACKEND == FT_SYSV
return semctl(ft_sem->semid, 0, IPC_RMID);
#endif
}
/* /*
* ======================================================================= * =======================================================================
* Shared memory related functions === SHM * Shared memory related functions === SHM