TLDR: if capabilities are kept in separate modules it mitigates the risk of a capability being compromised.
Everyone’s talking about Log4Shell. A lot of the talk is, of course, about the
nature of code injection attacks, and the failure to separate code and data.
It’s an area where I feel sympathy for the Log4J 2 devs - I think the mistake
they made is a terrifyingly easy one to make, as every SQL injection or naked
eval regularly demonstrates. Layers of abstraction can lead to forgetting when
an input to a function is now untrusted data.
However, I think there’s another aspect of it that deserves consideration - the fact that the ability to do this was installed on so many systems.
How many systems need to do LDAP JNDI lookups at all? I’m guessing a pretty small percentage. How many need to do so from their logging system? A smaller percentage. And of those, how many need to load complex classes (with static initialisation) from a remote location via their logging system? I’m guessing an absolutely tiny fraction of the systems vulnerable to Log4shell were actually benefiting from the feature(s) that caused the vulnerability.
And yet there the code was, sitting on all those systems, waiting for the moment someone found the vulnerability.
I’ve been experimenting with JPMS and jlink, and if you create a JRE without the
java.naming module then, unsurprisingly, you aren’t vulnerable to Log4Shell
even if you’re running an old Log4J 2 version. But inertia and a reasonably high
barrier to entry means that adoption of JPMS in general, and adoption of jlink
cut down JREs in particular, has been pretty minimal, unfortunately. And some of
java namespaced modules are still disconcertingly enormous - for instance
using Java Beans (as lots of things, including Log4J 2, require) means bringing
java.desktop, which brings in the
amongst others. Perhaps rather more than you expected to call
set on some data
However, this could have been managed at the Log4J 2 level.
JndiManager is in
org.apache.logging.log4j.core.net package in the
log4j-core jar, along
with rather a lot of things capable of doing I/O over multiple protocols. So you
can’t use Log4J 2 without having this logic installed. Had JNDI lookups been in
its own little jar, brought in as a separate dependency, the bug would still
have been in that code - but I’d hazard a guess that it would have been
drastically mitigated because most systems would not have had it sitting there,
a little otherwise pointless time bomb waiting to explode.
There’s a tension here with ease of use, and particularly keeping a low barrier to entry. It is nice when it turns out you can just use a feature without scrabbling around trying to work out which dependency you need to add to get it to work. But having so many features lying around unused can have a high price.
Keep it small. Keep it focussed. Leave it out by default. If you need a feature 99% of users do not need, it is entirely reasonable that you should need to add a new dependency for it to start working.