From 6343e08baa4ff121c47a8ef6fc4c249688b12544 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Guillermo=20Rodr=C3=ADguez?= Date: Tue, 4 Aug 2026 13:50:36 +0200 Subject: [PATCH 1/2] Keep pipe endpoints above standard descriptors when spawning MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The pipes that implement a child's stdio are created with pipe(), which allocates the lowest free descriptor. If a standard descriptor is closed at that moment, a pipe endpoint would land on fd 0, 1 or 2, and the child's stdio setup would then close that fd or wire a standard descriptor to the wrong pipe. Fix by relocating any endpoint that pipe() places on a standard descriptor: duplicate it above 2 with F_DUPFD and close the original. Fixes #52 (BZ#126636) Signed-off-by: Guillermo Rodríguez --- native/jni/native-lib/cpproc.c | 38 ++++++++++++++++++++++++++++++++-- 1 file changed, 36 insertions(+), 2 deletions(-) diff --git a/native/jni/native-lib/cpproc.c b/native/jni/native-lib/cpproc.c index dbce9efa7..5dd438800 100644 --- a/native/jni/native-lib/cpproc.c +++ b/native/jni/native-lib/cpproc.c @@ -81,6 +81,40 @@ static int get_max_fd(void) return (int) value; } +/* Create a pipe with both endpoints above the standard descriptors. + If fd 0, 1 or 2 is closed, pipe() would reuse the free slot, and + the child's stdio setup would then close that fd or wire a + standard descriptor to the wrong pipe. */ +static int pipe_above_stdio(int *fds) +{ + int i; + + if (pipe(fds) < 0) + return -1; + + for (i = 0; i < 2; i++) + { + if (fds[i] <= 2) + { + int newfd = fcntl(fds[i], F_DUPFD, 3); + + if (newfd < 0) + { + int err = errno; + + close(fds[0]); + close(fds[1]); + errno = err; + return -1; + } + close(fds[i]); + fds[i] = newfd; + } + } + + return 0; +} + int cpproc_forkAndExec (char * const *commandLine, char * const * newEnviron, int *fds, int pipe_count, pid_t *out_pid, const char *wd) { @@ -119,7 +153,7 @@ int cpproc_forkAndExec (char * const *commandLine, char * const * newEnviron, for (i = 0; i < (pipe_count * 2); i += 2) { - if (pipe(&local_fds[i]) < 0) + if (pipe_above_stdio(&local_fds[i]) < 0) { int err = errno; @@ -133,7 +167,7 @@ int cpproc_forkAndExec (char * const *commandLine, char * const * newEnviron, /* Extra pipe used by the child to report failure to the parent. On success the exec closes the write end (FD_CLOEXEC) and the parent reads EOF. */ - if (pipe(fail_fds) < 0) + if (pipe_above_stdio(fail_fds) < 0) { int err = errno; From 78c4f8ad39770974e9fb99807a106e72e7796ba2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Guillermo=20Rodr=C3=ADguez?= Date: Tue, 4 Aug 2026 14:15:02 +0200 Subject: [PATCH 2/2] Check dup2() results when wiring the child's stdio before exec MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Report a failed dup2() through the exec-failure pipe instead of executing the target with missing or misdirected standard streams. This is hardening: with pipe endpoints kept above the standard descriptors and with signals blocked during the child's setup, dup2() should not fail in practice. If it ever does, make sure the failure is reported. Signed-off-by: Guillermo Rodríguez --- native/jni/native-lib/cpproc.c | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/native/jni/native-lib/cpproc.c b/native/jni/native-lib/cpproc.c index 5dd438800..b73b756c0 100644 --- a/native/jni/native-lib/cpproc.c +++ b/native/jni/native-lib/cpproc.c @@ -514,12 +514,17 @@ static void child_process(char * const *commandLine, close(fail_fds[0]); - dup2(local_fds[0], 0); - dup2(local_fds[3], 1); + if (dup2(local_fds[0], 0) < 0) + goto child_error; + if (dup2(local_fds[3], 1) < 0) + goto child_error; if (pipe_count == 3) - dup2(local_fds[5], 2); - else - dup2(1, 2); + { + if (dup2(local_fds[5], 2) < 0) + goto child_error; + } + else if (dup2(1, 2) < 0) + goto child_error; close_fds(local_fds, pipe_count * 2);