Sensitive data in memory can be vulnerable to compromise. An adversary who can execute code on the same system as an application may be able to access such data if the application

  • Uses objects to store sensitive data whose contents are not cleared or garbage-collected after use
  • Has memory pages that can be swapped out to disk as required by the operating system (for example, to perform memory management tasks or to support hibernation)
  • Holds sensitive data in a buffer (such as BufferedReader) that retains copies of the data in the OS cache or in memory
  • Bases its control flow on reflection that allows countermeasures to circumvent the limiting of the lifetime of sensitive variables
  • Reveals sensitive data in debugging messages, log files, environment variables, or through thread and core dumps

Sensitive data leaks become more likely if the memory containing the data is not cleared after using the data. To limit the risk of exposure, programs must minimize the lifetime of sensitive data.

Complete mitigation (that is, foolproof protection of data in memory) requires support from the underlying operating system and Java Virtual Machine. For example, if swapping sensitive data out to disk is an issue, a secure operating system that disables swapping and hibernation is required.

Noncompliant Code Example

This noncompliant code example reads user name and password information from the console and stores the password as a String object. The credentials remain exposed until the garbage collector reclaims the memory associated with this String.

class Password {
  public static void main (String args[]) throws IOException {
    Console c = System.console();
    if (c == null) {
      System.err.println("No console.");
      System.exit(1);
    }

    String username = c.readLine("Enter your user name: ");
    String password = c.readLine("Enter your password: ");

    if (!verify(username, password)) {
      throw new SecurityException("Invalid Credentials"); 
    }

    // User is authorized, continue...
  }

  // Dummy verify method, always returns true   
  private static final boolean verify(String username, String password) {
    return true;
  }
}

Compliant Solution

This compliant solution uses the Console.readPassword() method to obtain the password from the console:

class Password {
  public static void main (String args[]) throws IOException {
    Console c = System.console();
    
    if (c == null) {
      System.err.println("No console.");
      System.exit(1);
    }

    String username = c.readLine("Enter your user name: ");
    char[] password = c.readPassword("Enter your password: ");
    boolean isValidUser = verify(username, password); 

    // Clear the password
    Arrays.fill(password,' ');

    if (!isValidUser) {
      throw new SecurityException("Invalid Credentials"); 
    }

    // User is authorized, continue...
  }

  // Dummy verify method, always returns true   
  private static final boolean verify(String username, char[] password) {
    return true;
  }
}

The Console.readPassword() method allows the password to be returned as a sequence of characters rather than as a String object. Because the password is never interned as a String, it will not survive garbage collection even if it matches another string. Consequently, the programmer can clear the password from the array immediately after use.

The Console.readPassword() method also disables echoing of the password to the console.

Noncompliant Code Example

This noncompliant code example uses a BufferedReader to wrap an InputStreamReader object so that sensitive data can be read from a file:

void readData() throws IOException {
  BufferedReader br = new BufferedReader(new InputStreamReader(
  new FileInputStream("file")));
  // Read from the file
  String data = br.readLine();
}

The BufferedReader.readLine() method returns the sensitive data as a String object, which can persist long after the data is no longer needed. The BufferedReader.read(char[], int, int) method can read and populate a char array. However, it requires the programmer to manually clear the sensitive data in the array after use. Alternatively, even if the BufferedReader were to wrap a FileReader object, it would suffer from the same pitfalls.

Compliant Solution

This compliant solution uses a directly allocated NIO (new I/O) buffer to read sensitive data from the file. The data can be cleared immediately after use and is not cached or buffered at multiple locations. It exists only in the system memory.

void readData() {
  int bufferSize = 16 * 1024;
  byte zeroes = new byte[bufferSize];
  ByteBuffer buffer = ByteBuffer.allocateDirect(bufferSize);
  try (FileChannel rdr = (new FileInputStream("file")).getChannel()) {
   	while (rdr.read(buffer) > 0) {

      // Do something with the buffer

	  buffer.clear();
      buffer.put(zeroes); // overwrite buffer with zeroes
	  buffer.clear();
	}
  } catch (Throwable e) {
    // Handle error
  }
} 

Note that manual clearing of the buffer data is mandatory because direct buffers are not garbage collected.

Applicability

Failure to limit the lifetime of sensitive data can lead to information leaks.

Bibliography

 


13 Comments

    • Not sure that in 1st NCE/CS IOException is the best choice
    • rdr.close() in 2nd CS should occur in a finally block
    1. I've addressed both bullet points.

      I also reworded the intro, but it could use more love. The problem is that an attacker who controls the file system can cause IOExceptions to occur on most IO access. For instance, she can exhaust disk space. Assuming they can execute code on the machine, they can also cause OutOfMemoryExceptions. For this rule to be worthwhile, we must assume the scenario here is to protect sensitive data living in memory from attackers who have access to the machine running the program. So we have to assume the attacker can't access the source of the sensitive data (eg they don't control the filesystem or network). We also assume the attacker can't run arbitrry code.

      I'd like to say that no JVM platform today provides security for data in-memory (nor do they provide guarentees for safe data erasure of data). But I'm not sure this is true.

  1. Daniel Wasser says, via email:

    Hi,
    there is a vulnerability in the Java Secure Coding Guidelines:
    Chapter: https://www.securecoding.cert.org/confluence/display/jg/01.+Limit
    +the+lifetime+of+sensitive+data
    First Compiliant Example:
    if(!verify
    (username,password)){
    throw new SecurityException("Invalid Credentials");
    }

    //Clear the password
    Arrays.fill(password,' ');

    The whiping is not done if the exception is thrown. Better would be to first overwrite the password array:
    boolean isValidUsr=verify(username,password);

    //Clear the password
    Arrays.fill(password,' ');
    password=null;
    System.gc();

    if(!isValidUsr){
    throw new SecurityException("Invalid Credentials");
    }

    Sorry for writing here,I did not find a possibility to send this sugestion direct at the Java Coding Guideline

    Gretings,
    Daniel

    1. Yow! Thanks for pointing this out. I've changed this compliant solution into an additional noncompliant code example, and added a new compliant solution following your suggestion.

      1. Oh! Good catch.

        I think we shouldn't have the following lines in the CS:

        1.   password = null;

        2.   System.gc();
        Line 1 because this is GC's job
        Line 2 because explicitly invoking the GC is a bad practice.
        1. I made this change.  I'm wondering why we even have a noncompliant example that shows garbage collection as a solution since we have another recommendation that says not to do this:  36. Write garbage-collection-friendly code

          We normally like the noncompliant examples to only show one problem.  I vote we get rid of this example because the issue is covered in this other recommendation.

          1. I agree that we should get rid of this example.

            1. I made this change. I had forgotten about guideline 36 :/

  2. Hello,
    maybe there should be a hint how to create a Hash-value out of the Byte Array e.g. for handling a given user

    password. To use the char array for sensitive data complicates the Hashing of the data because it also must 
    be done without using a string.
    Maybe this is a way to do this:
     
    // initializing char array
    char[] pwdChar = passwordField.getPassword();
    MessageDigest hasher = null;
    try {
    	hasher = MessageDigest.getInstance("SHA1");
    } catch (Exception e) {
        e.printStackTrace();
    }
     
    // convert pwd char[]
    byte[] pwdByte = toByteArray(pwdChar);
     
    // hash for pwd
    byte[] pwdHash = hasher.digest(pwdByte);
     
    // whipe pwd arrays
    Arrays.fill(pwdChar, '0');
    Arrays.fill(pwdByte, (byte) 0);
                
    // check password 
    if(pwdHash.equals(Database.getHashedPwd("User123")) != 0){
    	throw new LoginException("User not Authenticated");
    }
     
    ....
    
    
    // convert the char[] into a byte[]
    public static byte[] toByteArray(char[] charArray) {
    	byte[] bytes = new byte[charArray.length];
        for (int i = 0; i < charArray.length; i++) {
        	bytes[i] = (byte) charArray[i];
        }
        return bytes;
    }

     

    Hopefully the MessageDigest internally works without immutable temporary variables. I did not check this.  

    Greetings,
    Daniel

     

     

  3. Based on Javadoc and source code of Buffer.java calling buffer.clear() does not change the contents of the buffer:

    Clears this buffer. The position is set to zero, the limit is set to the capacity, and the mark is discarded.

     

     

     

       public final Buffer clear() {
            position = 0;
            limit = capacity;
            mark = -1;
            return this;
        }

     

    Seems like we need to add this call buffer.put(new byte[16 1024]) to write zeros into the memory area.

     

  4. I think the sequence needs to be buffer.clear(); buffer.put(new byte[16 1024]); buffer.clear()

     

    1. I checked the docs, you're right. A BufferOverflowException would result if we write the zeroes w/o clearing the buffer. Fixed.