The exec()
method of the java.lang.Runtime
class and the related ProcessBuilder.start()
method can be used to invoke external programs. These programs may require input to be sent to their input stream, and they may also produce output on their output stream or error stream. Incorrect handling of such external programs can cause unexpected exceptions, denial of service, and other security problems.
A process that tries to read input on an empty input stream will block until input is supplied. Consequently, input must be supplied when invoking a process that expects input.
Output from an external process can exhaust the available buffer for the output or error stream. When this occurs, it can block the external process as well, preventing any forward progress for both the Java program and the external processes. Note that many platforms limit the buffer size available for the output streams. Consequently, when invoking an external process, if the process sends any data to its output stream, the process's output stream must be emptied. And if the process sends any data to its error stream, the error stream must also be emptied.
We will assume that the following code samples use the external command notemaker
, a hypothetical cross-platform notepad application. We will also assume that notemaker
does not read its input stream, but does send output to both its output stream and error streamThis recommendation discusses several issues resulting from the improper use of the exec()
method. Similarly the is also prone to misuse.
Noncompliant Code Example (exitValue()
)
This noncompliant code example invokes notemaker
, a hypothetical cross-platform notepad application, using the exec()
method, which returns an object of a subclass of the abstract
class java.lang.Process
. The exitValue()
method returns the exit value for processes that have terminated; , but it throws an IllegalThreadStateException
when invoked on an active process. Because this noncompliant example program fails to wait for the notemaker
process to terminate, the call to exitValue()
is likely to throw an {IllegalThreadStateException}}.
Code Block | ||
---|---|---|
| ||
public class Exec { public static void main(String args[]) throws IOException { Runtime rt = Runtime.getRuntime(); Process proc = rt.exec("notemaker"); int exitVal = proc.exitValue(); } } |
Noncompliant Code Example (waitFor()
)
In this noncompliant code example, the waitFor()
method blocks the calling thread until the invoked process terminates. This prevents the IllegalThreadStateException
seen in the previous example. However, the example program may experience an arbitrary delay before termination. First, the invoked notemaker process could legitimately require lengthy execution before completion. Although this possibility can present difficulties in practice, it is irrelevant for the purposes of this guideline. Second, output Output from the notemaker process can exhaust the available buffer for the standard output or standard error stream . When this occurssince neither stream is read while waiting for the process to complete. If either buffer becomes full, it can block the notemaker
process as well, preventing all forward progress for both processes. Note that many platforms limit the buffer size available for the standard output streamsthe notemake process and the Java program.
Code Block | ||
---|---|---|
| ||
public class Exec { public static void main(String args[]) throws IOException { Runtime rt = Runtime.getRuntime(); Process proc = rt.exec("notemaker"); int exitVal = proc.waitFor(); } } |
Compliant Solution (1)
Noncompliant Code Example (input stream)
This noncompliant code example properly drains the input stream from the process, thereby preventing the input stream buffer from becoming full and blocking. However, it ignores the error stream, which can also fill and cause the process to block.An partial solution would be to exhaust the output and the stderr
streams before waiting for the invoked process. A better approach is to empty both the stderr
stream and the output stream, as shown in this compliant solution. The example code, however, fails to process any arguments passed to the external program (notemaker
) and exits with an OS-specific non-zero exit code. These limitations must also be addressed in production code.
Code Block | ||
---|---|---|
| ||
public class Exec { public static void main(String args[]) throws IOException, InterruptedException { Runtime rt = Runtime.getRuntime(); Process proc = rt.exec("notemaker"); InputStream is = proc.getInputStream(); InputStreamReader isr = new InputStreamReader(is); BufferedReader br = new BufferedReader(isr); String lineint c; while ((linec = bris.readLineread()) != null) { -1) System.out.println(line); // Prints the error lines } print((char) c); int exitVal = proc.waitFor(); } } |
Compliant Solution (
...
input stream and error stream)
This compliant solution spawns a command interpreter that executes the user supplied command. It reads the output from the external process, and write it to a separate OutputStream
two threads to consume the input stream and error stream. Consequently, the process does not block.
When the output and error streams are handled separately, they must be drained independently. Failure to do so can cause the program to block indefinitely.
Code Block | ||
---|---|---|
| ||
class StreamGobbler extends Thread { InputStream is; String type; OutputStream PrintStream os; StreamGobbler(InputStream is, StringPrintStream typeos) { this(is, type, null); } StreamGobbler(InputStream is, String type, OutputStream redirect) { this.is = is; this.typeos = type; this.os = redirect; } public void run() { try { PrintWriter pw = nullint c; while if (os != null) { pw = new PrintWriter(os); } InputStreamReader isr = new InputStreamReader(is); BufferedReader br = new BufferedReader(isr); String line = null; while ((line = br.readLine(c = is.read()) != null-1) { if (pw != null) { pw.println(line); pw.flush(); } System.out.println(type + ">" + line); os.print((char) c); } if catch (pw != nullIOException x) { // pw.flush();handle error } } catch (IOException ioe) { /* Forward to handler */ } } } public class Exec { public static void main(String[] args) { throws IOException, // ... perform command argument check ...InterruptedException { try { FileOutputStream fos = new FileOutputStream("c:\\output.txt"); Runtime rt = Runtime.getRuntime(); Process proc = rt.exec("notemaker"); // Any error message? StreamGobbler errorGobbler = new StreamGobbler(proc.getErrorStream(), "ERROR"System.err); // Any output? StreamGobbler outputGobbler = new StreamGobbler(proc.getInputStream(), "OUTPUT", fosSystem.out); errorGobbler.start(); outputGobbler.start(); // Any error? int exitVal = proc.waitFor(); errorGobbler.join(); // Handle condition where the outputGobbler.join(); // process ends before the threads finish fos.flush(); fos.close(); } catch (Throwable t) { /* forward to handler */ } } } |
When the output and error streams are handled separately, they must be drained independently. Failure to do so can cause the program to block indefinitely.
Compliant Solution (Windows)
This compliant solution uses a ProcessBuilder
to simplify the handling mechanism by merging the error and output streams. The readToPrompt()
method reads the output of the invoked process; details of its implementation necessarily depend on the exact formatting of the command prompt used by the platform's command interpreter.
Code Block | ||
---|---|---|
| ||
public class Exec { public static void main(String[] args) throws IOException { ProcessBuilder pb = new ProcessBuilder("cmd"); pb = pb.redirectErrorStream(true); Process p = pb.start(); InputStream is = p.getInputStream(); OutputStream os = p.getOutputStream(); PrintWriter pw = new PrintWriter(os, true); readToPrompt(is); pw.println("dir"); readToPrompt(is); } private static void readToPrompt(InputStream is) throws IOException { String s = ""; for (;;) { int i = is.read(); if (i < 0) { System.out.println(); System.out.println("EOF"); System.exit(0); } char c = (char)i; // Safe s += c; if (s.endsWith("\r\n") { System.out.print(s); s = ""; } // Detects prompt, to break out if (c == '>' && s.length() > 2 && s.charAt(1) == ':') { System.out.print(s); break; } } } } |
Exceptions
FIO10-EX0: A process that does not read input from its input stream need not have data supplied there. Likewise a process that does not send output to its output stream need not have its output stream emptied. And a proces that does not send output to its error stream need not have its error stream emptied.
Risk Assessment
Misuse of the exec()
method can result in runtime exceptions and in denial of service vulnerabilities.
Guideline | Severity | Likelihood | Remediation Cost | Priority | Level |
---|---|---|---|---|---|
FIO10-J | low | probable | medium | P4 | L3 |
Related Vulnerabilities
Bibliography
Wiki Markup |
---|
\[[API 06|AA. Bibliography#API 06]\] method [exec()|http://java.sun.com/javase/6/docs/api/java/lang/Runtime.html#exec(java.lang.String)] \[[Daconta 00|AA. Bibliography#Daconta 00]\] \[[Daconta 03|AA. Bibliography#Daconta 03]\] Pitfall 1 |
...