Skip to end of metadata
Go to start of metadata

A Java OutofMemoryError occurs when the program attempts to use more heap space than is available. Among other causes, this error may result from the following:

Some of these causes are platform-dependent and difficult to anticipate. Others, such as reading data from a file, are fairly easy to anticipate. As a result, programs must not accept untrusted input in a manner that can cause the program to exhaust memory.

Noncompliant Code Example (readLine())

This noncompliant code example reads lines of text from a file and adds each one to a vector until a line with the word "quit" is encountered:

class ReadNames {
  private Vector<String> names = new Vector<String>();
  private final InputStreamReader input;
  private final BufferedReader reader;

  public ReadNames(String filename) throws IOException {
    this.input = new FileReader(filename);
    this.reader = new BufferedReader(input);
  }

  public void addNames() throws IOException {
    try {
      String newName;
      while (((newName = reader.readLine()) != null) &&
             !(newName.equalsIgnoreCase("quit"))) {
        names.addElement(newName);
        System.out.println("adding " + newName);
      }
    } finally {
      input.close();
    }
  }

  public static void main(String[] args) throws IOException {
    if (args.length != 1) {
      System.out.println("Arguments: [filename]");
      return;
    }
    ReadNames demo = new ReadNames(args[0]);
    demo.addNames();
  }
}

The code places no upper bounds on the memory space required to execute the program. Consequently, the program can easily exhaust the available heap space in two ways. First, an attacker can supply arbitrarily many lines in the file, causing the vector to grow until memory is exhausted. Second, an attacker can simply supply an arbitrarily long line, causing the readLine() method to exhaust memory. According to the Java API documentation [API 2014], the BufferedReader.readLine() method

Reads a line of text. A line is considered to be terminated by any one of a line feed ('\n'), a carriage return ('\r'), or a carriage return followed immediately by a linefeed.

Any code that uses this method is susceptible to a resource exhaustion attack because the user can enter a string of any length.

Compliant Solution (Limited File Size)

This compliant solution imposes a limit on the size of the file being read. The limit is set with the Files.size() method, which was introduced in Java SE 7. If the file is within the limit, we can assume the standard readLine() method will not exhaust memory, nor will memory be exhausted by the while loop.

class ReadNames {
  // ... Other methods and variables

  public static final int fileSizeLimit = 1000000;

  public ReadNames(String filename) throws IOException {
    long size = Files.size( Paths.get( filename));
    if (size > fileSizeLimit) {
      throw new IOException("File too large");
    } else if (size == 0L) {
      throw new IOException("File size cannot be determined, possibly too large");
    }
    this.input = new FileReader(filename);
    this.reader = new BufferedReader(input);
  }
}

Compliant Solution (Limited Length Input)

This compliant solution imposes limits both on the length of each line and on the total number of items to add to the vector. (It does not depend on any Java SE 7 or later features.)

class ReadNames {
  // ... Other methods and variables

  public static String readLimitedLine(Reader reader, int limit) 
                                       throws IOException {
    StringBuilder sb = new StringBuilder();
    for (int i = 0; i < limit; i++) {
      int c = reader.read();
      if (c == -1) {
        return ((sb.length() > 0) ? sb.toString() : null);
      }
      if (((char) c == '\n') || ((char) c == '\r')) {
        break;
      }
      sb.append((char) c);
    }
    return sb.toString();
  }

  public static final int lineLengthLimit = 1024;
  public static final int lineCountLimit = 1000000;

  public void addNames() throws IOException {
    try {
      String newName;
      for (int i = 0; i < lineCountLimit; i++) {
        newName = readLimitedLine(reader, lineLengthLimit);
        if (newName == null || newName.equalsIgnoreCase("quit")) {
          break;
        }
        names.addElement(newName);
        System.out.println("adding " + newName);
      }
    } finally {
      input.close();
    }
  }

}

The readLimitedLine() method takes a numeric limit, indicating the total number of characters that may exist on one line. If a line contains more characters, the line is truncated, and the characters are returned on the next invocation. This prevents an attacker from exhausting memory by supplying input with no line breaks.

Noncompliant Code Example

In a server-class machine using a parallel garbage collector, the default initial and maximum heap sizes are as follows for Java SE 6 [Sun 2006]:

  • Initial heap size: larger of 1/64 of the machine's physical memory or some reasonable minimum.
  • Maximum heap size: smaller of 1/4 of the physical memory or 1GB.

This noncompliant code example requires more memory on the heap than is available by default:

/* Assuming the heap size as 512 MB 
 * (calculated as 1/4 of 2GB RAM = 512MB)
 * Considering long values being entered (64 bits each, 
 * the max number of elements would be 512MB/64 bits = 
 * 67108864)
 */
public class ReadNames {
  // Accepts unknown number of records
  Vector<Long> names = new Vector<Long>(); 
  long newID = 0L;
  int count = 67108865;
  int i = 0;
  InputStreamReader input = new InputStreamReader(System.in);
  Scanner reader = new Scanner(input);

  public void addNames() {
    try {
      do {
        // Adding unknown number of records to a list
        // The user can enter more IDs than the heap can support and,
        // as a result, exhaust the heap. Assume that the record ID
        // is a 64-bit long value
        System.out.print("Enter recordID (To quit, enter -1): ");
        newID = reader.nextLong();

        names.addElement(newID);
        i++;
      } while (i < count || newID != -1);
    } finally {
      input.close();
    }
  }

  public static void main(String[] args) {
    ReadNames demo = new ReadNames();
    demo.addNames();
  }
}

Compliant Solution

A simple compliant solution is to reduce the number of names to read:

  // ...
  int count = 10000000;
  // ...

Compliant Solution

The OutOfMemoryError can be avoided by ensuring the absence of infinite loops, memory leaks, and unnecessary object retention. When memory requirements are known ahead of time, the heap size can be tailored to fit the requirements using the following runtime parameters [Java 2006]:

java -Xms<initial heap size> -Xmx<maximum heap size>

For example,

java -Xms128m -Xmx512m ReadNames

Here the initial heap size is set to 128MB and the maximum heap size to 512MB.

These settings can be changed either using the Java Control Panel or from the command line. They cannot be adjusted through the application itself.

Risk Assessment

Assuming infinite heap space can result in denial of service.

Rule

Severity

Likelihood

Remediation Cost

Priority

Level

MSC05-J

Low

Probable

Medium

P4

L3

Related Vulnerabilities

The Apache Geronimo bug described by GERONIMO-4224 results in an OutOfMemoryError exception thrown by the WebAccessLogViewer when the access log file size is too large.

Related Guidelines

SEI CERT C Coding Standard

MEM11-C. Do not assume infinite heap space

SEI CERT C++ Coding Standard

VOID MEM12-CPP. Do not assume infinite heap space

ISO/IEC TR 24772:2010

Resource Exhaustion [XZP]

MITRE CWE

CWE-400, Uncontrolled Resource Consumption ("Resource Exhaustion")
CWE-770, Allocation of Resources without Limits or Throttling

Bibliography

 


  

17 Comments

  1. Kalpana,

    • I suspect the severity should be minimal, as a heap error causes a program to crash...it should only be medium if a heap error causes a program to reveal sensitive information.
    • The noncompliant code example is flawed...the code as is uses a fixed amount of memory (how much?). A better example would use an arbitrarily large amount of memory. Better still, the amount of memory used depends on external data, so an attacker can force the code to exhaust the heap.
    • The advice in the 'compliant solution' is good, but it is not itself a compliant solution. Need a compliant solution that provides a more secure solution at the code level.
    • A citation about how to handle heap exhaustion would be helpful...how does SUN recommend avoiding or mitigating this problem?
    1. David,

      I have fixed the example to depend on external input and the Javadocs just mention raising the error on running out of heap memory. I checked the forums on Sun's web site, and found that they give the same solution. I wanted to confirm if I can cite these forums.

      I also had a doubt: Dhruv Mohindra had entered this as a rule but it was empty and marked in red (I have updated it as a recommendation as you suggested). Would my work count towards the assignment? Please do let me know your thoughts.

      Regards,

      Kalpana

      1. Kalpana, this will count as one of your rules for the assignment.

        • I don't think a forum response qualifies as an authoritative citation. (Maybe if it was provided by James Gosling (smile) Do you have a Java book handy that talks about heap exhaustion? The 'Java References' page has a lot of books and some authoritative websites...try using one of those.
        • Unfortuantely now the compliant solution doesn't solve the problem, as the user merely has to enter more strings in order to exhaust the heap.

        I think you need 2 noncompliant code examples: One that uses a fixed amount of memory, which is more than the default maximum heap, consequently exhausting the heap. This is solved by your current compliant solution.

        The other uses arbitarily large memory. Your current noncompliant solution will do, although you might consider Dhruv's comment.
        As for solving this problem, you need to consider that the vector of strings can grow larger than any fixed heap, and thus you should provide a disk-based solution, such as a database. I think you can settle for discussing how to solve this problem without providing actual working code.

        • BTW your noncompliant code example needs some text explaining that if the user simply inputs enough strings, the program will happily exhaust the heap.
        1. David,

          I found the Java Docs which show ways to increase the default heap size (smile) 

          There are two examples now:

          1. fixed large amount of memory, which is fixed by increasing the default heap size

          2. arbitrary sized input where the input is written to a database instead of being maintained in a data structure in the program.

          I also added more comments about how I calculated the fixed size of my structure which will exhaust the heap. do let me know what you think.

          Kalpana

          1. Much better, Kalpana. Only one issue remains: I suspect the remediation cost is high, at least for replacing memory-based solutions with disk-based solutions.

  2. You could add that implementing common data structures (like hashtables) incorrectly can also lead to this OutOfMemoryError. For example, someone could forget that a complete database is not a good idea to have in memory, especially when it is growing rapidly.

  3. This rule can be made normative. The normative text is something like "do not allow an attacker to provide input that requires arbitrarily large memory to process". The IDS rule about ZIP files is an instance of this rule. The 2nd NCCE/CS is nonnormative; could still keep it, but separate it from the 1st and 3rd CS.

  4. The Serialization section has this title for a nonexistant rule:

    SER14-J. Do not use the writeUnshared and readUnshared methods

    I've studied readUnshared() and writeUnshared(). They do serialization except that they don't recognize references to previous objects. IOW they ignore any cycles in your object hierarchy. I suppose the danger then is that readUnshared could exhaust memory, which is covered by this rule. Best suggestion is to make a NCCE/CS about readUnshared (perhaps using readObject instead in the CS). In our copious free time, of course.

    • If the file is within the limit, we can assume the standard readLine() method will not exhaust memory

    But I don't see the CS using the readLine() method, then why this text there?

    1. The CS uses readLine() in the read function as the NCCE, which used readLine(). I swapped the two CS's around to make this more clear.

  5. In the compliant solution , it is mentioned to use Java SE 7 Files.size() method. Why cannot we use length() method that is provided by File object.Is there any issue in using length() method of File object? If not, is it a compliant solution?(Just in case older versions of java are used).

    1. The File.length() method may be used instead of Files.size() if you are using an older version of Java. If you are using Java 7, Files.size() is preferred. The Files.size() API documentation contains the following:

      The size may differ from the actual size on the file system due to compression, support for sparse files, or other reasons.

      No such text is provided for File.length() in Java 7 or earlier. So the value you will get from Files.size() more accurately reflects the number of bytes you can read.

      1. David,

        I checked Oracle comment for length() method of File object.It is like this(This is for earlier versions of Java  7):

         "Returns the length of the file denoted by this abstract pathname.
              The return value is unspecified if this pathname denotes a directory.
            
              @return  The length, in bytes, of the file denoted by this abstract
                       pathname, or <code>0L</code> if the file does not exist.  Some
                       operating systems may return <code>0L</code> for pathnames
                       denoting system-dependent entities such as devices or pipes"

        In FileSystem.java, there is an abstract  method called getLength(File f)(that is called from length()) whose description is as follows:

         " Return the length in bytes of the file denoted by the given abstract
              pathname, or zero if it does not exist, is a directory, or some other
              I/O error occurs."

        1. Unni:

          My Java 7 implementation (1.7.0-ea) has the same API doc text you cite. Since I haven't studied the JRE source code, it is possible that File.length() and Files.size() actually do the same thing...it's just that Files.size() promises more.

          I added a check to the Java 7 Compliant Solution to throw an exception if the size is 0, because this means you are dealing with a special file or some other beast whose length cannot be determined, and thus might not be within the limit.

  6. In addNames() method of the Compliant Solution (Limited Length Input), reader, names and input seem to be unknown in the scope.

    1. It is supposed to use the variables defined at the top of the class in the noncompliant code example. I tweaked the comments to make this clearer.

      1. Okay, thanks David!