Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SecurityUtils should not elminate calls to existing methods #12318

Open
stoty opened this issue Sep 26, 2024 · 11 comments
Open

SecurityUtils should not elminate calls to existing methods #12318

stoty opened this issue Sep 26, 2024 · 11 comments
Labels
Bug For general bugs on Jetty side

Comments

@stoty
Copy link

stoty commented Sep 26, 2024

Jetty version(s)
12.1.x

Jetty Environment
any

Java version/vendor (use: java -version)
any

OS type/version
any

Description
When "org.eclipse.jetty.util.security.useSecurityManager" is set to false, or JDK21 or later is used, then SecurityUtils treats doAs() callAs() as NOOP.

This is wrong, as both methods also perform functions unrelated to the SecurityManager (i.e. setting the subject).

The doPrivileged elimiation also seems bad , I suspect that it will lead to leaks if the SecurityManager is enabled.

In fact, I think that SecurityUtils should not try to guess the SecurityManager setting at all, but just call existing APIs if the methods exist.

The JVM will figure out the rest, and neither Jetty, nor its users have to worry about handling all the JVM/securityManager setting combinations.

How to reproduce?

@stoty stoty added the Bug For general bugs on Jetty side label Sep 26, 2024
@stoty stoty changed the title SecurityUtils should not eliminate doAs() / callAs() under any circumstances SecurityUtils should not elminate calls to existing methods Sep 26, 2024
@stoty
Copy link
Author

stoty commented Sep 26, 2024

Please take a look, @gregw .

@joakime
Copy link
Contributor

joakime commented Sep 26, 2024

The doPrivileged elimiation also seems bad , I suspect that it will lead to leaks if the SecurityManager is enabled.

This wholesale removal of SecurityManager is ongoing everywhere, as the SecurityManager has been flagged as deprecated and is scheduled for removal from the JVM itself.
Most of the Jakarta EE specs have already done this. (this effort will be considered complete by ee12)
Most of our 3rd party libraries have already done this as well.

@stoty
Copy link
Author

stoty commented Sep 26, 2024

This is a simple correctness issue.

These methods are called from a few places, but there is a reason for each call, and at least the callAs() and doPrivileged() calls are NOT for using the removed SecurityManager functionality.

callAs() is NOT scheduled to be removed, and this new method was specifically added to Java to avoid the SecurityManager dependecy of doAs() , so not calling it simply breaks any code depending on the java security framework for handling users, including Kerberos/SPENGO support in Jetty.

Gating callAs() behind some arbitray parameter is a glaringly obvious copy-paste bug.

doPrivileged() is used in one place to avoid a resources leak in the code.
If the application pulling iun Jetty re-enables securityManager, and Jetty eliminates the doPrivilieged() call, Jetty will leak resources (at least I expect that re-enabling securityManager re-enables the resource leak that adding the call was originally meant to fix).

doPriviliged() is a but more subtle bug, because in this case Jetty was never using it for SecurityManager related functionality, but for avoiding leaks via Classloaders when forking Threads.

Frankly, I did not dig into the Java EE context stuff, but I would expect that there are cases where the getSecurityManager() elimination also breaks uses cases that currently work, especially since most of those Java EE releases were written with the assumption of SecurityManager funtionalty, and may still be used by code that really does use SecurityManager functionality.

@stoty
Copy link
Author

stoty commented Sep 26, 2024

Specifically, for JDK 17 the code won't find the new callAs() method, because it was added in 18, and then won't even try the new API if the jetty property is set to false, even though the doAs()/callAs() functionaly is only marginally related to SecurityManager.

The only saving grace in that case is that for JDK21 and earlier Jetty defaults to using the old API, so this won't break on JDK17, unless someone explicitly sets the property to the wrong value.

@joakime
Copy link
Contributor

joakime commented Sep 26, 2024

doPrivileged() is used in one place to avoid a resources leak in the code. If the application pulling iun Jetty re-enables securityManager, and Jetty eliminates the doPrivilieged() call, Jetty will leak resources (at least I expect that re-enabling securityManager re-enables the resource leak that adding the call was originally meant to fix).

doPriviliged() is a but more subtle bug, because in this case Jetty was never using it for SecurityManager related functionality, but for avoiding leaks via Classloaders when forking Threads.

This leak is JDK version specific.
OpenJDK 17, and OpenJDK 21 do not exhibit this leak.

If you were running JDK 11, yes, this bug is very important to address.
The JDK recommended fix for this was to use Executors.privilegedThreadFactory() btw, not a SecurityManager directly.

In the OpenJDK 17 codebase, the AccessControlContext changed with how it is associated with the Class and Thread (the context exists, but is null). (this is the source of the leak you referenced)
This was done at the same time as Executors.privilegedThreadFactory() was deprecated.

Our current codebase, in Jetty 12, uses reflection against deprecated java.security.AccessController to find a doPrivileged method to call when creating a new Thread if that class and method exists, otherwise it creates the thread directly. (still no direct SecurityManager class use for that in Jetty).

@joakime
Copy link
Contributor

joakime commented Sep 26, 2024

These methods are called from a few places, but there is a reason for each call, and at least the callAs() and doPrivileged() calls are NOT for using the removed SecurityManager functionality.

callAs() is NOT scheduled to be removed, and this new method was specifically added to Java to avoid the SecurityManager dependecy of doAs() , so not calling it simply breaks any code depending on the java security framework for handling users, including Kerberos/SPENGO support in Jetty.

The two methods you referenced, callAs() and doAs() are new to JDK 18.

I find it entertaining that all of the doAs methods were added in JDK18 and immediately deprecated for removal.

We should probably have a Jetty SecurityUtils.callAs() method option.
I wonder how hard it would be to move usages of SecurityUtils.doAs() to this new non-deprecated javax.security.auth.Subject.callAs(Subject, Callable) replacement?
It uses `

And deprecate the SecurityUtils.doAs() method direct call.

@stoty
Copy link
Author

stoty commented Sep 26, 2024

We should probably have a Jetty SecurityUtils.callAs() method option.
I wonder how hard it would be to move usages of SecurityUtils.doAs() to this new non-deprecated javax.security.auth.Subject.callAs(Subject, Callable) replacement?
It uses `

And deprecate the SecurityUtils.doAs() method direct call.

This has already been done in 12.1.x (and it was a good change)

@stoty
Copy link
Author

stoty commented Sep 26, 2024

However, Subject.doAs() is so old, that I gave up trying to figure when it was added.
It has certainly been present in Java 7 :

https://docs.oracle.com/javase/7/docs/api/javax/security/auth/Subject.html#doAs(javax.security.auth.Subject,%20java.security.PrivilegedAction)

@stoty
Copy link
Author

stoty commented Sep 26, 2024

Regarding doPriviliged()

Now that we are talking about it, I remember how it broke SPNEGO support in Avatica when Jetty added it.
I had to add a new ThreadFactory that saved and reset the Subject because doPrivileged() has reset it.

Coincidently, this is another good example of how eliminating the doPrivileged() call for JDK22 and later causes (another) incompatible change in Jetty.

before the hack, Jetty did not change Subject.
after the hack, Jetty did reset the Subject.
With this new change, Jetty would not reset the Subject again.

@stoty
Copy link
Author

stoty commented Sep 26, 2024

Because you referred 12.0 behaviour, I want to stress that this problem only exists in 12.1.x and the problematic code was added qute recently in:

12db285

What I propose is basically reverting to the 12.0 behaviour.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Bug For general bugs on Jetty side
Projects
None yet
Development

No branches or pull requests

2 participants