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)
--------
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)
- duplicates
-
JDK-7117893 Runtime.exec(String[]) fails to handle the empty string and quotes correctly
- Closed
-
JDK-7177083 ProcessBuilder does not handle command parameters with spaces correctly
- Closed
- relates to
-
JDK-8016046 (process) Strict validation of input should be security manager case only [win]
- Closed
-
JDK-6468220 (process) Runtime.exec(String[]) does not pass command line arguments correctly (win)
- Closed
-
JDK-7028657 (process) Runtime.getRuntime().exec remove spaces from path and arguments
- Closed