Hi Volker!

> On 28 Apr 2023, at 16:38, Volker Simonis <volker.simo...@gmail.com> wrote:
> 
> I think it is a little unfortunate to put the usage of s.m.Unsafe and
> JNI/Instrumentation/JVMTI into the same category, especially when it
> comes to blaming developers for their usage. While s.m.Unsafe has
> always been an internal, undocumented and unsupported API, the latter
> three are part of the Java Platform (e.g. "native" is a Java keyword
> and Runtime.loadLibrary() is part of the Java API).

To have integrity by default, theses must all become restricted. In fact — not 
just them. Even the fully-official and brand-new FFM API, that a lot of 
investment has gone into very recently, must also be restricted. 

That these features must be restricted doesn’t mean they’re wrong or bad. It 
just means that they're superpowers, and so the user must acknowledge the 
choice to use them over the loss of integrity. Native libraries are good and 
integrity is good, but because they’re in contradiction, there must be a switch 
expressing the user’s preference like other switches offered by the runtime to 
select between alternatives.

Unsafe, on the other hand, may become more than just restricted over time. It 
may gradually be emptied out until it’s gone.

> 
> Do you really plan to make JNI an optional feature which will have to
> be manually enabled at startup?

Not optional at all, but an important, useful feature that is restricted; JNI’s 
replacement, FFM will be restricted, too (in its use of native libraries). The 
restriction of FFM is already mentioned in JEP 442. Another JEP addressing JNI 
will be published soonish.

> What will be the benefit?

Integrity. The ability to to bypass encapsulation when needed is not being 
taken away, but we need the new ability to establish and enforce invariants. We 
don’t yet have it.

> I understand
> that in an ideal world where you had no user-supplied JNI libraries at
> all, you might be able to perform more/better optimizations. But as
> you'd have to support JNI anyway, wouldn't the maintenance of the
> resulting code become a nightmare. How many "if (JNI) {..} else {..}"
> would we get?

There’s no need for such code. Modules that need JNI will use JNI. The 
application will simply give them permission to do so with 
--enable-native-access=MODULE-NAME, as it would also do to allow FFM to use 
native libraries.

> And what would be the benefit of disabling it by default
> for the user except increased "integrity"?

Not disabled, restricted, and integrity is the benefit for the user, e.g. in 
the form of programs not breaking (or breaking less) when upgrading the JDK. 
Integrity is required for the platform to continue evolve while keeping the 
ecosystem sustainable.

> I.e. do you have some
> concrete examples of planned features X, Y, Z which will only work
> with disabled JNI?

Not disabled, restricted. Like all encapsulation-breaking restricted 
superpowers, allowing them might have implications on possible Leyden features. 
For example, if a private method could be accessed from outside a module — 
whether through deep reflection or JNI — private methods could not be removed 
at link time.

> Will these features be Java SE features or
> implementation specific OpenJDK-only features?

As with all integrity and strong encapsulation features, all limitations will 
be part of the platform spec.

We realise that in each individual case there might be good reasons to allow 
knocking down encapsulation barriers. But whereas every application and library 
author rightfully want minimise the burden on their particular code, such 
individual decisions inevitably lead to a tragedy of the commons (as they 
already have). We must strive to minimise the overall burden integrated over 
the entire ecosystem *as a whole*. So the platform will have the right defaults 
for the ecosystem, and every application would be able to relax encapsulation 
to suit its particular needs.

Most Java program don’t use native libraries, agents (startup or dynamic), or 
deep reflection. Many do, and these features are very powerful and can be very 
useful, but with great power comes great responsibility, and that 
responsibility falls on the *application*. Libraries must not silently impose 
that responsibility on the application in a way that makes it infeasible to 
exercise.

Moreover, most encapsulation boundaries are never bypassed, but without 
integrity by default, the platform and its users still can’t be certain that 
code means what it says as long as any fourth-level dependency can decide on 
its own that any line of code in the program might mean something else.

> 
> I don't think it is fair to assume that profilers are the only "valid"
> use case for agents and imply that all other use cases are a mis-use
> of the API.

We are not assuming that at all. Only the use of *dynamically loaded* agents 
*by libraries* is misuse. Dynamically loaded agents were specifically designed 
to support serviceability tools, not to allow libraries to circumvent the need 
to ask the application for permission to break encapsulation.

> 
> I don't understand this "Non-Goal"? The Attach API [1] allows to
> dynamically attach to a running JVM and "Once a reference to a virtual
> machine is obtained, the loadAgent, loadAgentLibrary, and
> loadAgentPath methods are used to load agents into target virtual
> machine". So how can you achieve this JEP's goals without
> changing/restricting the Attach API? I therefore think this "Non-Goal"
> should be rephrased to explain which parts of the Attach API will be
> changed and moved to the "Goal" section instead.

It says “for monitoring and management purposes.” These purposes don’t require 
dynamically loaded agents. They rarely require agents at all, but when they do, 
they only need agents loaded at startup.

> 
> General comments:
> 
> - You go into great detail to explain why a human-operated tool is
> "superior" (in the sense of trust and security) to a library and
> "would ideally not be subject to the integrity constraints imposed on
> the application". I can't follow this argument, because both, the
> decision to use a specific tool as well as the decision to rely on a
> library is taken by a human.

A tool is not superior. Only:

1. Most libraries that break encapsulation are not chosen by application 
authors. They are usually low-level libraries chosen by the authors of the 
libraries that the application uses, i.e. they’re transitive dependencies. I 
don’t think that applications in the JDK 8 timeframe became non-portable as a 
result of a conscious choice. Moreover, it is practically infeasible to 
actually know everything the code you use may do even if you want to. So not 
only do application authors not know what libraries do (especially deep 
dependencies), but they *can’t* feasibly know.

2. You expect a mechanic to tune your car engine but you'd probably be 
surprised to learn that the little tree air freshener climbs down from the 
rearview mirror at night and crawls into the engine to make modifications. When 
an operator uses a serviceability tool, they expect it to open up the box and 
rummage through internals. That’s what servicing often means, in software as in 
the physical world. They do not expect that of libraries.


> I'd even argue that the decision to
> depend on a specific library which requires the dynmaic attach
> mechanism is taken by a more knowledgeable user (i.e. the developer
> himself). Of course both, a tool as well as a library can contain
> malicious code, but I don't see a fundamental difference between the
> two.

Malicious code is not a concern at all; we assume all code — whether in tools 
or libraries — is trusted and benevolent. (Even when looking at the security 
aspect in the server side ecosystem overall, malicious code amounts to a 
minuscule portion of the danger, judging by the number of attacks. When it 
comes to server security, benevolent code poses a much greater risk than 
malicious code, as the vast majority of security attacks exploit 
vulnerabilities in benevolent, trusted code. Of course, benevolent code imposes 
other risks covered in the JEP that are unrelated to security). 

Knowledgeable users who want to allow a library to arbitrarily change the 
meaning of code in the application are free to give it the permission to do so. 
But too many applications don’t even know that a dependency of a dependency of 
a dependency of theirs does it, and so the permission to do it cannot be the 
default.

> 
> - You may argue that users have to be protected from malicious
> libraries which gain their superpowers by secretly loading agents at
> runtime.

Again, malicious code is largely a non-issue for Java since Applets were 
removed.

Since you brought up malicious code in previous conversations, too, let me 
repeat that again: Even though there have been some software supply chain 
attacks on various language ecosystems, malicious code poses a relatively small 
risk to Java nowadays and it is *not* a major concern (at least for the 
moment); most risks — including security risks — are due to nice, helpful code.

> But users who don't know and don't care about their library
> dependencies will just as easy and without reflection (pun intended :)
> add the -XX:+EnableDynamicAgentLoading to their command line arguments
> (making this the new, most often used command line option even
> surpassing the usage of --add-opens :)

Adding permissions by “cargo cult” is, indeed, a problem, but at the very least 
the command line would still offer an auditable record of the risks taken up by 
the application. Responsible companies know that in some situations they may be 
held accountable for their technical decisions and deviations from recommended 
practices, and will have mechanisms in place to review command-line permissions 
just as they review code.

As a general rule, while we certainly want to help users do the right thing, we 
must first give those who want to do the right thing the ability to do so. 
Without strengthening strong encapsulation, even someone who really wants to 
know the integrity risks is unable to do so without an infeasible analysis of 
ever line of code in the application and all of its dependencies.

Moreover, because quite a few application authors do want to carefully consider 
risks, the fact that they need to explicitly accept more risk to use certain 
libraries would put pressure on libraries to reduce their superpower demands.

> 
> - I still can't understand the benefit of "only" changing the default
> behavior for dynamic agent loading. I could understand this if you'd
> do it with a plan to deprecate and completely remove the dynamic agent
> loading capability. But what are the benefits of changing the default
> if you'll have to support the functionality anyway?

The application can choose to knock down encapsulation barriers as it wishes 
(after all, it can even modify the Java runtime as it controls it), but we want 
the command line to offer a map of the codebase and its abilities. You get 
integrity by default, and an auditable record of the encapsulation choices 
always. We want tools to have superpowers, and it’s even arguably okay for 
certain libraries to be granted superpowers in certain situations provided that 
it’s done with the application’s explicit consent. It’s just that the situation 
where superpowers are given silently and by default has become untenable for 
the ecosystem as a whole.

> As mentioned in
> earlier discussions, my main concern with the proposed change is the
> impact it will have on the evolution of Java. Java's dynamic features
> are one of its biggest strength and a major reason for its success.

That’s right, but the Why Now? section covers that in detail. In short, the 
times — they are a changin’, and Java must be a changin’ with them. Even 
putting aside the new requirements and more Java-in-Java, the old situation has 
become untenable as we saw in the 8 -> 9+ migration. The reason the old way 
worked — until it didn’t — was that for a long while (the 6-8 timeframe) Java 
was relatively stagnant.

However, that relative stagnation didn’t just allow the encapsulation 
free-for-all to work; it’s also what made much of it necessary in the first 
place, to work around shortcomings in the JDK’s development. So not only can we 
not continue with the old regime, but there’s not as much need for it anymore.

However useful dynamism is at times, we must have the ability to control it. 
The faster Java evolves, the more important that control becomes. The most 
important thing to remember is that the need for integrity doesn’t come from 
some theoretical desire for architectural cleanliness, but from real user 
needs. Users want smoother upgrades; they want robust security; they want more 
features, and they want new kinds of features that reduce startup time. All 
these things require control over Java’s dynamism. Some users may want 
dynamism, too, but since these desires are in conflict, applications must 
choose between them, and that’s the idea of integrity by default (dynamism by 
default and integrity by choice can’t work because of the structure of the Java 
ecosystem).

> Sacrificing some of them or making their usage increasingly expensive
> requires a broader discussion in the community and shouldn't happen
> "under the hood" of a discussion about the default setting of a
> command line flag.

First, while requiring an auditable map of the codebase certainly does require 
some effort, let’s not exaggerate it. We’re talking about cost that is 
negligible compared to that of developing software, cost that is imposed only 
on those who want or need to use relatively advanced superpowered features, and 
cost that results in a something of value: a map of the codebase and its 
permissions. But yes, some will be inconvenienced by this, but so are those who 
cannot easily upgrade JDK versions due to non-portable libraries. You can call 
it a sacrifice if you wish, but whatever we do or don’t do *someone’s* 
convenience will be sacrificed, and this direction reduces rather than 
increases the overall sacrifice. That’s exactly why this is the direction — we 
want to reduce the overall pain for users and increase their value.

Second, that lengthy discussion about this direction already took place over 
years when Jigsaw was under development (as did the discussion about agents in 
particular). What was missing was a summary in JEP form, hence the 
informational JEP. We’ll post the JEP to jdk-dev once we finish writing it, but 
it describes a path that Java has already been on for several years.

> 
> - I don't understand why this JEP has scope "SE". As you rightly
> mentioned, the Attach API is a "non-standard" API which can be changed
> at any time and without affecting the Java SE specification, so this
> JEP should rather have scope "JDK" instead. On the other hand, the
> fact that this functionality is not governed by the SE specification
> will allow different OpenJDK distributors to use a different default
> setting for -XX:EnableDynamicAgentLoading which has the potential to
> cause a lot of confusion if we can't sattle on a common strategy.

The Attach API is JDK-specific, but agents are an SE feature (well JVM TI is 
optional), and the platform spec will say something along the lines of “if an 
implementation offers a way to attach an agent to a running JVM instance, that 
capability must be disabled by default and enabled with an explicit flag”.

> 
> - If doing this change at all, I think it would be better to do it in
> a non-LTS release first.

LTS is a service governed by Oracle Sales or something like that on the 
business side of things. OpenJDK has no concept of LTS. Nevertheless, given 
that for various business reasons more people are likely to be using JDK 21 
than other versions, we can take that expected popularity into account. See my 
reply to Dan.

— Ron

Reply via email to