Java – Integrity by Default (JVMLS 2024)(youtube.com)
youtube.com
Java – Integrity by Default (JVMLS 2024)
https://www.youtube.com/watch?v=wNQKRBLbKIs
50 comments
Fantastic talk and direction for the Java ecosystem. Looking forward to future mitigating supply chain attacks (mentioned being left for another day at 9:17).
I can't shake off a feeling that "integrity by default" is a populist bullshit.
If a third-party library is not updated to not use require reflection, the speaker considers it "a red flag that it's improperly maintained" and suggests looking for alternatives. This is just plain ignorance: for particular domains, there are libraries that either don't have alternatives, or the cost of switching to the alternatives is too high. Are JVM developers going to pay me to write a replacement for every old library essential for my projects? This simplistic approach severely downplays the impact on what the speaker calls "old applications", i.e. the battle-tested, money-generating, useful applications.
Like, I get the usefulness, and I get that it's an attack surface, but it seems the author never had to deal with real-world projects where going into the internals of other dependencies to fix them or access hidden functionality is an absolutely crucial requirement that creates value for the project.
If a third-party library is not updated to not use require reflection, the speaker considers it "a red flag that it's improperly maintained" and suggests looking for alternatives. This is just plain ignorance: for particular domains, there are libraries that either don't have alternatives, or the cost of switching to the alternatives is too high. Are JVM developers going to pay me to write a replacement for every old library essential for my projects? This simplistic approach severely downplays the impact on what the speaker calls "old applications", i.e. the battle-tested, money-generating, useful applications.
Like, I get the usefulness, and I get that it's an attack surface, but it seems the author never had to deal with real-world projects where going into the internals of other dependencies to fix them or access hidden functionality is an absolutely crucial requirement that creates value for the project.
This is a perfect example of people struggling to remain employed.
Java 1.8 is good enough for eternity, so they have to break it to keep getting a salary.
Just like Google/Apple have to break HTTP/1.1 to force adoption of their "obfuscated by complexity" protocols.
What needs to be fixed in Java is the Virtual Thread pinning, and that is probably the last usable feature they will ever be able to add.
They are also trying to deprecate AWT, for their non open-source JavaFX.
The only way to not remain a slave is to break free:
Compile your own Java!
Compile your own Linux!
Buy redundant hardware that does not break easily and is repairable.
Make your own devices if you can.
Make your own encryption.
Java 1.8 is good enough for eternity, so they have to break it to keep getting a salary.
Just like Google/Apple have to break HTTP/1.1 to force adoption of their "obfuscated by complexity" protocols.
What needs to be fixed in Java is the Virtual Thread pinning, and that is probably the last usable feature they will ever be able to add.
They are also trying to deprecate AWT, for their non open-source JavaFX.
The only way to not remain a slave is to break free:
Compile your own Java!
Compile your own Linux!
Buy redundant hardware that does not break easily and is repairable.
Make your own devices if you can.
Make your own encryption.
> Java 1.8 is good enough for eternity, so they have to break it to keep getting a salary.
Java 8 is an antiquated runtime that under performs in all categories against other modern runtimes.
> What needs to be fixed in Java is the Virtual Thread pinning, and that is probably the last usable feature they will ever be able to add.
It is fixed. Go download the EA release.
> They are also trying to deprecate AWT, for their non open-source JavaFX.
FX is under the same license as OpenJDK (GPL) and available on Github.
Java 8 is an antiquated runtime that under performs in all categories against other modern runtimes.
> What needs to be fixed in Java is the Virtual Thread pinning, and that is probably the last usable feature they will ever be able to add.
It is fixed. Go download the EA release.
> They are also trying to deprecate AWT, for their non open-source JavaFX.
FX is under the same license as OpenJDK (GPL) and available on Github.
>non open-source JavaFX
Is there a non open-source JavaFX? The only version of JavaFX I know of is GPLv2
https://github.com/openjdk/jfx
Is there a non open-source JavaFX? The only version of JavaFX I know of is GPLv2
https://github.com/openjdk/jfx
> Java 1.8 is good enough for eternity, so they have to break it to keep getting a salary.
I don't know, there are quality of life improvement that I quite like in more recent versions.
> They are also trying to deprecate AWT, for their non open-source JavaFX.
JavaFX is GPL with a classpath exception, what are you talking about?
> Make your own encryption.
Why? That's generally considered bad advice.
I don't know, there are quality of life improvement that I quite like in more recent versions.
> They are also trying to deprecate AWT, for their non open-source JavaFX.
JavaFX is GPL with a classpath exception, what are you talking about?
> Make your own encryption.
Why? That's generally considered bad advice.
Without watching the video, this seems to be about https://openjdk.org/jeps/8305968
It's really a long running effort that encompasses many JEPs. Modules, FFM, the Unsafe deprecation, and more all play a part.
I see why the SDK and standard library maintainers would want this change but as an application level programmer I wouldn't want this change. They mention "the impending removal of the Security Manager (JEP 411), we need strong encapsulation to support the creation of robust security layers protected from interference by other code". That should actually be a negative point against this JEP. The security manager was removed as no one used it well or found it useful. Every on e of these features were added because they were useful to java developers. The move to modules and Unsafe have been enough pain for recent times.
As an application developer I love these changes. I do not want some library calling out to architecture specific binaries without me knowing. I want to have to enable that for them so I know that my application is portable. I do not want random code outside of my package to be able to reflect on my class and pull data out of a private variable or set its data breaking any type of encapsulation or rules I have written.
> I do not want random code outside of my package to be able to reflect on my class and pull data out of a private variable or set its data breaking any type of encapsulation or rules I have written.
You have a very strange threat model given how stunningly trivial it is to decompile .class files
I'm with you on the JNI but I am not willing to "baby with the bathwater" the Reflection API just avoid having to check the bytecode for Runtime.loadLibrary
You have a very strange threat model given how stunningly trivial it is to decompile .class files
I'm with you on the JNI but I am not willing to "baby with the bathwater" the Reflection API just avoid having to check the bytecode for Runtime.loadLibrary
> You have a very strange threat model given how stunningly trivial it is to decompile .class files
I don't see how this is relevant. I'm talking at runtime. If I have a class:
I don't see how this is relevant. I'm talking at runtime. If I have a class:
class Point {
private final int x;
private final int y;
Point(int x, int y) {
if (x < 0) throw new IllegalArgumentException("x less than 0");
if (y < 0) throw new IllegalArgumentException("y less than 0");
this.x = x;
this.y = y;
}
}
You should not be able to use reflection to remove the private-ness of my field to set Y to -42, unless you are code within my module that I control.Serious question - why not?
Because implementation details are not an interface. Say that, for example, in the future the module code evolves and `y` becomes cached or lazy loaded. Then your external code reaching in and setting `y` to -42 directly may no longer have the effect that it did when you wrote your "monkey patch".
https://en.wikipedia.org/wiki/Monkey_patch#Pitfalls
https://en.wikipedia.org/wiki/Monkey_patch#Pitfalls
They are an interface, just clearly not the intended interface in this case. In the tower of abstraction, it is simply a lower level of abstraction than the public interface. It's important to speak at the appropriate level of abstraction for the problem at hand, and I believe library writers may not perfectly forsee the appropriate level of abstraction for everyone who uses the library.
For debugging, accessing private innards is fine because the version doesn't change before you're done. For long-lasting features, you can perform a risk-based analysis as to the likely stability of the implementation details you're accessing.
But as a general rule: as a library author I deeply resent people who cause ME to get blamed when I change an implementation detail and their fragile code breaks when my library gets upgraded.
https://xkcd.com/1172/
> This update broke my workflow! My control key is hard to reach, so I hold spacebar instead, and I configured Emacs to interpret a rapid temperature rise as "control".
But as a general rule: as a library author I deeply resent people who cause ME to get blamed when I change an implementation detail and their fragile code breaks when my library gets upgraded.
https://xkcd.com/1172/
> This update broke my workflow! My control key is hard to reach, so I hold spacebar instead, and I configured Emacs to interpret a rapid temperature rise as "control".
That would only would be an issue if there is untrusted code running in the same jvm. The security manager was a really difficult way to stop this. Another approach I’ve used is to scan the jar and disallow all but whitelisted classes in the bytecode. Worked well.
It really isn't. Most security problems are caused by accidental vulnerabilities in trusted code, which is far more risky than untrusted code. (Untrusted code is really easy to deal with -- you just run it in a sandbox; it's trusted, benevolent, well-meaning code you should be afraid of.)
But security is just a small part of the problem. Remember all the breakages people had upgrading from 8 to 9+? Virtually all of them were do to libraries reaching into internals that then changed. If no internal method could change because someone might be relying on it then the platform can't evolve. If an application author cannot know which of her dependencies may be non-portable, they can't weigh the risk.
Yet another problem is optimisations. We can't optimise code if any dependency can freely decide to change the meaning of any line of code in the program.
But security is just a small part of the problem. Remember all the breakages people had upgrading from 8 to 9+? Virtually all of them were do to libraries reaching into internals that then changed. If no internal method could change because someone might be relying on it then the platform can't evolve. If an application author cannot know which of her dependencies may be non-portable, they can't weigh the risk.
Yet another problem is optimisations. We can't optimise code if any dependency can freely decide to change the meaning of any line of code in the program.
How is decompiling the bytecode relevant? The issue isn't hiding secrets in the code, it's the application breaking at runtime due to violating invariants or assumptions.
private java.security.PrivateKey _key;
$ java -jar cfr.jar ...
$ sed -i"" -e s/private/public/g $(find . -name "*.java")
$ javac ...
and now Reflection doesn't need setAccessible and your threat model of "but my field is private!!11" has gone poofAwesome, you now have a private fork! You the application developer now know your code isn't portable with the library! The JVM also can do its optimizations without having integrity change from under its feet at runtime! Win win!
Ok, I hear you, so let's mandate that every class have a .gpg signature to really double down on integrity. The JVM should have to have every gpg public key registered as a Trusted Platform Module Author. Win win on how sekure everything will be, and I can always resign every class with my .gpg signature. That really sounds like a win for the day-to-day Java developer, doesn't it? I'm sure that would never cause weird errors in day to day work just to address some niche audience who is really, really into chain of trust
The point I'm making is that I don't understand for the life of me whose problem this egregiously disruptive change is solving outside of the NSA, who I'm sure have their own JVM builds and can leave the rest of us out of it
The point I'm making is that I don't understand for the life of me whose problem this egregiously disruptive change is solving outside of the NSA, who I'm sure have their own JVM builds and can leave the rest of us out of it
You're thinking about security, and this enhancement is about integrity. The idea is simply that some third-level dependency won't be able to make global changes to the program -- like making it non-portable (i.e. not allowing the upgrade of the JDK) -- without the application knowing about it. This is a brand new functionality that solves the problem that led to the 8 -> 9+ migration pain (and I hope you'll agree that was a relatively big problem).
Integrity is also a prerequisite to writing any security mechanism in any layer of the code that is less prone to accidental vulnerabilities in a third-level dependency, and it's also a prerequisite to performance optimisations. For example, if Java strings were immutable, the compiler would be able to optimise some operations on them. Except it can't because even thought they're almost always immutable, some third-level dependency may decide to mutate one string in the program, and because of that the compiler can't optimise any.
Such new and powerful functionality is indeed disruptive (in a good way), and all it demands is that libraries that need to, say, mutate Java strings or final fields let the application know so it could add a flag (if it's willing to take the portability/performance/security risks involved). That's it.
Integrity is also a prerequisite to writing any security mechanism in any layer of the code that is less prone to accidental vulnerabilities in a third-level dependency, and it's also a prerequisite to performance optimisations. For example, if Java strings were immutable, the compiler would be able to optimise some operations on them. Except it can't because even thought they're almost always immutable, some third-level dependency may decide to mutate one string in the program, and because of that the compiler can't optimise any.
Such new and powerful functionality is indeed disruptive (in a good way), and all it demands is that libraries that need to, say, mutate Java strings or final fields let the application know so it could add a flag (if it's willing to take the portability/performance/security risks involved). That's it.
Again I think you have a deep misunderstanding of how this works. This puts control into the hands of the everyday application developer! It ensures that the application developer knows what correctness, portability, and integrity guarantees their program has.
And I don't want the JVM hamstrung from a whole host of optimizations because this random stuff could be happening at any point.
Any general-purpose JVM is already going to have JNI entry points which bypass privacy. They can certainly restrict JNI as a whole and make users jump through hoops to enable it, but they're not going to be tearing it out for a long, long time.
Do you think you're like Apple: not allowing people to fix their phones, laptops ...?
I don’t want the community hijacking my library by fossilizing some implementation detail. If you want to fork it, own it and do it, but don’t force that on me by using something you weren’t meant to and then bitch and moan when I break your code by changing some detail.
You can make that argument either way. People will always complain about the decisions you make and the ones you don't.
The semantics of encapsulation are sufficient. Anyone who breaks them knows it's on them, and if they don't, they certainly should have. It's not worth sweating all the maybes and what-ifs.
The semantics of encapsulation are sufficient. Anyone who breaks them knows it's on them, and if they don't, they certainly should have. It's not worth sweating all the maybes and what-ifs.
> Anyone who breaks them knows it's on them, and if they don't, they certainly should have.
I agree, the issue arises when library developers make this decision and application developers have to deal with the fall out of that. Application developers need to know when things like this happen.
I agree, the issue arises when library developers make this decision and application developers have to deal with the fall out of that. Application developers need to know when things like this happen.
So use an older version of the library, or stop using the library, or work around it. Libraries with such bad behavior are going to be infrequently used, or abandoned quickly regardless.
I don't see how this is relevant. This purely puts control into the hands of application developers. It lets them know exactly what correctness, portability, and integrity guarantees their application has. The application developer STILL can "fix their phone", but they have to enable it at the command line. It pevents library developers from making decisions for application developers without them knowing.
First of all, what's been a pain wasn't modules (their encapsulation wasn't turned on until JDK 16; all JDKs before that had the exact same runtime access permissions as JDK 8), but rather the lack of strong encapsulation which led to many libraries simply being non-portable, which then made the applications using them non-portable without their knowledge. People only thought modules were the cause because modules landed in JDK 9 and were the most famous feature in that release (even though their runtime encapsulation wasn't turned on until JDK 16), and JDK 9 happened to be one of the biggest releases ever, which changed many internals that libraries had hacked into. If virtual threads had landed in JDK 9 rather than modules, the result would have been similar. The only reason you didn't see a lot of breakages due to virtual threads is that they were introduced after strong encapsulation.
Second, the point about SecurityManager is a subtle one. SecurityManager was a complex security mechanism; this isn't. However, because integrity is a prerequisite to robust security, SM had to allow configuring integrity, but it was extremely hard to configure correctly. Now it's on by default.
If you have any sort of authentication/authorisation mechanism in your application (nothing to do with SecurityManager), then an accidental mistake in any dependency or transitive dependency may result in a vulnerability that would allow a remote attacker to bypass your security mechanism, because the attack surface area is the entire program.
Finally, the lack of integrity meant that some powerful performance optimisations can't be performed. Even simple constant-folding of Strings or final fields cannot be done because any library could unilaterally choose to mutate a String or a final.
Second, the point about SecurityManager is a subtle one. SecurityManager was a complex security mechanism; this isn't. However, because integrity is a prerequisite to robust security, SM had to allow configuring integrity, but it was extremely hard to configure correctly. Now it's on by default.
If you have any sort of authentication/authorisation mechanism in your application (nothing to do with SecurityManager), then an accidental mistake in any dependency or transitive dependency may result in a vulnerability that would allow a remote attacker to bypass your security mechanism, because the attack surface area is the entire program.
Finally, the lack of integrity meant that some powerful performance optimisations can't be performed. Even simple constant-folding of Strings or final fields cannot be done because any library could unilaterally choose to mutate a String or a final.
> The security manager was removed as no one used it well or found it useful.
That's as much an indictment of the Security Manager than anything else. ;-)
> Every one of these features were added because they were useful to java developers.
Which creates a requirement in my mind that removing those features needs to be justified by adding more benefit than they take out, but it is entirely possible that those features were the best way to address the needs of Java developers.
The reality is that Java is no longer seen has being a high integrity runtime anymore, so nobody uses it that way. The use cases for a high integrity runtime haven't gone away though.
That's as much an indictment of the Security Manager than anything else. ;-)
> Every one of these features were added because they were useful to java developers.
Which creates a requirement in my mind that removing those features needs to be justified by adding more benefit than they take out, but it is entirely possible that those features were the best way to address the needs of Java developers.
The reality is that Java is no longer seen has being a high integrity runtime anymore, so nobody uses it that way. The use cases for a high integrity runtime haven't gone away though.
I agree with you, and think a perfectly reasonable middle ground would be the same way they allowed the SecurityManager in the first place: if you have such rampantly strict integrity requirements, launch the JVM with -Djava.security.manager (or I guess in this case -Djava.security.integrity) and your JVM will run in lockdown mode
I do see the "by default" in the title, but while I would be turbo sad I would also gladly accept -Djava.security.integrity=false to allow me to opt out of this DRM-esque stuff
And I say DRM but I could also imagine a world in which this stunt would be the death knell to Spring :-(
I do see the "by default" in the title, but while I would be turbo sad I would also gladly accept -Djava.security.integrity=false to allow me to opt out of this DRM-esque stuff
And I say DRM but I could also imagine a world in which this stunt would be the death knell to Spring :-(
How is this DRM-like? It seems to me that this is putting control in the hands of the application developer instead of the library developer, not moving it to Oracle or anyone else...
I consider any action in which the JVM thinks it knows better than me what I can and can't do to running code to be a restriction of my rights. The number of bugs I have fixed, worked around, or triaged using the Reflection API vastly outnumbers the big fat zero of times where someone has complained about an integrity violation on the JVM
So, like I said: if you're a big HFT fund and you need your JVM to do crazy integrity assertions, good for you, but don't burn the whole world down for them. Hell, I would be Azul makes a killing catering to that audience, so let them sell an integrity enabled JVM and leave the rest of us out of this fight
So, like I said: if you're a big HFT fund and you need your JVM to do crazy integrity assertions, good for you, but don't burn the whole world down for them. Hell, I would be Azul makes a killing catering to that audience, so let them sell an integrity enabled JVM and leave the rest of us out of this fight
The choice here is not given to the JDK but to the application developer who can choose which protections to turn off in the application's configuration. All it does is prevent libraries doing things that may interfere with the application or impose risks on it without the application knowing about it.
> the big fat zero of times where someone has complained about an integrity violation on the JVM
~99% of the migration difficulties from 8 to 9+ were due to integrity violations of the core library (remember, module encapsulation was turned off by default until JDK 16). The cost of that alone is, well, high, and that doesn't even count library vulnerabilities. Integrity violations have been the #1 cause of complaints over the last several years.
Integrity is important because it is a prerequisite to portability ("upgradability"), security, correctness, and some important performance optimisations. If you haven't heard people complain a lot about any of these things then you haven't been listening.
> the big fat zero of times where someone has complained about an integrity violation on the JVM
~99% of the migration difficulties from 8 to 9+ were due to integrity violations of the core library (remember, module encapsulation was turned off by default until JDK 16). The cost of that alone is, well, high, and that doesn't even count library vulnerabilities. Integrity violations have been the #1 cause of complaints over the last several years.
Integrity is important because it is a prerequisite to portability ("upgradability"), security, correctness, and some important performance optimisations. If you haven't heard people complain a lot about any of these things then you haven't been listening.
It seems like you have a misunderstanding of how this works. If you want or NEED to do the things that break integrity you still can do them. You just need to enable them at the command line and admit that "yes I know these things break integrity and that I may not get the best performance or may face crazy bugs from unsafe apis or non-portable code".
I'm always getting a little sad when I read JEPs. Our app needs to run on Android as well, so we will probably never be able to utilize these things as intended.
I would love to modularize our app but AFAIU Android doesn't care about that. It doesn't choke if it sees modules (anymore) but it does not enforce the access restrictions.
I would love to modularize our app but AFAIU Android doesn't care about that. It doesn't choke if it sees modules (anymore) but it does not enforce the access restrictions.
It's incredibly important to allow people to break the rules and suffer the consequences.
CDI, Spring, JPA, Mock Testing (Mockito) all break the rules in some manner, like creating instances of objects without calling constructors, changing final class definitions, etc.
CDI, JPA, Spring are all used "in production", while Mockito would be used only in testing.
I have no idea how you balance these two. sun.misc.Unsafe was pretty hard to use and any normal human knew to stay away from it.
CDI, Spring, JPA, Mock Testing (Mockito) all break the rules in some manner, like creating instances of objects without calling constructors, changing final class definitions, etc.
CDI, JPA, Spring are all used "in production", while Mockito would be used only in testing.
I have no idea how you balance these two. sun.misc.Unsafe was pretty hard to use and any normal human knew to stay away from it.
There's an argument to be made that to the extent they break the rules in some manner, they're doing more harm than good. Should you be testing scenarios that are impossible to encounter with the code? Should frameworks be breaking encapsulation and contracts of code?
Most of the time breaking the rules is a reflection of either a bad practice or lack of functionality in the language. I'm all for experimenting in either of those dimensions, but there's a lot of value to have a default behaviour of not doing that.
Most of the time breaking the rules is a reflection of either a bad practice or lack of functionality in the language. I'm all for experimenting in either of those dimensions, but there's a lot of value to have a default behaviour of not doing that.
>There's an argument to be made that to the extent they break the rules in some manner, they're doing more harm than good. Should you be testing scenarios that are impossible to encounter with the code?
Yes actually, especially when it comes to i/o. Let's say I want to simulate how many code handles a REST interaction with a third party service. I want to be able to mock the REST callout without performing any actual i/o. This is why these test frameworks are powerful and useful.
Later if I want I can test to see if the actual callout works by hitting a local endpoint.
Yes actually, especially when it comes to i/o. Let's say I want to simulate how many code handles a REST interaction with a third party service. I want to be able to mock the REST callout without performing any actual i/o. This is why these test frameworks are powerful and useful.
Later if I want I can test to see if the actual callout works by hitting a local endpoint.
"I want to be able to mock the REST callout without performing any actual i/o."
Why? Because it's hard for you to mock out actual I/O, which could be solved by a tool that makes it easy to mock out actual I/O.
Why? Because it's hard for you to mock out actual I/O, which could be solved by a tool that makes it easy to mock out actual I/O.
We're not in the business of testing I/O. We outsource whether or not I/O is correct that to the people writing the JVM.
Testing it as part of your test cases violates the "Test one thing and one thing exactly" principle. The goal is to reduce the surface area of the test as far as possible to distill what the author of the code wrote is correct.
Testing it as part of your test cases violates the "Test one thing and one thing exactly" principle. The goal is to reduce the surface area of the test as far as possible to distill what the author of the code wrote is correct.
That's an arbitrary division of "one thing" though. You've decided the "one thing" includes the HTTP request and response, but it doesn't involve the socket call for that request and response. I'm not saying the test has to involve a network, or a network card, or anything like that, but perhaps a more accurate reflection of the "unit" to be tested is something that reads and writes to a channel or even a socket (and again, not necessarily a TCP/IP socket).
In reality, you're actually testing a bunch of method invocations that won't happen when the code is being used, and to do it, you're also breaking a lot of the contracts that one would otherwise expect to be maintained... maybe that's not the optimal way to do things.
In reality, you're actually testing a bunch of method invocations that won't happen when the code is being used, and to do it, you're also breaking a lot of the contracts that one would otherwise expect to be maintained... maybe that's not the optimal way to do things.
The way you balance it is by allowing the application developer to opt in to unsafe features, which is exactly what this video and the referenced JEP is describing. The Foreign Function and Memory API is essentially unusable without an application-level opt-in, for example, which is good because FFI is inherently unsafe.
Sun.misc.unsafe has almost entirely been replaced by safe public APIs such as Varhandle.
Bunch of winers in the comments. This is totally necessary effort - there's nothing worse than the ground moving under your feet. In the end its no longer necessary to expose internals. Strong encapsulation even aids in disciplined development within the JDK.