JDK-7051946 : Runtime.exec(String command) / ProcessBuilder command parsing issues
  • Type: Bug
  • Component: core-libs
  • Sub-Component: java.lang
  • Affected Version: 6,7
  • Priority: P4
  • Status: Resolved
  • Resolution: Fixed
  • OS: generic,windows_xp,windows_7
  • CPU: generic,x86
  • Submitted: 2011-06-06
  • Updated: 2014-12-04
  • Resolved: 2013-07-10
The Version table provides details related to the release that this issue/RFE will be addressed.

Unresolved : Release in which this issue/RFE will be addressed.
Resolved: Release in which this issue/RFE has been resolved.
Fixed : Release in which this issue/RFE has been fixed. The release containing this fix may be available for download as an Early Access Release or a General Availability Release.

To download the current JDK release, click here.
JDK 7
7u40Fixed
Related Reports
Duplicate :  
Duplicate :  
Relates :  
Relates :  
Relates :  
Description
SYNOPSIS
--------
Runtime.exec(String command) / ProcessBuilder command parsing issues

OPERATING SYSTEM
----------------
All (illustrated below on Windows)

FULL JDK VERSION
----------------
All Java 6 updates since GA

PROBLEM DESCRIPTION from LICENSEE
---------------------------------
There are two problems, which share the same root cause. The problems occur when passing a command to Runtime.exec(String command) that contains spaces in the path. For example:

   C:\Program Files\xyz.exe

The String is split up into an array using a StringTokenizer, using space as the delimiter. This array is then (effectively) passed into Runtime.exec(String[] cmdarray). The end result is that ProcessBuilder.start() incorrectly assumes that the first element in the array is the program name. This has two consequences:

Problem 1
---------
If the target program does not exist, the resulting IOException message is confusing and incorrect. For example, with the command String above, we see the following Exception when xyz.exe does not exist:

Exception in thread "main" java.io.IOException: Cannot run program "C:\Program": CreateProcess error=2, The system cannot find the file specified.
       at java.lang.ProcessBuilder.start(ProcessBuilder.java:473)
       at java.lang.Runtime.exec(Runtime.java:605)
       at java.lang.Runtime.exec(Runtime.java:443)
       at java.lang.Runtime.exec(Runtime.java:340)
       at PMR41503.main(PMR41503.java:6)
Caused by: java.io.IOException: CreateProcess error=2, The system cannot find the file specified.
       at java.lang.ProcessImpl.create(Native Method)
       at java.lang.ProcessImpl.<init>(ProcessImpl.java:93)
       at java.lang.ProcessImpl.start(ProcessImpl.java:42)
       at java.lang.ProcessBuilder.start(ProcessBuilder.java:466)
       ... 4 more

Note that the program name is incorrect, so it is impossible to identify the failing program from this message.

In Java 5.0 on Windows, the Exception message was complete (because it was passed through directly from the OS):

Exception in thread "main" java.io.IOException: Cannot run program "C:\Program Files\xyz.exe": CreateProcess error=2, The system cannot find the file specified.
       at java.lang.ProcessBuilder.start(ProcessBuilder.java:484)
       at java.lang.Runtime.exec(Runtime.java:605)
       at java.lang.Runtime.exec(Runtime.java:478)
       at PMR41503.main(PMR41503.java:8)
Caused by: java.io.IOException: CreateProcess error=2, The system cannot find the file specified.
       at java.lang.ProcessImpl.create(Native Method)
       at java.lang.ProcessImpl.<init>(ProcessImpl.java:109)
       at java.lang.ProcessImpl.start(ProcessImpl.java:56)
       at java.lang.ProcessBuilder.start(ProcessBuilder.java:464)
       ... 3 more

Problem 2
---------
Perhaps more importantly, the security check in ProcessBuilder.start() is also executed against the incorrect program name:

   public Process start() throws IOException {
       ...
       String prog = cmdarray[0];

       SecurityManager security = System.getSecurityManager();
       if (security != null)
           security.checkExec(prog);
       ...
   }

REPRODUCTION INSTRUCTIONS
-------------------------
Compile and run the attached testcase (after ensuring that "C:\Program Files\xyz.exe" does not exist, of course!)

TESTCASE SOURCE
---------------
public class Test {
   public static void main(String[] args) throws Exception {
       Runtime.getRuntime().exec("C:\\Program Files\\xyz.exe");
   }
}

SUGGESTED FIX from LICENSEE
---------------------------
Both issues can be fixed by reusing logic from ProcessImpl.java. Here are diffs relative to 6u23:

*** ProcessBuilder.java    Fri Jun  3 20:01:57 2011
--- ProcessBuilder_with_changes.java Fri Jun  3 20:03:53 2011
***************
*** 441,447 ****
           if (arg == null)
               throw new NullPointerException();
       // Throws IndexOutOfBoundsException if command is empty
!       String prog = cmdarray[0];

       SecurityManager security = System.getSecurityManager();
       if (security != null)
--- 441,470 ----
           if (arg == null)
               throw new NullPointerException();
       // Throws IndexOutOfBoundsException if command is empty
!       //Convert the cmdarray back to original command string
!       StringBuilder cmdbuf = new StringBuilder(80);  //Assuming the command line not to exceed 80 chars
!         for (int i = 0; i < cmdarray.length; i++) {
!             if (i > 0) {
!                 cmdbuf.append(' ');
!             }
!             String s = cmdarray[i];
!             if (s.indexOf(' ') >= 0 || s.indexOf('\t') >= 0) {
!                 if (s.charAt(0) != '"') {
!                     cmdbuf.append('"');
!                     cmdbuf.append(s);
!                     cmdbuf.append('"');
!                 } else if (s.endsWith("\"")) {
!                     /* The argument has already been quoted. */
!                     cmdbuf.append(s);
!                 } else {
!                     /* Unmatched quote for the argument. */
!                     throw new IllegalArgumentException();
!                 }
!             } else {
!                 cmdbuf.append(s);
!             }
!         }
!         String  prog = cmdbuf.toString();

       SecurityManager security = System.getSecurityManager();
       if (security != null)

Comments
The final version of fix with right spelling for Java property "jdk.lang.Process.allowAmbiguousCommands" is available since 7u40 release. Please, read related bug threads for details.
10-07-2013

EVALUATION This is likely a dup of 6468220.
06-06-2011