From a786b6da92d6b4873fb00b492d3fa1ec6fedea89 Mon Sep 17 00:00:00 2001 From: hernandevelop Date: Sun, 5 Apr 2026 05:04:57 -0400 Subject: [PATCH 1/2] sysvipc: implement missing semctl commands (IPC_STAT, IPC_SET, GETNCNT, GETZCNT, GETPID) semctl for semaphores was missing several commands that are commonly used by applications to query semaphore state. Notably IPC_STAT was stubbed out with #if 0 and fell through to the default case returning -EINVAL, causing applications that verify semaphore existence via IPC_STAT (checking sem_otime) to incorrectly conclude the semaphore was destroyed. Changes: - Add SysVIpcSemidDs struct to sysvipc_sys.h (matching kernel layout) - Add stats field to SysVIpcSemaphore for tracking metadata - Implement IPC_STAT, SEM_STAT, SEM_STAT_ANY: return semaphore metadata - Implement IPC_SET: allow updating permissions - Implement GETPID, GETNCNT, GETZCNT: return waiter counts - Initialize sem_ctime and sem_perm.mode on semget - Update sem_otime on successful semop - Update sem_ctime on SETVAL Tested with JetBrains Toolbox which relies on IPC_STAT to verify semaphore initialization state. --- src/extension/sysvipc/sysvipc_internal.h | 1 + src/extension/sysvipc/sysvipc_sem.c | 67 ++++++++++++++++++++++-- src/extension/sysvipc/sysvipc_sys.h | 10 ++++ 3 files changed, 75 insertions(+), 3 deletions(-) diff --git a/src/extension/sysvipc/sysvipc_internal.h b/src/extension/sysvipc/sysvipc_internal.h index 06730083..5f4f97e1 100644 --- a/src/extension/sysvipc/sysvipc_internal.h +++ b/src/extension/sysvipc/sysvipc_internal.h @@ -39,6 +39,7 @@ struct SysVIpcSemaphore { bool valid; uint16_t *sems; int nsems; + struct SysVIpcSemidDs stats; }; /***************** diff --git a/src/extension/sysvipc/sysvipc_sem.c b/src/extension/sysvipc/sysvipc_sem.c index 89383ca9..05b7319c 100644 --- a/src/extension/sysvipc/sysvipc_sem.c +++ b/src/extension/sysvipc/sysvipc_sem.c @@ -6,6 +6,7 @@ #include /* E* */ #include /* memset */ #include /* assert */ +#include /* time */ #define SYSVIPC_MAX_SEMS 512 #define SYSVIPC_MAX_NSEMS 512 @@ -59,6 +60,10 @@ int sysvipc_semget(Tracee *tracee, struct SysVIpcConfig *config) { semaphore->sems = talloc_array(config->ipc_namespace, uint16_t, nsems); memset(semaphore->sems, 0, nsems * sizeof(uint16_t)); semaphore->nsems = nsems; + memset(&semaphore->stats, 0, sizeof(semaphore->stats)); + semaphore->stats.sem_ctime = time(NULL); + semaphore->stats.sem_nsems = nsems; + semaphore->stats.sem_perm.mode = semflg & 0777; } else { if ((semflg & IPC_CREAT) && (semflg & IPC_EXCL)) { return -EEXIST; @@ -115,6 +120,7 @@ static int sysvipc_sem_check(struct SysVIpcConfig *config, struct SysVIpcSemapho } } memcpy(semaphore->sems, new_sems, semaphore->nsems * sizeof(uint16_t)); + semaphore->stats.sem_otime = time(NULL); return 0; } @@ -211,6 +217,7 @@ int sysvipc_semctl(Tracee *tracee, struct SysVIpcConfig *config) { if (cmdarg > SYSVIPC_MAX_SEMVAL) return -ERANGE; if (semnum < 0 || semnum >= semaphore->nsems) return -EINVAL; semaphore->sems[semnum] = cmdarg; + semaphore->stats.sem_ctime = time(NULL); return 0; } case SYSVIPC_GETALL: @@ -237,14 +244,68 @@ int sysvipc_semctl(Tracee *tracee, struct SysVIpcConfig *config) { TALLOC_FREE(semaphore->sems); return 0; } -#if 0 case IPC_STAT: + case SYSVIPC_SEM_STAT: + case SYSVIPC_SEM_STAT_ANY: { - int status = write_data(tracee, buf, &queue->stats, sizeof(struct msqid_ds)); + semaphore->stats.sem_nsems = semaphore->nsems; + int status = write_data(tracee, cmdarg, &semaphore->stats, sizeof(struct SysVIpcSemidDs)); if (status < 0) return status; + if ((cmd & ~SYSVIPC_IPC_64) == SYSVIPC_SEM_STAT || (cmd & ~SYSVIPC_IPC_64) == SYSVIPC_SEM_STAT_ANY) { + return IPC_OBJECT_ID(semaphore_index, semaphore); + } return 0; } -#endif + case IPC_SET: + { + struct SysVIpcSemidDs buf; + int status = read_data(tracee, &buf, cmdarg, sizeof(struct SysVIpcSemidDs)); + if (status < 0) return status; + semaphore->stats.sem_perm.uid = buf.sem_perm.uid; + semaphore->stats.sem_perm.gid = buf.sem_perm.gid; + semaphore->stats.sem_perm.mode = buf.sem_perm.mode & 0777; + semaphore->stats.sem_ctime = time(NULL); + return 0; + } + case SYSVIPC_GETPID: + { + if (semnum < 0 || semnum >= semaphore->nsems) return -EINVAL; + return 0; /* sempid not tracked, return 0 */ + } + case SYSVIPC_GETNCNT: + { + if (semnum < 0 || semnum >= semaphore->nsems) return -EINVAL; + int count = 0; + Tracee *waiting_tracee; + struct SysVIpcConfig *waiting_config; + SYSVIPC_FOREACH_TRACEE(waiting_tracee, waiting_config, config->ipc_namespace) { + if ( + waiting_config->wait_reason == WR_WAIT_SEMOP && + waiting_config->waiting_object_index == semaphore_index) { + char wait_type = 0; + sysvipc_sem_check(waiting_config, semaphore, &wait_type); + if (wait_type == 'n') count++; + } + } + return count; + } + case SYSVIPC_GETZCNT: + { + if (semnum < 0 || semnum >= semaphore->nsems) return -EINVAL; + int count = 0; + Tracee *waiting_tracee; + struct SysVIpcConfig *waiting_config; + SYSVIPC_FOREACH_TRACEE(waiting_tracee, waiting_config, config->ipc_namespace) { + if ( + waiting_config->wait_reason == WR_WAIT_SEMOP && + waiting_config->waiting_object_index == semaphore_index) { + char wait_type = 0; + sysvipc_sem_check(waiting_config, semaphore, &wait_type); + if (wait_type == 'z') count++; + } + } + return count; + } case SYSVIPC_IPC_INFO: case SYSVIPC_SEM_INFO: { diff --git a/src/extension/sysvipc/sysvipc_sys.h b/src/extension/sysvipc/sysvipc_sys.h index 114b1d7c..ac861826 100644 --- a/src/extension/sysvipc/sysvipc_sys.h +++ b/src/extension/sysvipc/sysvipc_sys.h @@ -72,6 +72,16 @@ struct SysVIpcSeminfo }; +struct SysVIpcSemidDs +{ + struct ipc_perm sem_perm; /* operation permission struct */ + int64_t sem_otime; /* time of last semop() */ + int64_t sem_ctime; /* time of last change by semctl() */ + uint64_t sem_nsems; /* number of semaphores in set */ + uint64_t __glibc_reserved3; + uint64_t __glibc_reserved4; +}; + struct SysVIpcShmidDs { struct ipc_perm shm_perm; /* operation permission struct */ From 965edc22e28a0222f608a92e988959d3f882fc9b Mon Sep 17 00:00:00 2001 From: hernandevelop Date: Sun, 5 Apr 2026 06:11:43 -0400 Subject: [PATCH 2/2] sysvipc: add X11 SHM bridge for MIT-SHM compatibility with Termux:X11 The SHM helper process now creates an abstract Unix socket listener using Termux:X11's protocol (\0/dev/shm/), and generates shmids in Termux:X11-compatible format (socket_id << 16 | counter). When a process inside proot creates a SHM segment and sends the shmid to the X11 server via XShmAttach, the server can now find and attach the segment through the bridge socket, receiving the ashmem fd via SCM_RIGHTS. This eliminates the need for workarounds like: - GSK_RENDERER=cairo (disabling GL rendering in GTK4) - termux-x11 -extension MIT-SHM (disabling SHM entirely) - LD_PRELOAD with external shm_compat.so Changes: - sysvipc_shm.c: Add X11 bridge thread in helper process that serves the Termux:X11 fd-passing protocol. Register segments on allocation, unregister on free. Send bridge socket_id to proot main process via existing pipe. Refactor helper launch into sysvipc_shm_ensure_helper() so socket_id is available before generating shmids. - sysvipc_internal.h: Add x11_shmid field to SysVIpcSharedMem struct. Add LOOKUP_SHM_OBJECT macro for x11_shmid-based lookup. - GNUmakefile: Link with -lpthread for bridge thread. Tested with GTK4 file-roller (MIT-SHM) and Cinnamon desktop session on Termux:X11 with proot-distro. --- src/GNUmakefile | 2 +- src/extension/sysvipc/sysvipc_internal.h | 21 +++ src/extension/sysvipc/sysvipc_shm.c | 211 ++++++++++++++++++++--- 3 files changed, 212 insertions(+), 22 deletions(-) diff --git a/src/GNUmakefile b/src/GNUmakefile index 96035576..e426fee4 100644 --- a/src/GNUmakefile +++ b/src/GNUmakefile @@ -16,7 +16,7 @@ OBJDUMP ?= $(CROSS_COMPILE)objdump CPPFLAGS += -D_FILE_OFFSET_BITS=64 -D_GNU_SOURCE -I. -I$(VPATH) CFLAGS += -Wall -Wextra -O2 -LDFLAGS += -ltalloc -Wl,-z,noexecstack +LDFLAGS += -ltalloc -lpthread -Wl,-z,noexecstack OBJECTS += \ cli/cli.o \ diff --git a/src/extension/sysvipc/sysvipc_internal.h b/src/extension/sysvipc/sysvipc_internal.h index 5f4f97e1..ee6e2165 100644 --- a/src/extension/sysvipc/sysvipc_internal.h +++ b/src/extension/sysvipc/sysvipc_internal.h @@ -92,6 +92,7 @@ struct SysVIpcSharedMem { bool valid; bool rmid_pending; int fd; + int x11_shmid; /* Termux:X11-compatible shmid for MIT-SHM */ struct SysVIpcShmidDs stats; /** @@ -224,6 +225,26 @@ struct SysVIpcConfig { #define IPC_OBJECT_ID(index, object) \ ((index + 1) | (object->generation << 12)) +/** + * Find SHM object by x11_shmid (Termux:X11-compatible shmid). + */ +#define LOOKUP_SHM_OBJECT(out_index, out_object, objects_array) \ +{ \ + int object_id = peek_reg(tracee, CURRENT, SYSARG_1); \ + int num_objects = (int)talloc_array_length(objects_array); \ + out_index = (size_t)-1; \ + for (int _i = 0; _i < num_objects; _i++) { \ + if ((objects_array)[_i].valid && (objects_array)[_i].x11_shmid == object_id) { \ + out_index = _i; \ + break; \ + } \ + } \ + if (out_index == (size_t)-1) { \ + return -EINVAL; \ + } \ + out_object = &(objects_array)[out_index]; \ +} + /** * Iterate over all Tracees in given SysVIpc namespace * diff --git a/src/extension/sysvipc/sysvipc_shm.c b/src/extension/sysvipc/sysvipc_shm.c index 6cb56e17..911851dc 100644 --- a/src/extension/sysvipc/sysvipc_shm.c +++ b/src/extension/sysvipc/sysvipc_shm.c @@ -20,6 +20,7 @@ #include /* memset */ #include /* time */ #include /* open, fcntl */ +#include /* pthread_create */ #ifdef __ANDROID__ #include /* ASHMEM_* */ @@ -103,13 +104,39 @@ struct SysVIpcShmHelperRequest { #define SYSVIPC_SHMHELPER_SOCKET_LEN 108 static struct sockaddr_un sysvipc_shm_helper_addr; + +/* X11 SHM bridge: socket_id used to generate Termux:X11-compatible shmids */ +static int sysvipc_x11_socket_id = 0; + +static bool sysvipc_helper_launched = false; +static int sysvipc_proot2helper; +static int sysvipc_helper2proot; + +/* Ensure the helper process is launched and X11 bridge socket_id is known */ +static int sysvipc_shm_ensure_helper(void); + static int sysvipc_shm_send_helper_request(struct SysVIpcShmHelperRequest *request) { - static bool launched_helper; - static int proot2helper; - static int helper2proot; + if (!sysvipc_helper_launched) { + if (sysvipc_shm_ensure_helper() < 0) + return -1; + } + + write(sysvipc_proot2helper, request, sizeof(*request)); + if (request->op == SHMHELPER_ALLOC) { + int fd = -1; + read(sysvipc_helper2proot, &fd, sizeof(fd)); + return fd; + } + return 0; +} - if (!launched_helper) { +static int sysvipc_shm_ensure_helper(void) +{ + if (sysvipc_helper_launched) + return 0; + + { int pipe_proot2helper[2]; int pipe_helper2proot[2]; if (pipe2(pipe_proot2helper, O_CLOEXEC) < 0) { @@ -160,21 +187,19 @@ static int sysvipc_shm_send_helper_request(struct SysVIpcShmHelperRequest *reque return -1; } sysvipc_shm_helper_addr.sun_family = AF_UNIX; - launched_helper = true; - proot2helper = pipe_proot2helper[1]; - helper2proot = pipe_helper2proot[0]; + /* Read the X11 bridge socket_id from the helper */ + int id_nread = read(pipe_helper2proot[0], &sysvipc_x11_socket_id, sizeof(int)); + sysvipc_helper_launched = true; + sysvipc_proot2helper = pipe_proot2helper[1]; + sysvipc_helper2proot = pipe_helper2proot[0]; } } - - write(proot2helper, request, sizeof(*request)); - if (request->op == SHMHELPER_ALLOC) { - int fd = -1; - read(helper2proot, &fd, sizeof(fd)); - return fd; - } return 0; } +/* Counter for generating Termux:X11-compatible shmids */ +static int sysvipc_shm_counter = 0; + int sysvipc_shmget(Tracee *tracee, struct SysVIpcConfig *config) { word_t shm_key = peek_reg(tracee, CURRENT, SYSARG_1); @@ -210,9 +235,19 @@ int sysvipc_shmget(Tracee *tracee, struct SysVIpcConfig *config) memset(&shms[shm_index], 0, sizeof(shms[shm_index])); } shm = &shms[shm_index]; + + /* Ensure helper is launched first so sysvipc_x11_socket_id + * is available for generating compatible shmids */ + sysvipc_shm_ensure_helper(); + + /* Generate Termux:X11-compatible shmid: + * upper 16 bits = socket_id, lower 15 bits = counter */ + sysvipc_shm_counter = (sysvipc_shm_counter + 1) & 0x7fff; + shm->x11_shmid = sysvipc_x11_socket_id * 0x10000 + sysvipc_shm_counter; + struct SysVIpcShmHelperRequest request = { .op = SHMHELPER_ALLOC, - .fd = IPC_OBJECT_ID(shm_index, shm), + .fd = shm->x11_shmid, .size = shm_size }; shm->fd = sysvipc_shm_send_helper_request(&request); @@ -236,7 +271,7 @@ int sysvipc_shmget(Tracee *tracee, struct SysVIpcConfig *config) return -EINVAL; } } - return IPC_OBJECT_ID(shm_index, shm); + return shm->x11_shmid; } static void sysvipc_do_rmid(struct SysVIpcSharedMem *shm) @@ -288,7 +323,7 @@ int sysvipc_shmat(Tracee *tracee, struct SysVIpcConfig *config) { size_t shm_index; struct SysVIpcSharedMem *shm; - LOOKUP_IPC_OBJECT(shm_index, shm, config->ipc_namespace->shms) + LOOKUP_SHM_OBJECT(shm_index, shm, config->ipc_namespace->shms) config->waiting_object_index = shm_index; @@ -480,7 +515,7 @@ int sysvipc_shmctl(Tracee *tracee, struct SysVIpcConfig *config) { size_t shm_index; struct SysVIpcSharedMem *shm; - LOOKUP_IPC_OBJECT(shm_index, shm, config->ipc_namespace->shms) + LOOKUP_SHM_OBJECT(shm_index, shm, config->ipc_namespace->shms) int cmd = peek_reg(tracee, CURRENT, SYSARG_2); word_t buf = peek_reg(tracee, CURRENT, SYSARG_3); @@ -643,6 +678,133 @@ static int sysvipc_shm_do_allocate(size_t size, int shmid) { #endif } +/* + * X11 SHM Bridge + * + * Creates a listener on an abstract Unix socket using Termux:X11's + * protocol, so the X11 server can find and attach SHM segments + * created by processes inside proot. + * + * Protocol: client sends shmid (int), server responds with + * key (key_t) + fd (SCM_RIGHTS). + */ +#define X11BRIDGE_SOCKNAME "/dev/shm/%08x" +#define X11BRIDGE_MAX_SEGMENTS 256 + +static struct { + int shmid; + int fd; + key_t key; + int in_use; +} x11bridge_segments[X11BRIDGE_MAX_SEGMENTS]; + +static pthread_mutex_t x11bridge_mutex = PTHREAD_MUTEX_INITIALIZER; +static int x11bridge_socket_id = 0; + +static void x11bridge_register(int shmid, int fd, key_t key) { + pthread_mutex_lock(&x11bridge_mutex); + for (int i = 0; i < X11BRIDGE_MAX_SEGMENTS; i++) { + if (!x11bridge_segments[i].in_use) { + x11bridge_segments[i].shmid = shmid; + x11bridge_segments[i].fd = fd; + x11bridge_segments[i].key = key; + x11bridge_segments[i].in_use = 1; + break; + } + } + pthread_mutex_unlock(&x11bridge_mutex); +} + +static void x11bridge_unregister_by_fd(int fd) { + pthread_mutex_lock(&x11bridge_mutex); + for (int i = 0; i < X11BRIDGE_MAX_SEGMENTS; i++) { + if (x11bridge_segments[i].in_use && x11bridge_segments[i].fd == fd) { + x11bridge_segments[i].in_use = 0; + break; + } + } + pthread_mutex_unlock(&x11bridge_mutex); +} + +static void* x11bridge_thread(void* arg) { + int listen_sock = *(int*)arg; + free(arg); + for (;;) { + struct sockaddr_un client_addr; + socklen_t addr_len = sizeof(client_addr); + int client = accept(listen_sock, (struct sockaddr*)&client_addr, &addr_len); + if (client < 0) break; + + int shmid; + if (recv(client, &shmid, sizeof(shmid), 0) != sizeof(shmid)) { + close(client); + continue; + } + + pthread_mutex_lock(&x11bridge_mutex); + for (int i = 0; i < X11BRIDGE_MAX_SEGMENTS; i++) { + if (x11bridge_segments[i].in_use && x11bridge_segments[i].shmid == shmid) { + key_t key = x11bridge_segments[i].key; + int fd = x11bridge_segments[i].fd; + pthread_mutex_unlock(&x11bridge_mutex); + + /* Send key */ + write(client, &key, sizeof(key_t)); + + /* Send fd via SCM_RIGHTS */ + char nothing = '!'; + struct iovec iov = { .iov_base = ¬hing, .iov_len = 1 }; + struct { + struct cmsghdr align; + int fd[1]; + } anc; + struct msghdr msg = { + .msg_iov = &iov, .msg_iovlen = 1, + .msg_control = &anc, + .msg_controllen = sizeof(struct cmsghdr) + sizeof(int) + }; + struct cmsghdr* cmsg = CMSG_FIRSTHDR(&msg); + cmsg->cmsg_len = msg.msg_controllen; + cmsg->cmsg_level = SOL_SOCKET; + cmsg->cmsg_type = SCM_RIGHTS; + ((int*)CMSG_DATA(cmsg))[0] = fd; + sendmsg(client, &msg, 0); + goto next; + } + } + pthread_mutex_unlock(&x11bridge_mutex); +next: + close(client); + } + return NULL; +} + +static int x11bridge_start(void) { + int sock = socket(AF_UNIX, SOCK_STREAM, 0); + if (sock < 0) return 0; + + struct sockaddr_un addr; + memset(&addr, 0, sizeof(addr)); + addr.sun_family = AF_UNIX; + + int i; + for (i = 0; i < 4096; i++) { + x11bridge_socket_id = (getpid() + i) & 0xffff; + sprintf(&addr.sun_path[1], X11BRIDGE_SOCKNAME, x11bridge_socket_id); + int len = sizeof(addr.sun_family) + strlen(&addr.sun_path[1]) + 1; + if (bind(sock, (struct sockaddr*)&addr, len) == 0) break; + } + if (i == 4096) { close(sock); return 0; } + if (listen(sock, 4) != 0) { close(sock); return 0; } + + int *sock_arg = malloc(sizeof(int)); + *sock_arg = sock; + pthread_t tid; + pthread_create(&tid, NULL, x11bridge_thread, sock_arg); + pthread_detach(tid); + return x11bridge_socket_id; +} + void sysvipc_shm_helper_main() { char *path; int socket_server_fd = socket(AF_UNIX, SOCK_SEQPACKET, 0); @@ -655,7 +817,6 @@ void sysvipc_shm_helper_main() { if (strlen(path) > SYSVIPC_SHMHELPER_SOCKET_LEN) { close(socket_server_fd); - fprintf(stderr, "proot-shm-helper: Temporary path too long\n"); _exit(1); } @@ -682,6 +843,11 @@ void sysvipc_shm_helper_main() { } write(1, addr.sun_path, SYSVIPC_SHMHELPER_SOCKET_LEN); + + /* Start X11 SHM bridge and send socket_id to proot */ + int bridge_id = x11bridge_start(); + write(1, &bridge_id, sizeof(int)); + for (;;) { struct SysVIpcShmHelperRequest request; int status = TEMP_FAILURE_RETRY(read(0, &request, sizeof(request))); @@ -693,7 +859,6 @@ void sysvipc_shm_helper_main() { break; } if (status != sizeof(request)) { - fprintf(stderr, "proot-shm-helper: Incomplete request\n"); break; } switch (request.op) { @@ -701,9 +866,14 @@ void sysvipc_shm_helper_main() { { int fd = sysvipc_shm_do_allocate(request.size, request.fd); write(1, &fd, sizeof(int)); + if (fd >= 0) { + /* Register in X11 bridge so Termux:X11 can find it */ + x11bridge_register(request.fd, fd, IPC_PRIVATE); + } break; } case SHMHELPER_FREE: + x11bridge_unregister_by_fd(request.fd); close(request.fd); break; case SHMHELPER_DISTRIBUTE: @@ -739,7 +909,6 @@ void sysvipc_shm_helper_main() { break; } default: - fprintf(stderr, "proot-shm-helper: Bad request\n"); break; } }