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