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
andreadResolve
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:
- Finalizers are non-deterministic.
- A malicious subclass can run its finalizer method on a partially constructed corrupt superclass object.
- Moving the corrupt object's reference to a static field doesn’t let the JVM garbage collect.
- 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!