1 /* 2 * Copyright (c) 1995, 2015, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package java.lang; 27 28 import java.io.BufferedInputStream; 29 import java.io.BufferedOutputStream; 30 import java.io.File; 31 import java.io.FileDescriptor; 32 import java.io.FileInputStream; 33 import java.io.FileOutputStream; 34 import java.io.IOException; 35 import java.io.InputStream; 36 import java.io.OutputStream; 37 import java.lang.ProcessBuilder.Redirect; 38 import java.security.AccessController; 39 import java.security.PrivilegedAction; 40 import java.util.ArrayList; 41 import java.util.concurrent.CompletableFuture; 42 import java.util.concurrent.TimeUnit; 43 import java.util.regex.Matcher; 44 import java.util.regex.Pattern; 45 46 /* This class is for the exclusive use of ProcessBuilder.start() to 47 * create new processes. 48 * 49 * @author Martin Buchholz 50 * @since 1.5 51 */ 52 53 final class ProcessImpl extends Process { 54 private static final sun.misc.JavaIOFileDescriptorAccess fdAccess 55 = sun.misc.SharedSecrets.getJavaIOFileDescriptorAccess(); 56 57 // Windows platforms support a forcible kill signal. 58 static final boolean SUPPORTS_NORMAL_TERMINATION = false; 59 60 /** 61 * Open a file for writing. If {@code append} is {@code true} then the file 62 * is opened for atomic append directly and a FileOutputStream constructed 63 * with the resulting handle. This is because a FileOutputStream created 64 * to append to a file does not open the file in a manner that guarantees 65 * that writes by the child process will be atomic. 66 */ 67 private static FileOutputStream newFileOutputStream(File f, boolean append) 68 throws IOException 69 { 70 if (append) { 71 String path = f.getPath(); 72 SecurityManager sm = System.getSecurityManager(); 73 if (sm != null) 74 sm.checkWrite(path); 75 long handle = openForAtomicAppend(path); 76 final FileDescriptor fd = new FileDescriptor(); 77 fdAccess.setHandle(fd, handle); 78 return AccessController.doPrivileged( 79 new PrivilegedAction<FileOutputStream>() { 80 public FileOutputStream run() { 81 return new FileOutputStream(fd); 82 } 83 } 84 ); 85 } else { 86 return new FileOutputStream(f); 87 } 88 } 89 90 // System-dependent portion of ProcessBuilder.start() 91 static Process start(String cmdarray[], 92 java.util.Map<String,String> environment, 93 String dir, 94 ProcessBuilder.Redirect[] redirects, 95 boolean redirectErrorStream) 96 throws IOException 97 { 98 String envblock = ProcessEnvironment.toEnvironmentBlock(environment); 99 100 FileInputStream f0 = null; 101 FileOutputStream f1 = null; 102 FileOutputStream f2 = null; 103 104 try { 105 long[] stdHandles; 106 if (redirects == null) { 107 stdHandles = new long[] { -1L, -1L, -1L }; 108 } else { 109 stdHandles = new long[3]; 110 111 if (redirects[0] == Redirect.PIPE) 112 stdHandles[0] = -1L; 113 else if (redirects[0] == Redirect.INHERIT) 114 stdHandles[0] = fdAccess.getHandle(FileDescriptor.in); 115 else { 116 f0 = new FileInputStream(redirects[0].file()); 117 stdHandles[0] = fdAccess.getHandle(f0.getFD()); 118 } 119 120 if (redirects[1] == Redirect.PIPE) 121 stdHandles[1] = -1L; 122 else if (redirects[1] == Redirect.INHERIT) 123 stdHandles[1] = fdAccess.getHandle(FileDescriptor.out); 124 else { 125 f1 = newFileOutputStream(redirects[1].file(), 126 redirects[1].append()); 127 stdHandles[1] = fdAccess.getHandle(f1.getFD()); 128 } 129 130 if (redirects[2] == Redirect.PIPE) 131 stdHandles[2] = -1L; 132 else if (redirects[2] == Redirect.INHERIT) 133 stdHandles[2] = fdAccess.getHandle(FileDescriptor.err); 134 else { 135 f2 = newFileOutputStream(redirects[2].file(), 136 redirects[2].append()); 137 stdHandles[2] = fdAccess.getHandle(f2.getFD()); 138 } 139 } 140 141 return new ProcessImpl(cmdarray, envblock, dir, 142 stdHandles, redirectErrorStream); 143 } finally { 144 // In theory, close() can throw IOException 145 // (although it is rather unlikely to happen here) 146 try { if (f0 != null) f0.close(); } 147 finally { 148 try { if (f1 != null) f1.close(); } 149 finally { if (f2 != null) f2.close(); } 150 } 151 } 152 153 } 154 155 private static class LazyPattern { 156 // Escape-support version: 157 // "(\")((?:\\\\\\1|.)+?)\\1|([^\\s\"]+)"; 158 private static final Pattern PATTERN = 159 Pattern.compile("[^\\s\"]+|\"[^\"]*\""); 160 }; 161 162 /* Parses the command string parameter into the executable name and 163 * program arguments. 164 * 165 * The command string is broken into tokens. The token separator is a space 166 * or quota character. The space inside quotation is not a token separator. 167 * There are no escape sequences. 168 */ 169 private static String[] getTokensFromCommand(String command) { 170 ArrayList<String> matchList = new ArrayList<>(8); 171 Matcher regexMatcher = LazyPattern.PATTERN.matcher(command); 172 while (regexMatcher.find()) 173 matchList.add(regexMatcher.group()); 174 return matchList.toArray(new String[matchList.size()]); 175 } 176 177 private static final int VERIFICATION_CMD_BAT = 0; 178 private static final int VERIFICATION_WIN32 = 1; 179 private static final int VERIFICATION_LEGACY = 2; 180 private 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.aspx 183 {' ', '\t', '<', '>', '&', '|', '^'}, 184 185 {' ', '\t', '<', '>'}, 186 {' ', '\t'} 187 }; 188 189 private static String createCommandLine(int verificationType, 190 final String executablePath, 191 final String cmd[]) 192 { 193 StringBuilder cmdbuf = new StringBuilder(80); 194 195 cmdbuf.append(executablePath); 196 197 for (int i = 1; i < cmd.length; ++i) { 198 cmdbuf.append(' '); 199 String s = cmd[i]; 200 if (needsEscaping(verificationType, s)) { 201 cmdbuf.append('"').append(s); 202 203 // The code protects the [java.exe] and console command line 204 // parser, that interprets the [\"] combination as an escape 205 // sequence for the ["] char. 206 // http://msdn.microsoft.com/en-us/library/17w5ykft.aspx 207 // 208 // If the argument is an FS path, doubling of the tail [\] 209 // char is not a problem for non-console applications. 210 // 211 // The [\"] sequence is not an escape sequence for the [cmd.exe] 212 // command line parser. The case of the [""] tail escape 213 // sequence could not be realized due to the argument validation 214 // procedure. 215 if ((verificationType != VERIFICATION_CMD_BAT) && s.endsWith("\\")) { 216 cmdbuf.append('\\'); 217 } 218 cmdbuf.append('"'); 219 } else { 220 cmdbuf.append(s); 221 } 222 } 223 return cmdbuf.toString(); 224 } 225 226 private static boolean isQuoted(boolean noQuotesInside, String arg, 227 String errorMessage) { 228 int lastPos = arg.length() - 1; 229 if (lastPos >=1 && arg.charAt(0) == '"' && arg.charAt(lastPos) == '"') { 230 // The argument has already been quoted. 231 if (noQuotesInside) { 232 if (arg.indexOf('"', 1) != lastPos) { 233 // There is ["] inside. 234 throw new IllegalArgumentException(errorMessage); 235 } 236 } 237 return true; 238 } 239 if (noQuotesInside) { 240 if (arg.indexOf('"') >= 0) { 241 // There is ["] inside. 242 throw new IllegalArgumentException(errorMessage); 243 } 244 } 245 return false; 246 } 247 248 private static boolean needsEscaping(int verificationType, String arg) { 249 // Switch off MS heuristic for internal ["]. 250 // Please, use the explicit [cmd.exe] call 251 // if you need the internal ["]. 252 // Example: "cmd.exe", "/C", "Extended_MS_Syntax" 253 254 // For [.exe] or [.com] file the unpaired/internal ["] 255 // in the argument is not a problem. 256 boolean argIsQuoted = isQuoted( 257 (verificationType == VERIFICATION_CMD_BAT), 258 arg, "Argument has embedded quote, use the explicit CMD.EXE call."); 259 260 if (!argIsQuoted) { 261 char testEscape[] = ESCAPE_VERIFICATION[verificationType]; 262 for (int i = 0; i < testEscape.length; ++i) { 263 if (arg.indexOf(testEscape[i]) >= 0) { 264 return true; 265 } 266 } 267 } 268 return false; 269 } 270 271 private static String getExecutablePath(String path) 272 throws IOException 273 { 274 boolean pathIsQuoted = isQuoted(true, path, 275 "Executable name has embedded quote, split the arguments"); 276 277 // Win32 CreateProcess requires path to be normalized 278 File fileToRun = new File(pathIsQuoted 279 ? path.substring(1, path.length() - 1) 280 : path); 281 282 // From the [CreateProcess] function documentation: 283 // 284 // "If the file name does not contain an extension, .exe is appended. 285 // Therefore, if the file name extension is .com, this parameter 286 // must include the .com extension. If the file name ends in 287 // a period (.) with no extension, or if the file name contains a path, 288 // .exe is not appended." 289 // 290 // "If the file name !does not contain a directory path!, 291 // the system searches for the executable file in the following 292 // sequence:..." 293 // 294 // In practice ANY non-existent path is extended by [.exe] extension 295 // in the [CreateProcess] funcion with the only exception: 296 // the path ends by (.) 297 298 return fileToRun.getPath(); 299 } 300 301 302 private boolean isShellFile(String executablePath) { 303 String upPath = executablePath.toUpperCase(); 304 return (upPath.endsWith(".CMD") || upPath.endsWith(".BAT")); 305 } 306 307 private String quoteString(String arg) { 308 StringBuilder argbuf = new StringBuilder(arg.length() + 2); 309 return argbuf.append('"').append(arg).append('"').toString(); 310 } 311 312 313 private final long handle; 314 private final ProcessHandle processHandle; 315 private OutputStream stdin_stream; 316 private InputStream stdout_stream; 317 private InputStream stderr_stream; 318 319 private ProcessImpl(String cmd[], 320 final String envblock, 321 final String path, 322 final long[] stdHandles, 323 final boolean redirectErrorStream) 324 throws IOException 325 { 326 String cmdstr; 327 SecurityManager security = System.getSecurityManager(); 328 boolean allowAmbiguousCommands = false; 329 if (security == null) { 330 allowAmbiguousCommands = true; 331 String value = System.getProperty("jdk.lang.Process.allowAmbiguousCommands"); 332 if (value != null) 333 allowAmbiguousCommands = !"false".equalsIgnoreCase(value); 334 } 335 if (allowAmbiguousCommands) { 336 // Legacy mode. 337 338 // Normalize path if possible. 339 String executablePath = new File(cmd[0]).getPath(); 340 341 // No worry about internal, unpaired ["], and redirection/piping. 342 if (needsEscaping(VERIFICATION_LEGACY, executablePath) ) 343 executablePath = quoteString(executablePath); 344 345 cmdstr = createCommandLine( 346 //legacy mode doesn't worry about extended verification 347 VERIFICATION_LEGACY, 348 executablePath, 349 cmd); 350 } else { 351 String executablePath; 352 try { 353 executablePath = getExecutablePath(cmd[0]); 354 } catch (IllegalArgumentException e) { 355 // Workaround for the calls like 356 // Runtime.getRuntime().exec("\"C:\\Program Files\\foo\" bar") 357 358 // No chance to avoid CMD/BAT injection, except to do the work 359 // right from the beginning. Otherwise we have too many corner 360 // cases from 361 // Runtime.getRuntime().exec(String[] cmd [, ...]) 362 // calls with internal ["] and escape sequences. 363 364 // Restore original command line. 365 StringBuilder join = new StringBuilder(); 366 // terminal space in command line is ok 367 for (String s : cmd) 368 join.append(s).append(' '); 369 370 // Parse the command line again. 371 cmd = getTokensFromCommand(join.toString()); 372 executablePath = getExecutablePath(cmd[0]); 373 374 // Check new executable name once more 375 if (security != null) 376 security.checkExec(executablePath); 377 } 378 379 // Quotation protects from interpretation of the [path] argument as 380 // start of longer path with spaces. Quotation has no influence to 381 // [.exe] extension heuristic. 382 cmdstr = createCommandLine( 383 // We need the extended verification procedure for CMD files. 384 isShellFile(executablePath) 385 ? VERIFICATION_CMD_BAT 386 : VERIFICATION_WIN32, 387 quoteString(executablePath), 388 cmd); 389 } 390 391 handle = create(cmdstr, envblock, path, 392 stdHandles, redirectErrorStream); 393 processHandle = ProcessHandleImpl.getInternal(getProcessId0(handle)); 394 395 java.security.AccessController.doPrivileged( 396 new java.security.PrivilegedAction<Void>() { 397 public Void run() { 398 if (stdHandles[0] == -1L) 399 stdin_stream = ProcessBuilder.NullOutputStream.INSTANCE; 400 else { 401 FileDescriptor stdin_fd = new FileDescriptor(); 402 fdAccess.setHandle(stdin_fd, stdHandles[0]); 403 stdin_stream = new BufferedOutputStream( 404 new FileOutputStream(stdin_fd)); 405 } 406 407 if (stdHandles[1] == -1L) 408 stdout_stream = ProcessBuilder.NullInputStream.INSTANCE; 409 else { 410 FileDescriptor stdout_fd = new FileDescriptor(); 411 fdAccess.setHandle(stdout_fd, stdHandles[1]); 412 stdout_stream = new BufferedInputStream( 413 new FileInputStream(stdout_fd)); 414 } 415 416 if (stdHandles[2] == -1L) 417 stderr_stream = ProcessBuilder.NullInputStream.INSTANCE; 418 else { 419 FileDescriptor stderr_fd = new FileDescriptor(); 420 fdAccess.setHandle(stderr_fd, stdHandles[2]); 421 stderr_stream = new FileInputStream(stderr_fd); 422 } 423 424 return null; }}); 425 } 426 427 public OutputStream getOutputStream() { 428 return stdin_stream; 429 } 430 431 public InputStream getInputStream() { 432 return stdout_stream; 433 } 434 435 public InputStream getErrorStream() { 436 return stderr_stream; 437 } 438 439 protected void finalize() { 440 closeHandle(handle); 441 } 442 443 private static final int STILL_ACTIVE = getStillActive(); 444 private static native int getStillActive(); 445 446 public int exitValue() { 447 int exitCode = getExitCodeProcess(handle); 448 if (exitCode == STILL_ACTIVE) 449 throw new IllegalThreadStateException("process has not exited"); 450 return exitCode; 451 } 452 private static native int getExitCodeProcess(long handle); 453 454 public int waitFor() throws InterruptedException { 455 waitForInterruptibly(handle); 456 if (Thread.interrupted()) 457 throw new InterruptedException(); 458 return exitValue(); 459 } 460 461 private static native void waitForInterruptibly(long handle); 462 463 @Override 464 public boolean waitFor(long timeout, TimeUnit unit) 465 throws InterruptedException 466 { 467 long remainingNanos = unit.toNanos(timeout); // throw NPE before other conditions 468 if (getExitCodeProcess(handle) != STILL_ACTIVE) return true; 469 if (timeout <= 0) return false; 470 471 long deadline = System.nanoTime() + remainingNanos ; 472 do { 473 // Round up to next millisecond 474 long msTimeout = TimeUnit.NANOSECONDS.toMillis(remainingNanos + 999_999L); 475 waitForTimeoutInterruptibly(handle, msTimeout); 476 if (Thread.interrupted()) 477 throw new InterruptedException(); 478 if (getExitCodeProcess(handle) != STILL_ACTIVE) { 479 return true; 480 } 481 remainingNanos = deadline - System.nanoTime(); 482 } while (remainingNanos > 0); 483 484 return (getExitCodeProcess(handle) != STILL_ACTIVE); 485 } 486 487 private static native void waitForTimeoutInterruptibly( 488 long handle, long timeout); 489 490 @Override 491 public void destroy() { 492 terminateProcess(handle); 493 } 494 495 @Override 496 public CompletableFuture<Process> onExit() { 497 return ProcessHandleImpl.completion(getPid(), false) 498 .handleAsync((exitStatus, unusedThrowable) -> this); 499 } 500 501 @Override 502 public ProcessHandle toHandle() { 503 SecurityManager sm = System.getSecurityManager(); 504 if (sm != null) { 505 sm.checkPermission(new RuntimePermission("manageProcess")); 506 } 507 return processHandle; 508 } 509 510 @Override 511 public boolean supportsNormalTermination() { 512 return ProcessImpl.SUPPORTS_NORMAL_TERMINATION; 513 } 514 515 @Override 516 public Process destroyForcibly() { 517 destroy(); 518 return this; 519 } 520 521 private static native void terminateProcess(long handle); 522 523 @Override 524 public long getPid() { 525 return processHandle.getPid(); 526 } 527 528 private static native int getProcessId0(long handle); 529 530 @Override 531 public boolean isAlive() { 532 return isProcessAlive(handle); 533 } 534 535 private static native boolean isProcessAlive(long handle); 536 537 /** 538 * Create a process using the win32 function CreateProcess. 539 * The method is synchronized due to MS kb315939 problem. 540 * All native handles should restore the inherit flag at the end of call. 541 * 542 * @param cmdstr the Windows command line 543 * @param envblock NUL-separated, double-NUL-terminated list of 544 * environment strings in VAR=VALUE form 545 * @param dir the working directory of the process, or null if 546 * inheriting the current directory from the parent process 547 * @param stdHandles array of windows HANDLEs. Indexes 0, 1, and 548 * 2 correspond to standard input, standard output and 549 * standard error, respectively. On input, a value of -1 550 * means to create a pipe to connect child and parent 551 * processes. On output, a value which is not -1 is the 552 * parent pipe handle corresponding to the pipe which has 553 * been created. An element of this array is -1 on input 554 * if and only if it is <em>not</em> -1 on output. 555 * @param redirectErrorStream redirectErrorStream attribute 556 * @return the native subprocess HANDLE returned by CreateProcess 557 */ 558 private static synchronized native long create(String cmdstr, 559 String envblock, 560 String dir, 561 long[] stdHandles, 562 boolean redirectErrorStream) 563 throws IOException; 564 565 /** 566 * Opens a file for atomic append. The file is created if it doesn't 567 * already exist. 568 * 569 * @param path the file to open or create 570 * @return the native HANDLE 571 */ 572 private static native long openForAtomicAppend(String path) 573 throws IOException; 574 575 private static native boolean closeHandle(long handle); 576 }