Hi Mike!

On 30 Apr 2023, at 19:59, Mike Hearn <m...@plan99.net<mailto:m...@plan99.net>> 
wrote:

we’ve begun to explore means other than the flag to allow a tool to load an 
agent at runtime

How about restricting access to the jcmd socket. For in-VM code it can
be blocked at the filesystem implementation level, and for
sub-processes by using the operating system APIs to determine if the
other side of the socket is part of the same process tree at connect
time. This would avoid the need for new UI to re-enable existing jcmd
functionality, whilst preventing code loaded into the VM from
connecting back to that same VM. Only truly external tools could
trigger agent loading, or modules that had been given permission to do
that.


Determining the process on the other side and/or maintaining the integrity of 
the process tree is not easy on all OSes.

That is assuming native code is restricted of course but that's a
whole other kettle of fish. I don't quite understand how the plan is
meant to work when it reaches that stage. JNI is so widely used and so
many libraries would break that you'd either just immediately see
workarounds appear, like build systems concatenating lists of flags
from META-INF files, or it'd just cause a lot of chaos and upgrade
rejection. It'd also have to be dynamically controllable for plugins,
so then you'd also get people running their apps inside classloaders
that auto-grant any request to load native code.

No libraries will break and no workarounds would be possible once all loopholes 
are closed. All you’d need to do is grant the module permission to load/use 
native libraries, be it through JNI or FFM.

Build tools may do whatever they want, but it’s important to remember that the 
low-integrity free-for-all worked more-or-less only while Java was relatively 
stagnant. Once the platform started evolving more rapidly we saw many 
applications break in the Java 8 -> 9 upgrade due to deep dependencies that 
relied on JDK internals in the days before strong encapsulation.


There must be a better way?

Before thinking about other ways, I think it’s best to first acknowledge the 
problem, and that is that we wish for two fundamentally contradictory things: 
On the one hand, we want it to be easy to break other people’s code’s 
encapsulation when we need to, and on the other we want features that 
fundamentally depend on encapsulation not being broken, such as smooth 
upgrades, robust security mechanisms, and future Leyden features. Both may be 
good, but they are in conflict. Because we can't to have our invariants and 
break them too, someone must choose between one and the other.

But I also don’t think it’s a very complicated choice. The view of the Java 
ecosystem as a hotbed of encapsulation-breaking is gradually becoming outdated. 
Much of the necessity to break encapsulation came about due to the platform’s 
stagnation in the JDK 6-8 timeframe. While the ability to break encapsulation 
is being restricted, the problems that required it in the first place are also 
being addressed. The number of uses that require breaking strong encapsulation 
is shrinking.


Maybe the problem can be recast as one of build-time observability?
One concept I experimented with for my own build system is the notion
of "control reports", a generalization of lockfiles. The build
generates text files describing things you care about, and these are
then checked in to vcs. There is a build mode that doesn't write the
generated reports on disk, instead it verifies there's no difference
and exits if there are. By checking the reports into version control
and using OWNERS files + code reviews various policies can be
enforced.

In this context it'd work like so: the JVM would be changed to look
for a flags file in the JAR containing the main class/main module as
part of its startup. JVM flags can thus be shipped as part of the app,
formalizing an already existing convention that's today implemented
with shell scripts. Build systems can generate this flag file from
their existing lists of JVM flags, and/or compute it automatically by
merging flag files found in any library JARs. For people who don't
care about deprivileging libs (prototyping, learners, people who only
use in-house code etc) this will keep things working as it does today.

A main JAR, when used, is already given control over some command-line flags 
via its manifest, including strong encapsulation controls. That’s fine, because 
it’s controlled by the application, not libraries.

For people who wish to use this new security feature

It’s not a security feature. It’s an integrity feature.

they can review
the generated flag file and check it in. Now if they add a dependency
on a library that needs to use panama, jni, an agent, opened packages
etc, this will become visible as added lines in the flag file, can be
pushed back on when the commit is reviewed, and by placing an OWNERS
file in the directory containing the report tech leads can allow
delegates to add dependencies without being able to change JVM flags.

I think what you’re describing is an automated generation of what the 
informational JEP calls the codebase map (i.e. the command-line) based on what 
libraries want. Such automated permission-management could perhaps be 
appropriate for a security mechanism for mobile phone apps — requesting access 
to files, for example, is quite common — but not so much for integrity, as the 
need to break encapsulation is supposed to be abnormal and rare; it is 
certainly discouraged, and we believe that it will become less and less 
necessary (currently, serialization is probably the biggest issue, but we’re 
getting closer to a point where it won’t be: 
https://openjdk.org/projects/amber/design-notes/towards-better-serialization).

Of course, tools outside the JDK can offer such a mechanism, but if the number 
of libraries requiring bypassing of encapsulation continues to decline that may 
be a rather elaborate solution to a rather small problem. Personally, I think 
it might be best to see where the ecosystem is in a few years.

— Ron

Reply via email to