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.IOException; 29 import java.io.File; 30 import java.io.InputStream; 31 import java.io.OutputStream; 32 import java.io.FileInputStream; 33 import java.io.FileOutputStream; 34 import java.io.FileDescriptor; 35 import java.io.BufferedInputStream; 36 import java.io.BufferedOutputStream; 37 import java.lang.ProcessBuilder.Redirect; 38 import java.security.AccessController; 39 import java.security.PrivilegedAction; 40 import java.util.concurrent.TimeUnit; 41 42 /* This class is for the exclusive use of ProcessBuilder.start() to 43 * create new processes. 44 * 45 * @author Martin Buchholz 46 * @since 1.5 47 */ 48 49 final class ProcessImpl extends Process { 50 private static final sun.misc.JavaIOFileDescriptorAccess fdAccess 51 = sun.misc.SharedSecrets.getJavaIOFileDescriptorAccess(); 52 53 /** 54 * Open a file for writing. If {@code append} is {@code true} then the file 55 * is opened for atomic append directly and a FileOutputStream constructed 56 * with the resulting handle. This is because a FileOutputStream created 57 * to append to a file does not open the file in a manner that guarantees 58 * that writes by the child process will be atomic. 59 */ 128 f2 = newFileOutputStream(redirects[2].file(), 129 redirects[2].append()); 130 stdHandles[2] = fdAccess.getHandle(f2.getFD()); 131 } 132 } 133 134 return new ProcessImpl(cmdarray, envblock, dir, 135 stdHandles, redirectErrorStream); 136 } finally { 137 // In theory, close() can throw IOException 138 // (although it is rather unlikely to happen here) 139 try { if (f0 != null) f0.close(); } 140 finally { 141 try { if (f1 != null) f1.close(); } 142 finally { if (f2 != null) f2.close(); } 143 } 144 } 145 146 } 147 148 // We guarantee the only command file execution for implicit [cmd.exe] run. 149 // http://technet.microsoft.com/en-us/library/bb490954.aspx 150 private static final char CMD_BAT_ESCAPE[] = {' ', '\t', '<', '>', '&', '|', '^'}; 151 private static final char WIN32_EXECUTABLE_ESCAPE[] = {' ', '\t', '<', '>'}; 152 153 private static boolean isQuoted(boolean noQuotesInside, String arg, 154 String errorMessage) { 155 int lastPos = arg.length() - 1; 156 if (lastPos >=1 && arg.charAt(0) == '"' && arg.charAt(lastPos) == '"') { 157 // The argument has already been quoted. 158 if (noQuotesInside) { 159 if (arg.indexOf('"', 1) != lastPos) { 160 // There is ["] inside. 161 throw new IllegalArgumentException(errorMessage); 162 } 163 } 164 return true; 165 } 166 if (noQuotesInside) { 167 if (arg.indexOf('"') >= 0) { 210 // From the [CreateProcess] function documentation: 211 // 212 // "If the file name does not contain an extension, .exe is appended. 213 // Therefore, if the file name extension is .com, this parameter 214 // must include the .com extension. If the file name ends in 215 // a period (.) with no extension, or if the file name contains a path, 216 // .exe is not appended." 217 // 218 // "If the file name !does not contain a directory path!, 219 // the system searches for the executable file in the following 220 // sequence:..." 221 // 222 // In practice ANY non-existent path is extended by [.exe] extension 223 // in the [CreateProcess] funcion with the only exception: 224 // the path ends by (.) 225 226 return fileToRun.getPath(); 227 } 228 229 230 private long handle = 0; 231 private OutputStream stdin_stream; 232 private InputStream stdout_stream; 233 private InputStream stderr_stream; 234 235 private ProcessImpl(final String cmd[], 236 final String envblock, 237 final String path, 238 final long[] stdHandles, 239 final boolean redirectErrorStream) 240 throws IOException 241 { 242 // The [executablePath] is not quoted for any case. 243 String executablePath = getExecutablePath(cmd[0]); 244 245 // We need to extend the argument verification procedure 246 // to guarantee the only command file execution for implicit [cmd.exe] 247 // run. 248 String upPath = executablePath.toUpperCase(); 249 boolean isCmdFile = (upPath.endsWith(".CMD") || upPath.endsWith(".BAT")); 250 251 StringBuilder cmdbuf = new StringBuilder(80); 252 253 // Quotation protects from interpretation of the [path] argument as 254 // start of longer path with spaces. Quotation has no influence to 255 // [.exe] extension heuristic. 256 cmdbuf.append('"'); 257 cmdbuf.append(executablePath); 258 cmdbuf.append('"'); 259 260 for (int i = 1; i < cmd.length; i++) { 261 cmdbuf.append(' '); 262 String s = cmd[i]; 263 if (needsEscaping(isCmdFile, s)) { 264 cmdbuf.append('"'); 265 cmdbuf.append(s); 266 267 // The code protects the [java.exe] and console command line 268 // parser, that interprets the [\"] combination as an escape 269 // sequence for the ["] char. 270 // http://msdn.microsoft.com/en-us/library/17w5ykft.aspx 271 // 272 // If the argument is an FS path, doubling of the tail [\] 273 // char is not a problem for non-console applications. 274 // 275 // The [\"] sequence is not an escape sequence for the [cmd.exe] 276 // command line parser. The case of the [""] tail escape 277 // sequence could not be realized due to the argument validation 278 // procedure. 279 if (!isCmdFile && s.endsWith("\\")) { 280 cmdbuf.append('\\'); 281 } 282 cmdbuf.append('"'); 283 } else { 284 cmdbuf.append(s); 285 } 286 } 287 String cmdstr = cmdbuf.toString(); 288 289 handle = create(cmdstr, envblock, path, 290 stdHandles, redirectErrorStream); 291 292 java.security.AccessController.doPrivileged( 293 new java.security.PrivilegedAction<Void>() { 294 public Void run() { 295 if (stdHandles[0] == -1L) 296 stdin_stream = ProcessBuilder.NullOutputStream.INSTANCE; 297 else { 298 FileDescriptor stdin_fd = new FileDescriptor(); 299 fdAccess.setHandle(stdin_fd, stdHandles[0]); 300 stdin_stream = new BufferedOutputStream( 301 new FileOutputStream(stdin_fd)); 302 } 303 304 if (stdHandles[1] == -1L) 305 stdout_stream = ProcessBuilder.NullInputStream.INSTANCE; 306 else { 307 FileDescriptor stdout_fd = new FileDescriptor(); | 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.IOException; 29 import java.io.File; 30 import java.io.InputStream; 31 import java.io.OutputStream; 32 import java.io.FileInputStream; 33 import java.io.FileOutputStream; 34 import java.io.FileDescriptor; 35 import java.io.BufferedInputStream; 36 import java.io.BufferedOutputStream; 37 import java.lang.ProcessBuilder.Redirect; 38 import java.security.AccessController; 39 import java.security.PrivilegedAction; 40 import java.util.LinkedList; 41 import java.util.StringTokenizer; 42 import java.util.concurrent.TimeUnit; 43 44 /* This class is for the exclusive use of ProcessBuilder.start() to 45 * create new processes. 46 * 47 * @author Martin Buchholz 48 * @since 1.5 49 */ 50 51 final class ProcessImpl extends Process { 52 private static final sun.misc.JavaIOFileDescriptorAccess fdAccess 53 = sun.misc.SharedSecrets.getJavaIOFileDescriptorAccess(); 54 55 /** 56 * Open a file for writing. If {@code append} is {@code true} then the file 57 * is opened for atomic append directly and a FileOutputStream constructed 58 * with the resulting handle. This is because a FileOutputStream created 59 * to append to a file does not open the file in a manner that guarantees 60 * that writes by the child process will be atomic. 61 */ 130 f2 = newFileOutputStream(redirects[2].file(), 131 redirects[2].append()); 132 stdHandles[2] = fdAccess.getHandle(f2.getFD()); 133 } 134 } 135 136 return new ProcessImpl(cmdarray, envblock, dir, 137 stdHandles, redirectErrorStream); 138 } finally { 139 // In theory, close() can throw IOException 140 // (although it is rather unlikely to happen here) 141 try { if (f0 != null) f0.close(); } 142 finally { 143 try { if (f1 != null) f1.close(); } 144 finally { if (f2 != null) f2.close(); } 145 } 146 } 147 148 } 149 150 private static final String QUOTATION = "\""; 151 private static final String ARGS_SEPARATOR = QUOTATION + " \t\n\r\f"; 152 153 /* Parses the command string parameter into the executable name and 154 * program arguments. 155 * 156 * The command string is broken into tokens. The token separator is a space 157 * or quota character. The space inside quotation is not a token separator. 158 * There are no escape sequences. 159 */ 160 private static String[] getTokensFromCommand(String command) 161 { 162 StringTokenizer st = new StringTokenizer(command, ARGS_SEPARATOR, true); 163 LinkedList<String> args = new LinkedList<>(); 164 StringBuilder sb = null; 165 boolean quotation = false; 166 while(st.hasMoreTokens()) { 167 String token = st.nextToken(quotation 168 ? QUOTATION 169 : ARGS_SEPARATOR); 170 if (QUOTATION.equals(token)) { 171 if (quotation) { 172 args.addLast(sb.append(token).toString()); 173 sb = null; 174 } else { 175 sb = new StringBuilder(); 176 sb.append(token); 177 } 178 quotation = !quotation; 179 } else { 180 if (quotation) { 181 sb.append(token); 182 } else { 183 token = token.trim(); 184 if (!token.isEmpty()) { 185 args.addLast(token); 186 } 187 } 188 } 189 } 190 if (sb != null) { 191 args.addLast(sb.toString()); 192 } 193 return args.toArray(new String[args.size()]); 194 } 195 196 private String createCommandLine(boolean isCmdFile, 197 final String executablePath, 198 final String cmd[]) 199 { 200 StringBuilder cmdbuf = new StringBuilder(80); 201 202 cmdbuf.append(executablePath); 203 204 for (int i = 1; i < cmd.length; i++) { 205 cmdbuf.append(' '); 206 String s = cmd[i]; 207 if (needsEscaping(isCmdFile, s)) { 208 cmdbuf.append('"'); 209 cmdbuf.append(s); 210 211 // The code protects the [java.exe] and console command line 212 // parser, that interprets the [\"] combination as an escape 213 // sequence for the ["] char. 214 // http://msdn.microsoft.com/en-us/library/17w5ykft.aspx 215 // 216 // If the argument is an FS path, doubling of the tail [\] 217 // char is not a problem for non-console applications. 218 // 219 // The [\"] sequence is not an escape sequence for the [cmd.exe] 220 // command line parser. The case of the [""] tail escape 221 // sequence could not be realized due to the argument validation 222 // procedure. 223 if (!isCmdFile && s.endsWith("\\")) { 224 cmdbuf.append('\\'); 225 } 226 cmdbuf.append('"'); 227 } else { 228 cmdbuf.append(s); 229 } 230 } 231 return cmdbuf.toString(); 232 } 233 234 // We guarantee the only command file execution for implicit [cmd.exe] run. 235 // http://technet.microsoft.com/en-us/library/bb490954.aspx 236 private static final char CMD_BAT_ESCAPE[] = {' ', '\t', '<', '>', '&', '|', '^'}; 237 private static final char WIN32_EXECUTABLE_ESCAPE[] = {' ', '\t', '<', '>'}; 238 239 private static boolean isQuoted(boolean noQuotesInside, String arg, 240 String errorMessage) { 241 int lastPos = arg.length() - 1; 242 if (lastPos >=1 && arg.charAt(0) == '"' && arg.charAt(lastPos) == '"') { 243 // The argument has already been quoted. 244 if (noQuotesInside) { 245 if (arg.indexOf('"', 1) != lastPos) { 246 // There is ["] inside. 247 throw new IllegalArgumentException(errorMessage); 248 } 249 } 250 return true; 251 } 252 if (noQuotesInside) { 253 if (arg.indexOf('"') >= 0) { 296 // From the [CreateProcess] function documentation: 297 // 298 // "If the file name does not contain an extension, .exe is appended. 299 // Therefore, if the file name extension is .com, this parameter 300 // must include the .com extension. If the file name ends in 301 // a period (.) with no extension, or if the file name contains a path, 302 // .exe is not appended." 303 // 304 // "If the file name !does not contain a directory path!, 305 // the system searches for the executable file in the following 306 // sequence:..." 307 // 308 // In practice ANY non-existent path is extended by [.exe] extension 309 // in the [CreateProcess] funcion with the only exception: 310 // the path ends by (.) 311 312 return fileToRun.getPath(); 313 } 314 315 316 private boolean isShellFile(String executablePath) { 317 String upPath = executablePath.toUpperCase(); 318 return (upPath.endsWith(".CMD") || upPath.endsWith(".BAT")); 319 } 320 321 private String quoteString(String arg) { 322 StringBuilder argbuf = new StringBuilder(arg.length() + 2); 323 return argbuf.append('"').append(arg).append('"').toString(); 324 } 325 326 327 private long handle = 0; 328 private OutputStream stdin_stream; 329 private InputStream stdout_stream; 330 private InputStream stderr_stream; 331 332 private ProcessImpl(String cmd[], 333 final String envblock, 334 final String path, 335 final long[] stdHandles, 336 final boolean redirectErrorStream) 337 throws IOException 338 { 339 String cmdstr; 340 SecurityManager security = System.getSecurityManager(); 341 if (security == null 342 && System.getProperty("jdk.lang.Process.allowAmbigousCommands") != null) { 343 // Legacy mode. 344 345 // Normalize path if possible. 346 String executablePath = new File(cmd[0]).getPath(); 347 348 // No worry about internal and unpaired ["] . 349 if (needsEscaping(false, executablePath) ) { 350 executablePath = quoteString(executablePath); 351 } 352 cmdstr = createCommandLine( 353 false, //legacy mode doesn't worry about extended verification 354 executablePath, 355 cmd); 356 } else { 357 String executablePath; 358 try { 359 executablePath = getExecutablePath(cmd[0]); 360 } catch (IllegalArgumentException e) { 361 // Workaround for the calls like 362 // Runtime.getRuntime().exec("\"C:\\Program Files\\foo\" bar") 363 364 // No chance to avoid CMD/BAT injection, except to do the work 365 // right from the beginning. Otherwise we have too many corner 366 // cases from 367 // Runtime.getRuntime().exec(String[] cmd [, ...]) 368 // calls with internal ["] and escape sequences. 369 370 // Restore original command line. 371 StringBuilder join = new StringBuilder(); 372 for (String s : cmd) { 373 // terminal space in command line is ok 374 join.append(s).append(' '); 375 } 376 377 // Parse the command line again. 378 cmd = getTokensFromCommand(join.toString()); 379 executablePath = getExecutablePath(cmd[0]); 380 } 381 382 // Quotation protects from interpretation of the [path] argument as 383 // start of longer path with spaces. Quotation has no influence to 384 // [.exe] extension heuristic. 385 cmdstr = createCommandLine( 386 // We need the extended verification procedure for CMD files. 387 isShellFile(executablePath), 388 quoteString(executablePath), 389 cmd); 390 } 391 392 handle = create(cmdstr, envblock, path, 393 stdHandles, redirectErrorStream); 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(); |