Questions w.r.t. plans to restrict the FFM API

Maurizio Cimadamore maurizio.cimadamore at oracle.com
Thu Apr 29 15:52:38 UTC 2021


Hi Mike,
a disclaimer first - while I understand that JEP 411 (Deprecate the 
Security Manager for Removal) and JEP 412 (Foreign Function & Memory API 
(Incubator)) partially _do_ overlap, I'd like to point out that the 
approach proposed in JEP 412 has evolved rather independently from JEP 
411 - and is, instead, an evolution of the command line option that has 
been, in Java 15 and 16 required anyway when interacting with low lever 
memory access features, or the foreign linker (this flag used to be 
-Dforeign.restricted=permit).

So, I will refrain from commenting on security manager here, and I will 
focus on the specifics of the FFM needs.

On 29/04/2021 15:48, Mike Hearn wrote:
> The word "safe" has had a variety of meanings in the past but with the
> demise of the SM "safe" is only really means robust against developer
> mistakes. It's safety in the sense of rounded corners or airbags in cars
> and not in the sense of locked doors. And ultimately that is really about
> the quality of the debugging experience, or perhaps robustness in the field
> against unexpected bugs that can be caught and ignored e.g. IntelliJ is
> quite good at recovering from exceptions in plugins.

Here you are making an assumption: the kind of safety we want to enforce 
here is not too important. JEP 412 goes in quite a bit of lengths in 
showing exactly the opposite: having unrestricted access to native code 
in the way that JNI allows today is a permanent hole in the safety of 
the Java platform. This has ramifications not only in the terms of 
crashes that users will get when interacting (badly) with such native 
code (with potential to e.g. overwrite JVM's own data structure) but 
also in terms of limiting or inhibiting certain optimizations: if JNI 
code can set final fields, the optimizing compiler cannot trust them. So 
that's the price you pay for having a platform which is effectively 
unsafe by default (since JNI is enabled by default). "Putting an airbag 
in a car" is just not the right analogy here, I'm afraid.

 From here, the principle: access to potentially unsafe features should 
be disabled _by default_ (unlike JNI). We can quibble over which 
mechanism is used in order to opt-in (maybe a command line flag is not 
the way, maybe it is) but we do need an opt-in, as opposed to just make 
it unsafe by default.

>
> So the --enable-native-access flag is about signalling to the VM that I'm
> willing to make the crash experience worse in order to use a particular
> module. It can't actually *stop* anything because the JVM is giving up on
> restricting malicious code, so the flag only has meaning if people are
> playing along. Without the SM a malicious module can subvert the JVM in
> many ways e.g. by overwriting the application's own JAR file, or loading an
> agent, or attaching to the VM. So the flag is a form of confirmation, "are
> you sure? yes/no" type dialog but in the CLI context. If someone wants to
> use a module that needs native code then they're always going to set it.
Again I see a certain tendency of viewing safety as just a synonym for 
"better error recovery". As explain above, that's not it.
>
> Is a CLI flag the best way to ensure developers know they're risking the
> nice debugging experience?
Ok, this is a fair question - no objections here.
>
> Pros:
>
> 1. It's an explicit action that the user must take to opt-in.
>
> Cons:
>
> 1. Existing module metadata formats (POMs) don't support requesting command
> line flags. Therefore, library authors must communicate this via their
> documentation, and that need must be propagated up through the
> documentation of every library at every level of the dependency hierarchy.
> This is a lot of work and opens up many opportunities to forget it or
> overlook it, especially if you bring in a new version via a routine upgrade
> and don't re-check the upstream README. Due to the freeform nature of
> documentation there's no way to improve this situation with tooling.
I believe you are placing the burden of opting-in  in the wrong place. 
The places where opt-in will occur will be the "applications" which 
package one or more libraries using FFM. These applications will already 
come with launchers, or will be generated via tools like `jpackage`. I 
don't think adding an extra flag "in the application launcher" 
represents that much of an issue. For instance, I look at IntelliJ which 
is installed on my system; it comes with a pretty sizeable "sh" launcher 
which fires up the VM with the "right" configuration. All I'm saying is 
that applications are already providing launchers which add required 
options to the `java` launcher, according to their needs. What JEP 412 
proposes doesn't seem to leave these use cases particularly worse off.
>
> 2. Dynamically loaded plugins cannot alter the JVM command line flags.
> Thus, there is no way to make plugins that use native code with this
> approach. That's a major loss of functionality. A significant part of the
> Java ecosystem will *never* abandon JNI if the replacement requires the
> user to manually change JVM flags to use a new plugin. Consider for
> example, how painful this would be for Gradle or Maven, where almost all
> functionality is in the form of plugins. If a build system plugin is 1 line
> of code to install when using JNI, or an extended operation involving
> reconfiguring global settings files for every developer on the project when
> using FFM, plugin authors will just continue using the old JNI API if they
> care about their user experience.

Three things here:

* an application which supports plugins might not know, at startup, 
which modules will require native access. This is an issue, I agree. But 
it's just that we didn't go very far e.g. in the reflective support. 
Maybe it will be possible, for a module that is "inside the bubble" to 
grant (reflectively) native access to other modules.
* you don't see the flipside; consider now an application which supports 
plugins but which, for whatever reason, _doesn't want_ plugins to access 
native code. The proposed solution gives you a way to do that (if required).
* "A significant part of the Java ecosystem will *never* abandon JNI if 
the replacement requires the user to manually change JVM flags to use a 
new plugin" - maybe, maybe not, who knows? I always have problem with 
such general statements - what evidence do we have to back that claim 
up? Does that change if, at some point, the same flag will be required 
for JNI access too (which is something JEP 412 alludes to). One of the 
reasons as to why this API is still incubating [1] is exactly that - to 
allow for more feedback from real world use cases, and re-adjust based 
on concrete feedback.

>
> 3. It's (presumably?) not statically enforced. The check that a piece of
> code has access to a restricted API would happen at the time it's called,
> using reflection. This means either the API should be used carefully by
> users to avoid hitting too many access control checks, e.g. by not
> constantly creating new objects but rather by using a capability-oriented
> approach.
Again, support for the mechanism described in JEP 412 is still raw, but 
(i) the check doesn't cost much (optimized by C2 as part of 
@CallerSensitive machinery), and (ii) we have other building blocks in 
the pipeline: at some point we envision that it should be possible to 
mark modules requiring restricted methods as "native" (this might be 
done implicitly, by javac, or explicitly, in the module-info). When such 
tagging of modules occurs, we are now in a position to "fail fast": if a 
module is native, but is not listed in the command line flag we fail as 
soon as the application starts (as opposed to when the first restricted 
call occurs). Of course for stuff in the classpath there's not much we 
can do.
>
> 4. If a module is split/refactored the users will all break even if a
> "requires transitive" module is left in place under the old name, because
> the --enable-native-access will apply to the empty transitive module rather
> than the new module where the FFM API is used.
True, this is a sore point - the mechanism we have now is not very 
friendly w.r.t. shuffling code between modules. That said, again, this 
is mostly something that the application packager will have to think 
about - and the actions to fix these kind of mismatches should be 
trivial enough. Not to completely dismiss your point, but I think we 
have to be honest about whose usability are we concerned about.
>
> 5. That could be fixed by using stack walks but .... hmm, wait a minute.
> There's a feature in the JVM already that does this type of thing and is
> explicitly designed for making code safe in every sense of the word.
>
> It's hard to escape the feeling that this flag would end up as a sort of
> half-baked reinvention of the SecurityManager but less useful, because with
> the SM we can actually sandbox our libraries and protect ourselves from
> increasingly common supply chain attacks. It gives safety in *all* senses
> of the word, not just crash-safety. I won't try and argue for a stay of
> execution for the SM here, even though it seems more relevant now than in
> the past. But it does seem worth re-considering this flag. A couple of
> alternatives might be:
Again, crash safety is NOT the only thing we're after here - JEP 412 is 
pretty clear about that. And arguing against/for SM removal is not for 
this mailing list (as I explained JEP-412 and JEP-411 evolved rather 
independently).
>
> 1. No flag. If a library wants to use the unsafe FFM API then it just uses
> it and if my JVM segfaults, I realise that library is using buggy native
> code so switch to a competing library that's more robust. This seems fine.
> It's the same as how every other language/runtime does it, it's the same as
> how JNI works and frankly it's never really been a problem in my own work.
Yes, this is how JNI works, and is "as broken" as JNI. I've explained 
above why it is much better (maybe not for you specifically, but for the 
Java platform as a whole) to have potentially unsafe behavior disabled 
by default.
>
> 2. Don't deprecate the SM. Instead upgrade it to fix its problems and then
> put the unsafe FFM API under a permission. Random libraries off of Maven
> Central can be treated as genuinely "unsafe", which would make CISOs happy.
> But of course, it's also a lot of work.
I will read on ;-)
>
> 3. Putting the unsafe FFM API into a separate module so usage of it can be
> statically detected using JPMS metadata. Then it's up to the build system
> or IDE to show a warning if I add a dependency on a library that requires
> unsafe FFM API. This is better because the build system controls my
> dependencies, so it's helpful if it's also the place to ban dependencies
> for various reasons. Services like Maven/JetBrains Package Search can show
> unsafe modules in the UI this way too. Most importantly it solves the
> plugin problem. Now plugin loaders can study the module graph and (if they
> want to) alert the user if the module might be "unsafe". In practice I
> suspect nobody would, because - again due to the lack of the SM - the
> warning isn't actually actionable and the true meaning of "unsafe" would be
> hard to communicate. Arguably this type of safety doesn't matter to end
> users at all, because they won't be the ones debugging crashes. But there
> is at least an option to do so.

I believe this is the real deal in your email; let's leave aside 
considerations about crash-safety (it's not about that), or speculation 
about how many clients will migrate from JNI to the FFM API (we simply 
have no data to back any such claims at this point), or whether SM 
should be removed or not (which is outside the scope of this mailing 
list and JEP). The question is, _given we want an opt in mechanism_ is a 
command line flag the best way to provide such an opt in?

Your proposal of putting the FFM API into a separate module is a 
credible alternative: let's consider the FFM a low level API, and stash 
it in a different module. This module, like incubating modules, will be 
disabled by default. When added to the module graph, you will get some 
warning at runtime.

Does this meet the criteria? Yes and no. Let me expand on that.

* unsafe API are disabled by default - that's good
* as you say, it relies on the concept of modules not resolved by 
default, which we already have

but

* the entire FFM API (even its safe bits - basically the entire memory 
access API) are now disabled by default
* by putting things in a separate module will prevent us from e.g. 
adding more awareness to the new API points from class in in java.base - 
e.g. it would be nice to have j.l.Integer.LAYOUT -> MemoryLayout - but 
if MemoryLayout is defined elsewhere we can't do that (there are at 
least another 5-6 examples like that, and there will be more, I'm sure, 
as soon as the FFM API will land somewhere in java.base).
* if the module is not added to the graph, you get a generic sounding 
error like ClassNotFoundException - I think we'd like something more 
precise, like "native access X in class Y cannot occur because FFM 
module is disabled". But maybe this is just implementation.

You might be tempted to say (to avoid some of these problems): let's 
split the safe FFM API from the unsafe FFM API, and put the unsafe FFM 
API in a different module. This has been considered (and actually done 
before!). The problem with this approach is that now users have no clue 
that e.g. from a MemoryAddress they can "unsafely" obtain a 
MemorySegment because the unsafe method that lets them do that is not on 
MemoryAddress, it's somewhere else. API discoverability, IDE completion 
and the likes will suffer a great deal by such splitting.

I'm not completely closing the door to this idea (which has been 
considered, internally) - I'm merely pointing out the fact that it's no 
silver bullet; if we go down the path I think it will have to be an all 
or nothing approach (e.g. either the entire FFM API goes in a separate 
module, or none will), and moving the entire API into a separate module 
will have consequences on potential evolution of API in the Java SE API. 
For instance, we could provide support for closing a byte buffer - by 
associating a direct ByteBuffer with a ResourceScope, on creation. But 
if ResourceScope is in a different module... we can't do that (or we 
will have to add such a method somewhere else - where users will be a 
lot less likely to find it).

Maurizio

[1] - https://mail.openjdk.java.net/pipermail/jdk-dev/2021-April/005383.html

> If the functionality does stay behind a flag then I suspect the result will
> be that many developers just stick with JNI, as despite the far inferior
> API it will provide a superior experience for users. Alternatively people
> will just stay on the classpath, seeing this as one more reason to avoid
> the JPMS, because everyone will just end up with
> --enable-native-access=ALL-UNNAMED in their "canned" set of JVM flags.
> Attempts to restrict JNI will all face the same problems.


More information about the panama-dev mailing list