Several question about JEP 486

Peter Firmstone peter.firmstone at zeus.net.au
Mon Oct 7 12:00:20 UTC 2024


Hi Lim,

You raise a good point about how to prevent loading unauthorized code.  
I'm interested to see the responses you'll get.

I think OpenJDK was overly focused on sandboxes, applets and 
serialization, instead of authorization and principles of least 
privilege, where the focus should have been, when it mattered.   I guess 
applets were a lucrative business once, it's a stark lesson...

Java never really prevented unauthorized code from loading, as it had a 
"sandbox" everything was safe to run, that assumption turned out to be 
false (Serialization breaking encapsulation made security a game of 
whack a mole) and signing jar's was a way to elevate privileges.

As far as I can tell, support for authorization will be removed, and the 
only new meagre implementations available are only intended to replace 
Subject call functionality that currently depends on AccessController.

It's worth mentioning a lot of the functionality I'm about to discuss 
will no longer be possible to support from Java 24 onward.   We would 
need to maintain some kind of fork and patch in our own authorization 
code as low level support from the vm is required, this would only 
require a small team of people, as it wouldn't be subject to corporate 
regulation and we already have a lot of code that would make big 
improvements, can't be called Java compatible though, as it would be non 
compliant.

This permission was misnamed, it should have been called 
ClassLoadPermission, controls on class loading was started around 20 
years ago, when Sun still maintained Jini:

https://github.com/pfirmstone/JGDMS/blob/trunk/JGDMS/jgdms-platform/src/main/java/net/jini/loader/DownloadPermission.java

It's worth noting we disable Java deserialisation, and if you want it to 
stay disabled, you need to try deserialise something, because Java's 
deserialisation protection mechanism is lazily initialized.  This 
permission works with our serialization implementation, it can't 
deserialise circular object graphs either for security reasons.  
Basically the source of the data, the user providing the data needs to 
be authenticated and granted permission before it can be used.   It's 
hardened against attack, but we still advised against use on untrusted 
data sources. Without the hook for this permission check, it wouldn't be 
possible to prevent it using policy.

https://github.com/pfirmstone/JGDMS/blob/trunk/JGDMS/jgdms-platform/src/main/java/org/apache/river/api/io/DeSerializationPermission.java

This package, although deprecated, provides some historical context:

https://github.com/pfirmstone/JGDMS/blob/trunk/JGDMS/jgdms-platform/src/main/java/net/jini/security/proxytrust/package.html

We basically locked down the jvm with authentication, encryption and 
authorization, with as little impact on performance and scalability as 
possible.   JGDMS is a modern evolution of Jini, but with IPv6 for end 
to end connectivity, class resolution issues solved, no longer using 
Java serialization or RMI, concurrent, high scaling with full support 
for operating over insecure networks.

At a low level, when two remote systems first start communicating with 
each other through dynamic discovery, all data is checksummed, all code 
is checksummed or signed and the connections are authenticated and 
connections are encrypted.   Users at both ends are logged in and the 
threads they invoke run with their Subject, in both local and remote 
machines, including listeners, so events work seamlessly. Since proxy 
code is loaded dynamically, it is isolated with ClassLoader visibility, 
unique to the remote service's authenticated identity, even if third 
party code is transmitted via a network connection, before it is loaded, 
its source will be authenticated, and it will be loaded into a 
ClassLoader specific to its authenticated identity.   Service 
implementations and local code interacts using compatible service 
interfaces, constraints are placed on those services, by both endpoints, 
if constraints aren't satisfied, then a connection is not established.

Authorization is dynamic, permissions are granted dynamically following 
authentication and revoked typically following garbage collection.   We 
have a policy tool that's used to generate POLP policy files for 
deployment and auditing.  Permissions are granted to users, but only if 
they're using the code we want them to, we can't trust them to use those 
privileges running someone else's code.

https://github.com/pfirmstone/JGDMS/blob/trunk/JGDMS/jgdms-url-integrity/src/main/java/net/jini/url/httpmd/Handler.java

Some examples of dynamically generated POLP policy files:

https://github.com/pfirmstone/JGDMS/blob/trunk/qa/harness/policy/defaultsecuretest.policy

https://github.com/pfirmstone/JGDMS/blob/trunk/qa/harness/policy/defaultsecurephoenix.policy.new

One of the issues with Java, was the trusted platform has grown too 
large, Java typically assumes all platform code is trusted (not good), 
but as you can see in policy files, we're restricting a numerous 
platform files, the only reason we can't reduce the trusted platform 
further, is because platform code has a null ProtectionDomain.

Enforcing the principle of least privilege wasn't necessarily complex, 
at least not after we did a lot of work implementing tooling.   
Basically, you have your policy files, keystores and configuration 
files.   The software can be run without security in test environments, 
then all that's necessary is a configuration change, keystore 
configuration and policy files to enable it.

It's worth mentioning we removed DNS as part of the trusted security 
authorization mechanism (due to risks from DNS spoofing), and from 
SecureClassLoader:

https://github.com/pfirmstone/JGDMS/blob/trunk/JGDMS/jgdms-platform/src/main/java/org/apache/river/api/net/RFC3986URLClassLoader.java

We also added an immutable RFC3986 Uri implementation that supports 
RFC5952 IPv6 address normalization, under the hood it uses bit shift 
operations, we found case conversion during normalization was a hotspot:

https://github.com/pfirmstone/JGDMS/blob/trunk/JGDMS/jgdms-platform/src/main/java/org/apache/river/api/net/Uri.java

Security doesn't impact scalability or performance, we load remote 
classes much faster than standard java too, because we avoid DNS calls 
that occur in CodeSource.equals() and SecureClassLoader calls.

Java has developed a reputation as being insecure (if you ask the 
average it person), somewhat ironic, given it had the only authorization 
framework that really worked, but I guess programmers themselves and 
those determining project budgets have themselves to blame for not 
utilising it, soon it will be gone.

-- 
Regards,
  
Peter

On 3/10/2024 8:30 pm, Lim wrote:
> Hi, I have some questions about this JEP.
>
>
> Will something similar to Python's audit hooks[1][2] be considered, to
> give transparency what is happening inside,
> so that jvm behavior can be monitored - such as a security agent (it
> can interact with Antimalware Scan Interface (AMSI)[3]).
> Currently without using JFR, all the operations in the JDK are
> essentially a blackbox.
>
> [1] https://peps.python.org/pep-0578/
> [2] https://docs.python.org/3/library/audit_events.html#audit-events
> [3] https://learn.microsoft.com/en-us/windows/win32/amsi/how-amsi-helps
>
> Again, this is not sandboxing as in "Handler API", described in the
> alternative section in the JEP.
>
> I know that JFR has some events for file/network but it has limited
> coverage and events are delayed,
> making it not suitable for auditing in this context.
> See https://mail.openjdk.org/pipermail/hotspot-jfr-dev/2021-May/002714.html
> for discussion.
>
>
>
> Secondly, historically it is possible to disallow unsigned jar to run
> in the applet era.
>
> For centrally managed devices, while java is installed system wide in
> developers' machines,
> management do only allow signed jar to be executed. While AppLocker[4]/SRP is to
> restrict java executable itself from running, it does not know if the
> jar executed is signed.
>
> Other languages such as Powershell can only allow signed scripts to
> run [5][6] with an example for signing [7].
> Is the default java[w] launcher able to restrict jar from executing be
> available for this scenario?
>
> [4] https://learn.microsoft.com/en-us/windows/security/application-security/application-control/app-control-for-business/appcontrol-and-applocker-overview#app-control-for-business
> [5] https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.security/set-executionpolicy?view=powershell-7.4#-executionpolicy
> [6] https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_signing?view=powershell-7.4#to-permit-signed-scripts-to-run
> [7] https://adamtheautomator.com/how-to-sign-powershell-script/
>
>
>
> Thirdly, it is advised to use an agent to modify classes that call
> System::exit in the appendix section.
>
> There are libraries that have DRM checks at runtime to enforce certain
> restrictions such as licensing checks,
> to deliberately crash if an agent is found, or its class is tampered
> by the agent. (These checks can also be added by obfuscator)
>
> How does one handle such cases?
> Will the JDK offer to hide all the loaded agents or do I need to start
> modifying System::exit instead if this is the case?
>
>
> Thank you.


More information about the security-dev mailing list