Feedback about an experiment to embed Python interpreter with FFI API

Maxim Karpov me at m-k.mx
Mon Jan 10 19:13:35 UTC 2022


Hi Maurizio,

Well, yes, the definition of "programs" and "users" is rather 
oversimplified. So let's narrow it down to the fact that there are three 
distinct entities: (1) people who write actual native code, (2) people 
who use it in their applications (that is, write business logic that 
directly or indirectly uses native libraries) and (3) people who 
deploy/run these applications. Current permission system, along with 
amendments discussed in [1], mixes these groups together, making people 
suffer for the things over which they have no control.

Let's start from the "far end": people who deploy or run the code on 
their machines. There are two points to consider: trust and 
fault-tolerance. As it was pointed out, feature flag is by no means a 
security mechanism, and it cannot protect machine from malicious code. 
On deployment, application trust is usually evaluated "as a whole" and 
restrictions are usually enforced from OS level: restricted users, 
access permissions, containers, namespaces, firewalls and all those 
things. They work the same way regardless on whether module calls the 
native code or not -- after all, everything that executes on CPU is a 
native code. So the only area in which flag can be helpful is a fault 
tolerance, because errors in native programming could easily crash the 
JVM itself, ignoring any try-catches or other measures placed to limit 
error impact.

My personal opinion is that feature flag is completely useless for 
improvement of fault tolerance: the only thing that it could do is to 
transform application that crashes sometimes to an application that 
crashes always (albeit with higher-level exception). It is directly 
harmful for well-written application (by introducing deployment burden) 
and useless for poorly-written. DevOps receives most of the damage 
caused by introduction of a feature flag, but they have no power to 
magically make application use only safe API. They are deploying what 
they have to deploy. . From the user (or devops) perspective to usual 
reaction to crash is to (1) disable or bypass faulty component to 
restore current service operation and (2) to pass the crash log to the 
developers who will investigate it in greater detail. The only thing 
that feature flag could do here is to make application "fail fast" by 
denying native access in the first place. Surely, it could help to 
restore current service operation if some higher level traps exception 
and e.g. disables faulty module. But the point is that all this logic 
applies only when application has already crashed.

So I think that feature flag should be inverted at least: it should 
allow all native access by default and provide a way to switch module 
whitelist on. This way it would not hamper well-working applications 
(because default policy would be "allow all access", even without 
warnings) and could be a useful tool to isolate faulty code from the 
rest of a system. Just implementing tainting mechanism (and listing all 
modules with native access in a crash dumps) and providing a way to 
disable some of the modules in case of faults would be helpful for 
DevOps side. I still don't think that it's necessary: you can usually 
disable faulty components in application config, or remove plugin, or do 
similar application-level change. The only thing that you need is to 
identify faulty code. Feature flag does not help here: it just gives an 
upper bound. You cannot easily identify which module from the allowed 
set caused a crash. So I don't think that there is a need for even 
inverted version of the flag: tainting mechanism and useful debug 
reports (mentioning list of modules that have native access, e.g. module 
that did most recent native operation, e.g. stack trace of Java code 
that did most recent native operation in crashed thread if stack is not 
corrupted) would be much more helpful. But this way it will be not 
directly harmful at least.

Also I can't agree to claim that FFI allows you to easily abuse native 
API. There are usually very little things that you could do "easily" in 
native. For most of the use cases FFI without proper tooling is actually 
much more compilcated than JNI. FFI forces you to redo by yourself most 
of the work that will be otherwise done by gcc/clang such as parsing 
include files, finding function signatures and determining structure 
layouts. So you either do one-shot simple calls (like ioctl's on file 
descriptors) which I cannot call an "abuse" or setup a complex tooling 
to generate FFI wrappers. The only removed obstactle is, again, 
deployment one: you don't have to carry intermediate native library for 
every architecture you target to. Maybe it also has a performance 
benefits. But from development perspective it is no way easier (and 
harder, I would say) than JNI.

To summarize, I don't see any real-world situation where permission 
mechanism could help, but I do see many situations where it is just a 
burden that you have to carry. Not to mention the fact that adoption 
ratio of module ecosystem is far from stellar.

Best regards, Maxim.

[1] - 
https://mail.openjdk.java.net/pipermail/panama-dev/2021-September/015036.html


04.01.2022 12:09, Maurizio Cimadamore writes:
> Hi Maxim
>> The topic I want to talk about is a small flag 
>> `--enable-native-access=X`
>> that looks harmless and useful at first glance. tl,dr: it is neither
>> harmless nor useful. The reasoning that "user must opt-in to use unsafe
>> APIs" implies that: (1) unsafe APIs are something illegal and should be
>> avoided; (2) user has competencies to make such decisions; (3) user has
>> other choice than use unsafe APIs. From my point of view, all three
>> assumptions are false.
>
> This has been discussed before (see [1]). My main suggestion here is 
> to avoid over-generalization - e.g. speaking about "programs" and 
> "users" in general is almost always incorrect and prone to 
> simplifications and biases. Which programs? Which users? Note that 
> there are actual instances of users who found the flag very useful 
> (see [2]), which suggests that reality is probably more fragmented 
> than what we'd like it to be.
>
> Native access is not illegal, of course (though some of the wording 
> around the exceptions thrown when the flag isn't there might be 
> unfortunate and suggest that). But doing native access (of any kind) 
> puts the application under much greater risk of misuse, as there is no 
> way for the JVM to enforce safety for native code (e.g. module access 
> boundaries). The fact that other mechanisms such as System.exit, or 
> process builder exists by which bad things can happen and for which no 
> protection exists is not, in itself, a justification as to why 
> accessing native features of the Java API should not be protected.
>
> When a developer uses JNI a number of things have to happen for that 
> code to be in a workable state. When writing a program, native code 
> has to be compiled - when executing, native libraries must be made 
> available. These things all require command line flags (e.g. 
> -Djava.library.path). There are, of course, frameworks that will 
> side-step the library loading requirement by embedding the desired 
> library somewhere in a dependency jar file, extract it into a temp 
> folder, and then load the library directly from there, so that these 
> libraries appear to work "seamlessly".
>
> With the foreign API, when writing code there's no requirement to use 
> GCC/clang. So that's one obstacle removed for the developer. And, when 
> executing, assuming you are only using system libraries (e.g. posix, 
> windows API), an application could just start calling native functions 
> seamlessly (w/o any kind of workaround, unlike in JNI).
>
> This means that it is now much easier for seemingly innocuous Java 
> code to behave in unpredictable ways, hence the flag.
>
>
>>
>>
>> The constructive part: what will be more helpful and much less 
>> burdensome? I
>> would say that much better alternative to a feature flag is just to 
>> record
>> all modules (or classes) which do native access, either by FFI or (in
>> future) by JNI. Then just include list of such modules or classes in 
>> crash
>> dump. Straightforward: the VM has crashed, but classes A, B or C 
>> might be
>> an issue since they used native access. Try to removing these classes 
>> first
>> before reporting any bugs. No deployment burden, no breaking of 
>> compatibilty
>> by introducing new restrictions and much more helpful crash dumps.
>
>
> I think a better way, which we have thought about (but have not got to 
> yet) is to think about native access as a permission that can be 
> granted to a module to other modules. Let's say you have an 
> application module App - and App depends on Foo and Bar, where Bar 
> requires native access. If the required information is captured in the 
> module system, then the application developer can simply grant native 
> access to App, and App will be responsible to transfer the native 
> access permission as required to the modules which requires it. I 
> think such a solution would address most of the issues with the 
> current flag (e.g. finding all the modules which need native access 
> and granting them explicitly) and retain the spirit of capturing 
> important permission information at the module-graph level (so that, 
> if an application is executed w/o enough permission, it would fail to 
> start early, rather than wait for the first unsafe method to be called).
>
> Another option we considered was to turn native access errors into 
> warnings if no --enable-native-access flag is specified (but retain 
> existing behavior if the flag is specified). This would allow for a 
> smoother migration, while allowing clients that want to be strict 
> about native access (see [2]) to still function in the way they do 
> today. To conclude, I think there are possible tweaks to make the flag 
> more useful w/o merely turning it into a debugging feature as you 
> propose.
>
> Maurizio
>
> [1] - 
> https://mail.openjdk.java.net/pipermail/panama-dev/2021-September/015036.html
> [2] - 
> https://mail.openjdk.java.net/pipermail/panama-dev/2021-December/015981.html
>


More information about the panama-dev mailing list