Path: blob/master/src/java.base/windows/classes/java/lang/ProcessImpl.java
67794 views
/*1* Copyright (c) 1995, 2022, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation. Oracle designates this7* particular file as subject to the "Classpath" exception as provided8* by Oracle in the LICENSE file that accompanied this code.9*10* This code is distributed in the hope that it will be useful, but WITHOUT11* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or12* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License13* version 2 for more details (a copy is included in the LICENSE file that14* accompanied this code).15*16* You should have received a copy of the GNU General Public License version17* 2 along with this work; if not, write to the Free Software Foundation,18* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.19*20* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA21* or visit www.oracle.com if you need additional information or have any22* questions.23*/2425package java.lang;2627import java.io.BufferedInputStream;28import java.io.BufferedOutputStream;29import java.io.File;30import java.io.FileDescriptor;31import java.io.FileInputStream;32import java.io.FileOutputStream;33import java.io.IOException;34import java.io.InputStream;35import java.io.OutputStream;36import java.lang.ProcessBuilder.Redirect;37import java.security.AccessController;38import java.security.PrivilegedAction;39import java.util.ArrayList;40import java.util.Locale;41import java.util.concurrent.CompletableFuture;42import java.util.concurrent.TimeUnit;43import java.util.regex.Matcher;44import java.util.regex.Pattern;4546import jdk.internal.access.JavaIOFileDescriptorAccess;47import jdk.internal.access.SharedSecrets;48import jdk.internal.ref.CleanerFactory;49import sun.security.action.GetPropertyAction;5051/* This class is for the exclusive use of ProcessBuilder.start() to52* create new processes.53*54* @author Martin Buchholz55* @since 1.556*/5758final class ProcessImpl extends Process {59private static final JavaIOFileDescriptorAccess fdAccess60= SharedSecrets.getJavaIOFileDescriptorAccess();6162// Windows platforms support a forcible kill signal.63static final boolean SUPPORTS_NORMAL_TERMINATION = false;6465/**66* Open a file for writing. If {@code append} is {@code true} then the file67* is opened for atomic append directly and a FileOutputStream constructed68* with the resulting handle. This is because a FileOutputStream created69* to append to a file does not open the file in a manner that guarantees70* that writes by the child process will be atomic.71*/72@SuppressWarnings("removal")73private static FileOutputStream newFileOutputStream(File f, boolean append)74throws IOException75{76if (append) {77String path = f.getPath();78SecurityManager sm = System.getSecurityManager();79if (sm != null)80sm.checkWrite(path);81long handle = openForAtomicAppend(path);82final FileDescriptor fd = new FileDescriptor();83fdAccess.setHandle(fd, handle);84return AccessController.doPrivileged(85new PrivilegedAction<FileOutputStream>() {86public FileOutputStream run() {87return new FileOutputStream(fd);88}89}90);91} else {92return new FileOutputStream(f);93}94}9596// System-dependent portion of ProcessBuilder.start()97static Process start(String cmdarray[],98java.util.Map<String,String> environment,99String dir,100ProcessBuilder.Redirect[] redirects,101boolean redirectErrorStream)102throws IOException103{104String envblock = ProcessEnvironment.toEnvironmentBlock(environment);105106FileInputStream f0 = null;107FileOutputStream f1 = null;108FileOutputStream f2 = null;109110try {111boolean forceNullOutputStream = false;112long[] stdHandles;113if (redirects == null) {114stdHandles = new long[] { -1L, -1L, -1L };115} else {116stdHandles = new long[3];117118if (redirects[0] == Redirect.PIPE) {119stdHandles[0] = -1L;120} else if (redirects[0] == Redirect.INHERIT) {121stdHandles[0] = fdAccess.getHandle(FileDescriptor.in);122} else if (redirects[0] instanceof ProcessBuilder.RedirectPipeImpl) {123stdHandles[0] = fdAccess.getHandle(((ProcessBuilder.RedirectPipeImpl) redirects[0]).getFd());124} else {125f0 = new FileInputStream(redirects[0].file());126stdHandles[0] = fdAccess.getHandle(f0.getFD());127}128129if (redirects[1] == Redirect.PIPE) {130stdHandles[1] = -1L;131} else if (redirects[1] == Redirect.INHERIT) {132stdHandles[1] = fdAccess.getHandle(FileDescriptor.out);133} else if (redirects[1] instanceof ProcessBuilder.RedirectPipeImpl) {134stdHandles[1] = fdAccess.getHandle(((ProcessBuilder.RedirectPipeImpl) redirects[1]).getFd());135// Force getInputStream to return a null stream,136// the handle is directly assigned to the next process.137forceNullOutputStream = true;138} else {139f1 = newFileOutputStream(redirects[1].file(),140redirects[1].append());141stdHandles[1] = fdAccess.getHandle(f1.getFD());142}143144if (redirects[2] == Redirect.PIPE) {145stdHandles[2] = -1L;146} else if (redirects[2] == Redirect.INHERIT) {147stdHandles[2] = fdAccess.getHandle(FileDescriptor.err);148} else if (redirects[2] instanceof ProcessBuilder.RedirectPipeImpl) {149stdHandles[2] = fdAccess.getHandle(((ProcessBuilder.RedirectPipeImpl) redirects[2]).getFd());150} else {151f2 = newFileOutputStream(redirects[2].file(),152redirects[2].append());153stdHandles[2] = fdAccess.getHandle(f2.getFD());154}155}156157Process p = new ProcessImpl(cmdarray, envblock, dir,158stdHandles, forceNullOutputStream, redirectErrorStream);159if (redirects != null) {160// Copy the handles's if they are to be redirected to another process161if (stdHandles[0] >= 0162&& redirects[0] instanceof ProcessBuilder.RedirectPipeImpl) {163fdAccess.setHandle(((ProcessBuilder.RedirectPipeImpl) redirects[0]).getFd(),164stdHandles[0]);165}166if (stdHandles[1] >= 0167&& redirects[1] instanceof ProcessBuilder.RedirectPipeImpl) {168fdAccess.setHandle(((ProcessBuilder.RedirectPipeImpl) redirects[1]).getFd(),169stdHandles[1]);170}171if (stdHandles[2] >= 0172&& redirects[2] instanceof ProcessBuilder.RedirectPipeImpl) {173fdAccess.setHandle(((ProcessBuilder.RedirectPipeImpl) redirects[2]).getFd(),174stdHandles[2]);175}176}177return p;178} finally {179// In theory, close() can throw IOException180// (although it is rather unlikely to happen here)181try { if (f0 != null) f0.close(); }182finally {183try { if (f1 != null) f1.close(); }184finally { if (f2 != null) f2.close(); }185}186}187188}189190private static class LazyPattern {191// Escape-support version:192// "(\")((?:\\\\\\1|.)+?)\\1|([^\\s\"]+)";193private static final Pattern PATTERN =194Pattern.compile("[^\\s\"]+|\"[^\"]*\"");195};196197/* Parses the command string parameter into the executable name and198* program arguments.199*200* The command string is broken into tokens. The token separator is a space201* or quota character. The space inside quotation is not a token separator.202* There are no escape sequences.203*/204private static String[] getTokensFromCommand(String command) {205ArrayList<String> matchList = new ArrayList<>(8);206Matcher regexMatcher = LazyPattern.PATTERN.matcher(command);207while (regexMatcher.find())208matchList.add(regexMatcher.group());209return matchList.toArray(new String[matchList.size()]);210}211212private static final int VERIFICATION_CMD_BAT = 0;213private static final int VERIFICATION_WIN32 = 1;214private static final int VERIFICATION_WIN32_SAFE = 2; // inside quotes not allowed215private static final int VERIFICATION_LEGACY = 3;216// See Command shell overview for documentation of special characters.217// https://docs.microsoft.com/en-us/previous-versions/windows/it-pro/windows-xp/bb490954(v=technet.10)218private static final char ESCAPE_VERIFICATION[][] = {219// We guarantee the only command file execution for implicit [cmd.exe] run.220// http://technet.microsoft.com/en-us/library/bb490954.aspx221{' ', '\t', '\"', '<', '>', '&', '|', '^'},222{' ', '\t', '\"', '<', '>'},223{' ', '\t', '\"', '<', '>'},224{' ', '\t'}225};226227private static String createCommandLine(int verificationType,228final String executablePath,229final String cmd[])230{231StringBuilder cmdbuf = new StringBuilder(80);232233cmdbuf.append(executablePath);234235for (int i = 1; i < cmd.length; ++i) {236cmdbuf.append(' ');237String s = cmd[i];238if (needsEscaping(verificationType, s)) {239cmdbuf.append('"');240241if (verificationType == VERIFICATION_WIN32_SAFE) {242// Insert the argument, adding '\' to quote any interior quotes243int length = s.length();244for (int j = 0; j < length; j++) {245char c = s.charAt(j);246if (c == DOUBLEQUOTE) {247int count = countLeadingBackslash(verificationType, s, j);248while (count-- > 0) {249cmdbuf.append(BACKSLASH); // double the number of backslashes250}251cmdbuf.append(BACKSLASH); // backslash to quote the quote252}253cmdbuf.append(c);254}255} else {256cmdbuf.append(s);257}258// The code protects the [java.exe] and console command line259// parser, that interprets the [\"] combination as an escape260// sequence for the ["] char.261// http://msdn.microsoft.com/en-us/library/17w5ykft.aspx262//263// If the argument is an FS path, doubling of the tail [\]264// char is not a problem for non-console applications.265//266// The [\"] sequence is not an escape sequence for the [cmd.exe]267// command line parser. The case of the [""] tail escape268// sequence could not be realized due to the argument validation269// procedure.270if (verificationType == VERIFICATION_WIN32_SAFE ||271verificationType == VERIFICATION_LEGACY) {272int count = countLeadingBackslash(verificationType, s, s.length());273while (count-- > 0) {274cmdbuf.append(BACKSLASH); // double the number of backslashes275}276}277cmdbuf.append('"');278} else if (verificationType == VERIFICATION_WIN32_SAFE &&279(s.startsWith("\"") && s.endsWith("\"") && s.length() > 2)) {280// Check that quoted argument does not escape the final quote281cmdbuf.append(s);282int count = countLeadingBackslash(verificationType, s, s.length() - 1);283while (count-- > 0) {284cmdbuf.insert(cmdbuf.length() - 1, BACKSLASH); // double the number of backslashes285}286} else {287cmdbuf.append(s);288}289}290return cmdbuf.toString();291}292293/**294* Return the argument without quotes (first and last) if quoted, otherwise the arg.295* @param str a string296* @return the string without quotes297*/298private static String unQuote(String str) {299if (!str.startsWith("\"") || !str.endsWith("\"") || str.length() < 2)300return str; // no beginning or ending quote, or too short not quoted301302// Strip leading and trailing quotes303return str.substring(1, str.length() - 1);304}305306private static boolean needsEscaping(int verificationType, String arg) {307if (arg.isEmpty())308return true; // Empty string is to be quoted309310// Switch off MS heuristic for internal ["].311// Please, use the explicit [cmd.exe] call312// if you need the internal ["].313// Example: "cmd.exe", "/C", "Extended_MS_Syntax"314315// For [.exe] or [.com] file the unpaired/internal ["]316// in the argument is not a problem.317String unquotedArg = unQuote(arg);318boolean argIsQuoted = !arg.equals(unquotedArg);319boolean embeddedQuote = unquotedArg.indexOf(DOUBLEQUOTE) >= 0;320321switch (verificationType) {322case VERIFICATION_CMD_BAT:323if (embeddedQuote) {324throw new IllegalArgumentException("Argument has embedded quote, " +325"use the explicit CMD.EXE call.");326}327break; // break determine whether to quote328case VERIFICATION_WIN32_SAFE:329if (argIsQuoted && embeddedQuote) {330throw new IllegalArgumentException("Malformed argument has embedded quote: "331+ unquotedArg);332}333break;334default:335break;336}337338if (!argIsQuoted) {339char testEscape[] = ESCAPE_VERIFICATION[verificationType];340for (int i = 0; i < testEscape.length; ++i) {341if (arg.indexOf(testEscape[i]) >= 0) {342return true;343}344}345}346return false;347}348349private static String getExecutablePath(String path)350throws IOException351{352String name = unQuote(path);353if (name.indexOf(DOUBLEQUOTE) >= 0) {354throw new IllegalArgumentException("Executable name has embedded quote, " +355"split the arguments: " + name);356}357// Win32 CreateProcess requires path to be normalized358File fileToRun = new File(name);359360// From the [CreateProcess] function documentation:361//362// "If the file name does not contain an extension, .exe is appended.363// Therefore, if the file name extension is .com, this parameter364// must include the .com extension. If the file name ends in365// a period (.) with no extension, or if the file name contains a path,366// .exe is not appended."367//368// "If the file name !does not contain a directory path!,369// the system searches for the executable file in the following370// sequence:..."371//372// In practice ANY non-existent path is extended by [.exe] extension373// in the [CreateProcess] function with the only exception:374// the path ends by (.)375376return fileToRun.getPath();377}378379/**380* An executable is any program that is an EXE or does not have an extension381* and the Windows createProcess will be looking for .exe.382* The comparison is case insensitive based on the name.383* @param executablePath the executable file384* @return true if the path ends in .exe or does not have an extension.385*/386private boolean isExe(String executablePath) {387File file = new File(executablePath);388String upName = file.getName().toUpperCase(Locale.ROOT);389return (upName.endsWith(".EXE") || upName.indexOf('.') < 0);390}391392// Old version that can be bypassed393private boolean isShellFile(String executablePath) {394String upPath = executablePath.toUpperCase();395return (upPath.endsWith(".CMD") || upPath.endsWith(".BAT"));396}397398private String quoteString(String arg) {399StringBuilder argbuf = new StringBuilder(arg.length() + 2);400return argbuf.append('"').append(arg).append('"').toString();401}402403// Count backslashes before start index of string.404// .bat files don't include backslashes as part of the quote405private static int countLeadingBackslash(int verificationType,406CharSequence input, int start) {407if (verificationType == VERIFICATION_CMD_BAT)408return 0;409int j;410for (j = start - 1; j >= 0 && input.charAt(j) == BACKSLASH; j--) {411// just scanning backwards412}413return (start - 1) - j; // number of BACKSLASHES414}415416private static final char DOUBLEQUOTE = '\"';417private static final char BACKSLASH = '\\';418419private final long handle;420private final ProcessHandle processHandle;421private OutputStream stdin_stream;422private InputStream stdout_stream;423private InputStream stderr_stream;424425@SuppressWarnings("removal")426private ProcessImpl(String cmd[],427final String envblock,428final String path,429final long[] stdHandles,430boolean forceNullOutputStream,431final boolean redirectErrorStream)432throws IOException433{434String cmdstr;435final SecurityManager security = System.getSecurityManager();436final String value = GetPropertyAction.437privilegedGetProperty("jdk.lang.Process.allowAmbiguousCommands",438(security == null ? "true" : "false"));439final boolean allowAmbiguousCommands = !"false".equalsIgnoreCase(value);440441if (allowAmbiguousCommands && security == null) {442// Legacy mode.443444// Normalize path if possible.445String executablePath = new File(cmd[0]).getPath();446447// No worry about internal, unpaired ["], and redirection/piping.448if (needsEscaping(VERIFICATION_LEGACY, executablePath) )449executablePath = quoteString(executablePath);450451cmdstr = createCommandLine(452//legacy mode doesn't worry about extended verification453VERIFICATION_LEGACY,454executablePath,455cmd);456} else {457String executablePath;458try {459executablePath = getExecutablePath(cmd[0]);460} catch (IllegalArgumentException e) {461// Workaround for the calls like462// Runtime.getRuntime().exec("\"C:\\Program Files\\foo\" bar")463464// No chance to avoid CMD/BAT injection, except to do the work465// right from the beginning. Otherwise we have too many corner466// cases from467// Runtime.getRuntime().exec(String[] cmd [, ...])468// calls with internal ["] and escape sequences.469470// Restore original command line.471StringBuilder join = new StringBuilder();472// terminal space in command line is ok473for (String s : cmd)474join.append(s).append(' ');475476// Parse the command line again.477cmd = getTokensFromCommand(join.toString());478executablePath = getExecutablePath(cmd[0]);479480// Check new executable name once more481if (security != null)482security.checkExec(executablePath);483}484485// Quotation protects from interpretation of the [path] argument as486// start of longer path with spaces. Quotation has no influence to487// [.exe] extension heuristic.488boolean isShell = allowAmbiguousCommands ? isShellFile(executablePath)489: !isExe(executablePath);490cmdstr = createCommandLine(491// We need the extended verification procedures492isShell ? VERIFICATION_CMD_BAT493: (allowAmbiguousCommands ? VERIFICATION_WIN32 : VERIFICATION_WIN32_SAFE),494quoteString(executablePath),495cmd);496}497498handle = create(cmdstr, envblock, path,499stdHandles, redirectErrorStream);500// Register a cleaning function to close the handle501final long local_handle = handle; // local to prevent capture of this502CleanerFactory.cleaner().register(this, () -> closeHandle(local_handle));503504processHandle = ProcessHandleImpl.getInternal(getProcessId0(handle));505506java.security.AccessController.doPrivileged(507new java.security.PrivilegedAction<Void>() {508public Void run() {509if (stdHandles[0] == -1L)510stdin_stream = ProcessBuilder.NullOutputStream.INSTANCE;511else {512FileDescriptor stdin_fd = new FileDescriptor();513fdAccess.setHandle(stdin_fd, stdHandles[0]);514fdAccess.registerCleanup(stdin_fd);515stdin_stream = new BufferedOutputStream(516new FileOutputStream(stdin_fd));517}518519if (stdHandles[1] == -1L || forceNullOutputStream)520stdout_stream = ProcessBuilder.NullInputStream.INSTANCE;521else {522FileDescriptor stdout_fd = new FileDescriptor();523fdAccess.setHandle(stdout_fd, stdHandles[1]);524fdAccess.registerCleanup(stdout_fd);525stdout_stream = new BufferedInputStream(526new PipeInputStream(stdout_fd));527}528529if (stdHandles[2] == -1L)530stderr_stream = ProcessBuilder.NullInputStream.INSTANCE;531else {532FileDescriptor stderr_fd = new FileDescriptor();533fdAccess.setHandle(stderr_fd, stdHandles[2]);534fdAccess.registerCleanup(stderr_fd);535stderr_stream = new PipeInputStream(stderr_fd);536}537538return null; }});539}540541public OutputStream getOutputStream() {542return stdin_stream;543}544545public InputStream getInputStream() {546return stdout_stream;547}548549public InputStream getErrorStream() {550return stderr_stream;551}552553private static final int STILL_ACTIVE = getStillActive();554private static native int getStillActive();555556public int exitValue() {557int exitCode = getExitCodeProcess(handle);558if (exitCode == STILL_ACTIVE)559throw new IllegalThreadStateException("process has not exited");560return exitCode;561}562private static native int getExitCodeProcess(long handle);563564public int waitFor() throws InterruptedException {565waitForInterruptibly(handle);566if (Thread.interrupted())567throw new InterruptedException();568return exitValue();569}570571private static native void waitForInterruptibly(long handle);572573@Override574public boolean waitFor(long timeout, TimeUnit unit)575throws InterruptedException576{577long remainingNanos = unit.toNanos(timeout); // throw NPE before other conditions578if (getExitCodeProcess(handle) != STILL_ACTIVE) return true;579if (timeout <= 0) return false;580581long deadline = System.nanoTime() + remainingNanos;582do {583// Round up to next millisecond584long msTimeout = TimeUnit.NANOSECONDS.toMillis(remainingNanos + 999_999L);585if (msTimeout < 0) {586// if wraps around then wait a long while587msTimeout = Integer.MAX_VALUE;588}589waitForTimeoutInterruptibly(handle, msTimeout);590if (Thread.interrupted())591throw new InterruptedException();592if (getExitCodeProcess(handle) != STILL_ACTIVE) {593return true;594}595remainingNanos = deadline - System.nanoTime();596} while (remainingNanos > 0);597598return (getExitCodeProcess(handle) != STILL_ACTIVE);599}600601private static native void waitForTimeoutInterruptibly(602long handle, long timeoutMillis);603604@Override605public void destroy() {606terminateProcess(handle);607}608609@Override610public CompletableFuture<Process> onExit() {611return ProcessHandleImpl.completion(pid(), false)612.handleAsync((exitStatus, unusedThrowable) -> this);613}614615@Override616public ProcessHandle toHandle() {617@SuppressWarnings("removal")618SecurityManager sm = System.getSecurityManager();619if (sm != null) {620sm.checkPermission(new RuntimePermission("manageProcess"));621}622return processHandle;623}624625@Override626public boolean supportsNormalTermination() {627return ProcessImpl.SUPPORTS_NORMAL_TERMINATION;628}629630@Override631public Process destroyForcibly() {632destroy();633return this;634}635636private static native void terminateProcess(long handle);637638@Override639public long pid() {640return processHandle.pid();641}642643private static native int getProcessId0(long handle);644645@Override646public boolean isAlive() {647return isProcessAlive(handle);648}649650private static native boolean isProcessAlive(long handle);651652/**653* The {@code toString} method returns a string consisting of654* the native process ID of the process and the exit value of the process.655*656* @return a string representation of the object.657*/658@Override659public String toString() {660int exitCode = getExitCodeProcess(handle);661return new StringBuilder("Process[pid=").append(pid())662.append(", exitValue=").append(exitCode == STILL_ACTIVE ? "\"not exited\"" : exitCode)663.append("]").toString();664}665666/**667* Create a process using the win32 function CreateProcess.668* The method is synchronized due to MS kb315939 problem.669* All native handles should restore the inherit flag at the end of call.670*671* @param cmdstr the Windows command line672* @param envblock NUL-separated, double-NUL-terminated list of673* environment strings in VAR=VALUE form674* @param dir the working directory of the process, or null if675* inheriting the current directory from the parent process676* @param stdHandles array of windows HANDLEs. Indexes 0, 1, and677* 2 correspond to standard input, standard output and678* standard error, respectively. On input, a value of -1679* means to create a pipe to connect child and parent680* processes. On output, a value which is not -1 is the681* parent pipe handle corresponding to the pipe which has682* been created. An element of this array is -1 on input683* if and only if it is <em>not</em> -1 on output.684* @param redirectErrorStream redirectErrorStream attribute685* @return the native subprocess HANDLE returned by CreateProcess686*/687private static synchronized native long create(String cmdstr,688String envblock,689String dir,690long[] stdHandles,691boolean redirectErrorStream)692throws IOException;693694/**695* Opens a file for atomic append. The file is created if it doesn't696* already exist.697*698* @param path the file to open or create699* @return the native HANDLE700*/701private static native long openForAtomicAppend(String path)702throws IOException;703704private static native boolean closeHandle(long handle);705}706707708