8307990: jspawnhelper must close its writing side of a pipe before reading from it

Reviewed-by: stuefe, rriggs
This commit is contained in:
Volker Simonis 2023-06-01 10:56:31 +00:00
parent 4460429d7a
commit 39f6d807db
5 changed files with 378 additions and 38 deletions

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2013, 2022, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2013, 2023, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -23,6 +23,7 @@
* questions.
*/
#include <assert.h>
#include <errno.h>
#include <fcntl.h>
#include <signal.h>
@ -83,11 +84,15 @@ void initChildStuff (int fdin, int fdout, ChildStuff *c) {
error (fdout, ERR_PIPE);
}
if (readFully (fdin, c, sizeof(*c)) == -1) {
#ifdef DEBUG
jtregSimulateCrash(0, 5);
#endif
if (readFully (fdin, c, sizeof(*c)) != sizeof(*c)) {
error (fdout, ERR_PIPE);
}
if (readFully (fdin, &sp, sizeof(sp)) == -1) {
if (readFully (fdin, &sp, sizeof(sp)) != sizeof(sp)) {
error (fdout, ERR_PIPE);
}
@ -96,7 +101,7 @@ void initChildStuff (int fdin, int fdout, ChildStuff *c) {
ALLOC(buf, bufsize);
if (readFully (fdin, buf, bufsize) == -1) {
if (readFully (fdin, buf, bufsize) != bufsize) {
error (fdout, ERR_PIPE);
}
@ -132,12 +137,15 @@ int main(int argc, char *argv[]) {
ChildStuff c;
struct stat buf;
/* argv[0] contains the fd number to read all the child info */
int r, fdin, fdout;
int r, fdinr, fdinw, fdout;
sigset_t unblock_signals;
r = sscanf (argv[argc-1], "%d:%d", &fdin, &fdout);
if (r == 2 && fcntl(fdin, F_GETFD) != -1) {
fstat(fdin, &buf);
#ifdef DEBUG
jtregSimulateCrash(0, 4);
#endif
r = sscanf (argv[argc-1], "%d:%d:%d", &fdinr, &fdinw, &fdout);
if (r == 3 && fcntl(fdinr, F_GETFD) != -1 && fcntl(fdinw, F_GETFD) != -1) {
fstat(fdinr, &buf);
if (!S_ISFIFO(buf.st_mode))
shutItDown();
} else {
@ -148,7 +156,18 @@ int main(int argc, char *argv[]) {
sigemptyset(&unblock_signals);
sigprocmask(SIG_SETMASK, &unblock_signals, NULL);
initChildStuff (fdin, fdout, &c);
// Close the writing end of the pipe we use for reading from the parent.
// We have to do this before we start reading from the parent to avoid
// blocking in the case the parent exits before we finished reading from it.
close(fdinw); // Deliberately ignore errors (see https://lwn.net/Articles/576478/).
initChildStuff (fdinr, fdout, &c);
// Now set the file descriptor for the pipe's writing end to -1
// for the case that somebody tries to close it again.
assert(c.childenv[1] == fdinw);
c.childenv[1] = -1;
// The file descriptor for reporting errors back to our parent we got on the command
// line should be the same like the one in the ChildStuff struct we've just read.
assert(c.fail[1] == fdout);
childProcess (&c);
return 0; /* NOT REACHED */

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 1995, 2022, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 1995, 2023, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -487,14 +487,14 @@ static pid_t
spawnChild(JNIEnv *env, jobject process, ChildStuff *c, const char *helperpath) {
pid_t resultPid;
int i, offset, rval, bufsize, magic;
char *buf, buf1[(2 * 11) + 2]; // "%d:%d\0"
char *buf, buf1[(3 * 11) + 3]; // "%d:%d:%d\0"
char *hlpargs[2];
SpawnInfo sp;
/* need to tell helper which fd is for receiving the childstuff
* and which fd to send response back on
*/
snprintf(buf1, sizeof(buf1), "%d:%d", c->childenv[0], c->fail[1]);
snprintf(buf1, sizeof(buf1), "%d:%d:%d", c->childenv[0], c->childenv[1], c->fail[1]);
/* put the fd string as argument to the helper cmd */
hlpargs[0] = buf1;
hlpargs[1] = 0;
@ -527,7 +527,7 @@ spawnChild(JNIEnv *env, jobject process, ChildStuff *c, const char *helperpath)
if (c->fds[i] != -1) {
int flags = fcntl(c->fds[i], F_GETFD);
if (flags & FD_CLOEXEC) {
fcntl(c->fds[i], F_SETFD, flags & (~1));
fcntl(c->fds[i], F_SETFD, flags & (~FD_CLOEXEC));
}
}
}
@ -538,6 +538,10 @@ spawnChild(JNIEnv *env, jobject process, ChildStuff *c, const char *helperpath)
return -1;
}
#ifdef DEBUG
jtregSimulateCrash(resultPid, 1);
#endif
/* now the lengths are known, copy the data */
buf = NEW(char, bufsize);
if (buf == 0) {
@ -553,11 +557,24 @@ spawnChild(JNIEnv *env, jobject process, ChildStuff *c, const char *helperpath)
magic = magicNumber();
/* write the two structs and the data buffer */
write(c->childenv[1], (char *)&magic, sizeof(magic)); // magic number first
write(c->childenv[1], (char *)c, sizeof(*c));
write(c->childenv[1], (char *)&sp, sizeof(sp));
write(c->childenv[1], buf, bufsize);
if (writeFully(c->childenv[1], (char *)&magic, sizeof(magic)) != sizeof(magic)) { // magic number first
return -1;
}
#ifdef DEBUG
jtregSimulateCrash(resultPid, 2);
#endif
if (writeFully(c->childenv[1], (char *)c, sizeof(*c)) != sizeof(*c) ||
writeFully(c->childenv[1], (char *)&sp, sizeof(sp)) != sizeof(sp) ||
writeFully(c->childenv[1], buf, bufsize) != bufsize) {
return -1;
}
/* We're done. Let jspwanhelper know he can't expect any more data from us. */
close(c->childenv[1]);
c->childenv[1] = -1;
free(buf);
#ifdef DEBUG
jtregSimulateCrash(resultPid, 3);
#endif
/* In this mode an external main() in invoked which calls back into
* childProcess() in this file, rather than directly
@ -610,6 +627,8 @@ Java_java_lang_ProcessImpl_forkAndExec(JNIEnv *env,
in[0] = in[1] = out[0] = out[1] = err[0] = err[1] = fail[0] = fail[1] = -1;
childenv[0] = childenv[1] = -1;
// Reset errno to protect against bogus error messages
errno = 0;
if ((c = NEW(ChildStuff, 1)) == NULL) return -1;
c->argv = NULL;
@ -708,11 +727,9 @@ Java_java_lang_ProcessImpl_forkAndExec(JNIEnv *env,
goto Catch;
}
case sizeof(errnum):
assert(errnum == CHILD_IS_ALIVE);
if (errnum != CHILD_IS_ALIVE) {
/* Should never happen since the first thing the spawn
* helper should do is to send an alive ping to the parent,
* before doing any subsequent work. */
/* This can happen if the spawn helper encounters an error
* before or during the handshake with the parent. */
throwIOException(env, 0, "Bad code from spawn helper "
"(Failed to exec spawn helper)");
goto Catch;
@ -748,8 +765,12 @@ Java_java_lang_ProcessImpl_forkAndExec(JNIEnv *env,
/* Always clean up fail and childEnv descriptors */
closeSafely(fail[0]);
closeSafely(fail[1]);
closeSafely(childenv[0]);
closeSafely(childenv[1]);
/* We use 'c->childenv' here rather than 'childenv' because 'spawnChild()' might have
* already closed 'c->childenv[1]' and signaled this by setting 'c->childenv[1]' to '-1'.
* Otherwise 'c->childenv' and 'childenv' are the same because we just copied 'childenv'
* to 'c->childenv' (with 'copyPipe()') before calling 'startChild()'. */
closeSafely(c->childenv[0]);
closeSafely(c->childenv[1]);
releaseBytes(env, helperpath, phelperpath);
releaseBytes(env, prog, pprog);

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2013, 2020, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2013, 2023, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -36,14 +36,6 @@
const char * const *parentPathv;
ssize_t
restartableWrite(int fd, const void *buf, size_t count)
{
ssize_t result;
RESTARTABLE(write(fd, buf, count), result);
return result;
}
int
restartableDup2(int fd_from, int fd_to)
{
@ -163,6 +155,46 @@ readFully(int fd, void *buf, size_t nbyte)
}
}
/*
* Writes nbyte bytes from buf into file descriptor fd,
* The write operation is retried in case of EINTR or partial writes.
*
* Returns number of bytes written (normally nbyte).
* In case of write errors, returns -1 and sets errno.
*/
ssize_t
writeFully(int fd, const void *buf, size_t nbyte)
{
#ifdef DEBUG
/* This code is only used in debug builds for testing truncated writes
* during the handshake with the spawn helper for MODE_POSIX_SPAWN.
* See: test/jdk/java/lang/ProcessBuilder/JspawnhelperProtocol.java
*/
const char* env = getenv("JTREG_JSPAWNHELPER_PROTOCOL_TEST");
if (env != NULL && atoi(env) == 99 && nbyte == sizeof(ChildStuff)) {
printf("posix_spawn: truncating write of ChildStuff struct\n");
fflush(stdout);
nbyte = nbyte / 2;
}
#endif
ssize_t remaining = nbyte;
for (;;) {
ssize_t n = write(fd, buf, remaining);
if (n > 0) {
remaining -= n;
if (remaining <= 0)
return nbyte;
/* We were interrupted in the middle of writing the bytes.
* Unlikely, but possible. */
buf = (void *) (((char *)buf) + n);
} else if (n == -1 && errno == EINTR) {
/* Retry */
} else {
return -1;
}
}
}
void
initVectorFromBlock(const char**vector, const char* block, int count)
{
@ -210,7 +242,7 @@ execve_with_shell_fallback(int mode, const char *file,
const char *argv[],
const char *const envp[])
{
if (mode == MODE_CLONE || mode == MODE_VFORK) {
if (mode == MODE_VFORK) {
/* shared address space; be very careful. */
execve(file, (char **) argv, (char **) envp);
if (errno == ENOEXEC)
@ -321,9 +353,14 @@ childProcess(void *arg)
/* Child shall signal aliveness to parent at the very first
* moment. */
int code = CHILD_IS_ALIVE;
restartableWrite(fail_pipe_fd, &code, sizeof(code));
if (writeFully(fail_pipe_fd, &code, sizeof(code)) != sizeof(code)) {
goto WhyCantJohnnyExec;
}
}
#ifdef DEBUG
jtregSimulateCrash(0, 6);
#endif
/* Close the parent sides of the pipes.
Closing pipe fds here is redundant, since closeDescriptors()
would do it anyways, but a little paranoia is a good thing. */
@ -390,9 +427,26 @@ childProcess(void *arg)
*/
{
int errnum = errno;
restartableWrite(fail_pipe_fd, &errnum, sizeof(errnum));
writeFully(fail_pipe_fd, &errnum, sizeof(errnum));
}
close(fail_pipe_fd);
_exit(-1);
return 0; /* Suppress warning "no return value from function" */
}
#ifdef DEBUG
/* This method is only used in debug builds for testing MODE_POSIX_SPAWN
* in the light of abnormal program termination of either the parent JVM
* or the newly created jspawnhelper child process during the execution of
* Java_java_lang_ProcessImpl_forkAndExec().
* See: test/jdk/java/lang/ProcessBuilder/JspawnhelperProtocol.java
*/
void jtregSimulateCrash(pid_t child, int stage) {
const char* env = getenv("JTREG_JSPAWNHELPER_PROTOCOL_TEST");
if (env != NULL && atoi(env) == stage) {
printf("posix_spawn:%d\n", child);
fflush(stdout);
_exit(stage);
}
}
#endif

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2013, 2020, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2013, 2023, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -85,7 +85,6 @@ extern char **environ;
#define MODE_FORK 1
#define MODE_POSIX_SPAWN 2
#define MODE_VFORK 3
#define MODE_CLONE 4
typedef struct _ChildStuff
{
@ -128,7 +127,7 @@ typedef struct _SpawnInfo {
*/
extern const char * const *parentPathv;
ssize_t restartableWrite(int fd, const void *buf, size_t count);
ssize_t writeFully(int fd, const void *buf, size_t count);
int restartableDup2(int fd_from, int fd_to);
int closeSafely(int fd);
int isAsciiDigit(char c);
@ -149,4 +148,14 @@ void JDK_execvpe(int mode, const char *file,
const char *const envp[]);
int childProcess(void *arg);
#ifdef DEBUG
/* This method is only used in debug builds for testing MODE_POSIX_SPAWN
* in the light of abnormal program termination of either the parent JVM
* or the newly created jspawnhelper child process during the execution of
* Java_java_lang_ProcessImpl_forkAndExec().
* See: test/jdk/java/lang/ProcessBuilder/JspawnhelperProtocol.java
*/
void jtregSimulateCrash(pid_t child, int stage);
#endif
#endif