Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.
Comment: Eviscerated the intro.

...

Many classes also offer invariants, which are guarantees made about the state of their objects' fields upon the completion of any of their methods. For instance, classes whose member fields may not be modified once they have assumed a value are called immutable classes. An important consequence of immutability is that the invariants of instances of these classes are preserved throughout their lifetimes.

A fundamental principle of object-oriented design is that a subclass that extends a superclass must preserve the invariants provided by the superclass. Unfortunately, design principles fail to constrain attackers, who can (and do) construct malicious classes that extend benign classes and provide methods that deliberately violate the invariants of the benign classes.

Similarly, classes can rely on invariants in order to properly implement their public interfaces. These invariants might relate to the state of member fields or the implementation of member methods. Generally, classes can rely on encapsulation to help maintain these invariants, for example, by making member fields private. However, encapsulation can be incompatible with extensibility. For example, a class designer might want a method to be publicly accessible, yet rely on the particulars of its implementation when using it in another method within the class. In this case, overriding the method in a subclass can break the internal invariants of the class.

Therefore, extensibility caries with it two significant risks: a subclass can fail to satisfy the invariants promised to clients by its superclass, and it can break the internal invariants on which the superclass relies. For instance, an immutable class that lacks the final qualifier can be extended by a malicious subclass that can modify the state of the supposedly immutable object. Furthermore, a malicious subclass object can impersonate the immutable object while actually remaining mutable. Such malicious subclasses can violate program invariants on which clients depend, consequently introducing security vulnerabilities.

To prevent misuse, classes with invariants on which other code depends should be declared final. Furthermore, immutable classes must be declared final.

Note that the same can be said for a benign subclass which mistakenly supports mutability. The risks above relate to both benign and malicious development.

To mitigate these risks, classes must be declared final by default. Developers should permit extensibility only when there is a perceived need for it and must, in that case, carefully design the class with extensibility in mind. As a specific instance of this rule, classes that are designed to be treated as immutable must either be declared final or all of their member methods and fields must be declared final or private.

In systems where code can come from mixed protection domains, some superclasses might want to Some superclasses must permit extension by trusted subclasses while simultaneously preventing extension by untrusted code. Declaring such superclasses to be final is infeasible because it would prevent the required extension by trusted code. Such problems require careful design for inheritance.

Consider two classes belonging to different protection domains: one is malicious and extends the other, which is trusted. Consider an object of the malicious subclass with a fully qualified invocation of a method defined by the trusted superclass, not overridden by the malicious class. In this case, the trusted superclass's permissions are examined to execute the method, and, as a result, the malicious object gets the method invoked inside the protection domain of the trusted superclass [Gong 2003].

One commonly suggested solution approach is to place code at each point where the superclass can be instantiated to ensure check that the instance being created has the same type as the superclass. When the type is found to be that of a subclass rather than the superclass's type, the checking code performs a security manager check to ensure that malicious classes cannot misuse the superclass. This approach is insecure because it allows a malicious class to add a finalizer and obtain a partially initialized instance of the superclass. This attack is detailed in class being instantiated is either the superclass itself or a trustworthy subclass. However, this approach is brittle and is only safe in Java SE 6 or higher. See rule OBJ11-J. Be wary of letting constructors throw exceptions.For non-final classes, the method that performs the security manager check must be invoked as an argument to a private constructor to ensure that the security check is performed before any superclass's constructor can exit. For an example of this technique, see rule OBJ11-J. Be wary of letting constructors throw exceptions for a full discussion of the issues involved.

Noncompliant Code Example (BigInteger)

...