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

Compare with Current View Page History

« Previous Version 41 Next »

From a security point of view, Java's garbage collection feature provides significant benefits over traditional languages such as C and C++. The garbage collector (GC) is designed to automatically reclaim unreachable memory and as a result avoid memory leaks. While it is quite adept at performing this task, a malicious attacker can launch a Denial of Service (DoS) attack by inducing abnormal heap memory allocation as well as prolonged object retention.

For example, the GC needs to halt all executing threads to keep up with the incoming requests that command increased heap management in terms of space allocation. System throughput rapidly diminishes in this scenario. Real-time systems in particular, are vulnerable to a more subtle slow heap exhaustion DoS attack, perpetrated by stealing CPU cycles. An attacker can source memory allocations in a way that keeps resource consumption (such as CPU, battery power, memory) high without triggering an OutOfMemoryError.

Writing garbage collection friendly code helps restrict many attack avenues. The best practices have been collated and enumerated below.

Use Short-Lived Immutable Objects

Since JDK 1.2, the new generational garbage collector has reduced memory allocation related costs to minimal levels, even lesser than C/C++. Deallocation has also become cheaper such that the cost of garbage collection is commensurate with the number of live objects in the younger generation and not the total number of objects allocated since the last run. Memory is managed in generations to optimize the collection. The younger generation consists of short-lived objects. A minor collection on the younger generation is performed when it fills up with dead objects [[Oracle 2010a]].

Note that objects in the younger generation that persist for longer durations are tenured and are moved to the tenured generation. Very few younger generation objects continue to live through to the next garbage collection cycle; the rest become ready to be collected in the impending collection cycle [[Oracle 2010a]].

With generational GCs it is advantageous to use short-lived immutable objects instead of long-lived mutable objects. Object pools are examples of the latter and should be avoided to increase the garbage collector's efficiency. Moreover, object pools can create synchronization problems, deallocations have to be managed explicitly leading to dangers of dangling pointers, and the size of the pool plays a dominant role in mission critical code. Exceptions to this recommendation can be made when the allocation takes longer in comparison, such as when performing multiple joins across databases or when using objects that represent scarce resources such as thread pools and database connections.

Noncompliant Code Example

This noncompliant code example (based on [[Goetz 2004]]) shows a container, MutableHolder. In MutableHolder, the instance field value can be updated to reference a new value using the setValue() method which makes its existence long-term. This slows down garbage collection.

public class MutableHolder {
  private Hashtable<Integer, String> value; // not final

  public Object getValue() { 
    return value; 
  }

  public void setValue(Hashtable<Integer, String> ht) { 
    value = (Hashtable<Integer, String>)ht;
  }
}

This example also violates guideline FIO00-J. Defensively copy mutable inputs and mutable internal components.

Compliant Solution

This compliant solution highlights a custom container called ImmutableHolder. To aid garbage collection, it is recommended that short-lived ImmutableHolder objects be created by passing Hashtable instances to the constructor.

public class ImmutableHolder {
  private final Hashtable<Integer, String> value;

  // create defensive copy of inputs 
  public ImmutableHolder(Hashtable<Integer, String> ht) { 
    value = (Hashtable<Integer, String>)ht.clone(); 
  }

  // create defensive copy while returning  
  public Object getValue() { 
    return value.clone(); 
  }
}

When value is assigned in ImmutableHolder's constructor during object creation, it is a younger member field (of type ImmutableHolder.Hashtable<Integer, String>) that is referencing an older object in the client code (of the same type Hashtable<Integer, String>). This is a much better position to be in as far as the garbage collector is concerned. Note that a shallow copy is used in this case to preserve references to the older value.

Avoid Large Objects

The allocation for large objects is expensive and the initialization cost is proportional to their size. Sometimes large objects of different sizes can cause fragmentation issues or non compacting collect.

Do Not Use Direct Buffers for Short Lived, Infrequently Used Objects

The new IO classes (NIO) in java.nio allow the creation and use of direct buffers. These buffers tremendously increase throughput for repeated IO activities, however, their creation and reclamation for one-time use is more expensive than heap based non-direct buffers. This is because OS specific native code is used to manage them. An OutOfMemoryError may result if large objects are allocated frequently using this technique. Direct buffers are also not subject to Java's garbage collector which may cause memory leaks.

Noncompliant Code Example

This noncompliant code example uses a short-lived local object buffer. The buffer is allocated in non-heap memory and is not garbage collected.

ByteBuffer buffer = ByteBuffer.allocateDirect(8192);
// use buffer once

Compliant Solution

This compliant solution uses an indirect buffer to allocate the short-lived, infrequently used object.

ByteBuffer buffer = ByteBuffer.allocate(8192);
// use buffer once
Nulling References

Reference nulling to "help the garbage collector" is unnecessary. In fact, it just adds clutter to the code and sometimes introduces subtle bugs. Assigning null to local variables is also not very useful as the Java Just-In-Time compiler (JIT) can equivalently do a liveness analysis. A related bad practice is to use a finalizer to null out references. This practice can cause a huge performance hit.

Noncompliant Code Example

This noncompliant code example assigns null to the buffer array.

int[] buffer = new int[100];
doSomething(buffer);
buffer = null  // No need to explicitly assign null

Compliant Solution

This compliant solution improves by narrowing down the scope of the variable buffer so that the garbage collector collects the object as soon as it goes out of scope [[Bloch 2008]].

{ // limit the scope of buffer 
  int[] buffer = new int[100];
  doSomething(buffer);
}

Array based data structures such as ArrayLists are exceptions because the programmer has to explicitly set a few of the array elements to null to indicate their absence or demise.

Long-Lived Objects Containing Short-Lived Objects

Always remove short-lived objects from the long-lived container objects when the task is over. For example, objects attached to a java.nio.channels.SelectionKey object must be removed when they are no longer needed. Doing so reduces the possibility of memory leaks.

Do Not Explicitly Invoke the Garbage Collector

The garbage collector can be explicitly invoked by calling the System.gc() method. Even though the documentation says that it "Runs the garbage collector", there is no guarantee on when the garbage collector will actually run because the call only suggests that it will subsequently execute. Other reasons include the following:

  • Irresponsible use of this feature can severely degrade system performance as the garbage collector would not wait until ripe periods when it is safe to garbage collect without interrupting the program's execution significantly.
  • The application does not have enough information available on when to call System.gc().

In the Java Hotspot VM (default since JDK 1.2), System.gc() does an explicit garbage collection. Sometimes these calls are buried deep within libraries and are hard to trace. To ignore the call in such cases, use the flag -XX:+DisableExplicitGC. To avoid long pauses while doing a full GC, a less demanding concurrent cycle can be invoked by specifying the flag -XX:ExplicitGCInvokedConcurrent.

There are some exceptions to this recommendation. The garbage collector can be explicitly called when the application goes through several phases like the initialization and the ready phase. The heap needs to be compacted between these phases. Given an uneventful period, System.gc() may be explicitly invoked in this case. Also, it may be invoked as a last resort in a catch block to recover from an OutOfMemoryError.

Risk Assessment

Misusing some garbage collection utilities can cause Denial Of Service (DoS) related issues and severe performance degradation.

Guideline

Severity

Likelihood

Remediation Cost

Priority

Level

OBJ13-J

low

likely

high

P3

L3

Automated Detection

TODO

Related Vulnerabilities

GERONIMO-4574

Bibliography

[[API 2006]] Class System
[[Commes 2007]] Garbage Collection Concepts and Programming Tips
[[Goetz 2004]]
[[Lo 2005]]
[[Bloch 2008]] Item 6: "Eliminate obsolete object references"
[[MITRE 2009]] CWE ID 405 "Asymmetric Resource Consumption (Amplification)"


OBJ12-J. Use checked collections against external code      08. Object Orientation (OBJ)      OBJ14-J. Encapsulate the absence of an object by using a Null Object

  • No labels