Skip to content

8357089: Remove VFORK launch mechanism from Process implementation (linux) #25768

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 9 additions & 25 deletions src/java.base/unix/classes/java/lang/ProcessImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -82,8 +82,7 @@ final class ProcessImpl extends Process {
private static enum LaunchMechanism {
// order IS important!
FORK,
POSIX_SPAWN,
VFORK
POSIX_SPAWN
}

/**
Expand All @@ -98,29 +97,15 @@ private static LaunchMechanism launchMechanism() {

try {
// Should be value of a LaunchMechanism enum
LaunchMechanism lm = LaunchMechanism.valueOf(s.toUpperCase(Locale.ROOT));
switch (OperatingSystem.current()) {
case LINUX: {
// All options are valid for Linux, but VFORK is deprecated and results
// in a warning
if (lm == LaunchMechanism.VFORK) {
System.err.println("VFORK MODE DEPRECATED");
System.err.println("""
The VFORK launch mechanism has been deprecated for being dangerous.
It will be removed in a future java version. Either remove the
jdk.lang.Process.launchMechanism property (preferred) or use FORK mode
instead (-Djdk.lang.Process.launchMechanism=FORK).
""");
}
return lm;
}
case AIX:
case MACOS:
if (lm != LaunchMechanism.VFORK) {
return lm; // All but VFORK are valid
}
break;
String launchMechanism = s.toUpperCase(Locale.ROOT);
if (launchMechanism.equals("VFORK") && OperatingSystem.isLinux()) {
System.err.println("""
The VFORK launch mechanism has been removed. Please either remove the
jdk.lang.Process.launchMechanism property (preferred) or use FORK mode
instead (-Djdk.lang.Process.launchMechanism=FORK).
""");
}
return LaunchMechanism.valueOf(launchMechanism);
} catch (IllegalArgumentException e) {
}

Expand Down Expand Up @@ -266,7 +251,6 @@ static Process start(String[] cmdarray,
* <pre>
* 1 - fork(2) and exec(2)
* 2 - posix_spawn(3P)
* 3 - vfork(2) and exec(2)
* </pre>
* @param fds an array of three file descriptors.
* Indexes 0, 1, and 2 correspond to standard input,
Expand Down
67 changes: 13 additions & 54 deletions src/java.base/unix/native/libjava/ProcessImpl_md.c
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@
* changing paths...
* - then exec(2) the target binary
*
* There are three ways to fork off:
* On the OS-side are four ways to fork off:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would mention the two ways that are implemented, only mentioning vfork after the supported mechanisms to inhibit someone wanting to go backwards.

*
* A) fork(2). Portable and safe (no side effects) but may fail with ENOMEM on
* all Unices when invoked from a VM with a high memory footprint. On Unices
Expand All @@ -74,34 +74,23 @@
*
* B) vfork(2): Portable and fast but very unsafe. It bypasses the memory
* problems related to fork(2) by starting the child in the memory image of
* the parent. Things that can go wrong include:
* - Programming errors in the child process before the exec(2) call may
* trash memory in the parent process, most commonly the stack of the
* thread invoking vfork.
* - Signals received by the child before the exec(2) call may be at best
* misdirected to the parent, at worst immediately kill child and parent.
*
* This is mitigated by very strict rules about what one is allowed to do in
* the child process between vfork(2) and exec(2), which is basically nothing.
* However, we always broke this rule by doing the pre-exec work between
* vfork(2) and exec(2).
*
* Also note that vfork(2) has been deprecated by the OpenGroup, presumably
* because of its many dangers.
* the parent.
* *** This mode is inherently dangerous, and the danger partly outside the control
* of the programmer (for details, see JDK-8357090). Therefore, we deprecated
* the vfork mode with JDK 25 and removed if with JDK 26. ***
*
* C) clone(2): This is a Linux specific call which gives the caller fine
* grained control about how exactly the process fork is executed. It is
* powerful, but Linux-specific.
*
* Aside from these three possibilities there is a forth option: posix_spawn(3).
* Where fork/vfork/clone all fork off the process and leave pre-exec work and
* calling exec(2) to the user, posix_spawn(3) offers the user fork+exec-like
* functionality in one package, similar to CreateProcess() on Windows.
*
* It is not a system call in itself, but usually a wrapper implemented within
* the libc in terms of one of (fork|vfork|clone)+exec - so whether or not it
* has advantages over calling the naked (fork|vfork|clone) functions depends
* on how posix_spawn(3) is implemented.
* D) posix_spawn(3): Where fork/vfork/clone all fork off the process and leave
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would downplay the OS launch options we don't use, only saying that vfork is dangerous (and a bit as to why) but there's no need to mention clone. And there's no need to mention the the deprecation or removal, when this is integrated its gone.

* pre-exec work and calling exec(2) to the user, posix_spawn(3) offers the user
* fork+exec-like functionality in one package, similar to CreateProcess() on Windows.
* It is not a system call, but usually a wrapper implemented within the libc in terms
* of one of (fork|vfork|clone)+exec - so whether or not it has advantages over calling
* the naked (fork|vfork|clone) functions depends on how posix_spawn(3) is implemented.
* Note, however, that even if posix_spawn(3) uses vfork(2) internally, that is still fine -
* the assumption here is that the libc developers know how to mitigate the vfork problems.
*
* Note that when using posix_spawn(3), we exec twice: first a tiny binary called
* the jspawnhelper, then in the jspawnhelper we do the pre-exec work and exec a
Expand Down Expand Up @@ -486,28 +475,6 @@ static int copystrings(char *buf, int offset, const char * const *arg) {
__attribute_noinline__
#endif

/* vfork(2) is deprecated on Darwin */
#ifndef __APPLE__
static pid_t
vforkChild(ChildStuff *c) {
volatile pid_t resultPid;

/*
* We separate the call to vfork into a separate function to make
* very sure to keep stack of child from corrupting stack of parent,
* as suggested by the scary gcc warning:
* warning: variable 'foo' might be clobbered by 'longjmp' or 'vfork'
*/
resultPid = vfork();

if (resultPid == 0) {
childProcess(c);
}
assert(resultPid != 0); /* childProcess never returns */
return resultPid;
}
#endif

static pid_t
forkChild(ChildStuff *c) {
pid_t resultPid;
Expand Down Expand Up @@ -652,11 +619,6 @@ spawnChild(JNIEnv *env, jobject process, ChildStuff *c, const char *helperpath)
static pid_t
startChild(JNIEnv *env, jobject process, ChildStuff *c, const char *helperpath) {
switch (c->mode) {
/* vfork(2) is deprecated on Darwin*/
#ifndef __APPLE__
case MODE_VFORK:
return vforkChild(c);
#endif
case MODE_FORK:
return forkChild(c);
case MODE_POSIX_SPAWN:
Expand Down Expand Up @@ -765,9 +727,6 @@ Java_java_lang_ProcessImpl_forkAndExec(JNIEnv *env,

if (resultPid < 0) {
switch (c->mode) {
case MODE_VFORK:
throwInternalIOException(env, errno, "vfork failed", c->mode);
break;
case MODE_FORK:
throwInternalIOException(env, errno, "fork failed", c->mode);
break;
Expand Down
48 changes: 7 additions & 41 deletions src/java.base/unix/native/libjava/childproc.c
Original file line number Diff line number Diff line change
Expand Up @@ -205,31 +205,6 @@ initVectorFromBlock(const char**vector, const char* block, int count)
vector[count] = NULL;
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This mode should be mentioned in the CSR, even if no-one knew it was there.

/**
* Exec FILE as a traditional Bourne shell script (i.e. one without #!).
* If we could do it over again, we would probably not support such an ancient
* misfeature, but compatibility wins over sanity. The original support for
* this was imported accidentally from execvp().
*/
static void
execve_as_traditional_shell_script(const char *file,
const char *argv[],
const char *const envp[])
{
/* Use the extra word of space provided for us in argv by caller. */
const char *argv0 = argv[0];
const char *const *end = argv;
while (*end != NULL)
++end;
memmove(argv+2, argv+1, (end-argv) * sizeof(*end));
argv[0] = "/bin/sh";
argv[1] = file;
execve(argv[0], (char **) argv, (char **) envp);
/* Can't even exec /bin/sh? Big trouble, but let's soldier on... */
memmove(argv+1, argv+2, (end-argv) * sizeof(*end));
argv[0] = argv0;
}

/**
* Like execve(2), except that in case of ENOEXEC, FILE is assumed to
* be a shell script and the system default shell is invoked to run it.
Expand All @@ -239,16 +214,9 @@ execve_with_shell_fallback(int mode, const char *file,
const char *argv[],
const char *const envp[])
{
if (mode == MODE_VFORK) {
/* shared address space; be very careful. */
execve(file, (char **) argv, (char **) envp);
if (errno == ENOEXEC)
execve_as_traditional_shell_script(file, argv, envp);
} else {
/* unshared address space; we can mutate environ. */
environ = (char **) envp;
execvp(file, (char **) argv);
}
/* unshared address space; we can mutate environ. */
environ = (char **) envp;
execvp(file, (char **) argv);
}

/**
Expand Down Expand Up @@ -406,12 +374,10 @@ childProcess(void *arg)
if (p->pdir != NULL && chdir(p->pdir) < 0)
goto WhyCantJohnnyExec;

// Reset any mask signals from parent, but not in VFORK mode
if (p->mode != MODE_VFORK) {
sigset_t unblock_signals;
sigemptyset(&unblock_signals);
sigprocmask(SIG_SETMASK, &unblock_signals, NULL);
}
// Reset any mask signals from parent
sigset_t unblock_signals;
sigemptyset(&unblock_signals);
sigprocmask(SIG_SETMASK, &unblock_signals, NULL);

if (fcntl(FAIL_FILENO, F_SETFD, FD_CLOEXEC) == -1)
goto WhyCantJohnnyExec;
Expand Down
1 change: 0 additions & 1 deletion src/java.base/unix/native/libjava/childproc.h
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,6 @@ extern char **environ;
*/
#define MODE_FORK 1
#define MODE_POSIX_SPAWN 2
#define MODE_VFORK 3

typedef struct _ChildStuff
{
Expand Down