src/windows/classes/java/lang/ProcessImpl.java

Print this page

        

@@ -35,10 +35,12 @@
 import java.io.BufferedInputStream;
 import java.io.BufferedOutputStream;
 import java.lang.ProcessBuilder.Redirect;
 import java.security.AccessController;
 import java.security.PrivilegedAction;
+import java.util.LinkedList;
+import java.util.StringTokenizer;
 import java.util.concurrent.TimeUnit;
 
 /* This class is for the exclusive use of ProcessBuilder.start() to
  * create new processes.
  *

@@ -143,10 +145,94 @@
             }
         }
 
     }
 
+    private static final String QUOTATION = "\"";
+    private static final String ARGS_SEPARATOR = QUOTATION + " \t\n\r\f";
+    
+    /* Parses the command string parameter into the executable name and 
+     * program arguments.
+     * 
+     * The command string is broken into tokens. The token separator is a space 
+     * or quota character. The space inside quotation is not a token separator. 
+     * There are no escape sequences.
+     */
+    private static String[] getTokensFromCommand(String command)
+    {            
+        StringTokenizer st = new StringTokenizer(command, ARGS_SEPARATOR, true);
+        LinkedList<String> args = new LinkedList<>();
+        StringBuilder sb = null;
+        boolean quotation = false;
+        while(st.hasMoreTokens()) {
+            String token = st.nextToken(quotation
+                ? QUOTATION
+                : ARGS_SEPARATOR);
+            if (QUOTATION.equals(token)) {
+                if (quotation) {
+                    args.addLast(sb.append(token).toString());
+                    sb = null;
+                } else {
+                    sb = new StringBuilder();
+                    sb.append(token);
+                }
+                quotation = !quotation; 
+            } else {
+                if (quotation) {
+                    sb.append(token);
+                } else {
+                    token = token.trim();
+                    if (!token.isEmpty()) {
+                        args.addLast(token);
+                    }
+                }
+            }
+        }
+        if (sb != null) {
+            args.addLast(sb.toString());
+        }
+        return args.toArray(new String[args.size()]);
+    }
+    
+    private String createCommandLine(boolean isCmdFile, 
+                                     final String executablePath, 
+                                     final String cmd[])
+    {
+        StringBuilder cmdbuf = new StringBuilder(80);
+
+        cmdbuf.append(executablePath);
+
+        for (int i = 1; i < cmd.length; i++) {
+            cmdbuf.append(' ');
+            String s = cmd[i];
+            if (needsEscaping(isCmdFile, s)) {
+                cmdbuf.append('"');
+                cmdbuf.append(s);
+
+                // The code protects the [java.exe] and console command line
+                // parser, that interprets the [\"] combination as an escape
+                // sequence for the ["] char.
+                //     http://msdn.microsoft.com/en-us/library/17w5ykft.aspx
+                //
+                // If the argument is an FS path, doubling of the tail [\]
+                // char is not a problem for non-console applications.
+                //
+                // The [\"] sequence is not an escape sequence for the [cmd.exe]
+                // command line parser. The case of the [""] tail escape
+                // sequence could not be realized due to the argument validation
+                // procedure.
+                if (!isCmdFile && s.endsWith("\\")) {
+                    cmdbuf.append('\\');
+                }
+                cmdbuf.append('"');
+            } else {
+                cmdbuf.append(s);
+            }
+        }
+        return cmdbuf.toString();
+    }    
+    
     // We guarantee the only command file execution for implicit [cmd.exe] run.
     //    http://technet.microsoft.com/en-us/library/bb490954.aspx
     private static final char CMD_BAT_ESCAPE[] = {' ', '\t', '<', '>', '&', '|', '^'};
     private static final char WIN32_EXECUTABLE_ESCAPE[] = {' ', '\t', '<', '>'};
 

@@ -225,68 +311,85 @@
 
         return fileToRun.getPath();
     }
 
 
+    private boolean isShellFile(String executablePath) {
+        String upPath = executablePath.toUpperCase();
+        return (upPath.endsWith(".CMD") || upPath.endsWith(".BAT"));
+    }
+    
+    private String quoteString(String arg) {
+        StringBuilder argbuf = new StringBuilder(arg.length() + 2);
+        return argbuf.append('"').append(arg).append('"').toString();
+    }
+    
+    
     private long handle = 0;
     private OutputStream stdin_stream;
     private InputStream stdout_stream;
     private InputStream stderr_stream;
 
-    private ProcessImpl(final String cmd[],
+    private ProcessImpl(String cmd[],
                         final String envblock,
                         final String path,
                         final long[] stdHandles,
                         final boolean redirectErrorStream)
         throws IOException
     {
-        // The [executablePath] is not quoted for any case.
-        String executablePath = getExecutablePath(cmd[0]);
-
-        // We need to extend the argument verification procedure
-        // to guarantee the only command file execution for implicit [cmd.exe]
-        // run.
-        String upPath = executablePath.toUpperCase();
-        boolean isCmdFile = (upPath.endsWith(".CMD") || upPath.endsWith(".BAT"));
-
-        StringBuilder cmdbuf = new StringBuilder(80);
+        String cmdstr;
+        SecurityManager security = System.getSecurityManager();        
+        if (security == null 
+         && System.getProperty("jdk.lang.Process.allowAmbigousCommands") != null) {
+            // Legacy mode.
+            
+            // Normalize path if possible.
+            String executablePath = new File(cmd[0]).getPath();
+            
+            // No worry about internal and unpaired ["] .
+            if (needsEscaping(false, executablePath) ) {
+                executablePath = quoteString(executablePath);
+            }
+            cmdstr = createCommandLine(
+                false, //legacy mode doesn't worry about extended verification
+                executablePath,
+                cmd);
+        } else {            
+            String executablePath;
+            try {
+                executablePath = getExecutablePath(cmd[0]);
+            } catch (IllegalArgumentException e) {
+                // Workaround for the calls like 
+                // Runtime.getRuntime().exec("\"C:\\Program Files\\foo\" bar")
+                
+                // No chance to avoid CMD/BAT injection, except to do the work 
+                // right from the beginning. Otherwise we have too many corner 
+                // cases from
+                //    Runtime.getRuntime().exec(String[] cmd [, ...])
+                // calls with internal ["] and escape sequences. 
+                
+                // Restore original command line.
+                StringBuilder join = new StringBuilder();
+                for (String s : cmd) {
+                    // terminal space in command line is ok
+                    join.append(s).append(' ');
+                } 
+                
+                // Parse the command line again.
+                cmd = getTokensFromCommand(join.toString());
+                executablePath = getExecutablePath(cmd[0]);
+            }    
 
         // Quotation protects from interpretation of the [path] argument as
         // start of longer path with spaces. Quotation has no influence to
         // [.exe] extension heuristic.
-        cmdbuf.append('"');
-        cmdbuf.append(executablePath);
-        cmdbuf.append('"');
-
-        for (int i = 1; i < cmd.length; i++) {
-            cmdbuf.append(' ');
-            String s = cmd[i];
-            if (needsEscaping(isCmdFile, s)) {
-                cmdbuf.append('"');
-                cmdbuf.append(s);
-
-                // The code protects the [java.exe] and console command line
-                // parser, that interprets the [\"] combination as an escape
-                // sequence for the ["] char.
-                //     http://msdn.microsoft.com/en-us/library/17w5ykft.aspx
-                //
-                // If the argument is an FS path, doubling of the tail [\]
-                // char is not a problem for non-console applications.
-                //
-                // The [\"] sequence is not an escape sequence for the [cmd.exe]
-                // command line parser. The case of the [""] tail escape
-                // sequence could not be realized due to the argument validation
-                // procedure.
-                if (!isCmdFile && s.endsWith("\\")) {
-                    cmdbuf.append('\\');
-                }
-                cmdbuf.append('"');
-            } else {
-                cmdbuf.append(s);
-            }
+            cmdstr = createCommandLine(
+                    // We need the extended verification procedure for CMD files.
+                    isShellFile(executablePath), 
+                    quoteString(executablePath),
+                    cmd);
         }
-        String cmdstr = cmdbuf.toString();
 
         handle = create(cmdstr, envblock, path,
                         stdHandles, redirectErrorStream);
 
         java.security.AccessController.doPrivileged(