--- old/src/java.base/unix/native/libjava/ProcessImpl_md.c 2019-05-14 08:24:19.267433411 +0200 +++ new/src/java.base/unix/native/libjava/ProcessImpl_md.c 2019-05-14 08:24:19.075431505 +0200 @@ -39,6 +39,7 @@ #include #include #include +#include #include #include #include @@ -324,7 +325,7 @@ #define IOE_FORMAT "error=%d, %s" static void -throwIOException(JNIEnv *env, int errnum, const char *defaultDetail) +throwIOException(JNIEnv *env, int errnum, const char *defaultDetail, const char* addinfo) { const char *detail = defaultDetail; char *errmsg; @@ -338,12 +339,19 @@ detail = tmpbuf; } /* ASCII Decimal representation uses 2.4 times as many bits as binary. */ - fmtsize = sizeof(IOE_FORMAT) + strlen(detail) + 3 * sizeof(errnum); + fmtsize = sizeof(IOE_FORMAT) + strlen(detail) + 3 * sizeof(errnum) + + (addinfo == NULL ? 0 : 1 + strlen(addinfo) + 2); // " ()" errmsg = NEW(char, fmtsize); if (errmsg == NULL) return; snprintf(errmsg, fmtsize, IOE_FORMAT, errnum, detail); + if (addinfo != NULL) { + strcat(errmsg, " ("); + strcat(errmsg, addinfo); + strcat(errmsg, ")"); + } + s = JNU_NewStringPlatform(env, errmsg); if (s != NULL) { jobject x = JNU_NewObjectByName(env, "java/io/IOException", @@ -471,15 +479,67 @@ return resultPid; } +/* Returns 0 if at least one executable bit is set on the spawn helper binary, + * -1 otherwise. */ +static int isHelperExecutable(const char* helperpath) { + /* Note that this check will never be sufficient; exec() may still fail + * for many reasons and it is impossible to predict them all. The point of + * this test is to catch the most common failure reason and give the caller + * a clear diagnostic message. + * But never shall this test give us false positives (make us avoid exec()ing + * where it would have actually worked.) So since exec() success cannot be + * completely predicted, we err on the side of false negatives - when in doubt, + * try it. + */ + static int cached_rc = -2; + if (cached_rc == 0 || cached_rc == -1) { + /* The helper binary is part of the JDK and should have been set up + * correctly. This is very unlikely to happen intermittently. Therefore + * it should be okay to cache the result. */ + return cached_rc; + } + + struct stat s; + int rc = stat(helperpath, &s); + if (rc == 0) { + /* Require at least one exec bit set since this is how the spawn helper + * is set up in a canonical installation. */ + if (s.st_mode & S_IXUSR || + s.st_mode & S_IXGRP || + s.st_mode & S_IXOTH) { + cached_rc = 0; + return 0; + } + } + cached_rc = -1; + return -1; +} + static pid_t spawnChild(JNIEnv *env, jobject process, ChildStuff *c, const char *helperpath) { - pid_t resultPid; + pid_t resultPid = -1; jboolean isCopy; int i, offset, rval, bufsize, magic; char *buf, buf1[16]; char *hlpargs[2]; SpawnInfo sp; + /* Some posix_spawn() implementations do not report exec() errors back to the caller; + * POSIX leaves that up to the implementor, it only requires the child process to fail with + * error code 127. + * The problem with that is that posix_spawn() will seemingly succeed, child process will + * have been started but will immediately die with exit code 127. This is impossible to + * tell apart from cases where we successfully exec()'d the target program and it died + * with 127. In the former case we want to throw an IOException, the latter case is fine + * and should be handled by the java program. + * There is no perfect solution, but here we try - before starting the child - to catch + * obvious cases which will make the first exec() fail. */ + if (isHelperExecutable(helperpath) != 0) { + errno = EACCES; + c->error_detail = "No execute permission for spawn helper."; + return -1; + } + /* need to tell helper which fd is for receiving the childstuff * and which fd to send response back on */ @@ -639,7 +699,7 @@ (fds[2] == -1 && pipe(err) < 0) || (pipe(childenv) < 0) || (pipe(fail) < 0)) { - throwIOException(env, errno, "Bad file descriptor"); + throwIOException(env, errno, "Bad file descriptor", NULL); goto Catch; } c->fds[0] = fds[0]; @@ -654,6 +714,7 @@ c->redirectErrorStream = redirectErrorStream; c->mode = mode; + c->error_detail = NULL; resultPid = startChild(env, process, c, phelperpath); assert(resultPid != 0); @@ -661,13 +722,13 @@ if (resultPid < 0) { switch (c->mode) { case MODE_VFORK: - throwIOException(env, errno, "vfork failed"); + throwIOException(env, errno, "vfork failed", c->error_detail); break; case MODE_FORK: - throwIOException(env, errno, "fork failed"); + throwIOException(env, errno, "fork failed", c->error_detail); break; case MODE_POSIX_SPAWN: - throwIOException(env, errno, "posix_spawn failed"); + throwIOException(env, errno, "posix_spawn failed", c->error_detail); break; } goto Catch; @@ -678,10 +739,10 @@ case 0: break; /* Exec succeeded */ case sizeof(errnum): waitpid(resultPid, NULL, 0); - throwIOException(env, errnum, "Exec failed"); + throwIOException(env, errnum, "Exec failed", NULL); goto Catch; default: - throwIOException(env, errno, "Read failed"); + throwIOException(env, errno, "Read failed", NULL); goto Catch; }