External programs can be invoked from Java code using the The exec()
method of the java.lang.Runtime
class . The exec()
method returns an object of a subclass of the abstract
class java.lang.Process
. The exitValue()
method can be used to observe the return value of the process. This recommendation discusses several issues resulting from the improper use of the exec()
method.
...
and the related ProcessBuilder.start()
method can be used to invoke external programs. While running, these programs are represented by a java.lang.Process
object. This process contains an input stream, output stream, and error stream. Because the Process
object allows a Java program to communicate with its external program, the process's input stream is an OutputStream
object, accessible by the Process.getOutputStream()
method. Likewise, the process's output stream and error streams are both represented by InputStream
objects, accessible by the Process.getInputStream()
and Process.getErrorStream()
methods.
These processes may require input to be sent to their input stream, and they may also produce output on their output stream, their error stream, or both. Incorrect handling of such external programs can cause unexpected exceptions, denial of service (DoS), 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 such a process.
Output from an external process can exhaust the available buffer reserved for its output or error stream. When this occurs, the Java program can block the external process as well, preventing any forward progress for both the Java program and the external process. Note that many platforms limit the buffer size available for output streams. Consequently, when invoking an external process, if the process sends any data to its output stream, the output stream must be emptied. Similarly, if the process sends any data to its error stream, the error stream must also be emptied.
Noncompliant Code Example (exitValue()
)
This noncompliant code example invokes notemaker
, a hypothetical cross-platform notepad application using the external command notemaker
. The program does not notemaker
application does not read its input stream but sends output to both its output stream and error stream.
This noncompliant code example invokes notemaker
using the exec()
method, which returns a Process
object. 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, and throws an IllegalThreadStateException
if the notemaker
process has not completed when the the call to exitValue()
method is calledlikely 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 notemaker
process terminates. However, this also has a shortcoming in that, the program may remain blocked for a long time because of the limited buffer size available for the standard output streams on many platforms. The output from the external program may exhaust the buffer, causing this conditionThis approach prevents the IllegalThreadStateException
from the previous example. However, the example program may experience an arbitrary delay before termination. Output from the notemaker
process can exhaust the available buffer for the output or error stream because 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 progress for both the notemaker
process and the Java program.
Code Block | ||
---|---|---|
| ||
public class Exec { public static void main(String args[]) throws IOException, InterruptedException { Runtime rt = Runtime.getRuntime(); Process proc = rt.exec("notemaker"); int exitVal = proc.waitFor(); } } |
Compliant Solution (1)
Noncompliant Code Example (Process Output Stream)
This noncompliant code example properly empties the process's output stream, thereby preventing the output stream buffer from becoming full and blocking. However, it ignores the process's error stream, which can also fill and cause the process to blockAn inefficient solution is to exhaust the output and the stderr
streams before beginning to wait for the process. A better option is to empty both the stderr
and output streams. This compliant solution shows this but is not the best solution as it does not process any arguments passed to the external program (notemaker
) and exits with an OS-specific non-zero exit 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)int c; BufferedReader br = new BufferedReader(isr); String line; while ((linec = bris.readLineread()) != null-1) { System.out.println(line); // Prints the error linesprint((char) c); } int exitVal = proc.waitFor(); } } |
Compliant Solution (
...
redirectErrorStream()
)
This compliant solution spawns a command interpreter and executes the user supplied command. It uses a separate OutputStream
to write the output that is read in from the external processredirects the process's error stream to its output stream. Consequently, the program can empty the single output stream without fear of blockage.
Code Block | ||
---|---|---|
| ||
public class Exec extends Thread { InputStreampublic is; static void main(String type;args[]) OutputStream os; Exec(InputStream is, String type) { this(is, type, null); } Exec(InputStream is, String type, OutputStream redirect) { throws IOException, InterruptedException this.is{ = is; ProcessBuilder pb this.type= = typenew ProcessBuilder("notemaker"); this.ospb = redirectpb.redirectErrorStream(true); } Process publicproc void= runpb.start() {; tryInputStream { is = proc.getInputStream(); PrintWriter pw = nullint c; while ((c if= is.read(os)) != null-1) { pw = new PrintWriter(osSystem.out.print((char) c); } int InputStreamReader isr = new InputStreamReader(is); BufferedReader br = new BufferedReader(isr); String line = null; while ((line = br.readLine()) != nullexitVal = proc.waitFor(); } } |
Compliant Solution (Process Output Stream and Error Stream)
This compliant solution spawns two threads to consume the process's output stream and error stream. Consequently, the process cannot block indefinitely on those streams.
When the output and error streams are handled separately, they must be emptied independently. Failure to do so can cause the program to block indefinitely.
Code Block | ||
---|---|---|
| ||
class StreamGobbler implements Runnable { private final InputStream is; private final PrintStream os; StreamGobbler(InputStream is, PrintStream os) { if (pw != null) {this.is = is; this.os pw.println(line)= os; } public pw.flushvoid run(); { try }{ System.out.println(type + ">" + line)int c; } if (pwwhile ((c = is.read()) != null-1) pw.flush() os.print((char) c); } catch (IOException ioex) { /* Forward to handler *// Handle error } } } public class ExecMeExec { public static void main(String[] args) { // ... perform command argument check ... try { FileOutputStream fos = new FileOutputStream("c:\\output.txt");throws IOException, InterruptedException { Runtime rt = Runtime.getRuntime(); Process proc = rt.exec("notemaker"); // Any error message? Thread errorGobbler Exec errorGobbler = new Thread(new ExecStreamGobbler(proc.getErrorStream(), "ERROR"System.err)); // Any output? Thread outputGobbler Exec outputGobbler = new Thread(new ExecStreamGobbler(proc.getInputStream(), "OUTPUT", fosSystem.out)); errorGobbler.start(); outputGobbler.start(); // Any error? int exitVal = proc.waitFor(); errorGobbler.join(); // handleHandle condition where the outputGobbler.join(); // process ends before the threads finish fos.flush(); fos.close(); } catch (Throwable t) { /* forward to handler */ } } } |
Note that if the streams are not merged (as shown in the next compliant solution), they must be drained concurrently. Failure to do this can cause the program to block indefinitely.
Compliant Solution (3) (Windows)
This compliant solution (based on the Sun forums query Runtime.exec hangs even If I drain output), uses the ProcessBuilder
to merge the error and output streams to simplify the handling mechanism. The readToPrompt()
method interacts with the command prompt and reads the output of the invoked process.
Code Block | ||
---|---|---|
| ||
public class Cmd {
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;
}
}
}
}
|
Risk Assessment
Exceptions
FIO07-J-EX0: Failure to supply input to a process that never reads input from its input stream is harmless and can be beneficial. Failure to empty the output or error streams of a process that never sends output to its output or error streams is similarly harmless or even beneficial. Consequently, programs are permitted to ignore the input, output, or error streams of processes that are guaranteed not to use those streams.
Risk Assessment
Failure to properly manage the I/O streams of external processes can result in runtime exceptions and in DoS Misuse of the exec()
method can result in runtime exceptions and denial of service vulnerabilities.
Rule | Severity | Likelihood | Remediation Cost | Priority | Level |
---|
FIO07-J |
Low |
Probable |
Medium | P4 | L3 |
Automated Detection
TODO
Related Vulnerabilities
Search for vulnerabilities resulting from the violation of this rule on the CERT website.
References
Wiki Markup |
---|
\[[API 06|AA. Java References#API 06]\] method [exec()|http://java.sun.com/javase/6/docs/api/java/lang/Runtime.html#exec(java.lang.String)]
\[[Daconta 00|AA. Java References#Daconta 00]\]
\[[Daconta 03|AA. Java References#Daconta 03]\] Pitfall 1 |
Tool | Version | Checker | Description | ||||||
---|---|---|---|---|---|---|---|---|---|
Parasoft Jtest |
| CERT.FIO07.EXEC | Do not use 'Runtime.exec()' |
Related Vulnerabilities
Bibliography
[API 2014] | |
Pitfall 1 |
...
FIO00-J. Canonicalize path names originating from untrusted sources 09. Input Output (FIO) FIO02-J. Keep track of bytes read and account for character encoding while reading data