java-topology/test/jdk/java/lang/ProcessBuilder/PipesCloseOnExecTest/libPipesCloseOnExec.c
russell@unturf.com 0a580b313d undefect. CWE-407 — 63 sites patched across 27 ecosystems
Authors: russell@unturf.com · brackishbert@gmail.com · foxhop.net · TimeHexOn.com

Patches, unit tests, benchmarks, whitepaper, and outreach briefs.
Public domain — no copyright claimed. Use freely.
2026-03-26 17:11:57 -04:00

198 lines
5.4 KiB
C

/*
* Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2026, IBM Corp.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code 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
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
#include <assert.h>
#include <errno.h>
#include <fcntl.h>
#include <pthread.h>
#include <stdbool.h>
#include <stdatomic.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>
#include <jni.h>
#include "testlib_thread_barriers.h"
static void trc(const char* fmt, ...) {
char buf [1024];
va_list ap;
va_start(ap, fmt);
vsnprintf(buf, sizeof(buf), fmt, ap);
va_end(ap);
printf("%s\n", buf);
fflush(stdout);
}
/* Set 1 to restrict this test to pipes, 0 to test all file descriptors.
* (for now, we ignore regular files opened with CLOEXEC since loaded jars seem not tagged as CLOEXEc.
* We should probably fix that eventually. */
#define TEST_PIPES_ONLY 1
/* stdin/out/err file descriptors are usually not CLOEXEC */
#define IGNORE_BELOW 4
/* Only query file descriptors up to this point */
#define MAX_FD 1024
static pthread_t tid_tester;
static pthread_barrier_t start_barrier;
static atomic_bool stop_now = false;
/* Mainly to prevent tracing the same fd over and over again:
* 1 - present, 2 - present, cloexec */
static unsigned fd_state[MAX_FD];
static bool is_pipe(int fd) {
struct stat mystat;
if (fstat(fd, &mystat) == -1) {
return false;
}
return mystat.st_mode & S_IFIFO;
}
static void print_fd_details(int fd, char* out, size_t outlen) {
const char* type = "unknown";
char link[1024] = { 0 };
char procfd[129];
struct stat mystat;
out[0] = '\0';
if (fstat(fd, &mystat) == -1) {
snprintf(out, outlen, "%s", errno == EBADF ? "EBADF" : "???");
return;
}
switch (mystat.st_mode & S_IFMT) {
case S_IFBLK: type = "blk"; break;
case S_IFCHR: type = "char"; break;
case S_IFDIR: type = "dir"; break;
case S_IFIFO: type = "fifo"; break;
case S_IFLNK: type = "lnk"; break;
case S_IFREG: type = "reg"; break;
case S_IFSOCK: type = "sock"; break;
}
snprintf(procfd, sizeof(procfd) - 1, "/proc/self/fd/%d", fd);
int linklen = readlink(procfd, link, sizeof(link) - 1);
if (linklen > 0) {
link[linklen] = '\0';
snprintf(out, outlen, "%s (%s)", type, link);
} else {
snprintf(out, outlen, "%s", type);
}
}
/* Returns true for error */
static bool testFD(int fd) {
int rc = fcntl(fd, F_GETFD);
if (rc == -1) {
return false;
}
const bool has_cloexec = (rc & FD_CLOEXEC);
const bool is_a_pipe = is_pipe(fd);
const unsigned state = has_cloexec ? 2 : 1;
bool had_error = false;
if (fd_state[fd] != state) {
fd_state[fd] = state;
char buf[1024];
print_fd_details(fd, buf, sizeof(buf));
if (has_cloexec) {
trc("%d: %s", fd, buf);
} else {
if (fd < IGNORE_BELOW) {
trc("%d: %s ** CLOEXEC MISSING ** (ignored - below scanned range)", fd, buf);
} else if (TEST_PIPES_ONLY && !is_a_pipe) {
trc("%d: %s ** CLOEXEC MISSING ** (ignored - not a pipe)", fd, buf);
} else {
trc("%d: %s ** CLOEXEC MISSING ** (ERROR)", fd, buf);
had_error = true;
}
}
}
return had_error;
}
static void* testerLoop(void* dummy) {
pthread_barrier_wait(&start_barrier);
trc("Tester is alive");
bool had_error = false;
while (!atomic_load(&stop_now)) {
for (int fd = 0; fd < MAX_FD; fd++) {
bool rc = testFD(fd);
had_error = had_error || rc;
}
}
trc("Tester dies");
return had_error ? (void*)1 : NULL;
}
JNIEXPORT jboolean JNICALL
Java_PipesCloseOnExecTest_startTester(JNIEnv* env, jclass cls)
{
pthread_attr_t attr;
int rc = 0;
if (pthread_barrier_init(&start_barrier, NULL, 2) != 0) {
trc("pthread_barrier_init failed (%d)", errno);
return false;
}
pthread_attr_init(&attr);
if (pthread_create(&tid_tester, &attr, testerLoop, NULL) != 0) {
trc("pthread_create failed (%d)", errno);
return JNI_FALSE;
}
pthread_barrier_wait(&start_barrier);
trc("Started tester");
return JNI_TRUE;
}
JNIEXPORT jboolean JNICALL
Java_PipesCloseOnExecTest_stopTester(JNIEnv* env, jclass cls)
{
atomic_store(&stop_now, true);
void* retval = NULL;
pthread_join(tid_tester, &retval);
pthread_barrier_destroy(&start_barrier);
return retval == NULL ? JNI_TRUE : JNI_FALSE;
}