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.

Reply via email to