Path: blob/aarch64-shenandoah-jdk8u272-b10/jdk/src/windows/classes/java/lang/ProcessImpl.java
32287 views
/*1* Copyright (c) 1995, 2013, 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.IOException;28import java.io.File;29import java.io.InputStream;30import java.io.OutputStream;31import java.io.FileInputStream;32import java.io.FileOutputStream;33import java.io.FileDescriptor;34import java.io.BufferedInputStream;35import java.io.BufferedOutputStream;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.TimeUnit;42import java.util.regex.Matcher;43import java.util.regex.Pattern;44import sun.security.action.GetPropertyAction;4546/* This class is for the exclusive use of ProcessBuilder.start() to47* create new processes.48*49* @author Martin Buchholz50* @since 1.551*/5253final class ProcessImpl extends Process {54private static final sun.misc.JavaIOFileDescriptorAccess fdAccess55= sun.misc.SharedSecrets.getJavaIOFileDescriptorAccess();5657/**58* Open a file for writing. If {@code append} is {@code true} then the file59* is opened for atomic append directly and a FileOutputStream constructed60* with the resulting handle. This is because a FileOutputStream created61* to append to a file does not open the file in a manner that guarantees62* that writes by the child process will be atomic.63*/64private static FileOutputStream newFileOutputStream(File f, boolean append)65throws IOException66{67if (append) {68String path = f.getPath();69SecurityManager sm = System.getSecurityManager();70if (sm != null)71sm.checkWrite(path);72long handle = openForAtomicAppend(path);73final FileDescriptor fd = new FileDescriptor();74fdAccess.setHandle(fd, handle);75return AccessController.doPrivileged(76new PrivilegedAction<FileOutputStream>() {77public FileOutputStream run() {78return new FileOutputStream(fd);79}80}81);82} else {83return new FileOutputStream(f);84}85}8687// System-dependent portion of ProcessBuilder.start()88static Process start(String cmdarray[],89java.util.Map<String,String> environment,90String dir,91ProcessBuilder.Redirect[] redirects,92boolean redirectErrorStream)93throws IOException94{95String envblock = ProcessEnvironment.toEnvironmentBlock(environment);9697FileInputStream f0 = null;98FileOutputStream f1 = null;99FileOutputStream f2 = null;100101try {102long[] stdHandles;103if (redirects == null) {104stdHandles = new long[] { -1L, -1L, -1L };105} else {106stdHandles = new long[3];107108if (redirects[0] == Redirect.PIPE)109stdHandles[0] = -1L;110else if (redirects[0] == Redirect.INHERIT)111stdHandles[0] = fdAccess.getHandle(FileDescriptor.in);112else {113f0 = new FileInputStream(redirects[0].file());114stdHandles[0] = fdAccess.getHandle(f0.getFD());115}116117if (redirects[1] == Redirect.PIPE)118stdHandles[1] = -1L;119else if (redirects[1] == Redirect.INHERIT)120stdHandles[1] = fdAccess.getHandle(FileDescriptor.out);121else {122f1 = newFileOutputStream(redirects[1].file(),123redirects[1].append());124stdHandles[1] = fdAccess.getHandle(f1.getFD());125}126127if (redirects[2] == Redirect.PIPE)128stdHandles[2] = -1L;129else if (redirects[2] == Redirect.INHERIT)130stdHandles[2] = fdAccess.getHandle(FileDescriptor.err);131else {132f2 = newFileOutputStream(redirects[2].file(),133redirects[2].append());134stdHandles[2] = fdAccess.getHandle(f2.getFD());135}136}137138return new ProcessImpl(cmdarray, envblock, dir,139stdHandles, redirectErrorStream);140} finally {141// In theory, close() can throw IOException142// (although it is rather unlikely to happen here)143try { if (f0 != null) f0.close(); }144finally {145try { if (f1 != null) f1.close(); }146finally { if (f2 != null) f2.close(); }147}148}149150}151152private static class LazyPattern {153// Escape-support version:154// "(\")((?:\\\\\\1|.)+?)\\1|([^\\s\"]+)";155private static final Pattern PATTERN =156Pattern.compile("[^\\s\"]+|\"[^\"]*\"");157};158159/* Parses the command string parameter into the executable name and160* program arguments.161*162* The command string is broken into tokens. The token separator is a space163* or quota character. The space inside quotation is not a token separator.164* There are no escape sequences.165*/166private static String[] getTokensFromCommand(String command) {167ArrayList<String> matchList = new ArrayList<>(8);168Matcher regexMatcher = LazyPattern.PATTERN.matcher(command);169while (regexMatcher.find())170matchList.add(regexMatcher.group());171return matchList.toArray(new String[matchList.size()]);172}173174private static final int VERIFICATION_CMD_BAT = 0;175private static final int VERIFICATION_WIN32 = 1;176private static final int VERIFICATION_WIN32_SAFE = 2; // inside quotes not allowed177private static final int VERIFICATION_LEGACY = 3;178// See Command shell overview for documentation of special characters.179// https://docs.microsoft.com/en-us/previous-versions/windows/it-pro/windows-xp/bb490954(v=technet.10)180private static final char ESCAPE_VERIFICATION[][] = {181// We guarantee the only command file execution for implicit [cmd.exe] run.182// http://technet.microsoft.com/en-us/library/bb490954.aspx183{' ', '\t', '\"', '<', '>', '&', '|', '^'},184{' ', '\t', '\"', '<', '>'},185{' ', '\t', '\"', '<', '>'},186{' ', '\t'}187};188189private static String createCommandLine(int verificationType,190final String executablePath,191final String cmd[])192{193StringBuilder cmdbuf = new StringBuilder(80);194195cmdbuf.append(executablePath);196197for (int i = 1; i < cmd.length; ++i) {198cmdbuf.append(' ');199String s = cmd[i];200if (needsEscaping(verificationType, s)) {201cmdbuf.append('"');202203if (verificationType == VERIFICATION_WIN32_SAFE) {204// Insert the argument, adding '\' to quote any interior quotes205int length = s.length();206for (int j = 0; j < length; j++) {207char c = s.charAt(j);208if (c == DOUBLEQUOTE) {209int count = countLeadingBackslash(verificationType, s, j);210while (count-- > 0) {211cmdbuf.append(BACKSLASH); // double the number of backslashes212}213cmdbuf.append(BACKSLASH); // backslash to quote the quote214}215cmdbuf.append(c);216}217} else {218cmdbuf.append(s);219}220// The code protects the [java.exe] and console command line221// parser, that interprets the [\"] combination as an escape222// sequence for the ["] char.223// http://msdn.microsoft.com/en-us/library/17w5ykft.aspx224//225// If the argument is an FS path, doubling of the tail [\]226// char is not a problem for non-console applications.227//228// The [\"] sequence is not an escape sequence for the [cmd.exe]229// command line parser. The case of the [""] tail escape230// sequence could not be realized due to the argument validation231// procedure.232int count = countLeadingBackslash(verificationType, s, s.length());233while (count-- > 0) {234cmdbuf.append(BACKSLASH); // double the number of backslashes235}236cmdbuf.append('"');237} else {238cmdbuf.append(s);239}240}241return cmdbuf.toString();242}243244/**245* Return the argument without quotes (1st and last) if properly quoted, else the arg.246* A properly quoted string has first and last characters as quote and247* the last quote is not escaped.248* @param str a string249* @return the string without quotes250*/251private static String unQuote(String str) {252if (!str.startsWith("\"") || !str.endsWith("\"") || str.length() < 2)253return str; // no beginning or ending quote, or too short not quoted254255if (str.endsWith("\\\"")) {256return str; // not properly quoted, treat as unquoted257}258// Strip leading and trailing quotes259return str.substring(1, str.length() - 1);260}261262private static boolean needsEscaping(int verificationType, String arg) {263if (arg.isEmpty())264return true; // Empty string is to be quoted265266// Switch off MS heuristic for internal ["].267// Please, use the explicit [cmd.exe] call268// if you need the internal ["].269// Example: "cmd.exe", "/C", "Extended_MS_Syntax"270271// For [.exe] or [.com] file the unpaired/internal ["]272// in the argument is not a problem.273String unquotedArg = unQuote(arg);274boolean argIsQuoted = !arg.equals(unquotedArg);275boolean embeddedQuote = unquotedArg.indexOf(DOUBLEQUOTE) >= 0;276277switch (verificationType) {278case VERIFICATION_CMD_BAT:279if (embeddedQuote) {280throw new IllegalArgumentException("Argument has embedded quote, " +281"use the explicit CMD.EXE call.");282}283break; // break determine whether to quote284case VERIFICATION_WIN32_SAFE:285if (argIsQuoted && embeddedQuote) {286throw new IllegalArgumentException("Malformed argument has embedded quote: "287+ unquotedArg);288}289break;290default:291break;292}293294if (!argIsQuoted) {295char testEscape[] = ESCAPE_VERIFICATION[verificationType];296for (int i = 0; i < testEscape.length; ++i) {297if (arg.indexOf(testEscape[i]) >= 0) {298return true;299}300}301}302return false;303}304305private static String getExecutablePath(String path)306throws IOException307{308String name = unQuote(path);309if (name.indexOf(DOUBLEQUOTE) >= 0) {310throw new IllegalArgumentException("Executable name has embedded quote, " +311"split the arguments: " + name);312}313// Win32 CreateProcess requires path to be normalized314File fileToRun = new File(name);315316// From the [CreateProcess] function documentation:317//318// "If the file name does not contain an extension, .exe is appended.319// Therefore, if the file name extension is .com, this parameter320// must include the .com extension. If the file name ends in321// a period (.) with no extension, or if the file name contains a path,322// .exe is not appended."323//324// "If the file name !does not contain a directory path!,325// the system searches for the executable file in the following326// sequence:..."327//328// In practice ANY non-existent path is extended by [.exe] extension329// in the [CreateProcess] function with the only exception:330// the path ends by (.)331332return fileToRun.getPath();333}334335/**336* An executable is any program that is an EXE or does not have an extension337* and the Windows createProcess will be looking for .exe.338* The comparison is case insensitive based on the name.339* @param executablePath the executable file340* @return true if the path ends in .exe or does not have an extension.341*/342private boolean isExe(String executablePath) {343File file = new File(executablePath);344String upName = file.getName().toUpperCase(Locale.ROOT);345return (upName.endsWith(".EXE") || upName.indexOf('.') < 0);346}347348// Old version that can be bypassed349private boolean isShellFile(String executablePath) {350String upPath = executablePath.toUpperCase();351return (upPath.endsWith(".CMD") || upPath.endsWith(".BAT"));352}353354private String quoteString(String arg) {355StringBuilder argbuf = new StringBuilder(arg.length() + 2);356return argbuf.append('"').append(arg).append('"').toString();357}358359// Count backslashes before start index of string.360// .bat files don't include backslashes as part of the quote361private static int countLeadingBackslash(int verificationType,362CharSequence input, int start) {363if (verificationType == VERIFICATION_CMD_BAT)364return 0;365int j;366for (j = start - 1; j >= 0 && input.charAt(j) == BACKSLASH; j--) {367// just scanning backwards368}369return (start - 1) - j; // number of BACKSLASHES370}371372private static final char DOUBLEQUOTE = '\"';373private static final char BACKSLASH = '\\';374375private long handle = 0;376private OutputStream stdin_stream;377private InputStream stdout_stream;378private InputStream stderr_stream;379380private ProcessImpl(String cmd[],381final String envblock,382final String path,383final long[] stdHandles,384final boolean redirectErrorStream)385throws IOException386{387String cmdstr;388final SecurityManager security = System.getSecurityManager();389String propertyValue = GetPropertyAction.390privilegedGetProperty("jdk.lang.Process.allowAmbiguousCommands");391final String value = propertyValue != null ? propertyValue392: (security == null ? "true" : "false");393final boolean allowAmbiguousCommands = !"false".equalsIgnoreCase(value);394395if (allowAmbiguousCommands && security == null) {396// Legacy mode.397398// Normalize path if possible.399String executablePath = new File(cmd[0]).getPath();400401// No worry about internal, unpaired ["], and redirection/piping.402if (needsEscaping(VERIFICATION_LEGACY, executablePath) )403executablePath = quoteString(executablePath);404405cmdstr = createCommandLine(406//legacy mode doesn't worry about extended verification407VERIFICATION_LEGACY,408executablePath,409cmd);410} else {411String executablePath;412try {413executablePath = getExecutablePath(cmd[0]);414} catch (IllegalArgumentException e) {415// Workaround for the calls like416// Runtime.getRuntime().exec("\"C:\\Program Files\\foo\" bar")417418// No chance to avoid CMD/BAT injection, except to do the work419// right from the beginning. Otherwise we have too many corner420// cases from421// Runtime.getRuntime().exec(String[] cmd [, ...])422// calls with internal ["] and escape sequences.423424// Restore original command line.425StringBuilder join = new StringBuilder();426// terminal space in command line is ok427for (String s : cmd)428join.append(s).append(' ');429430// Parse the command line again.431cmd = getTokensFromCommand(join.toString());432executablePath = getExecutablePath(cmd[0]);433434// Check new executable name once more435if (security != null)436security.checkExec(executablePath);437}438439// Quotation protects from interpretation of the [path] argument as440// start of longer path with spaces. Quotation has no influence to441// [.exe] extension heuristic.442boolean isShell = allowAmbiguousCommands ? isShellFile(executablePath)443: !isExe(executablePath);444cmdstr = createCommandLine(445// We need the extended verification procedures446isShell ? VERIFICATION_CMD_BAT447: (allowAmbiguousCommands ? VERIFICATION_WIN32 : VERIFICATION_WIN32_SAFE),448quoteString(executablePath),449cmd);450}451452handle = create(cmdstr, envblock, path,453stdHandles, redirectErrorStream);454455java.security.AccessController.doPrivileged(456new java.security.PrivilegedAction<Void>() {457public Void run() {458if (stdHandles[0] == -1L)459stdin_stream = ProcessBuilder.NullOutputStream.INSTANCE;460else {461FileDescriptor stdin_fd = new FileDescriptor();462fdAccess.setHandle(stdin_fd, stdHandles[0]);463stdin_stream = new BufferedOutputStream(464new FileOutputStream(stdin_fd));465}466467if (stdHandles[1] == -1L)468stdout_stream = ProcessBuilder.NullInputStream.INSTANCE;469else {470FileDescriptor stdout_fd = new FileDescriptor();471fdAccess.setHandle(stdout_fd, stdHandles[1]);472stdout_stream = new BufferedInputStream(473new FileInputStream(stdout_fd));474}475476if (stdHandles[2] == -1L)477stderr_stream = ProcessBuilder.NullInputStream.INSTANCE;478else {479FileDescriptor stderr_fd = new FileDescriptor();480fdAccess.setHandle(stderr_fd, stdHandles[2]);481stderr_stream = new FileInputStream(stderr_fd);482}483484return null; }});485}486487public OutputStream getOutputStream() {488return stdin_stream;489}490491public InputStream getInputStream() {492return stdout_stream;493}494495public InputStream getErrorStream() {496return stderr_stream;497}498499protected void finalize() {500closeHandle(handle);501}502503private static final int STILL_ACTIVE = getStillActive();504private static native int getStillActive();505506public int exitValue() {507int exitCode = getExitCodeProcess(handle);508if (exitCode == STILL_ACTIVE)509throw new IllegalThreadStateException("process has not exited");510return exitCode;511}512private static native int getExitCodeProcess(long handle);513514public int waitFor() throws InterruptedException {515waitForInterruptibly(handle);516if (Thread.interrupted())517throw new InterruptedException();518return exitValue();519}520521private static native void waitForInterruptibly(long handle);522523@Override524public boolean waitFor(long timeout, TimeUnit unit)525throws InterruptedException526{527long remainingNanos = unit.toNanos(timeout); // throw NPE before other conditions528if (getExitCodeProcess(handle) != STILL_ACTIVE) return true;529if (timeout <= 0) return false;530531long deadline = System.nanoTime() + remainingNanos;532do {533// Round up to next millisecond534long msTimeout = TimeUnit.NANOSECONDS.toMillis(remainingNanos + 999_999L);535if (msTimeout < 0) {536// if wraps around then wait a long while537msTimeout = Integer.MAX_VALUE;538}539waitForTimeoutInterruptibly(handle, msTimeout);540if (Thread.interrupted())541throw new InterruptedException();542if (getExitCodeProcess(handle) != STILL_ACTIVE) {543return true;544}545remainingNanos = deadline - System.nanoTime();546} while (remainingNanos > 0);547548return (getExitCodeProcess(handle) != STILL_ACTIVE);549}550551private static native void waitForTimeoutInterruptibly(552long handle, long timeoutMillis);553554public void destroy() { terminateProcess(handle); }555556@Override557public Process destroyForcibly() {558destroy();559return this;560}561562private static native void terminateProcess(long handle);563564@Override565public boolean isAlive() {566return isProcessAlive(handle);567}568569private static native boolean isProcessAlive(long handle);570571/**572* Create a process using the win32 function CreateProcess.573* The method is synchronized due to MS kb315939 problem.574* All native handles should restore the inherit flag at the end of call.575*576* @param cmdstr the Windows command line577* @param envblock NUL-separated, double-NUL-terminated list of578* environment strings in VAR=VALUE form579* @param dir the working directory of the process, or null if580* inheriting the current directory from the parent process581* @param stdHandles array of windows HANDLEs. Indexes 0, 1, and582* 2 correspond to standard input, standard output and583* standard error, respectively. On input, a value of -1584* means to create a pipe to connect child and parent585* processes. On output, a value which is not -1 is the586* parent pipe handle corresponding to the pipe which has587* been created. An element of this array is -1 on input588* if and only if it is <em>not</em> -1 on output.589* @param redirectErrorStream redirectErrorStream attribute590* @return the native subprocess HANDLE returned by CreateProcess591*/592private static synchronized native long create(String cmdstr,593String envblock,594String dir,595long[] stdHandles,596boolean redirectErrorStream)597throws IOException;598599/**600* Opens a file for atomic append. The file is created if it doesn't601* already exist.602*603* @param file the file to open or create604* @return the native HANDLE605*/606private static native long openForAtomicAppend(String path)607throws IOException;608609private static native boolean closeHandle(long handle);610}611612613