Path: blob/master/src/java.base/windows/classes/java/lang/ProcessImpl.java
41133 views
/*1* Copyright (c) 1995, 2021, 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.GetBooleanAction;50import sun.security.action.GetPropertyAction;5152/* This class is for the exclusive use of ProcessBuilder.start() to53* create new processes.54*55* @author Martin Buchholz56* @since 1.557*/5859final class ProcessImpl extends Process {60private static final JavaIOFileDescriptorAccess fdAccess61= SharedSecrets.getJavaIOFileDescriptorAccess();6263// Windows platforms support a forcible kill signal.64static final boolean SUPPORTS_NORMAL_TERMINATION = false;6566/**67* Open a file for writing. If {@code append} is {@code true} then the file68* is opened for atomic append directly and a FileOutputStream constructed69* with the resulting handle. This is because a FileOutputStream created70* to append to a file does not open the file in a manner that guarantees71* that writes by the child process will be atomic.72*/73@SuppressWarnings("removal")74private static FileOutputStream newFileOutputStream(File f, boolean append)75throws IOException76{77if (append) {78String path = f.getPath();79SecurityManager sm = System.getSecurityManager();80if (sm != null)81sm.checkWrite(path);82long handle = openForAtomicAppend(path);83final FileDescriptor fd = new FileDescriptor();84fdAccess.setHandle(fd, handle);85return AccessController.doPrivileged(86new PrivilegedAction<FileOutputStream>() {87public FileOutputStream run() {88return new FileOutputStream(fd);89}90}91);92} else {93return new FileOutputStream(f);94}95}9697// System-dependent portion of ProcessBuilder.start()98static Process start(String cmdarray[],99java.util.Map<String,String> environment,100String dir,101ProcessBuilder.Redirect[] redirects,102boolean redirectErrorStream)103throws IOException104{105String envblock = ProcessEnvironment.toEnvironmentBlock(environment);106107FileInputStream f0 = null;108FileOutputStream f1 = null;109FileOutputStream f2 = null;110111try {112boolean forceNullOutputStream = false;113long[] stdHandles;114if (redirects == null) {115stdHandles = new long[] { -1L, -1L, -1L };116} else {117stdHandles = new long[3];118119if (redirects[0] == Redirect.PIPE) {120stdHandles[0] = -1L;121} else if (redirects[0] == Redirect.INHERIT) {122stdHandles[0] = fdAccess.getHandle(FileDescriptor.in);123} else if (redirects[0] instanceof ProcessBuilder.RedirectPipeImpl) {124stdHandles[0] = fdAccess.getHandle(((ProcessBuilder.RedirectPipeImpl) redirects[0]).getFd());125} else {126f0 = new FileInputStream(redirects[0].file());127stdHandles[0] = fdAccess.getHandle(f0.getFD());128}129130if (redirects[1] == Redirect.PIPE) {131stdHandles[1] = -1L;132} else if (redirects[1] == Redirect.INHERIT) {133stdHandles[1] = fdAccess.getHandle(FileDescriptor.out);134} else if (redirects[1] instanceof ProcessBuilder.RedirectPipeImpl) {135stdHandles[1] = fdAccess.getHandle(((ProcessBuilder.RedirectPipeImpl) redirects[1]).getFd());136// Force getInputStream to return a null stream,137// the handle is directly assigned to the next process.138forceNullOutputStream = true;139} else {140f1 = newFileOutputStream(redirects[1].file(),141redirects[1].append());142stdHandles[1] = fdAccess.getHandle(f1.getFD());143}144145if (redirects[2] == Redirect.PIPE) {146stdHandles[2] = -1L;147} else if (redirects[2] == Redirect.INHERIT) {148stdHandles[2] = fdAccess.getHandle(FileDescriptor.err);149} else if (redirects[2] instanceof ProcessBuilder.RedirectPipeImpl) {150stdHandles[2] = fdAccess.getHandle(((ProcessBuilder.RedirectPipeImpl) redirects[2]).getFd());151} else {152f2 = newFileOutputStream(redirects[2].file(),153redirects[2].append());154stdHandles[2] = fdAccess.getHandle(f2.getFD());155}156}157158Process p = new ProcessImpl(cmdarray, envblock, dir,159stdHandles, forceNullOutputStream, redirectErrorStream);160if (redirects != null) {161// Copy the handles's if they are to be redirected to another process162if (stdHandles[0] >= 0163&& redirects[0] instanceof ProcessBuilder.RedirectPipeImpl) {164fdAccess.setHandle(((ProcessBuilder.RedirectPipeImpl) redirects[0]).getFd(),165stdHandles[0]);166}167if (stdHandles[1] >= 0168&& redirects[1] instanceof ProcessBuilder.RedirectPipeImpl) {169fdAccess.setHandle(((ProcessBuilder.RedirectPipeImpl) redirects[1]).getFd(),170stdHandles[1]);171}172if (stdHandles[2] >= 0173&& redirects[2] instanceof ProcessBuilder.RedirectPipeImpl) {174fdAccess.setHandle(((ProcessBuilder.RedirectPipeImpl) redirects[2]).getFd(),175stdHandles[2]);176}177}178return p;179} finally {180// In theory, close() can throw IOException181// (although it is rather unlikely to happen here)182try { if (f0 != null) f0.close(); }183finally {184try { if (f1 != null) f1.close(); }185finally { if (f2 != null) f2.close(); }186}187}188189}190191private static class LazyPattern {192// Escape-support version:193// "(\")((?:\\\\\\1|.)+?)\\1|([^\\s\"]+)";194private static final Pattern PATTERN =195Pattern.compile("[^\\s\"]+|\"[^\"]*\"");196};197198/* Parses the command string parameter into the executable name and199* program arguments.200*201* The command string is broken into tokens. The token separator is a space202* or quota character. The space inside quotation is not a token separator.203* There are no escape sequences.204*/205private static String[] getTokensFromCommand(String command) {206ArrayList<String> matchList = new ArrayList<>(8);207Matcher regexMatcher = LazyPattern.PATTERN.matcher(command);208while (regexMatcher.find())209matchList.add(regexMatcher.group());210return matchList.toArray(new String[matchList.size()]);211}212213private static final int VERIFICATION_CMD_BAT = 0;214private static final int VERIFICATION_WIN32 = 1;215private static final int VERIFICATION_WIN32_SAFE = 2; // inside quotes not allowed216private static final int VERIFICATION_LEGACY = 3;217// See Command shell overview for documentation of special characters.218// https://docs.microsoft.com/en-us/previous-versions/windows/it-pro/windows-xp/bb490954(v=technet.10)219private static final char ESCAPE_VERIFICATION[][] = {220// We guarantee the only command file execution for implicit [cmd.exe] run.221// http://technet.microsoft.com/en-us/library/bb490954.aspx222{' ', '\t', '\"', '<', '>', '&', '|', '^'},223{' ', '\t', '\"', '<', '>'},224{' ', '\t', '\"', '<', '>'},225{' ', '\t'}226};227228private static String createCommandLine(int verificationType,229final String executablePath,230final String cmd[])231{232StringBuilder cmdbuf = new StringBuilder(80);233234cmdbuf.append(executablePath);235236for (int i = 1; i < cmd.length; ++i) {237cmdbuf.append(' ');238String s = cmd[i];239if (needsEscaping(verificationType, s)) {240cmdbuf.append('"');241242if (verificationType == VERIFICATION_WIN32_SAFE) {243// Insert the argument, adding '\' to quote any interior quotes244int length = s.length();245for (int j = 0; j < length; j++) {246char c = s.charAt(j);247if (c == DOUBLEQUOTE) {248int count = countLeadingBackslash(verificationType, s, j);249while (count-- > 0) {250cmdbuf.append(BACKSLASH); // double the number of backslashes251}252cmdbuf.append(BACKSLASH); // backslash to quote the quote253}254cmdbuf.append(c);255}256} else {257cmdbuf.append(s);258}259// The code protects the [java.exe] and console command line260// parser, that interprets the [\"] combination as an escape261// sequence for the ["] char.262// http://msdn.microsoft.com/en-us/library/17w5ykft.aspx263//264// If the argument is an FS path, doubling of the tail [\]265// char is not a problem for non-console applications.266//267// The [\"] sequence is not an escape sequence for the [cmd.exe]268// command line parser. The case of the [""] tail escape269// sequence could not be realized due to the argument validation270// procedure.271int count = countLeadingBackslash(verificationType, s, s.length());272while (count-- > 0) {273cmdbuf.append(BACKSLASH); // double the number of backslashes274}275cmdbuf.append('"');276} else {277cmdbuf.append(s);278}279}280return cmdbuf.toString();281}282283/**284* Return the argument without quotes (1st and last) if properly quoted, else the arg.285* A properly quoted string has first and last characters as quote and286* the last quote is not escaped.287* @param str a string288* @return the string without quotes289*/290private static String unQuote(String str) {291if (!str.startsWith("\"") || !str.endsWith("\"") || str.length() < 2)292return str; // no beginning or ending quote, or too short not quoted293294if (str.endsWith("\\\"")) {295return str; // not properly quoted, treat as unquoted296}297// Strip leading and trailing quotes298return str.substring(1, str.length() - 1);299}300301private static boolean needsEscaping(int verificationType, String arg) {302if (arg.isEmpty())303return true; // Empty string is to be quoted304305// Switch off MS heuristic for internal ["].306// Please, use the explicit [cmd.exe] call307// if you need the internal ["].308// Example: "cmd.exe", "/C", "Extended_MS_Syntax"309310// For [.exe] or [.com] file the unpaired/internal ["]311// in the argument is not a problem.312String unquotedArg = unQuote(arg);313boolean argIsQuoted = !arg.equals(unquotedArg);314boolean embeddedQuote = unquotedArg.indexOf(DOUBLEQUOTE) >= 0;315316switch (verificationType) {317case VERIFICATION_CMD_BAT:318if (embeddedQuote) {319throw new IllegalArgumentException("Argument has embedded quote, " +320"use the explicit CMD.EXE call.");321}322break; // break determine whether to quote323case VERIFICATION_WIN32_SAFE:324if (argIsQuoted && embeddedQuote) {325throw new IllegalArgumentException("Malformed argument has embedded quote: "326+ unquotedArg);327}328break;329default:330break;331}332333if (!argIsQuoted) {334char testEscape[] = ESCAPE_VERIFICATION[verificationType];335for (int i = 0; i < testEscape.length; ++i) {336if (arg.indexOf(testEscape[i]) >= 0) {337return true;338}339}340}341return false;342}343344private static String getExecutablePath(String path)345throws IOException346{347String name = unQuote(path);348if (name.indexOf(DOUBLEQUOTE) >= 0) {349throw new IllegalArgumentException("Executable name has embedded quote, " +350"split the arguments: " + name);351}352// Win32 CreateProcess requires path to be normalized353File fileToRun = new File(name);354355// From the [CreateProcess] function documentation:356//357// "If the file name does not contain an extension, .exe is appended.358// Therefore, if the file name extension is .com, this parameter359// must include the .com extension. If the file name ends in360// a period (.) with no extension, or if the file name contains a path,361// .exe is not appended."362//363// "If the file name !does not contain a directory path!,364// the system searches for the executable file in the following365// sequence:..."366//367// In practice ANY non-existent path is extended by [.exe] extension368// in the [CreateProcess] function with the only exception:369// the path ends by (.)370371return fileToRun.getPath();372}373374/**375* An executable is any program that is an EXE or does not have an extension376* and the Windows createProcess will be looking for .exe.377* The comparison is case insensitive based on the name.378* @param executablePath the executable file379* @return true if the path ends in .exe or does not have an extension.380*/381private boolean isExe(String executablePath) {382File file = new File(executablePath);383String upName = file.getName().toUpperCase(Locale.ROOT);384return (upName.endsWith(".EXE") || upName.indexOf('.') < 0);385}386387// Old version that can be bypassed388private boolean isShellFile(String executablePath) {389String upPath = executablePath.toUpperCase();390return (upPath.endsWith(".CMD") || upPath.endsWith(".BAT"));391}392393private String quoteString(String arg) {394StringBuilder argbuf = new StringBuilder(arg.length() + 2);395return argbuf.append('"').append(arg).append('"').toString();396}397398// Count backslashes before start index of string.399// .bat files don't include backslashes as part of the quote400private static int countLeadingBackslash(int verificationType,401CharSequence input, int start) {402if (verificationType == VERIFICATION_CMD_BAT)403return 0;404int j;405for (j = start - 1; j >= 0 && input.charAt(j) == BACKSLASH; j--) {406// just scanning backwards407}408return (start - 1) - j; // number of BACKSLASHES409}410411private static final char DOUBLEQUOTE = '\"';412private static final char BACKSLASH = '\\';413414private final long handle;415private final ProcessHandle processHandle;416private OutputStream stdin_stream;417private InputStream stdout_stream;418private InputStream stderr_stream;419420@SuppressWarnings("removal")421private ProcessImpl(String cmd[],422final String envblock,423final String path,424final long[] stdHandles,425boolean forceNullOutputStream,426final boolean redirectErrorStream)427throws IOException428{429String cmdstr;430final SecurityManager security = System.getSecurityManager();431final String value = GetPropertyAction.432privilegedGetProperty("jdk.lang.Process.allowAmbiguousCommands",433(security == null ? "true" : "false"));434final boolean allowAmbiguousCommands = !"false".equalsIgnoreCase(value);435436if (allowAmbiguousCommands && security == null) {437// Legacy mode.438439// Normalize path if possible.440String executablePath = new File(cmd[0]).getPath();441442// No worry about internal, unpaired ["], and redirection/piping.443if (needsEscaping(VERIFICATION_LEGACY, executablePath) )444executablePath = quoteString(executablePath);445446cmdstr = createCommandLine(447//legacy mode doesn't worry about extended verification448VERIFICATION_LEGACY,449executablePath,450cmd);451} else {452String executablePath;453try {454executablePath = getExecutablePath(cmd[0]);455} catch (IllegalArgumentException e) {456// Workaround for the calls like457// Runtime.getRuntime().exec("\"C:\\Program Files\\foo\" bar")458459// No chance to avoid CMD/BAT injection, except to do the work460// right from the beginning. Otherwise we have too many corner461// cases from462// Runtime.getRuntime().exec(String[] cmd [, ...])463// calls with internal ["] and escape sequences.464465// Restore original command line.466StringBuilder join = new StringBuilder();467// terminal space in command line is ok468for (String s : cmd)469join.append(s).append(' ');470471// Parse the command line again.472cmd = getTokensFromCommand(join.toString());473executablePath = getExecutablePath(cmd[0]);474475// Check new executable name once more476if (security != null)477security.checkExec(executablePath);478}479480// Quotation protects from interpretation of the [path] argument as481// start of longer path with spaces. Quotation has no influence to482// [.exe] extension heuristic.483boolean isShell = allowAmbiguousCommands ? isShellFile(executablePath)484: !isExe(executablePath);485cmdstr = createCommandLine(486// We need the extended verification procedures487isShell ? VERIFICATION_CMD_BAT488: (allowAmbiguousCommands ? VERIFICATION_WIN32 : VERIFICATION_WIN32_SAFE),489quoteString(executablePath),490cmd);491}492493handle = create(cmdstr, envblock, path,494stdHandles, redirectErrorStream);495// Register a cleaning function to close the handle496final long local_handle = handle; // local to prevent capture of this497CleanerFactory.cleaner().register(this, () -> closeHandle(local_handle));498499processHandle = ProcessHandleImpl.getInternal(getProcessId0(handle));500501java.security.AccessController.doPrivileged(502new java.security.PrivilegedAction<Void>() {503public Void run() {504if (stdHandles[0] == -1L)505stdin_stream = ProcessBuilder.NullOutputStream.INSTANCE;506else {507FileDescriptor stdin_fd = new FileDescriptor();508fdAccess.setHandle(stdin_fd, stdHandles[0]);509fdAccess.registerCleanup(stdin_fd);510stdin_stream = new BufferedOutputStream(511new FileOutputStream(stdin_fd));512}513514if (stdHandles[1] == -1L || forceNullOutputStream)515stdout_stream = ProcessBuilder.NullInputStream.INSTANCE;516else {517FileDescriptor stdout_fd = new FileDescriptor();518fdAccess.setHandle(stdout_fd, stdHandles[1]);519fdAccess.registerCleanup(stdout_fd);520stdout_stream = new BufferedInputStream(521new PipeInputStream(stdout_fd));522}523524if (stdHandles[2] == -1L)525stderr_stream = ProcessBuilder.NullInputStream.INSTANCE;526else {527FileDescriptor stderr_fd = new FileDescriptor();528fdAccess.setHandle(stderr_fd, stdHandles[2]);529fdAccess.registerCleanup(stderr_fd);530stderr_stream = new PipeInputStream(stderr_fd);531}532533return null; }});534}535536public OutputStream getOutputStream() {537return stdin_stream;538}539540public InputStream getInputStream() {541return stdout_stream;542}543544public InputStream getErrorStream() {545return stderr_stream;546}547548private static final int STILL_ACTIVE = getStillActive();549private static native int getStillActive();550551public int exitValue() {552int exitCode = getExitCodeProcess(handle);553if (exitCode == STILL_ACTIVE)554throw new IllegalThreadStateException("process has not exited");555return exitCode;556}557private static native int getExitCodeProcess(long handle);558559public int waitFor() throws InterruptedException {560waitForInterruptibly(handle);561if (Thread.interrupted())562throw new InterruptedException();563return exitValue();564}565566private static native void waitForInterruptibly(long handle);567568@Override569public boolean waitFor(long timeout, TimeUnit unit)570throws InterruptedException571{572long remainingNanos = unit.toNanos(timeout); // throw NPE before other conditions573if (getExitCodeProcess(handle) != STILL_ACTIVE) return true;574if (timeout <= 0) return false;575576long deadline = System.nanoTime() + remainingNanos;577do {578// Round up to next millisecond579long msTimeout = TimeUnit.NANOSECONDS.toMillis(remainingNanos + 999_999L);580if (msTimeout < 0) {581// if wraps around then wait a long while582msTimeout = Integer.MAX_VALUE;583}584waitForTimeoutInterruptibly(handle, msTimeout);585if (Thread.interrupted())586throw new InterruptedException();587if (getExitCodeProcess(handle) != STILL_ACTIVE) {588return true;589}590remainingNanos = deadline - System.nanoTime();591} while (remainingNanos > 0);592593return (getExitCodeProcess(handle) != STILL_ACTIVE);594}595596private static native void waitForTimeoutInterruptibly(597long handle, long timeoutMillis);598599@Override600public void destroy() {601terminateProcess(handle);602}603604@Override605public CompletableFuture<Process> onExit() {606return ProcessHandleImpl.completion(pid(), false)607.handleAsync((exitStatus, unusedThrowable) -> this);608}609610@Override611public ProcessHandle toHandle() {612@SuppressWarnings("removal")613SecurityManager sm = System.getSecurityManager();614if (sm != null) {615sm.checkPermission(new RuntimePermission("manageProcess"));616}617return processHandle;618}619620@Override621public boolean supportsNormalTermination() {622return ProcessImpl.SUPPORTS_NORMAL_TERMINATION;623}624625@Override626public Process destroyForcibly() {627destroy();628return this;629}630631private static native void terminateProcess(long handle);632633@Override634public long pid() {635return processHandle.pid();636}637638private static native int getProcessId0(long handle);639640@Override641public boolean isAlive() {642return isProcessAlive(handle);643}644645private static native boolean isProcessAlive(long handle);646647/**648* The {@code toString} method returns a string consisting of649* the native process ID of the process and the exit value of the process.650*651* @return a string representation of the object.652*/653@Override654public String toString() {655int exitCode = getExitCodeProcess(handle);656return new StringBuilder("Process[pid=").append(pid())657.append(", exitValue=").append(exitCode == STILL_ACTIVE ? "\"not exited\"" : exitCode)658.append("]").toString();659}660661/**662* Create a process using the win32 function CreateProcess.663* The method is synchronized due to MS kb315939 problem.664* All native handles should restore the inherit flag at the end of call.665*666* @param cmdstr the Windows command line667* @param envblock NUL-separated, double-NUL-terminated list of668* environment strings in VAR=VALUE form669* @param dir the working directory of the process, or null if670* inheriting the current directory from the parent process671* @param stdHandles array of windows HANDLEs. Indexes 0, 1, and672* 2 correspond to standard input, standard output and673* standard error, respectively. On input, a value of -1674* means to create a pipe to connect child and parent675* processes. On output, a value which is not -1 is the676* parent pipe handle corresponding to the pipe which has677* been created. An element of this array is -1 on input678* if and only if it is <em>not</em> -1 on output.679* @param redirectErrorStream redirectErrorStream attribute680* @return the native subprocess HANDLE returned by CreateProcess681*/682private static synchronized native long create(String cmdstr,683String envblock,684String dir,685long[] stdHandles,686boolean redirectErrorStream)687throws IOException;688689/**690* Opens a file for atomic append. The file is created if it doesn't691* already exist.692*693* @param path the file to open or create694* @return the native HANDLE695*/696private static native long openForAtomicAppend(String path)697throws IOException;698699private static native boolean closeHandle(long handle);700}701702703