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.
This commit is contained in:
commit
0a580b313d
70422 changed files with 17213626 additions and 0 deletions
|
|
@ -0,0 +1,129 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test id=POSIX_SPAWN
|
||||
* @bug 8377907
|
||||
* @summary Test that demonstrates the hanging-parent-on-native-concurrent-forks problem
|
||||
* @requires os.family != "windows"
|
||||
* @requires vm.flagless
|
||||
* @library /test/lib
|
||||
* @run main/othervm/manual -Djdk.lang.Process.launchMechanism=POSIX_SPAWN ConcNativeForkTest
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test id=FORK
|
||||
* @bug 8377907
|
||||
* @summary Test that demonstrates the hanging-parent-on-native-concurrent-forks problem
|
||||
* @requires os.family != "windows"
|
||||
* @requires vm.flagless
|
||||
* @library /test/lib
|
||||
* @run main/othervm/manual -Djdk.lang.Process.launchMechanism=FORK ConcNativeForkTest
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test id=VFORK
|
||||
* @bug 8377907
|
||||
* @summary Test that demonstrates the hanging-parent-on-native-concurrent-forks problem
|
||||
* @requires os.family == "linux"
|
||||
* @requires vm.flagless
|
||||
* @library /test/lib
|
||||
* @run main/othervm/manual -Djdk.lang.Process.launchMechanism=VFORK ConcNativeForkTest
|
||||
*/
|
||||
|
||||
public class ConcNativeForkTest {
|
||||
|
||||
// How this works:
|
||||
// - We start a child process via ProcessBuilder. Does not matter what, we just call "/bin/true".
|
||||
// - Concurrently, we continuously (up to a limit) fork natively; these forks will all exec "sleep 30".
|
||||
// - If the natively forked child process forks off at the right (wrong) moment, it will catch the open pipe from
|
||||
// the "/bin/true" child process, and forcing the parent process (this test) to wait in ProcessBuilder.start()
|
||||
// (inside forkAndExec()) until the natively forked child releases the pipe file descriptors it inherited.
|
||||
|
||||
// Notes:
|
||||
//
|
||||
// Obviously, this is racy and depends on scheduler timings of the underlying OS. The test succeeding is
|
||||
// no proof the bug does not exist (see PipesCloseOnExecTest as a complimentary test that is more reliable, but
|
||||
// only works on Linux).
|
||||
// That said, in tests it reliably reproduces the bug on Linux x64 and MacOS Arm.
|
||||
//
|
||||
// This test is not well suited for automatic test execution, since the test essentially
|
||||
// fork-bombs itself, and that may run into issues in containerized CI/CD environments.
|
||||
|
||||
native static boolean prepareNativeForkerThread(int numForks);
|
||||
native static void releaseNativeForkerThread();
|
||||
native static void stopNativeForkerThread();
|
||||
|
||||
private static final int numIterations = 20;
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
|
||||
System.out.println("jdk.lang.Process.launchMechanism=" +
|
||||
System.getProperty("jdk.lang.Process.launchMechanism"));
|
||||
|
||||
System.loadLibrary("ConcNativeFork");
|
||||
|
||||
// A very simple program returning immediately (/bin/true)
|
||||
ProcessBuilder pb = new ProcessBuilder("true").inheritIO();
|
||||
final int numJavaProcesses = 10;
|
||||
final int numNativeProcesses = 250;
|
||||
Process[] processes = new Process[numJavaProcesses];
|
||||
|
||||
for (int iteration = 0; iteration < numIterations; iteration ++) {
|
||||
|
||||
if (!prepareNativeForkerThread(numNativeProcesses)) {
|
||||
throw new RuntimeException("Failed to start native forker thread (see stdout)");
|
||||
}
|
||||
|
||||
long[] durations = new long[numJavaProcesses];
|
||||
|
||||
releaseNativeForkerThread();
|
||||
|
||||
for (int np = 0; np < numJavaProcesses; np ++) {
|
||||
long t1 = System.currentTimeMillis();
|
||||
try (Process p = pb.start()) {
|
||||
durations[np] = System.currentTimeMillis() - t1;
|
||||
processes[np] = p;
|
||||
}
|
||||
}
|
||||
|
||||
stopNativeForkerThread();
|
||||
|
||||
long longestDuration = 0;
|
||||
for (int np = 0; np < numJavaProcesses; np ++) {
|
||||
processes[np].waitFor();
|
||||
System.out.printf("Duration: %dms%n", durations[np]);
|
||||
longestDuration = Math.max(durations[np], longestDuration);
|
||||
}
|
||||
|
||||
System.out.printf("Longest startup time: %dms%n", longestDuration);
|
||||
|
||||
if (longestDuration >= 30000) {
|
||||
throw new RuntimeException("Looks like we blocked on native fork");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,147 @@
|
|||
/*
|
||||
* 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 <pthread.h>
|
||||
#include <sched.h>
|
||||
#include <signal.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdatomic.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/wait.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("(pid: %d): %s\n", (int)getpid(), buf);
|
||||
fflush(stdout);
|
||||
}
|
||||
|
||||
static pthread_t tid_forker;
|
||||
|
||||
static pthread_barrier_t start_barrier;
|
||||
static atomic_bool stop_now = false;
|
||||
|
||||
static void* forkerLoop(void* info) {
|
||||
|
||||
const int numForks = (int)(intptr_t)info;
|
||||
pid_t* pids = calloc(numForks, sizeof(pid_t));
|
||||
|
||||
trc("Forker: Waiting for Go.");
|
||||
|
||||
pthread_barrier_wait(&start_barrier);
|
||||
|
||||
for (int i = 0; i < numForks; i++) {
|
||||
const pid_t pid = fork();
|
||||
if (pid == 0) {
|
||||
/* Exec sleep. Properly opened file descriptors in parents (tagged CLOEXEC) should be released now.
|
||||
* Note that we use bash to not have to deal with path resolution. For our case, it does not matter if
|
||||
* sleep is a builtin or not. */
|
||||
char* env[] = { "PATH=/usr/bin:/bin", NULL };
|
||||
char* argv[] = { "sh", "-c", "sleep 30", NULL };
|
||||
execve("/bin/sh", argv, env);
|
||||
trc("Native child: sleep exec failed? %d", errno);
|
||||
/* The simplest way to handle this is to just wait here; this *will* cause the test to fail. */
|
||||
sleep(120);
|
||||
trc("Native child: exiting");
|
||||
exit(0);
|
||||
} else {
|
||||
pids[i] = pid;
|
||||
sched_yield();
|
||||
}
|
||||
}
|
||||
|
||||
trc("Forker: All native child processes started.");
|
||||
|
||||
/* Wait for test to signal end */
|
||||
while (!atomic_load(&stop_now)) {
|
||||
sleep(1);
|
||||
}
|
||||
|
||||
trc("Forker: Cleaning up.");
|
||||
|
||||
/* Reap children */
|
||||
for (int i = 0; i < numForks; i ++) {
|
||||
if (pids[i] != 0) {
|
||||
kill(pids[i], SIGKILL); /* if still running */
|
||||
waitpid(pids[i], NULL, 0);
|
||||
}
|
||||
}
|
||||
|
||||
trc("Forker: Done.");
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
JNIEXPORT jboolean JNICALL
|
||||
Java_ConcNativeForkTest_prepareNativeForkerThread(JNIEnv* env, jclass cls, jint numForks)
|
||||
{
|
||||
pthread_attr_t attr;
|
||||
int rc = 0;
|
||||
|
||||
const int cap = 1000;
|
||||
const int numForksCapped = numForks > cap ? cap : numForks;
|
||||
if (numForks > numForksCapped) {
|
||||
trc("Main: Capping max. number of forks at %d", numForksCapped); /* don't forkbomb me */
|
||||
}
|
||||
|
||||
if (pthread_barrier_init(&start_barrier, NULL, 2) != 0) {
|
||||
trc("Main: pthread_barrier_init failed (%d)", errno);
|
||||
return false;
|
||||
}
|
||||
|
||||
pthread_attr_init(&attr);
|
||||
if (pthread_create(&tid_forker, &attr, forkerLoop, (void*)(intptr_t)numForksCapped) != 0) {
|
||||
trc("Main: pthread_create failed (%d)", errno);
|
||||
return JNI_FALSE;
|
||||
}
|
||||
|
||||
trc("Main: Prepared native forker thread");
|
||||
|
||||
return JNI_TRUE;
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL
|
||||
Java_ConcNativeForkTest_releaseNativeForkerThread(JNIEnv* env, jclass cls)
|
||||
{
|
||||
pthread_barrier_wait(&start_barrier);
|
||||
trc("Main: signaled GO");
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL
|
||||
Java_ConcNativeForkTest_stopNativeForkerThread(JNIEnv* env, jclass cls)
|
||||
{
|
||||
atomic_store(&stop_now, true);
|
||||
pthread_join(tid_forker, NULL);
|
||||
pthread_barrier_destroy(&start_barrier);
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue