Skip to content

Check services in legacy security providers - #1197

Open
KostasTsiounis wants to merge 7 commits into
ibmruntimes:openj9from
KostasTsiounis:register_legacy
Open

Check services in legacy security providers#1197
KostasTsiounis wants to merge 7 commits into
ibmruntimes:openj9from
KostasTsiounis:register_legacy

Conversation

@KostasTsiounis

Copy link
Copy Markdown
Contributor

Legacy security providers do not use the more modern getService() and putService() methods, but rather other more map based methods.

Further checks are added to restrict through RestrictedSecurity the registration and retrieval of services through legacy providers.

Tests are, also, added to verify these checks.

Signed-off-by: Kostas Tsiounis kostas.tsiounis@ibm.com

@KostasTsiounis
KostasTsiounis force-pushed the register_legacy branch 2 times, most recently from 376f20b to ca852dd Compare March 30, 2026 15:53
@keithc-ca
keithc-ca self-requested a review March 30, 2026 20:31
Comment thread closed/test/jdk/openj9/internal/security/legacy-java.security Outdated
Comment thread closed/test/jdk/openj9/internal/security/TestLegacyProviders.java Outdated
Comment thread closed/test/jdk/openj9/internal/security/TestLegacyProviders.java Outdated
Comment thread closed/test/jdk/openj9/internal/security/TestLegacyProviders.java Outdated
Comment thread src/java.base/share/classes/java/security/Provider.java Outdated
Comment thread src/java.base/share/classes/java/security/Provider.java Outdated
Comment thread src/java.base/share/classes/java/security/Provider.java Outdated
Comment thread src/java.base/share/classes/java/security/Provider.java Outdated
Comment thread src/java.base/share/classes/java/security/Provider.java Outdated
Comment thread src/java.base/share/classes/java/security/Provider.java
@KostasTsiounis

Copy link
Copy Markdown
Contributor Author

I moved the mock provider in the test/lib directory to allow putting it under a package. This is needed because only fully-qualified names are allowed when declaring providers in RestrictedSecurity mode profiles.

Legacy security providers do not use the more modern
getService() and putService() methods, but rather other more
map based methods.

Further checks are added to restrict through RestrictedSecurity
the registration and retrieval of services through legacy
providers.

Tests are, also, added to verify these checks.

Signed-off-by: Kostas Tsiounis <kostas.tsiounis@ibm.com>
Comment thread closed/test/jdk/openj9/internal/security/TestLegacyProviders.java Outdated
Comment thread closed/test/jdk/openj9/internal/security/TestLegacyProviders.java Outdated
Comment thread test/lib/jdk/test/lib/LegacyProvider.java Outdated
Comment thread src/java.base/share/classes/java/security/Provider.java Outdated
Comment thread src/java.base/share/classes/java/security/Provider.java Outdated
Comment thread src/java.base/share/classes/java/security/Provider.java Outdated
Comment thread closed/test/jdk/openj9/internal/security/TestLegacyProviders.java Outdated
Comment thread closed/test/jdk/openj9/internal/security/TestLegacyProviders.java
Comment thread closed/test/jdk/openj9/internal/security/TestLegacyProviders.java Outdated
Comment thread src/java.base/share/classes/java/security/Provider.java Outdated
Comment thread src/java.base/share/classes/java/security/Provider.java Outdated
Comment thread src/java.base/share/classes/java/security/Provider.java Outdated
Comment on lines +479 to +488
Set<Map.Entry<Object, Object>> entrySet = entrySet();
Collection<Object> valuesToReturn = new ArrayList<>(entrySet.size());
for (Map.Entry<Object, Object> entry : entrySet) {
if (checkRestrictedSecurityKey(entry.getKey())) {
// We're in restricted security mode which allows this service
// or provider info, so add it to list of values to be returned.
valuesToReturn.add(entry.getValue());
}
}
return Collections.unmodifiableCollection(valuesToReturn);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The set should have already been filtered by entrySet(); this only needs to collect the values:

            return entrySet().stream().map(entry -> entry.getValue()).toList();

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed to suggestion mostly. I changed the last part to be collect(Collectors.toUnmodifiableList()) instead of toList() since that's what the method is supposed to return.

Comment thread src/java.base/share/classes/java/security/Provider.java Outdated
Comment thread src/java.base/share/classes/java/security/Provider.java Outdated
Comment thread src/java.base/share/classes/java/security/Provider.java Outdated
Comment thread src/java.base/share/classes/java/security/Provider.java
Comment thread src/java.base/share/classes/java/security/Provider.java Outdated
Comment thread test/lib/jdk/test/lib/LegacyProvider.java Outdated

import java.security.Provider;

public class LegacyProvider extends Provider {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

test/lib/jdk/test/lib/LegacyProvider.java:33: warning: [serial] serializable class LegacyProvider has no definition of serialVersionUID
public class LegacyProvider extends Provider {
       ^

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't see any of the other helper classes have anything to imitate. How do you think I should handle this? Just assign 1L since it won't be actually serialized?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can pick a UID at random; eclipse suggested this:

    private static final long serialVersionUID = 5997431841873045817L;

import java.util.function.BiConsumer;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.stream.Collectors;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please move this after line 48 (which is out of place, but from upstream).

checkInitialized();

if (RestrictedSecurity.isEnabled()) {
return entrySet().stream().map(entry -> entry.getValue()).collect(Collectors.toUnmodifiableList());

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please use the toList() convenience method (which promises an unmodifiable list):

            return entrySet().stream().map(entry -> entry.getValue()).toList();

Comment on lines +671 to 672
}
return super.get(key);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add a blank line between lines 671 and 672, like other updated methods.

private Object implPut(Object key, Object value) {
if (!checkLegacy(key)) return null;

if (!canRestrictedSecurityServiceBeRegistered(key)) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please explain the rules regarding which of isRestrictedSecurityServiceAllowed() and canRestrictedSecurityServiceBeRegistered() should be used. implCompute() uses the former, while implPut(), implReplace() and implReplaceAll() use the latter. This doesn't appear to be consistent.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The idea is that when doing put() or replace() you already know the value to be inserted, so you are not actually retrieving any information and should thus be allowed to be used from any place of your code (i.e., canRestrictedSecurityServiceBeRegistered()). But with compute() and all of its variants, one can use it to just retrieve an existing value, which should only be allowed from the specific class defined in the constraint (i.e., isRestrictedSecurityServiceAllowed()).

What I just noticed though is that merge() does a thing very similar to compute(), so I might have to switch that to behave like it.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think the result is consistent yet; examples include:

  • keys() and keySet() may yield a different elements than the keys in entrySet()
  • forEach() exposes entries that would not be included in entrySet()
  • merge() and put() allow storing entries that would not be permitted via compute*()

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think the result is consistent yet; examples include:

  • keys() and keySet() may yield a different elements than the keys in entrySet()
  • forEach() exposes entries that would not be included in entrySet()
  • merge() and put() allow storing entries that would not be permitted via compute*()

Regarding each of the points:

  • The whole idea is to restrict getting services that potentially should not be allowed in a specific setting. Getting the keys doesn't give you access to any such services, thus the decision to not restrict keys() and keySet(). I could restrict that too, I just think it might be defeating the purpose. I would also need to add a debug path for cases where we want to see what is available through the provider.
  • I actually missed that. I will update forEach().
  • Like I mentioned before, I will update merge() to match compute(), but I think there should be a difference between them and put(), because we need to allow users to register from wherever but only retrieve from explicitly specified classes. I know that I'm hindering the functionality of compute() and merge() that could be used to just update/register new values, unless I manually check whether the supplied BiFunction is a tautology (i.e., just returns the same value) and it's just used to get a value. Do you think that would make sense to do?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The whole idea is to restrict getting services that potentially should not be allowed in a specific setting

Perhaps I'm missing something. How does restricting access to keys or values affect access to services offered by a given provider?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps I'm missing something. How does restricting access to keys or values affect access to services offered by a given provider?

This whole change was inspired by a use case of someone using a legacy Kafka provider, where services were inserted using put() and then retrieved using get() and subsequently being initialized through reflection. In these cases RestrictedSecurity wasn't able to enforce constraints from the loaded profile. Of course, we couldn't just check these two methods.

As far as keys() goes, my initial thought process was that it could not possibly affect the aforementioned scenario. I will, however, restrict it too for consistency reasons.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If one has access to a service to be able to insert it via put() it's too late to prevent distribution of that service to others.

It's not clear that it's possible to completely restrict access to services by participants that employ reflection. The most direct route might be to reflect Properties.map and operate directly on that object, bypassing anything we might do the implementation of Provider.

How much effort should we expend on this when it can always be circumvented?


private boolean canRestrictedSecurityServiceBeRegistered(Object key) {
Service service = createServiceFromKey(key);
return ((service == null) || RestrictedSecurity.canServiceBeRegistered(service));

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please remove the outermost parentheses, here and on line 1078.

*
* ===========================================================================
*/

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please omit this blank line.


import java.security.Provider;

public class LegacyProvider extends Provider {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can pick a UID at random; eclipse suggested this:

    private static final long serialVersionUID = 5997431841873045817L;

* the RestrictedSecurity mode.
*/
public class LegacyProvider extends Provider {
public LegacyProvider() {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This triggers "this-escape" warnings; making the class final addresses that.

@keithc-ca

Copy link
Copy Markdown
Member

How does this, and RestrictedSecurity in general, interact with eec1769?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants