The exec() method of the java.lang.Runtime class 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 a hypothetical cross-platform notepad application using the external command notemaker. The 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, the call to exitValue() is likely to throw an IllegalThreadStateException.

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 notemaker process terminates. This 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.

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();
  }
}

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 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();
    int c;
    while ((c = is.read()) != -1) {
      System.out.print((char) c);
    }
    int exitVal = proc.waitFor();
  }
}

Compliant Solution (redirectErrorStream())

This compliant solution redirects the process's error stream to its output stream. Consequently, the program can empty the single output stream without fear of blockage.

public class Exec {
  public static void main(String args[])
                          throws IOException, InterruptedException {
    ProcessBuilder pb = new ProcessBuilder("notemaker");
    pb = pb.redirectErrorStream(true);
    Process proc = pb.start();
    InputStream is = proc.getInputStream();
    int c;
    while ((c = is.read()) != -1) {
      System.out.print((char) c);
    }
    int exitVal = 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.

class StreamGobbler implements Runnable {
  private final InputStream is;
  private final PrintStream os;

  StreamGobbler(InputStream is, PrintStream os) {
    this.is = is;
    this.os = os;
  }

  public void run() {
    try {
      int c;
      while ((c = is.read()) != -1)
          os.print((char) c);
    } catch (IOException x) {
      // Handle error
    }
  }
}

public class Exec {
  public static void main(String[] args)
    throws IOException, InterruptedException {

    Runtime rt = Runtime.getRuntime();
    Process proc = rt.exec("notemaker");

    // Any error message?
    Thread errorGobbler
      = new Thread(new StreamGobbler(proc.getErrorStream(), System.err));
 
    // Any output?
    Thread outputGobbler
      = new Thread(new StreamGobbler(proc.getInputStream(), System.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
  }
}

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 vulnerabilities.

Rule

Severity

Likelihood

Remediation Cost

Priority

Level

FIO07-J

Low

Probable

Medium

P4

L3

Automated Detection

ToolVersionCheckerDescription
Parasoft Jtest
2023.1
CERT.FIO07.EXECDo not use 'Runtime.exec()'

Related Vulnerabilities

GROOVY-3275

Bibliography



6 Comments

  1. It wouldn't hurt to have a one or two line description of why GROOVY-3275 is a violation of this rule.

  2. This compliant solution redirects the process's error stream to its input stream. Consequently, the program can empty the single output stream without fear of blockage.

    Rather, should that be "single input stream"?

    1. Dhruv, don't cross the streams. That would be bad (smile)

      Dean and I wrestled with this rule last week; sounds like we lost. I added a blurb to the intro showing the confusion (to get a process's output stream, you call Process.getInputStream), and made sure that every mention of input, output, or error stream specifically applies to a process (rather than the Java program), to disabmguate.

  3. Because reassignment of the two instance fields is and os on an already instanced StreamGobbler object (second compliant solution) would be dangerous, i would suggest declaring the two fields final. This would be in accordance with OBJ51-J:

    "Classes and class members must be given the minimum possible access so that malicious code has the least opportunity to compromise security."

    Personally i would declare them both  private final, but the private declaration is not necessary, because the class is package-private (as also OBJ01-EX1 says).

    1. Agreed, I made both fields private and final. I also made StreamGobbler a Runnable rather than inherit Thread, which is a safer design.

  4. I sorrry I want to ask  a different issue.  I have to call every time another company's application like (exe, jar) . My develope environment is Windows 7 64. The problem is about handle count.  It's handle count in the Windows Task Manager increases When i use the  Runtime.getRuntime() or ProcessBuilder. I attached  " proc.getErrorStream().close() ,  proc.getInputStream().close() ,  proc.getOutputStream().close(),  proc.destroy() " in the end of source.  But It increase still.  Do you know how to solve this problem.