You are viewing an old version of this page. View the current version.

Compare with Current View Page History

« Previous Version 46 Next »

External programs can be invoked from Java code using 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. Similarly the ProcessBuilder.start() method is also prone to misuse.

Noncompliant Code Example

This noncompliant code example invokes notemaker, a hypothetical cross-platform notepad application. The program does not wait for the notemaker process to terminate, and throws an IllegalThreadStateException if the notemaker process has not completed when the exitValue() method is called.

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

In this noncompliant code example, the waitFor() method blocks the calling thread until the invoked 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 condition.

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)

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

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 line;
    while ((line = br.readLine()) != null) {  
      System.out.println(line);  // Prints the error lines
    }

    int exitVal = proc.waitFor();   
  }
}

Compliant Solution (2)

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

class Exec extends Thread  {
  InputStream is;
  String type;
  OutputStream os;

  Exec(InputStream is, String type) {
    this(is, type, null);
  }

  Exec(InputStream is, String type, OutputStream redirect) {
    this.is = is;
    this.type = type;
    this.os = redirect;
  }

  public void run() {
    try {
      PrintWriter pw = null;
      if (os != null) {
        pw = new PrintWriter(os);
      }  
     
      InputStreamReader isr = new InputStreamReader(is);
      BufferedReader br = new BufferedReader(isr);

      String line = null; 
      while ((line = br.readLine()) != null) {
        if (pw != null) {
          pw.println(line);
          pw.flush();
        }
 
        System.out.println(type + ">" + line);
      }
 
      if (pw != null) {
        pw.flush();
      }
    } catch (IOException ioe) { /* Forward to handler */ }
  }
}
	
public class ExecMe {
  public static void main(String[] args) {
    // ... perform command argument check  ...
	
    try {
      FileOutputStream fos = new FileOutputStream("c:\\output.txt");
      Runtime rt = Runtime.getRuntime();
      Process proc = rt.exec("notemaker");

      // Any error message?
      Exec errorGobbler = new Exec(proc.getErrorStream(), "ERROR");
	
      // Any output?
      Exec outputGobbler = new Exec(proc.getInputStream(), "OUTPUT", fos);
	
      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 */ }
  }
}

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.

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

Misuse of the exec() method can result in runtime exceptions and denial of service vulnerabilities.

Rule

Severity

Likelihood

Remediation Cost

Priority

Level

FIO01- J

low

probable

medium

P4

L3

Automated Detection

TODO

Related Vulnerabilities

GROOVY-3275

References

[[API 06]] method exec()
[[Daconta 00]]
[[Daconta 03]] Pitfall 1


FIO00-J. Canonicalize path names before validating      09. Input Output (FIO)      FIO02-J. Keep track of bytes read and account for character encoding while reading data

  • No labels