Rahul
Rahul

Reputation: 849

Why do Java finalizers have security problems?

I'm reading Effective Java by Joshua Bloch. In ITEM 8: AVOID FINALIZERS AND CLEANERS of CHAPTER 2 he states:

Finalizers have a serious security problem: they open your class up to finalizer attacks.The idea behind a finalizer attack is simple: If an exception is thrown from a constructor or its serialization equivalents—the readObject and readResolve methods (Chapter 12)—the finalizer of a malicious subclass can run on the partially constructed object that should have “died on the vine.” This finalizer can record a reference to the object in a static field, preventing it from being garbage collected. Once the malformed object has been recorded, it is a simple matter to invoke arbitrary methods on this object that should never have been allowed to exist in the first place. Throwing an exception from a constructor should be sufficient to prevent an object from coming into existence; in the presence of finalizers, it is not. Such attacks can have dire consequences. Final classes are immune to finalizer attacks because no one can write a malicious subclass of a final class.

Firstly, I know finalizers have been deprecated since Java 18. Nevertheless, I think it's important to understand the reason behind this decision. My understanding of the excerpt above is as follows:

  1. Finalizers are non-deterministic.
  2. A malicious subclass can run its finalizer method on a partially constructed corrupt superclass object.
  3. Moving the corrupt object's reference to a static field doesn’t let the JVM garbage collect.
  4. The attacker can use this object that should've “died on the vine” and do as they will. Thus, the security flaw.

And secondly, I hope my conceptual understanding of the issue is correct. However, Bloch hasn't demonstrated this issue in a tangible code example. Perhaps because he doesn't want us to mess around with the finalize mechanism in Object.

Could you please demonstrate this to me in code?

For instance, if I have a superclass:

/** Superclass */
public class DemoSecurityProblem {

}

And then the subclass either by inheritance or composition:

public class MaliciousSubClass extends DemoSecurityProblem {
    DemoSecurityProblem demoSecurityProblem = new DemoSecurityProblem();
}

How can an attacker exploit this via the finalize mechanism?

Thanks a lot!

Upvotes: 6

Views: 1726

Answers (1)

Holger
Holger

Reputation: 298123

Your description is basically correct, but overcomplicating things. There is no need to store something in a static variable; as soon as the finalize() method is invoked, the object is already resurrected, as invoking a method on an object implies invoking code with access to the object.

Storing the object reference in a variable is a way to expand the lifetime beyond the execution of the finalize() method but this is not a necessary thing for the attack. Also, instead of using a static variable, the attacker could also make the subclass an inner class and store the reference in the still reachable outer object.

So the following program is already enough to demonstrate the issue

public class FinalizerAttackExample {
    public static void main(String[] args) throws InterruptedException {
      try {
          new MaliciousSubclass();
      } catch(SecurityException ex) {
          System.out.println("wouldn't get hands on a ResourceClass instance");
      }
      System.gc();
      Thread.sleep(2000);
    }

    static class ResourceClass {
        ResourceClass() {
            if(!checkCaller()) throw new SecurityException();
        }
        public void criticalAction() {
            System.out.println("ResourceClass.criticalAction()");
        }
    }

    /** For our demonstration, all callers are invalid */
    static boolean checkCaller() {
        return false;
    }

    static class MaliciousSubclass extends ResourceClass {
        @Override
        protected void finalize() {
            System.out.println("see, I got hands on " + this);
            criticalAction();
        }
    }
}

While garbage collection is non-deterministic and the execution of finalizers not guaranteed in general, this example will print

wouldn't get hands on a ResourceClass instance
see, I got hands on FinalizerAttackExample$MaliciousSubclass@7ad74083
ResourceClass.criticalAction()

on a lot of implementations, demonstrating that criticalAction() could be invoked on an object that shouldn’t exist as the constructor threw an exception.

Upvotes: 7

Related Questions