From ebd446b39a0fcf5ba8e33b5aa8c55ce5d525a326 Mon Sep 17 00:00:00 2001 From: Vojin Jovanovic Date: Fri, 19 Jun 2026 16:31:59 +0200 Subject: [PATCH 01/63] Avoid accidental security provider inclusion --- .../native-image/JCASecurityServices.md | 8 +- .../guides/troubleshoot-run-time-errors.md | 5 +- .../core/jdk/SecurityProvidersSupport.java | 80 +++----- .../svm/core/jdk/SecuritySubstitutions.java | 12 +- .../SecuritySubstitutionRuntimeInit.java | 8 +- .../svm/hosted/SecurityServicesFeature.java | 191 +++++++++++++----- .../svm/hosted/ServiceLoaderFeature.java | 17 +- .../hosted/image/PreserveOptionsSupport.java | 15 -- .../reachability-metadata.json | 13 ++ .../META-INF/services/java.security.Provider | 1 + .../test/services/SecurityServiceTest.java | 115 ++++++++++- 11 files changed, 324 insertions(+), 141 deletions(-) create mode 100644 substratevm/src/com.oracle.svm.test/src/META-INF/native-image/com.oracle.svm.test/reachability-metadata.json create mode 100644 substratevm/src/com.oracle.svm.test/src/META-INF/services/java.security.Provider diff --git a/docs/reference-manual/native-image/JCASecurityServices.md b/docs/reference-manual/native-image/JCASecurityServices.md index fa4dd54d94fa..5d2614ab9e4d 100644 --- a/docs/reference-manual/native-image/JCASecurityServices.md +++ b/docs/reference-manual/native-image/JCASecurityServices.md @@ -18,7 +18,7 @@ By default the `native-image` builder uses static analysis to discover which of The automatic registration of security services can be disabled with `-H:-EnableSecurityServicesFeature`. Then a custom reflection configuration file or feature can be used to register the security services required by a specific application. Note that when automatic registration of security providers is disabled, all providers are, by default, filtered from special JDK caches that are necessary for security functionality. -In this case, you must manually mark used providers with `-H:AdditionalSecurityProviders`. +In this case, register the provider classes for reflection, for example with reachability metadata collected by the Tracing Agent. ## Security Services Automatic Registration @@ -73,9 +73,11 @@ To avoid capturing state from the machine that runs the `native-image` builder, By default, only services specified in the JCA framework are automatically registered. To automatically register custom service types, you can use the `-H:AdditionalSecurityServiceTypes` option. Note that for automatic registration to work, the service interface must have a `getInstance` method and have the same name as the service type. -If relying on the third-party code that does not comply to the above requirements, a manual configuration will be required. In that case, providers for such services must explicitly be registered using the `-H:AdditionalSecurityProviders` option. Note that these options are only required in very specific cases and should not normally be needed. +If you rely on third-party code that does not comply with these requirements, manual configuration is required. +Register the provider class for reflection in _reachability-metadata.json_ or collect the metadata with the Tracing Agent. +The deprecated `-H:AdditionalSecurityProviders` option still registers the listed provider classes for reflection as a compatibility path. ### Further Reading * [URL Protocols in Native Image](URLProtocols.md) -* [Jipher JCE with Native Image](../../security/JipherJCE.md) \ No newline at end of file +* [Jipher JCE with Native Image](../../security/JipherJCE.md) diff --git a/docs/reference-manual/native-image/guides/troubleshoot-run-time-errors.md b/docs/reference-manual/native-image/guides/troubleshoot-run-time-errors.md index 4892960995b6..22963f26719f 100644 --- a/docs/reference-manual/native-image/guides/troubleshoot-run-time-errors.md +++ b/docs/reference-manual/native-image/guides/troubleshoot-run-time-errors.md @@ -64,8 +64,9 @@ This might increase the size of the resulting binary. ### 4. Add Missing Security Providers -If your application is using Security Providers, try to pre-initialize security providers by passing the option `-H:AdditionalSecurityProviders=` at build time. -Here is a list of all JDK security providers to choose from: +If your application uses security providers that are not included in the native executable, run the application with the Tracing Agent and rebuild with the collected reachability metadata. +You can also register the provider classes for reflection in _reachability-metadata.json_, or build with `-H:Preserve=all` to include all JDK providers. +Here is a list of JDK security provider classes: `sun.security.provider.Sun,sun.security.rsa.SunRsaSign,sun.security.ec.SunEC,sun.security.ssl.SunJSSE,com.sun.crypto.provider.SunJCE,sun.security.jgss.SunProvider,com.sun.security.sasl.Provider,org.jcp.xml.dsig.internal.dom.XMLDSigRI,sun.security.smartcardio.SunPCSC,sun.security.provider.certpath.ldap.JdkLDAP,com.sun.security.sasl.gsskerb.JdkSASL`. ### 5. File a Native Image Run-Time Issue diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SecurityProvidersSupport.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SecurityProvidersSupport.java index 87e5c45fd50c..c9106195516a 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SecurityProvidersSupport.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SecurityProvidersSupport.java @@ -28,13 +28,9 @@ import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.security.Provider; -import java.util.List; import java.util.Properties; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; import org.graalvm.collections.EconomicMap; -import org.graalvm.collections.EconomicSet; import org.graalvm.nativeimage.ImageSingletons; import org.graalvm.nativeimage.Platform; import org.graalvm.nativeimage.Platforms; @@ -51,27 +47,13 @@ import sun.security.util.Debug; /** - * The class that holds various build-time and run-time structures necessary for security providers, - * but only in case they are initialized at run time (see the * JCA Security Services documentation for details). */ @SingletonTraits(access = AllAccess.class, layeredCallbacks = NoLayeredCallbacks.class, layeredInstallationKind = Duplicable.class, other = DisallowLayered.class) public final class SecurityProvidersSupport { - /** - * A set of providers to be loaded using the service-loading technique at runtime, but not - * discoverable at build-time when processing services in the feature (see - * ServiceLoaderFeature#handleServiceClassIsReachable). This occurs when the user does not - * explicitly request a provider, but the provider is discovered via static analysis from a - * JCA-compliant security service used by the user's code (see - * SecurityServicesFeature#registerServiceReachabilityHandlers). - */ - @Platforms(Platform.HOSTED_ONLY.class)// - private final Set markedAsNotLoaded = ConcurrentHashMap.newKeySet(); - - /** Set of fully qualified provider names, required for runtime resource access. */ - private final EconomicSet userRequestedSecurityProviders = EconomicSet.create(); - /** * A map of providers, identified by their names (see {@link Provider#getName()}), and the * results of their verification (see javax.crypto.JceSecurity#getVerificationResult). This @@ -79,14 +61,14 @@ public final class SecurityProvidersSupport { * avoid keeping provider objects in the image heap. */ private final EconomicMap verifiedSecurityProviders = ImageHeapMap.create("verifiedSecurityProviders"); + private final EconomicMap verifiedSecurityProviderClasses = ImageHeapMap.create("verifiedSecurityProviderClasses"); private Properties savedInitialSecurityProperties; private Constructor sunECConstructor; @Platforms(Platform.HOSTED_ONLY.class) - public SecurityProvidersSupport(List userRequestedSecurityProviders) { - this.userRequestedSecurityProviders.addAll(userRequestedSecurityProviders); + public SecurityProvidersSupport() { } @Fold @@ -95,36 +77,22 @@ public static SecurityProvidersSupport singleton() { } @Platforms(Platform.HOSTED_ONLY.class) - public void addVerifiedSecurityProvider(String key, Object verificationResult) { - verifiedSecurityProviders.put(key, verificationResult); + public void addVerifiedSecurityProvider(String providerName, String providerClassName, Object verificationResult) { + verifiedSecurityProviders.put(providerName, verificationResult); + verifiedSecurityProviderClasses.put(providerClassName, verificationResult); } - public Object getSecurityProviderVerificationResult(String key) { - return verifiedSecurityProviders.get(key); - } - - @Platforms(Platform.HOSTED_ONLY.class) - public void markSecurityProviderAsNotLoaded(String provider) { - markedAsNotLoaded.add(provider); - } - - @Platforms(Platform.HOSTED_ONLY.class) - public boolean isSecurityProviderNotLoaded(String provider) { - return markedAsNotLoaded.contains(provider); - } - - @Platforms(Platform.HOSTED_ONLY.class) - public boolean isUserRequestedSecurityProvider(String provider) { - return userRequestedSecurityProviders.contains(provider); + public Object getSecurityProviderVerificationResult(Provider provider) { + Object result = verifiedSecurityProviderClasses.get(provider.getClass().getName()); + return result != null ? result : verifiedSecurityProviders.get(provider.getName()); } /** * Returns {@code true} if the provider, identified by either its name (e.g., SUN) or fully - * qualified name (e.g., sun.security.provider.Sun), is either user-requested or reachable via a - * security service. + * qualified name (e.g., sun.security.provider.Sun), was included in the native image. */ - public boolean isSecurityProviderRequested(String providerName, String providerFQName) { - return verifiedSecurityProviders.containsKey(providerName) || userRequestedSecurityProviders.contains(providerFQName); + public boolean isSecurityProviderIncluded(String providerName, String providerFQName) { + return verifiedSecurityProviders.containsKey(providerName) || verifiedSecurityProviderClasses.containsKey(providerFQName); } @Platforms(Platform.HOSTED_ONLY.class) @@ -176,7 +144,7 @@ public static String getBuiltInProviderClassName(String provName) { public boolean isMissingBuiltInProvider(String provName) { String providerName = getBuiltInProviderName(provName); String providerFQName = getBuiltInProviderClassName(provName); - return providerName != null && !isSecurityProviderRequested(providerName, providerFQName); + return providerName != null && !isSecurityProviderIncluded(providerName, providerFQName); } public static SecurityException missingBuiltInProvider(String provName) { @@ -186,23 +154,27 @@ public static SecurityException missingBuiltInProvider(String provName) { throw VMError.shouldNotReachHere("Unsupported built-in provider: " + provName); } return new SecurityException( - "The security provider '" + providerName + "' (" + providerFQName + ") was requested at run time, " + - "but it was not registered for inclusion in the native image. " + - "Add the option -H:AdditionalSecurityProviders=" + providerFQName + " to the native-image build and rebuild the image."); + missingProviderMessage(providerName, providerFQName)); + } + + public static String missingProviderMessage(String providerName, String providerFQName) { + return "The security provider '" + providerName + "' (" + providerFQName + ") was requested at run time but was not included in the native image. " + + "Run your application with the tracing agent so the provider is recorded automatically, register " + providerFQName + + " for reflection in reachability-metadata.json, or build with -H:Preserve=all to include all JDK providers."; } public Provider loadBuiltInProvider(String provName, Debug debug) { return switch (provName) { case "SUN", "sun.security.provider.Sun" -> - isSecurityProviderRequested("SUN", "sun.security.provider.Sun") ? new sun.security.provider.Sun() : null; + isSecurityProviderIncluded("SUN", "sun.security.provider.Sun") ? new sun.security.provider.Sun() : null; case "SunRsaSign", "sun.security.rsa.SunRsaSign" -> - isSecurityProviderRequested("SunRsaSign", "sun.security.rsa.SunRsaSign") ? new sun.security.rsa.SunRsaSign() : null; + isSecurityProviderIncluded("SunRsaSign", "sun.security.rsa.SunRsaSign") ? new sun.security.rsa.SunRsaSign() : null; case "SunJCE", "com.sun.crypto.provider.SunJCE" -> - isSecurityProviderRequested("SunJCE", "com.sun.crypto.provider.SunJCE") ? new com.sun.crypto.provider.SunJCE() : null; + isSecurityProviderIncluded("SunJCE", "com.sun.crypto.provider.SunJCE") ? new com.sun.crypto.provider.SunJCE() : null; case "SunJSSE", "sun.security.ssl.SunJSSE" -> - isSecurityProviderRequested("SunJSSE", "sun.security.ssl.SunJSSE") ? new sun.security.ssl.SunJSSE() : null; + isSecurityProviderIncluded("SunJSSE", "sun.security.ssl.SunJSSE") ? new sun.security.ssl.SunJSSE() : null; case "SunEC", "sun.security.ec.SunEC" -> - isSecurityProviderRequested("SunEC", "sun.security.ec.SunEC") ? allocateSunECProvider() : null; + isSecurityProviderIncluded("SunEC", "sun.security.ec.SunEC") ? allocateSunECProvider() : null; case "Apple", "apple.security.AppleProvider" -> { try { Class c = Class.forName("apple.security.AppleProvider"); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SecuritySubstitutions.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SecuritySubstitutions.java index 2d7564b87b79..2215b9ff9c74 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SecuritySubstitutions.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SecuritySubstitutions.java @@ -274,17 +274,19 @@ static Exception getVerificationResult(Provider p) { } else if (o != null) { return (Exception) o; } + o = SecurityProvidersSupport.singleton().getSecurityProviderVerificationResult(p); + if (o == Boolean.TRUE) { + return null; + } else if (o != null) { + return (Exception) o; + } /* * If the verification result is not found in the verificationResults map, HotSpot will * attempt to verify the provider. This requires accessing the code base, which isn't * supported in Native Image, so we need to fail. We could either fail here or substitute * getCodeBase() and fail there, but handling it here is a cleaner approach. */ - String providerFQN = p.getClass().getName(); - throw new SecurityException( - "Attempted to verify a provider that was not registered at build time: " + providerFQN + ". " + - "All security providers must be registered and verified during native image generation. " + - "Try adding the option: -H:AdditionalSecurityProviders=" + providerFQN + " and rebuild the image."); + throw new SecurityException(SecurityProvidersSupport.missingProviderMessage(p.getName(), p.getClass().getName())); } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/runtimeinit/SecuritySubstitutionRuntimeInit.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/runtimeinit/SecuritySubstitutionRuntimeInit.java index 1bee623a5b43..6aca0f11dd0b 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/runtimeinit/SecuritySubstitutionRuntimeInit.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/runtimeinit/SecuritySubstitutionRuntimeInit.java @@ -94,7 +94,7 @@ final class Target_javax_crypto_JceSecurity { @Substitute static Exception getVerificationResult(Provider p) { /* The verification results map key is an identity wrapper object. */ - Object o = SecurityProvidersSupport.singleton().getSecurityProviderVerificationResult(p.getName()); + Object o = SecurityProvidersSupport.singleton().getSecurityProviderVerificationResult(p); if (o == Boolean.TRUE) { return null; } else if (o != null) { @@ -106,11 +106,7 @@ static Exception getVerificationResult(Provider p) { * supported in Native Image, so we need to fail. We could either fail here or substitute * getCodeBase() and fail there, but handling it here is a cleaner approach. */ - String providerFQN = p.getClass().getName(); - throw new SecurityException( - "Attempted to verify a provider that was not registered at build time: " + providerFQN + ". " + - "All security providers must be registered and verified during native image generation. " + - "Try adding the option: -H:AdditionalSecurityProviders=" + providerFQN + " and rebuild the image."); + throw new SecurityException(SecurityProvidersSupport.missingProviderMessage(p.getName(), p.getClass().getName())); } } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SecurityServicesFeature.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SecurityServicesFeature.java index c2ea9ce85385..aa5a00a1f124 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SecurityServicesFeature.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SecurityServicesFeature.java @@ -61,7 +61,6 @@ import java.util.Collection; import java.util.Collections; import java.util.HashMap; -import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Objects; @@ -89,6 +88,7 @@ import org.graalvm.nativeimage.hosted.RuntimeReflection; import org.graalvm.nativeimage.impl.RuntimeClassInitializationSupport; +import com.oracle.graal.pointsto.constraints.UnsupportedPlatformException; import com.oracle.graal.pointsto.meta.AnalysisMethod; import com.oracle.graal.pointsto.reports.ReportUtils; import com.oracle.svm.core.FutureDefaultsOptions; @@ -107,6 +107,7 @@ import com.oracle.svm.hosted.FeatureImpl.DuringSetupAccessImpl; import com.oracle.svm.hosted.analysis.Inflation; import com.oracle.svm.hosted.c.NativeLibraries; +import com.oracle.svm.hosted.substitute.DeletedElementException; import com.oracle.svm.hosted.substitute.AnnotationSubstitutionProcessor; import com.oracle.svm.shared.BuildPhaseProvider; import com.oracle.svm.shared.feature.AutomaticallyRegisteredFeature; @@ -127,6 +128,7 @@ import jdk.graal.compiler.options.Option; import jdk.internal.access.SharedSecrets; import jdk.vm.ci.meta.ResolvedJavaMethod; +import jdk.vm.ci.meta.ResolvedJavaType; import sun.security.jca.ProviderList; import sun.security.provider.NativePRNG; import sun.security.x509.OIDMap; @@ -172,8 +174,9 @@ public static class Options { public static final HostedOptionKey AdditionalSecurityServiceTypes = new HostedOptionKey<>( AccumulatingLocatableMultiOptionValue.Strings.build()); - @Option(help = "Comma-separated list of additional security provider fully qualified class names to mark as used." + - "Note that this option is only necessary if you use custom engine classes not available in JCA that are not JCA compliant.")// + @Option(help = "Deprecated. Security providers are now detected automatically (use the tracing agent, register the provider class for reflection, or build with -H:Preserve=all).", + deprecated = true, + deprecationMessage = "Register the security provider class for reflection instead; the tracing agent does this automatically.")// public static final HostedOptionKey AdditionalSecurityProviders = new HostedOptionKey<>( AccumulatingLocatableMultiOptionValue.Strings.buildWithCommaDelimiter()); } @@ -242,9 +245,6 @@ public static class Options { /** All providers deemed to be used by this feature. */ private final Set usedProviders = ConcurrentHashMap.newKeySet(); - /** Providers marked as used by the user. */ - private final EconomicSet manuallyMarkedUsedProviderClassNames = EconomicSet.create(); - private Field verificationResultsField; private Field providerListField; private Field oidTableField; @@ -261,11 +261,11 @@ public static class Options { private final ScanReason scanReason = new OtherReason("Manual rescan triggered from " + SecurityServicesFeature.class); + private final Map buildTimeProvidersByClassName = new HashMap<>(); + @Override public void afterRegistration(AfterRegistrationAccess a) { - if (FutureDefaultsOptions.securityProvidersInitializedAtRunTime()) { - ImageSingletons.add(SecurityProvidersSupport.class, new SecurityProvidersSupport(Options.AdditionalSecurityProviders.getValue().values())); - } + ImageSingletons.add(SecurityProvidersSupport.class, new SecurityProvidersSupport()); ModuleSupport.accessPackagesToClass(ModuleSupport.Access.OPEN, getClass(), false, "java.base", "sun.security.x509"); ModuleSupport.accessModuleByClass(ModuleSupport.Access.OPEN, getClass(), Security.class); @@ -280,7 +280,6 @@ public void duringSetup(DuringSetupAccess a) { oidTableField = access.findField("sun.security.util.ObjectIdentifier", "oidTable"); oidMapField = access.findField(OIDMap.class, "oidMap"); if (!FutureDefaultsOptions.securityProvidersInitializedAtRunTime()) { - addManuallyConfiguredUsedProviders(a); verificationResultsField = access.findField("javax.crypto.JceSecurity", "verificationResults"); providerListField = access.findField("sun.security.jca.Providers", "providerList"); classCacheField = access.findField(Service.class, "classCache"); @@ -361,6 +360,7 @@ public void beforeAnalysis(BeforeAnalysisAccess a) { BeforeAnalysisAccessImpl access = (BeforeAnalysisAccessImpl) a; loader = access.getImageClassLoader(); jceSecurityClass = loader.findClassOrFail("javax.crypto.JceSecurity"); + substitutionProcessor = ((Inflation) access.getBigBang()).getAnnotationSubstitutionProcessor(); /* Ensure sun.security.provider.certpath.CertPathHelper.instance is initialized. */ access.ensureInitialized("java.security.cert.TrustAnchor"); @@ -387,6 +387,9 @@ public void beforeAnalysis(BeforeAnalysisAccess a) { access.ensureInitialized("sun.security.util.AnchorCertificates"); if (Options.EnableSecurityServicesFeature.getValue()) { + initializeServiceRegistrationData(); + access.registerSubtypeReachabilityHandler((analysisAccess, providerClass) -> includeProviderClass(analysisAccess, providerClass), Provider.class); + registerManuallyConfiguredProvidersForReflection(access); registerServiceReachabilityHandlers(access); } @@ -406,8 +409,6 @@ public void beforeAnalysis(BeforeAnalysisAccess a) { } if (!FutureDefaultsOptions.securityProvidersInitializedAtRunTime()) { - substitutionProcessor = ((Inflation) access.getBigBang()).getAnnotationSubstitutionProcessor(); - access.registerFieldValueTransformer(providerListField, new FieldValueTransformerWithAvailability() { // JVMCI migration blocked by GR-72131: Refactor security service code for project // Terminus. @@ -485,18 +486,33 @@ private List filterProviderList(Object originalValue) { return ((ProviderList) originalValue).providers().stream().filter(p -> !shouldRemoveProvider(p)).toList(); } - private void addManuallyConfiguredUsedProviders(DuringSetupAccess access) { + private void registerManuallyConfiguredProvidersForReflection(BeforeAnalysisAccess access) { + BeforeAnalysisAccessImpl accessImpl = (BeforeAnalysisAccessImpl) access; for (String value : Options.AdditionalSecurityProviders.getValue().values()) { for (String className : value.split(",")) { Class classByName = access.findClassByName(className); UserError.guarantee(classByName != null, "Manually marked security provider class doesn't exist: %s. Make sure that the class name is correct and that the class is on the image builder classpath.", className); - trace("Marked provider %s as used", className); - manuallyMarkedUsedProviderClassNames.add(className); + if (shouldRegisterProviderClassForReflection(accessImpl, classByName)) { + registerProviderClassForReflection(classByName); + } } } } + private boolean shouldRegisterProviderClassForReflection(BeforeAnalysisAccessImpl access, Class providerClass) { + if (!Provider.class.isAssignableFrom(providerClass)) { + return false; + } + ResolvedJavaType providerType; + try { + providerType = access.findTypeByName(providerClass.getName()); + } catch (UnsupportedPlatformException | DeletedElementException e) { + return false; + } + return providerType != null && access.getHostVM().platformSupported(providerType) && !substitutionProcessor.isDeleted(providerType); + } + public boolean shouldRemoveProvider(Provider p) { if (p == null) { return true; @@ -507,7 +523,7 @@ public boolean shouldRemoveProvider(Provider p) { if (substitutionProcessor.isDeleted(GuestAccess.get().lookupType(p.getClass()))) { return true; } - return !manuallyMarkedUsedProviderClassNames.contains(p.getClass().getName()); + return true; } private static void traceRemovedProviders(List removedProviders) { @@ -629,10 +645,6 @@ private void registerSASLReachabilityHandlers(BeforeAnalysisAccess access) { } private void registerServiceReachabilityHandlers(BeforeAnalysisAccess access) { - ctrParamClassAccessor = getConstructorParameterClassAccessor(loader); - getSpiClassMethod = getSpiClassMethod(); - availableServices = computeAvailableServices(); - /* * The JCA defines the list of standard service classes available in the JDK. Each service * class implements a series of getInstance() factory methods which return concrete service @@ -675,6 +687,16 @@ private void registerServiceReachabilityHandlers(BeforeAnalysisAccess access) { defaultSecureRandomService.ifPresent(m -> access.registerMethodOverrideReachabilityHandler((a, t) -> registerServices(a, t, SECURE_RANDOM_SERVICE), OriginalMethodProvider.getJavaMethod(m))); } + private void initializeServiceRegistrationData() { + ctrParamClassAccessor = getConstructorParameterClassAccessor(loader); + getSpiClassMethod = getSpiClassMethod(); + availableServices = computeAvailableServices(); + buildTimeProvidersByClassName.clear(); + for (Provider provider : Security.getProviders()) { + buildTimeProvidersByClassName.put(provider.getClass().getName(), provider); + } + } + private void registerServices(DuringAnalysisAccess access, Object trigger, Class serviceClass) { /* * SPI classes, i.e., base classes for concrete service implementations, such as @@ -836,37 +858,68 @@ private void registerProvider(DuringAnalysisAccess access, Provider provider) { * support. See also Target_javax_crypto_JceSecurity. */ Object result = getVerificationResult.invoke(null, provider); - if (FutureDefaultsOptions.securityProvidersInitializedAtRunTime()) { - /* - * Note that after verification, we move the result to a separate structure - * since we don't want to keep the provider object in the image heap. - * - * The verification result can be either null, in case of success, or an - * Exception, in case of failure. Null is interpreted as Boolean.TRUE at - * runtime, signifying successful verification. - */ - String providerName = provider.getName(); - SecurityProvidersSupport support = SecurityProvidersSupport.singleton(); - support.addVerifiedSecurityProvider(providerName, result instanceof Exception ? result : Boolean.TRUE); - - /* - * If this provider is not yet loaded via the service loading mechanism, we need - * to manually prepare reflection metadata now, so that service loading works at - * runtime (see sun.security.jca.ProviderConfig.doLoadProvider). - */ - String providerFQName = provider.getClass().getName(); - if (support.isSecurityProviderNotLoaded(providerFQName)) { - Set registeredProviders = new HashSet<>(); // noEconomicSet(unimplemented) - ServiceLoaderFeature.registerProviderForRuntimeReflectionAccess(access, providerFQName, registeredProviders); - ServiceLoaderFeature.registerProviderForRuntimeResourceAccess(access.getApplicationClassLoader().getUnnamedModule(), Provider.class.getName(), registeredProviders); - } - } + /* + * Note that after verification, we move the result to a separate structure since we + * don't want to keep provider objects that were only instantiated for verification in + * the image heap. + * + * The verification result can be either null, in case of success, or an Exception, + * in case of failure. Null is interpreted as Boolean.TRUE at runtime, signifying + * successful verification. + */ + SecurityProvidersSupport.singleton().addVerifiedSecurityProvider(provider.getName(), provider.getClass().getName(), result instanceof Exception ? result : Boolean.TRUE); } catch (ReflectiveOperationException ex) { throw VMError.shouldNotReachHere(ex); } } } + private void includeProviderClass(DuringAnalysisAccess access, Class providerClass) { + if (!isLoadableProviderClass(access, providerClass)) { + return; + } + Provider provider = buildTimeProvidersByClassName.get(providerClass.getName()); + if (provider == null) { + provider = instantiateProvider(providerClass); + } + registerProvider(access, provider); + for (Service service : provider.getServices()) { + if (isValid(service)) { + registerService(access, service); + } + } + } + + private boolean isLoadableProviderClass(DuringAnalysisAccess access, Class providerClass) { + if (providerClass == null || providerClass.isArray() || providerClass.isPrimitive() || Modifier.isAbstract(providerClass.getModifiers())) { + return false; + } + + DuringAnalysisAccessImpl accessImpl = (DuringAnalysisAccessImpl) access; + ResolvedJavaType providerType; + try { + providerType = accessImpl.findTypeByName(providerClass.getName()); + } catch (UnsupportedPlatformException | DeletedElementException e) { + return false; + } + if (providerType == null || !accessImpl.getHostVM().platformSupported(providerType) || substitutionProcessor.isDeleted(providerType)) { + return false; + } + return hasDeclaredNullaryConstructor(providerClass) || findProviderMethod(providerClass) != null; + } + + private static Provider instantiateProvider(Class providerClass) { + try { + Constructor constructor = findDeclaredNullaryConstructor(providerClass); + if (constructor != null) { + return (Provider) constructor.newInstance(); + } + return (Provider) findProviderMethod(providerClass).invoke(null); + } catch (ReflectiveOperationException ex) { + throw VMError.shouldNotReachHere("Security provider class is reachable but cannot be instantiated: " + providerClass.getName(), ex); + } + } + private void registerService(DuringAnalysisAccess a, Service service) { TypeResult> serviceClassResult = loader.findClass(service.getClassName()); if (serviceClassResult.isPresent()) { @@ -897,6 +950,54 @@ private void registerService(DuringAnalysisAccess a, Service service) { } } + private static void registerProviderClassForReflection(Class providerClass) { + RuntimeReflection.register(providerClass); + Constructor constructor = findDeclaredNullaryConstructor(providerClass); + if (constructor != null) { + RuntimeReflection.register(constructor); + } else { + RuntimeReflection.registerConstructorLookup(providerClass); + } + Method providerMethod = findProviderMethod(providerClass); + if (providerMethod != null) { + RuntimeReflection.register(providerMethod); + } else { + RuntimeReflection.registerMethodLookup(providerClass, "provider"); + } + trace("Registered provider %s for reflection", providerClass.getName()); + } + + private static boolean hasDeclaredNullaryConstructor(Class providerClass) { + return findDeclaredNullaryConstructor(providerClass) != null; + } + + private static Constructor findDeclaredNullaryConstructor(Class providerClass) { + return ReflectionUtil.lookupConstructor(true, providerClass); + } + + private static Method findProviderMethod(Class providerClass) { + Method nullaryProviderMethod = null; + try { + if (providerClass.getModule().isNamed() && !providerClass.getModule().getDescriptor().isAutomatic()) { + for (Method method : providerClass.getDeclaredMethods()) { + if (Modifier.isPublic(method.getModifiers()) && Modifier.isStatic(method.getModifiers()) && method.getParameterCount() == 0 && method.getName().equals("provider") && + Provider.class.isAssignableFrom(method.getReturnType())) { + if (nullaryProviderMethod == null) { + ModuleSupport.accessModuleByClass(ModuleSupport.Access.OPEN, SecurityServicesFeature.class, providerClass); + method.setAccessible(true); + nullaryProviderMethod = method; + } else { + return null; + } + } + } + } + } catch (SecurityException | LinkageError e) { + return null; + } + return nullaryProviderMethod; + } + /** * Register the default JavaKeyStore, JKS, for reflection. It is not registered as a key store * implementation in any provider, but it is registered as a primary key store for diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ServiceLoaderFeature.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ServiceLoaderFeature.java index 588dceb35732..e19602e6830f 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ServiceLoaderFeature.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ServiceLoaderFeature.java @@ -43,7 +43,6 @@ import com.oracle.graal.pointsto.constraints.UnsupportedPlatformException; import com.oracle.svm.core.FutureDefaultsOptions; import com.oracle.svm.core.feature.InternalFeature; -import com.oracle.svm.core.jdk.SecurityProvidersSupport; import com.oracle.svm.core.jdk.ServiceCatalogSupport; import com.oracle.svm.hosted.analysis.Inflation; import com.oracle.svm.hosted.substitute.DeletedElementException; @@ -145,6 +144,7 @@ public static class Options { "jdk.jshell.execution.impl.ConsoleImpl$ConsoleProviderImpl"); private final EconomicSet serviceProvidersToSkip = EconomicSet.create(SKIPPED_PROVIDERS); + private final EconomicSet serviceLoadedSecurityProviders = EconomicSet.create(); @Override public boolean isInConfiguration(IsInConfigurationAccess access) { @@ -161,6 +161,7 @@ public void afterRegistration(AfterRegistrationAccess access) { } servicesToSkip.addAll(Options.ServiceLoaderFeatureExcludeServices.getValue().values()); serviceProvidersToSkip.addAll(Options.ServiceLoaderFeatureExcludeServiceProviders.getValue().values()); + serviceLoadedSecurityProviders.addAll(SecurityServicesFeature.Options.AdditionalSecurityProviders.getValue().values()); } @Override @@ -203,16 +204,22 @@ public void beforeAnalysis(BeforeAnalysisAccess access) { void handleServiceClassIsReachable(DuringAnalysisAccess access, ResolvedJavaType serviceProvider, Collection providers) { FeatureImpl.DuringAnalysisAccessImpl accessImpl = (FeatureImpl.DuringAnalysisAccessImpl) access; + boolean isSecurityProviderService = serviceProvider.equals(accessImpl.getMetaAccess().lookupJavaType(java.security.Provider.class)); LinkedHashSet registeredProviders = new LinkedHashSet<>(); for (String provider : providers) { if (serviceProvidersToSkip.contains(provider)) { continue; } - if (serviceProvider.equals(accessImpl.getMetaAccess().lookupJavaType(java.security.Provider.class)) && !SecurityProvidersSupport.singleton().isUserRequestedSecurityProvider(provider)) { - SecurityProvidersSupport.singleton().markSecurityProviderAsNotLoaded(provider); - } else { - registerProviderForRuntimeReflectionAccess(access, provider, registeredProviders); + /* + * Security providers are included by SecurityServicesFeature when the provider class is + * explicitly registered for reflection or configured via AdditionalSecurityProviders. + * Do not let a service descriptor create the reflection registration that is supposed + * to prove explicit inclusion. + */ + if (isSecurityProviderService && !serviceLoadedSecurityProviders.contains(provider)) { + continue; } + registerProviderForRuntimeReflectionAccess(access, provider, registeredProviders); } registerProviderForRuntimeResourceAccess(access.getApplicationClassLoader().getUnnamedModule(), serviceProvider.toClassName(), registeredProviders); } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/PreserveOptionsSupport.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/PreserveOptionsSupport.java index c5d7a215da63..79848a9a2ebb 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/PreserveOptionsSupport.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/PreserveOptionsSupport.java @@ -29,7 +29,6 @@ import static com.oracle.svm.core.SubstrateOptions.Preserve; import static com.oracle.svm.core.jdk.JRTSupport.Options.AllowJRTFileSystem; import static com.oracle.svm.core.metadata.MetadataTracer.Options.MetadataTracingSupport; -import static com.oracle.svm.hosted.SecurityServicesFeature.Options.AdditionalSecurityProviders; import static com.oracle.svm.hosted.jdk.localization.LocalizationFeature.Options.AddAllCharsets; import static com.oracle.svm.hosted.jdk.localization.LocalizationFeature.Options.IncludeAllLocales; @@ -39,13 +38,10 @@ import java.lang.reflect.Field; import java.lang.reflect.Method; import java.lang.reflect.Modifier; -import java.security.Provider; -import java.security.Security; import java.util.Arrays; import java.util.Comparator; import java.util.HashSet; import java.util.Set; -import java.util.StringJoiner; import java.util.stream.Stream; import org.graalvm.collections.EconomicMap; @@ -177,17 +173,6 @@ public static void enableAllJDKFeatures(EconomicMap, Object> hosted AllowJRTFileSystem.update(hostedValues, true); EnableURLProtocols.update(hostedValues, "all"); - AdditionalSecurityProviders.update(hostedValues, getSecurityProvidersCSV()); - } - - private static String getSecurityProvidersCSV() { - StringJoiner joiner = new StringJoiner(","); - for (Provider provider : Security.getProviders()) { - Class aClass = provider.getClass(); - String typeName = aClass.getTypeName(); - joiner.add(typeName); - } - return joiner.toString(); } /** diff --git a/substratevm/src/com.oracle.svm.test/src/META-INF/native-image/com.oracle.svm.test/reachability-metadata.json b/substratevm/src/com.oracle.svm.test/src/META-INF/native-image/com.oracle.svm.test/reachability-metadata.json new file mode 100644 index 000000000000..d12c49d3918f --- /dev/null +++ b/substratevm/src/com.oracle.svm.test/src/META-INF/native-image/com.oracle.svm.test/reachability-metadata.json @@ -0,0 +1,13 @@ +{ + "reflection": [ + { + "type": "com.oracle.svm.test.services.SecurityServiceTest$ReflectionMetadataProvider", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + } + ] +} diff --git a/substratevm/src/com.oracle.svm.test/src/META-INF/services/java.security.Provider b/substratevm/src/com.oracle.svm.test/src/META-INF/services/java.security.Provider new file mode 100644 index 000000000000..d7142d1becbe --- /dev/null +++ b/substratevm/src/com.oracle.svm.test/src/META-INF/services/java.security.Provider @@ -0,0 +1 @@ +com.oracle.svm.test.services.SecurityServiceTest$ServiceLoadedProvider diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/services/SecurityServiceTest.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/services/SecurityServiceTest.java index 5f2ea7321d95..743945f4bcec 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/services/SecurityServiceTest.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/services/SecurityServiceTest.java @@ -24,12 +24,22 @@ */ package com.oracle.svm.test.services; +import java.security.InvalidAlgorithmParameterException; +import java.security.InvalidKeyException; +import java.security.Key; import java.security.NoSuchAlgorithmException; import java.security.Provider; import java.security.Security; +import java.security.spec.AlgorithmParameterSpec; import java.util.Iterator; +import java.util.ServiceConfigurationError; +import java.util.ServiceLoader; import java.util.Set; +import javax.crypto.Mac; +import javax.crypto.MacSpi; + +import org.graalvm.nativeimage.ImageInfo; import org.graalvm.nativeimage.hosted.Feature; import org.graalvm.nativeimage.hosted.RuntimeClassInitialization; import org.graalvm.nativeimage.hosted.RuntimeReflection; @@ -54,7 +64,13 @@ public class SecurityServiceTest { private static final String OMITTED_PROVIDER_ALGORITHM = "SHA256withECDSA"; private static final String OMITTED_PROVIDER_SERVICE = "Signature"; private static final String OMITTED_PROVIDER_ERROR = "SHA256withECDSA Signature not available"; - private static final String OMITTED_PROVIDER_OPTION = "-H:AdditionalSecurityProviders=sun.security.ec.SunEC"; + private static final String OMITTED_PROVIDER_HINT = "-H:Preserve=all"; + private static final String REFLECTION_METADATA_PROVIDER_CLASS_NAME = "com.oracle.svm.test.services.SecurityServiceTest$ReflectionMetadataProvider"; + private static final String REFLECTION_METADATA_PROVIDER_NAME = "reflection-metadata-provider"; + private static final String REFLECTION_METADATA_PROVIDER_ALGORITHM = "reflection-metadata-algo"; + private static final String REFLECTION_METADATA_PROVIDER_MAC_ALGORITHM = "reflection-metadata-mac"; + private static final String SERVICE_LOADED_PROVIDER_CLASS_NAME = "com.oracle.svm.test.services.SecurityServiceTest$ServiceLoadedProvider"; + private static final String SERVICE_LOADED_PROVIDER_ALGORITHM = "service-loaded-provider-algo"; public static class TestFeature implements Feature { @Override @@ -63,7 +79,8 @@ public void afterRegistration(AfterRegistrationAccess access) { Security.addProvider(new NoOpProvider()); Security.addProvider(new NoOpProviderTwo()); // open sun.security.jca.GetInstance - ModuleSupport.accessModuleByClass(ModuleSupport.Access.EXPORT, JCACompliantNoOpService.class, ReflectionUtil.lookupClass(false, "sun.security.jca.GetInstance")); + ModuleSupport.accessModuleByClass(ModuleSupport.Access.EXPORT, JCACompliantNoOpService.class, + ReflectionUtil.lookupClass(false, "sun.security.jca.GetInstance")); } @Override @@ -132,6 +149,37 @@ public void testAutomaticSecurityServiceRegistration() { } } + @Test + public void testReflectionMetadataProviderRegistration() throws Exception { + Provider provider = (Provider) Class.forName(REFLECTION_METADATA_PROVIDER_CLASS_NAME).getDeclaredConstructor().newInstance(); + int position = Security.addProvider(provider); + try { + Assert.assertTrue("Provider should be registered.", position > 0); + JCACompliantNoOpService service = JCACompliantNoOpService.getInstance(REFLECTION_METADATA_PROVIDER_ALGORITHM); + Assert.assertNotNull("No service instance was created", service); + Assert.assertEquals("Unexpected service implementation class", ReflectionMetadataNoOpServiceImpl.class.getName(), service.getClass().getName()); + Assert.assertNotNull("No JCE service instance was created", Mac.getInstance(REFLECTION_METADATA_PROVIDER_MAC_ALGORITHM, provider)); + } finally { + Security.removeProvider(REFLECTION_METADATA_PROVIDER_NAME); + } + } + + @Test + public void testServiceLoaderProviderWithoutMetadataIsOmitted() { + Assume.assumeTrue("native image runtime only", ImageInfo.inImageRuntimeCode()); + Assume.assumeTrue("needs runtime initialization", FutureDefaultsOptions.securityProvidersInitializedAtRunTime()); + + try { + boolean foundProvider = ServiceLoader.load(Provider.class).stream() + .anyMatch(provider -> provider.type().getName().equals(SERVICE_LOADED_PROVIDER_CLASS_NAME)); + Assert.assertFalse("Service descriptors alone must not include security providers.", foundProvider); + } catch (ServiceConfigurationError e) { + Assert.fail("Service-only security provider should be omitted, not left as an unloadable service entry: " + e); + } + + Assert.assertThrows(NoSuchAlgorithmException.class, () -> JCACompliantNoOpService.getInstance(SERVICE_LOADED_PROVIDER_ALGORITHM)); + } + @Delete @TargetClass(className = "sun.security.pkcs11.SunPKCS11") static final class Target_sun_security_pkcs11_SunPKCS11 { @@ -155,8 +203,9 @@ public void testMissingBuiltInProviderErrorMessage() { } catch (SecurityException e) { Assert.assertTrue("Missing provider message should mention the provider name.", e.getMessage().contains("SunEC")); Assert.assertTrue("Missing provider message should mention the provider class.", e.getMessage().contains("sun.security.ec.SunEC")); - Assert.assertTrue("Missing provider message should mention AdditionalSecurityProviders.", - e.getMessage().contains("-H:AdditionalSecurityProviders=sun.security.ec.SunEC")); + Assert.assertTrue("Missing provider message should mention the tracing agent.", e.getMessage().contains("tracing agent")); + Assert.assertTrue("Missing provider message should mention reflection metadata.", e.getMessage().contains("reachability-metadata.json")); + Assert.assertTrue("Missing provider message should mention preserve all.", e.getMessage().contains(OMITTED_PROVIDER_HINT)); } } @@ -169,7 +218,7 @@ public void testGenericMissingBuiltInProviderGetServiceUsesBroadError() { } catch (NoSuchAlgorithmException e) { Assert.assertEquals(OMITTED_PROVIDER_ERROR, e.getMessage()); Assert.assertFalse("Generic discovery should not use the explicit-provider diagnostic yet.", - e.getMessage().contains(OMITTED_PROVIDER_OPTION)); + e.getMessage().contains(OMITTED_PROVIDER_HINT)); } } @@ -182,7 +231,7 @@ public void testGenericMissingBuiltInProviderGetInstanceUsesBroadError() { } catch (NoSuchAlgorithmException e) { Assert.assertEquals(OMITTED_PROVIDER_ERROR, e.getMessage()); Assert.assertFalse("Generic discovery should not use the explicit-provider diagnostic yet.", - e.getMessage().contains(OMITTED_PROVIDER_OPTION)); + e.getMessage().contains(OMITTED_PROVIDER_HINT)); } } @@ -257,4 +306,58 @@ public static JCACompliantNoOpService getInstance(String algorithm) throws NoSuc public static class JcaCompliantNoOpServiceImpl extends JCACompliantNoOpService { } + + public static final class ReflectionMetadataNoOpServiceImpl extends JCACompliantNoOpService { + } + + public static final class ReflectionMetadataProvider extends Provider { + static final long serialVersionUID = 1234L; + + @SuppressWarnings("deprecation") + public ReflectionMetadataProvider() { + super(REFLECTION_METADATA_PROVIDER_NAME, 1.0, "Provider registered through reflection metadata"); + putService(new Service(this, "JCACompliantNoOpService", REFLECTION_METADATA_PROVIDER_ALGORITHM, + ReflectionMetadataNoOpServiceImpl.class.getName(), null, null)); + putService(new Service(this, "Mac", REFLECTION_METADATA_PROVIDER_MAC_ALGORITHM, ReflectionMetadataMacSpi.class.getName(), null, null)); + } + } + + public static final class ReflectionMetadataMacSpi extends MacSpi { + @Override + protected int engineGetMacLength() { + return 0; + } + + @Override + protected void engineInit(Key key, AlgorithmParameterSpec params) throws InvalidKeyException, InvalidAlgorithmParameterException { + } + + @Override + protected void engineUpdate(byte input) { + } + + @Override + protected void engineUpdate(byte[] input, int offset, int len) { + } + + @Override + protected byte[] engineDoFinal() { + return new byte[0]; + } + + @Override + protected void engineReset() { + } + } + + public static final class ServiceLoadedProvider extends Provider { + static final long serialVersionUID = 1234L; + + @SuppressWarnings("deprecation") + public ServiceLoadedProvider() { + super("service-loaded-provider", 1.0, "Provider registered only through META-INF/services"); + putService(new Service(this, "JCACompliantNoOpService", SERVICE_LOADED_PROVIDER_ALGORITHM, + ReflectionMetadataNoOpServiceImpl.class.getName(), null, null)); + } + } } From b269a9cb4b943a44e9ea60b337175d6604f62adc Mon Sep 17 00:00:00 2001 From: Vojin Jovanovic Date: Fri, 19 Jun 2026 18:48:31 +0200 Subject: [PATCH 02/63] [GR-69858] Fix security provider verification cache --- .../svm/hosted/SecurityServicesFeature.java | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SecurityServicesFeature.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SecurityServicesFeature.java index aa5a00a1f124..85552e011767 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SecurityServicesFeature.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SecurityServicesFeature.java @@ -478,10 +478,19 @@ private ConcurrentHashMap, Object> filterVerificationCac * WeakReference and so using WeakReference.get() is sufficient for us. */ var cleanedCache = new ConcurrentHashMap<>((ConcurrentHashMap, Object>) originalValue); - cleanedCache.keySet().removeIf(key -> shouldRemoveProvider(key.get())); + cleanedCache.keySet().removeIf(key -> shouldRemoveVerificationResult(key.get())); return cleanedCache; } + private boolean shouldRemoveVerificationResult(Provider provider) { + /* + * Verification results for used providers are copied into SecurityProvidersSupport, keyed by + * provider name and class name. Keep them out of JceSecurity.verificationResults so the weak + * cache keys do not keep build-time provider objects reachable in the image heap. + */ + return provider == null || usedProviders.contains(provider) || shouldRemoveProvider(provider); + } + private List filterProviderList(Object originalValue) { return ((ProviderList) originalValue).providers().stream().filter(p -> !shouldRemoveProvider(p)).toList(); } @@ -844,7 +853,7 @@ private static void registerSpiClass(Method getSpiClassMethod, String serviceTyp } } - private void registerProvider(DuringAnalysisAccess access, Provider provider) { + private void registerProvider(Provider provider) { if (usedProviders.add(provider)) { registerForReflection(provider.getClass()); /* Trigger initialization of lazy field java.security.Provider.entrySet. */ @@ -882,7 +891,7 @@ private void includeProviderClass(DuringAnalysisAccess access, Class provider if (provider == null) { provider = instantiateProvider(providerClass); } - registerProvider(access, provider); + registerProvider(provider); for (Service service : provider.getServices()) { if (isValid(service)) { registerService(access, service); @@ -943,7 +952,7 @@ private void registerService(DuringAnalysisAccess a, Service service) { if (isCertificateFactory(service) && service.getAlgorithm().equals(X509)) { registerX509Extensions(a); } - registerProvider(a, service.getProvider()); + registerProvider(service.getProvider()); } } else { trace("Cannot register service %s. Reason: %s.", asString(service), serviceClassResult.getException()); From 28c60fe70d3c0d626a66e1f0faab0d12f971f669 Mon Sep 17 00:00:00 2001 From: Vojin Jovanovic Date: Sat, 20 Jun 2026 08:40:35 +0200 Subject: [PATCH 03/63] [GR-69858] Preserve explicit security provider metadata --- .../svm/hosted/SecurityServicesFeature.java | 6 +++--- .../oracle/svm/hosted/ServiceLoaderFeature.java | 16 +++++++++++++++- .../hosted/reflect/ReflectionDataBuilder.java | 7 +++++++ .../META-INF/services/java.security.Provider | 1 + .../svm/test/services/SecurityServiceTest.java | 17 +++++++++++++++++ 5 files changed, 43 insertions(+), 4 deletions(-) diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SecurityServicesFeature.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SecurityServicesFeature.java index 85552e011767..243698daf6f0 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SecurityServicesFeature.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SecurityServicesFeature.java @@ -386,10 +386,10 @@ public void beforeAnalysis(BeforeAnalysisAccess a) { */ access.ensureInitialized("sun.security.util.AnchorCertificates"); + initializeServiceRegistrationData(); + access.registerSubtypeReachabilityHandler((analysisAccess, providerClass) -> includeProviderClass(analysisAccess, providerClass), Provider.class); + registerManuallyConfiguredProvidersForReflection(access); if (Options.EnableSecurityServicesFeature.getValue()) { - initializeServiceRegistrationData(); - access.registerSubtypeReachabilityHandler((analysisAccess, providerClass) -> includeProviderClass(analysisAccess, providerClass), Provider.class); - registerManuallyConfiguredProvidersForReflection(access); registerServiceReachabilityHandlers(access); } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ServiceLoaderFeature.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ServiceLoaderFeature.java index e19602e6830f..8b172d66bd6f 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ServiceLoaderFeature.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ServiceLoaderFeature.java @@ -26,6 +26,7 @@ import java.lang.reflect.Modifier; import java.nio.charset.StandardCharsets; +import java.security.Provider; import java.util.Collection; import java.util.HashSet; import java.util.LinkedHashSet; @@ -36,15 +37,18 @@ import java.util.stream.Stream; import org.graalvm.collections.EconomicSet; +import org.graalvm.nativeimage.ImageSingletons; import org.graalvm.nativeimage.Platform; import org.graalvm.nativeimage.hosted.RuntimeReflection; import org.graalvm.nativeimage.hosted.RuntimeResourceAccess; +import org.graalvm.nativeimage.impl.RuntimeReflectionSupport; import com.oracle.graal.pointsto.constraints.UnsupportedPlatformException; import com.oracle.svm.core.FutureDefaultsOptions; import com.oracle.svm.core.feature.InternalFeature; import com.oracle.svm.core.jdk.ServiceCatalogSupport; import com.oracle.svm.hosted.analysis.Inflation; +import com.oracle.svm.hosted.reflect.ReflectionDataBuilder; import com.oracle.svm.hosted.substitute.DeletedElementException; import com.oracle.svm.shared.feature.AutomaticallyRegisteredFeature; import com.oracle.svm.shared.option.AccumulatingLocatableMultiOptionValue; @@ -216,7 +220,8 @@ void handleServiceClassIsReachable(DuringAnalysisAccess access, ResolvedJavaType * Do not let a service descriptor create the reflection registration that is supposed * to prove explicit inclusion. */ - if (isSecurityProviderService && !serviceLoadedSecurityProviders.contains(provider)) { + if (isSecurityProviderService && !serviceLoadedSecurityProviders.contains(provider) && + !isSecurityProviderRegisteredForReflection(access, provider)) { continue; } registerProviderForRuntimeReflectionAccess(access, provider, registeredProviders); @@ -224,6 +229,15 @@ void handleServiceClassIsReachable(DuringAnalysisAccess access, ResolvedJavaType registerProviderForRuntimeResourceAccess(access.getApplicationClassLoader().getUnnamedModule(), serviceProvider.toClassName(), registeredProviders); } + private static boolean isSecurityProviderRegisteredForReflection(DuringAnalysisAccess access, String provider) { + Class providerClass = access.findClassByName(provider); + if (providerClass == null || !Provider.class.isAssignableFrom(providerClass)) { + return false; + } + ReflectionDataBuilder reflectionData = (ReflectionDataBuilder) ImageSingletons.lookup(RuntimeReflectionSupport.class); + return reflectionData.isTypeRegisteredForReflectiveAccess(providerClass); + } + @BasedOnJDKFile("https://github.com/graalvm/labs-openjdk/blob/jdk-25+21/src/java.base/share/classes/java/util/ServiceLoader.java#L745-L793") public static void registerProviderForRuntimeReflectionAccess(DuringAnalysisAccess access, String provider, Set registeredProviders) { FeatureImpl.DuringAnalysisAccessImpl accessImpl = (FeatureImpl.DuringAnalysisAccessImpl) access; diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/ReflectionDataBuilder.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/ReflectionDataBuilder.java index 62bea4b019f9..acc098e09077 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/ReflectionDataBuilder.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/ReflectionDataBuilder.java @@ -1659,6 +1659,13 @@ private static Set g } } + public boolean isTypeRegisteredForReflectiveAccess(Class clazz) { + AnalysisType analysisType = metaAccess.lookupJavaType(clazz); + TypeData typeData = types.get(analysisType); + return (typeData != null && typeData.isRegisteredAs(ACCESSED)) || + (layeredReflectionDataBuilder != null && layeredReflectionDataBuilder.isTypeRegistered(analysisType)); + } + public static class TestBackdoor { public static void registerField(ReflectionDataBuilder reflectionDataBuilder, ConfigurationMemberAccessibility accessibility, Field field) { reflectionDataBuilder.registerField(unconditional(), accessibility, false, GuestAccess.get().lookupField(field)); diff --git a/substratevm/src/com.oracle.svm.test/src/META-INF/services/java.security.Provider b/substratevm/src/com.oracle.svm.test/src/META-INF/services/java.security.Provider index d7142d1becbe..7f332be98753 100644 --- a/substratevm/src/com.oracle.svm.test/src/META-INF/services/java.security.Provider +++ b/substratevm/src/com.oracle.svm.test/src/META-INF/services/java.security.Provider @@ -1 +1,2 @@ +com.oracle.svm.test.services.SecurityServiceTest$ReflectionMetadataProvider com.oracle.svm.test.services.SecurityServiceTest$ServiceLoadedProvider diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/services/SecurityServiceTest.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/services/SecurityServiceTest.java index 743945f4bcec..0d661cefb3be 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/services/SecurityServiceTest.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/services/SecurityServiceTest.java @@ -180,6 +180,23 @@ public void testServiceLoaderProviderWithoutMetadataIsOmitted() { Assert.assertThrows(NoSuchAlgorithmException.class, () -> JCACompliantNoOpService.getInstance(SERVICE_LOADED_PROVIDER_ALGORITHM)); } + @Test + public void testServiceLoaderProviderWithMetadataIsPreserved() { + Assume.assumeTrue("native image runtime only", ImageInfo.inImageRuntimeCode()); + Assume.assumeTrue("needs runtime initialization", FutureDefaultsOptions.securityProvidersInitializedAtRunTime()); + + try { + Provider provider = ServiceLoader.load(Provider.class).stream() + .filter(candidate -> candidate.type().getName().equals(REFLECTION_METADATA_PROVIDER_CLASS_NAME)) + .findFirst() + .orElseThrow(() -> new AssertionError("Metadata-registered security provider should be visible through ServiceLoader.")) + .get(); + Assert.assertEquals("Unexpected provider name", REFLECTION_METADATA_PROVIDER_NAME, provider.getName()); + } catch (ServiceConfigurationError e) { + Assert.fail("Metadata-registered security provider should be loadable through ServiceLoader: " + e); + } + } + @Delete @TargetClass(className = "sun.security.pkcs11.SunPKCS11") static final class Target_sun_security_pkcs11_SunPKCS11 { From 385d29724286b0502b50d1832bbbde7db9aa2c6e Mon Sep 17 00:00:00 2001 From: Vojin Jovanovic Date: Sat, 20 Jun 2026 08:58:49 +0200 Subject: [PATCH 04/63] [GR-69858] Remove security provider troubleshooting section --- .../native-image/guides/troubleshoot-run-time-errors.md | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/docs/reference-manual/native-image/guides/troubleshoot-run-time-errors.md b/docs/reference-manual/native-image/guides/troubleshoot-run-time-errors.md index 22963f26719f..cc0384a02d2c 100644 --- a/docs/reference-manual/native-image/guides/troubleshoot-run-time-errors.md +++ b/docs/reference-manual/native-image/guides/troubleshoot-run-time-errors.md @@ -62,14 +62,7 @@ Other handy options are `-H:+AddAllCharsets` to add charsets support, and `-H:+I Pass those options at build time. This might increase the size of the resulting binary. -### 4. Add Missing Security Providers - -If your application uses security providers that are not included in the native executable, run the application with the Tracing Agent and rebuild with the collected reachability metadata. -You can also register the provider classes for reflection in _reachability-metadata.json_, or build with `-H:Preserve=all` to include all JDK providers. -Here is a list of JDK security provider classes: -`sun.security.provider.Sun,sun.security.rsa.SunRsaSign,sun.security.ec.SunEC,sun.security.ssl.SunJSSE,com.sun.crypto.provider.SunJCE,sun.security.jgss.SunProvider,com.sun.security.sasl.Provider,org.jcp.xml.dsig.internal.dom.XMLDSigRI,sun.security.smartcardio.SunPCSC,sun.security.provider.certpath.ldap.JdkLDAP,com.sun.security.sasl.gsskerb.JdkSASL`. - -### 5. File a Native Image Run-Time Issue +### 4. File a Native Image Run-Time Issue Only if you tried all the above suggestions, file a [Native Image Run-Time Issue Report](https://github.com/oracle/graal/issues/new?assignees=&labels=native-image%2Cbug%2Crun-time&projects=&template=1_1_native_image_run_time_bug_report.yml&title=%5BNative+Image%5D+) at GitHub, filling out the necessary information. From 5cfaff448bc1a719aecb1ef2d6aeda1877691af1 Mon Sep 17 00:00:00 2001 From: Vojin Jovanovic Date: Sat, 20 Jun 2026 09:00:32 +0200 Subject: [PATCH 05/63] [GR-69858] Remove deprecated provider option docs --- docs/reference-manual/native-image/JCASecurityServices.md | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/reference-manual/native-image/JCASecurityServices.md b/docs/reference-manual/native-image/JCASecurityServices.md index 5d2614ab9e4d..94a23f488891 100644 --- a/docs/reference-manual/native-image/JCASecurityServices.md +++ b/docs/reference-manual/native-image/JCASecurityServices.md @@ -75,7 +75,6 @@ By default, only services specified in the JCA framework are automatically regis Note that for automatic registration to work, the service interface must have a `getInstance` method and have the same name as the service type. If you rely on third-party code that does not comply with these requirements, manual configuration is required. Register the provider class for reflection in _reachability-metadata.json_ or collect the metadata with the Tracing Agent. -The deprecated `-H:AdditionalSecurityProviders` option still registers the listed provider classes for reflection as a compatibility path. ### Further Reading From 82fac2e6786584db08926f47b3d01c8f33721f1f Mon Sep 17 00:00:00 2001 From: Vojin Jovanovic Date: Sat, 20 Jun 2026 09:26:32 +0200 Subject: [PATCH 06/63] [GR-69858] Use reflection lookup for service-loaded providers --- .../svm/hosted/ServiceLoaderFeature.java | 76 +++++++++---------- .../hosted/reflect/ReflectionDataBuilder.java | 8 -- .../test/services/SecurityServiceTest.java | 14 ++-- 3 files changed, 42 insertions(+), 56 deletions(-) diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ServiceLoaderFeature.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ServiceLoaderFeature.java index 8b172d66bd6f..dbded5746b73 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ServiceLoaderFeature.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ServiceLoaderFeature.java @@ -26,7 +26,6 @@ import java.lang.reflect.Modifier; import java.nio.charset.StandardCharsets; -import java.security.Provider; import java.util.Collection; import java.util.HashSet; import java.util.LinkedHashSet; @@ -37,18 +36,15 @@ import java.util.stream.Stream; import org.graalvm.collections.EconomicSet; -import org.graalvm.nativeimage.ImageSingletons; import org.graalvm.nativeimage.Platform; import org.graalvm.nativeimage.hosted.RuntimeReflection; import org.graalvm.nativeimage.hosted.RuntimeResourceAccess; -import org.graalvm.nativeimage.impl.RuntimeReflectionSupport; import com.oracle.graal.pointsto.constraints.UnsupportedPlatformException; import com.oracle.svm.core.FutureDefaultsOptions; import com.oracle.svm.core.feature.InternalFeature; import com.oracle.svm.core.jdk.ServiceCatalogSupport; import com.oracle.svm.hosted.analysis.Inflation; -import com.oracle.svm.hosted.reflect.ReflectionDataBuilder; import com.oracle.svm.hosted.substitute.DeletedElementException; import com.oracle.svm.shared.feature.AutomaticallyRegisteredFeature; import com.oracle.svm.shared.option.AccumulatingLocatableMultiOptionValue; @@ -148,7 +144,6 @@ public static class Options { "jdk.jshell.execution.impl.ConsoleImpl$ConsoleProviderImpl"); private final EconomicSet serviceProvidersToSkip = EconomicSet.create(SKIPPED_PROVIDERS); - private final EconomicSet serviceLoadedSecurityProviders = EconomicSet.create(); @Override public boolean isInConfiguration(IsInConfigurationAccess access) { @@ -165,7 +160,6 @@ public void afterRegistration(AfterRegistrationAccess access) { } servicesToSkip.addAll(Options.ServiceLoaderFeatureExcludeServices.getValue().values()); serviceProvidersToSkip.addAll(Options.ServiceLoaderFeatureExcludeServiceProviders.getValue().values()); - serviceLoadedSecurityProviders.addAll(SecurityServicesFeature.Options.AdditionalSecurityProviders.getValue().values()); } @Override @@ -215,13 +209,13 @@ void handleServiceClassIsReachable(DuringAnalysisAccess access, ResolvedJavaType continue; } /* - * Security providers are included by SecurityServicesFeature when the provider class is - * explicitly registered for reflection or configured via AdditionalSecurityProviders. - * Do not let a service descriptor create the reflection registration that is supposed - * to prove explicit inclusion. + * Security provider service descriptors are preserved, but the provider classes are + * registered for reflection only by explicit reflection metadata or + * SecurityServicesFeature. If no such metadata is present, ServiceLoader will report + * the regular Class.forName failure at run time. */ - if (isSecurityProviderService && !serviceLoadedSecurityProviders.contains(provider) && - !isSecurityProviderRegisteredForReflection(access, provider)) { + if (isSecurityProviderService) { + registerProviderForRuntimeResourceAccess(access, provider, registeredProviders); continue; } registerProviderForRuntimeReflectionAccess(access, provider, registeredProviders); @@ -229,37 +223,11 @@ void handleServiceClassIsReachable(DuringAnalysisAccess access, ResolvedJavaType registerProviderForRuntimeResourceAccess(access.getApplicationClassLoader().getUnnamedModule(), serviceProvider.toClassName(), registeredProviders); } - private static boolean isSecurityProviderRegisteredForReflection(DuringAnalysisAccess access, String provider) { - Class providerClass = access.findClassByName(provider); - if (providerClass == null || !Provider.class.isAssignableFrom(providerClass)) { - return false; - } - ReflectionDataBuilder reflectionData = (ReflectionDataBuilder) ImageSingletons.lookup(RuntimeReflectionSupport.class); - return reflectionData.isTypeRegisteredForReflectiveAccess(providerClass); - } - @BasedOnJDKFile("https://github.com/graalvm/labs-openjdk/blob/jdk-25+21/src/java.base/share/classes/java/util/ServiceLoader.java#L745-L793") public static void registerProviderForRuntimeReflectionAccess(DuringAnalysisAccess access, String provider, Set registeredProviders) { - FeatureImpl.DuringAnalysisAccessImpl accessImpl = (FeatureImpl.DuringAnalysisAccessImpl) access; /* Make provider reflectively instantiable */ - ResolvedJavaType providerClass; - try { - providerClass = accessImpl.findTypeByName(provider); - } catch (UnsupportedPlatformException e) { - return; - } catch (DeletedElementException e) { - /* Disallow services with implementation classes that are marked as @Deleted */ - return; - } - - if (providerClass == null || providerClass.isArray() || providerClass.isPrimitive()) { - return; - } - if (!accessImpl.getHostVM().platformSupported(providerClass)) { - return; - } - if (((Inflation) accessImpl.getBigBang()).getAnnotationSubstitutionProcessor().isDeleted(providerClass)) { - /* Disallow services with implementation classes that are marked as @Deleted */ + ResolvedJavaType providerClass = findServiceProviderType(access, provider); + if (providerClass == null) { return; } @@ -308,6 +276,34 @@ public static void registerProviderForRuntimeReflectionAccess(DuringAnalysisAcce registeredProviders.add(provider); } + private static void registerProviderForRuntimeResourceAccess(DuringAnalysisAccess access, String provider, Set registeredProviders) { + if (findServiceProviderType(access, provider) != null) { + registeredProviders.add(provider); + } + } + + private static ResolvedJavaType findServiceProviderType(DuringAnalysisAccess access, String provider) { + FeatureImpl.DuringAnalysisAccessImpl accessImpl = (FeatureImpl.DuringAnalysisAccessImpl) access; + ResolvedJavaType providerClass; + try { + providerClass = accessImpl.findTypeByName(provider); + } catch (UnsupportedPlatformException | DeletedElementException e) { + return null; + } + + if (providerClass == null || providerClass.isArray() || providerClass.isPrimitive()) { + return null; + } + if (!accessImpl.getHostVM().platformSupported(providerClass)) { + return null; + } + if (((Inflation) accessImpl.getBigBang()).getAnnotationSubstitutionProcessor().isDeleted(providerClass)) { + /* Disallow services with implementation classes that are marked as @Deleted */ + return null; + } + return providerClass; + } + public static void registerProviderForRuntimeResourceAccess(Module module, String serviceProviderName, Set registeredProviders) { if (!registeredProviders.isEmpty()) { String serviceResourceLocation = "META-INF/services/" + serviceProviderName; diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/ReflectionDataBuilder.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/ReflectionDataBuilder.java index acc098e09077..4c72dd7dad59 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/ReflectionDataBuilder.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/ReflectionDataBuilder.java @@ -1658,14 +1658,6 @@ private static Set g .collect(Collectors.toUnmodifiableSet()); } } - - public boolean isTypeRegisteredForReflectiveAccess(Class clazz) { - AnalysisType analysisType = metaAccess.lookupJavaType(clazz); - TypeData typeData = types.get(analysisType); - return (typeData != null && typeData.isRegisteredAs(ACCESSED)) || - (layeredReflectionDataBuilder != null && layeredReflectionDataBuilder.isTypeRegistered(analysisType)); - } - public static class TestBackdoor { public static void registerField(ReflectionDataBuilder reflectionDataBuilder, ConfigurationMemberAccessibility accessibility, Field field) { reflectionDataBuilder.registerField(unconditional(), accessibility, false, GuestAccess.get().lookupField(field)); diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/services/SecurityServiceTest.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/services/SecurityServiceTest.java index 0d661cefb3be..1ee84e812d6b 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/services/SecurityServiceTest.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/services/SecurityServiceTest.java @@ -165,17 +165,15 @@ public void testReflectionMetadataProviderRegistration() throws Exception { } @Test - public void testServiceLoaderProviderWithoutMetadataIsOmitted() { + public void testServiceLoaderProviderWithoutMetadataUsesReflectionLookupFailure() { Assume.assumeTrue("native image runtime only", ImageInfo.inImageRuntimeCode()); Assume.assumeTrue("needs runtime initialization", FutureDefaultsOptions.securityProvidersInitializedAtRunTime()); - try { - boolean foundProvider = ServiceLoader.load(Provider.class).stream() - .anyMatch(provider -> provider.type().getName().equals(SERVICE_LOADED_PROVIDER_CLASS_NAME)); - Assert.assertFalse("Service descriptors alone must not include security providers.", foundProvider); - } catch (ServiceConfigurationError e) { - Assert.fail("Service-only security provider should be omitted, not left as an unloadable service entry: " + e); - } + Assert.assertThrows(ClassNotFoundException.class, () -> Class.forName(SERVICE_LOADED_PROVIDER_CLASS_NAME)); + ServiceConfigurationError serviceLoaderError = Assert.assertThrows(ServiceConfigurationError.class, () -> ServiceLoader.load(Provider.class).stream() + .anyMatch(provider -> provider.type().getName().equals(SERVICE_LOADED_PROVIDER_CLASS_NAME))); + Assert.assertTrue("ServiceLoader should report the missing provider class.", serviceLoaderError.getMessage().contains(SERVICE_LOADED_PROVIDER_CLASS_NAME)); + Assert.assertTrue("ServiceLoader should use the standard reflection lookup failure.", serviceLoaderError.getCause() instanceof ClassNotFoundException); Assert.assertThrows(NoSuchAlgorithmException.class, () -> JCACompliantNoOpService.getInstance(SERVICE_LOADED_PROVIDER_ALGORITHM)); } From d2ef2d408dbf0edadbad1afe4f4727c2245e4f96 Mon Sep 17 00:00:00 2001 From: Vojin Jovanovic Date: Sat, 20 Jun 2026 18:44:41 +0200 Subject: [PATCH 07/63] [GR-69858] Trace security provider lookups in the agent --- .../svm/agent/BreakpointInterceptor.java | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/BreakpointInterceptor.java b/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/BreakpointInterceptor.java index df1a0735791d..367524917c55 100644 --- a/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/BreakpointInterceptor.java +++ b/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/BreakpointInterceptor.java @@ -728,6 +728,26 @@ private static boolean newInstance(JNIEnvironment jni, JNIObjectHandle thread, B return true; } + private static boolean getStaticSecurityProviderByName(JNIEnvironment jni, JNIObjectHandle thread, Breakpoint bp, InterceptedState state) { + JNIObjectHandle callerClass = state.getDirectCallerClass(); + JNIObjectHandle name = getObjectArgument(thread, 0); + JNIObjectHandle provider = Support.callStaticObjectMethodL(jni, bp.clazz, bp.method, name); + boolean validResult = !clearException(jni) && provider.notEqual(nullHandle()); + traceSecurityProvider(jni, provider, validResult, callerClass, state); + return true; + } + + private static void traceSecurityProvider(JNIEnvironment jni, JNIObjectHandle provider, boolean validResult, JNIObjectHandle callerClass, InterceptedState state) { + if (!validResult) { + return; + } + JNIObjectHandle providerClass = Support.callObjectMethod(jni, provider, agent.handles().javaLangObjectGetClass); + if (clearException(jni)) { + providerClass = nullHandle(); + } + traceReflectBreakpoint(jni, providerClass, providerClass, callerClass, "invokeConstructor", providerClass.notEqual(nullHandle()), state.getFullStackTraceOrNull(), Arrays.asList()); + } + private static boolean newArrayInstance(JNIEnvironment jni, JNIObjectHandle thread, Breakpoint bp, InterceptedState state) { JNIValue args = StackValue.get(2, JNIValue.class); args.addressOf(0).setObject(getObjectArgument(thread, 0)); @@ -1823,6 +1843,8 @@ private interface BreakpointHandler { brk("java/lang/reflect/Array", "newInstance", "(Ljava/lang/Class;I)Ljava/lang/Object;", BreakpointInterceptor::newArrayInstance), brk("java/lang/reflect/Array", "newInstance", "(Ljava/lang/Class;[I)Ljava/lang/Object;", BreakpointInterceptor::newArrayInstanceMulti), + brk("java/security/Security", "getProvider", "(Ljava/lang/String;)Ljava/security/Provider;", BreakpointInterceptor::getStaticSecurityProviderByName), + brk("java/lang/ClassLoader", "findSystemClass", "(Ljava/lang/String;)Ljava/lang/Class;", BreakpointInterceptor::findSystemClass), From d625be82faaf02a5707967c94ea37a6fe793db2e Mon Sep 17 00:00:00 2001 From: Vojin Jovanovic Date: Sat, 20 Jun 2026 21:52:15 +0200 Subject: [PATCH 08/63] [GR-69858] Trace security provider lookups natively --- .../svm/core/jdk/SecurityProvidersSupport.java | 15 +++++++++++++++ .../SecuritySubstitutionRuntimeInit.java | 13 +++++++++++-- 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SecurityProvidersSupport.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SecurityProvidersSupport.java index c9106195516a..95eb14d5680d 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SecurityProvidersSupport.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SecurityProvidersSupport.java @@ -25,6 +25,8 @@ package com.oracle.svm.core.jdk; +import static com.oracle.svm.core.annotate.TargetElement.CONSTRUCTOR_NAME; + import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.security.Provider; @@ -35,6 +37,9 @@ import org.graalvm.nativeimage.Platform; import org.graalvm.nativeimage.Platforms; +import com.oracle.svm.configure.config.ConfigurationMemberInfo; +import com.oracle.svm.configure.config.SignatureUtil; +import com.oracle.svm.core.metadata.MetadataTracer; import com.oracle.svm.guest.staging.util.ImageHeapMap; import com.oracle.svm.shared.singletons.traits.BuiltinTraits.AllAccess; import com.oracle.svm.shared.singletons.traits.BuiltinTraits.DisallowLayered; @@ -54,6 +59,8 @@ */ @SingletonTraits(access = AllAccess.class, layeredCallbacks = NoLayeredCallbacks.class, layeredInstallationKind = Duplicable.class, other = DisallowLayered.class) public final class SecurityProvidersSupport { + private static final Class[] NO_PARAMETERS = new Class[0]; + /** * A map of providers, identified by their names (see {@link Provider#getName()}), and the * results of their verification (see javax.crypto.JceSecurity#getVerificationResult). This @@ -163,6 +170,14 @@ public static String missingProviderMessage(String providerName, String provider " for reflection in reachability-metadata.json, or build with -H:Preserve=all to include all JDK providers."; } + public static Provider traceProviderLookup(Provider provider) { + if (provider != null && MetadataTracer.enabled()) { + MetadataTracer.singleton().traceMethodAccess(provider.getClass(), CONSTRUCTOR_NAME, SignatureUtil.toInternalSignature(NO_PARAMETERS), + ConfigurationMemberInfo.ConfigurationMemberDeclaration.DECLARED); + } + return provider; + } + public Provider loadBuiltInProvider(String provName, Debug debug) { return switch (provName) { case "SUN", "sun.security.provider.Sun" -> diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/runtimeinit/SecuritySubstitutionRuntimeInit.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/runtimeinit/SecuritySubstitutionRuntimeInit.java index 6aca0f11dd0b..3a4f60907b5f 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/runtimeinit/SecuritySubstitutionRuntimeInit.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/runtimeinit/SecuritySubstitutionRuntimeInit.java @@ -48,6 +48,15 @@ final class Target_java_security_Security { static Properties props; } +@TargetClass(value = java.security.Security.class) +final class Target_java_security_Security_MetadataTracing { + + @Substitute + public static Provider getProvider(String name) { + return SecurityProvidersSupport.traceProviderLookup(sun.security.jca.Providers.getProviderList().getProvider(name)); + } +} + @TargetClass(value = java.security.Security.class, innerClass = "SecPropLoader", onlyWith = SecurityProvidersInitializedAtRunTime.class) final class Target_java_security_Security_SecPropLoader { @@ -199,7 +208,7 @@ final class Target_sun_security_jca_ProviderList { public Provider getProvider(String name) { int index = getIndex(name); if (index >= 0) { - return getProvider(index); + return SecurityProvidersSupport.traceProviderLookup(getProvider(index)); } for (Target_sun_security_jca_ProviderConfig config : configs) { String configuredProviderName = config.provName; @@ -210,7 +219,7 @@ public Provider getProvider(String name) { if (SecurityProvidersSupport.singleton().isMissingBuiltInProvider(configuredProviderName)) { throw SecurityProvidersSupport.missingBuiltInProvider(configuredProviderName); } - return config.getProvider(); + return SecurityProvidersSupport.traceProviderLookup(config.getProvider()); } } return null; From 22530f0f7b27cb54830b0534dd18d7b73065da31 Mon Sep 17 00:00:00 2001 From: Vojin Jovanovic Date: Sun, 21 Jun 2026 06:21:54 +0200 Subject: [PATCH 09/63] [GR-69858] Fix security provider gate failures --- .../com/oracle/svm/core/jdk/SecurityProvidersSupport.java | 5 ++++- .../jdk/runtimeinit/SecuritySubstitutionRuntimeInit.java | 4 ++-- .../com/oracle/svm/hosted/SecurityServicesFeature.java | 8 +++++--- 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SecurityProvidersSupport.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SecurityProvidersSupport.java index 95eb14d5680d..434f83b8495b 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SecurityProvidersSupport.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SecurityProvidersSupport.java @@ -171,7 +171,10 @@ public static String missingProviderMessage(String providerName, String provider } public static Provider traceProviderLookup(Provider provider) { - if (provider != null && MetadataTracer.enabled()) { + if (provider == null || singleton().isMissingBuiltInProvider(provider.getName()) || singleton().isMissingBuiltInProvider(provider.getClass().getName())) { + return null; + } + if (MetadataTracer.enabled()) { MetadataTracer.singleton().traceMethodAccess(provider.getClass(), CONSTRUCTOR_NAME, SignatureUtil.toInternalSignature(NO_PARAMETERS), ConfigurationMemberInfo.ConfigurationMemberDeclaration.DECLARED); } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/runtimeinit/SecuritySubstitutionRuntimeInit.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/runtimeinit/SecuritySubstitutionRuntimeInit.java index 3a4f60907b5f..453969916150 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/runtimeinit/SecuritySubstitutionRuntimeInit.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/runtimeinit/SecuritySubstitutionRuntimeInit.java @@ -48,8 +48,8 @@ final class Target_java_security_Security { static Properties props; } -@TargetClass(value = java.security.Security.class) -final class Target_java_security_Security_MetadataTracing { +@TargetClass(java.security.Security.class) +final class Target_java_security_Security_ProviderLookup { @Substitute public static Provider getProvider(String name) { diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SecurityServicesFeature.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SecurityServicesFeature.java index 243698daf6f0..c0cf92f24fbc 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SecurityServicesFeature.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SecurityServicesFeature.java @@ -164,6 +164,10 @@ public class SecurityServicesFeature extends JNIRegistrationUtil implements InternalFeature { public static class Options { + private static final String ADDITIONAL_SECURITY_PROVIDERS_DEPRECATION_HELP = "Deprecated. Security providers are now detected automatically (use the tracing agent, register the provider class " + + "for reflection, or build with -H:Preserve=all)."; + private static final String ADDITIONAL_SECURITY_PROVIDERS_DEPRECATION_MESSAGE = "Register the security provider class for reflection instead; the tracing agent does this automatically."; + @Option(help = "Enable automatic registration of security services.")// public static final HostedOptionKey EnableSecurityServicesFeature = new HostedOptionKey<>(true); @@ -174,9 +178,7 @@ public static class Options { public static final HostedOptionKey AdditionalSecurityServiceTypes = new HostedOptionKey<>( AccumulatingLocatableMultiOptionValue.Strings.build()); - @Option(help = "Deprecated. Security providers are now detected automatically (use the tracing agent, register the provider class for reflection, or build with -H:Preserve=all).", - deprecated = true, - deprecationMessage = "Register the security provider class for reflection instead; the tracing agent does this automatically.")// + @Option(help = ADDITIONAL_SECURITY_PROVIDERS_DEPRECATION_HELP, deprecated = true, deprecationMessage = ADDITIONAL_SECURITY_PROVIDERS_DEPRECATION_MESSAGE)// public static final HostedOptionKey AdditionalSecurityProviders = new HostedOptionKey<>( AccumulatingLocatableMultiOptionValue.Strings.buildWithCommaDelimiter()); } From b639817e04283a6c41ed5cd99d350287ec909345 Mon Sep 17 00:00:00 2001 From: Vojin Jovanovic Date: Sun, 21 Jun 2026 09:41:00 +0200 Subject: [PATCH 10/63] Fix exact metadata handling for SunEC provider --- .../core/jdk/SecurityProvidersSupport.java | 4 +-- .../svm/hosted/SecurityServicesFeature.java | 29 +++++++++++++++++++ .../hosted/reflect/ReflectionDataBuilder.java | 6 ++++ 3 files changed, 37 insertions(+), 2 deletions(-) diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SecurityProvidersSupport.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SecurityProvidersSupport.java index 434f83b8495b..43848a9f5574 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SecurityProvidersSupport.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SecurityProvidersSupport.java @@ -218,8 +218,8 @@ public static boolean isBuiltInProvider(String provName) { case "SUN", "sun.security.provider.Sun", "SunRsaSign", "sun.security.rsa.SunRsaSign", "SunJCE", "com.sun.crypto.provider.SunJCE", - "SunJSSE", - "SunEC", + "SunJSSE", "sun.security.ssl.SunJSSE", + "SunEC", "sun.security.ec.SunEC", "Apple", "apple.security.AppleProvider" -> true; default -> false; diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SecurityServicesFeature.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SecurityServicesFeature.java index c0cf92f24fbc..b48ea7d7a078 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SecurityServicesFeature.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SecurityServicesFeature.java @@ -87,11 +87,13 @@ import org.graalvm.nativeimage.ImageSingletons; import org.graalvm.nativeimage.hosted.RuntimeReflection; import org.graalvm.nativeimage.impl.RuntimeClassInitializationSupport; +import org.graalvm.nativeimage.impl.RuntimeReflectionSupport; import com.oracle.graal.pointsto.constraints.UnsupportedPlatformException; import com.oracle.graal.pointsto.meta.AnalysisMethod; import com.oracle.graal.pointsto.reports.ReportUtils; import com.oracle.svm.core.FutureDefaultsOptions; +import com.oracle.svm.core.MissingRegistrationUtils; import com.oracle.svm.core.OS; import com.oracle.svm.core.SubstrateOptions; import com.oracle.svm.core.feature.InternalFeature; @@ -107,6 +109,7 @@ import com.oracle.svm.hosted.FeatureImpl.DuringSetupAccessImpl; import com.oracle.svm.hosted.analysis.Inflation; import com.oracle.svm.hosted.c.NativeLibraries; +import com.oracle.svm.hosted.reflect.ReflectionDataBuilder; import com.oracle.svm.hosted.substitute.DeletedElementException; import com.oracle.svm.hosted.substitute.AnnotationSubstitutionProcessor; import com.oracle.svm.shared.BuildPhaseProvider; @@ -886,6 +889,9 @@ private void registerProvider(Provider provider) { } private void includeProviderClass(DuringAnalysisAccess access, Class providerClass) { + if (shouldSkipOmittedSunECProviderClass(providerClass)) { + return; + } if (!isLoadableProviderClass(access, providerClass)) { return; } @@ -932,6 +938,10 @@ private static Provider instantiateProvider(Class providerClass) { } private void registerService(DuringAnalysisAccess a, Service service) { + if (shouldSkipOmittedBuiltInProviderService(service)) { + trace("Skipped service %s because provider %s was not included by reachability metadata.", asString(service), service.getProvider().getClass().getName()); + return; + } TypeResult> serviceClassResult = loader.findClass(service.getClassName()); if (serviceClassResult.isPresent()) { try (TracingAutoCloseable _ = trace(service)) { @@ -961,6 +971,25 @@ private void registerService(DuringAnalysisAccess a, Service service) { } } + private static boolean shouldSkipOmittedBuiltInProviderService(Service service) { + if (!MissingRegistrationUtils.throwMissingRegistrationErrors()) { + return false; + } + Provider provider = service.getProvider(); + return shouldSkipOmittedSunECProviderClass(provider.getClass()); + } + + private static boolean shouldSkipOmittedSunECProviderClass(Class providerClass) { + return MissingRegistrationUtils.throwMissingRegistrationErrors() && + providerClass.getName().equals("sun.security.ec.SunEC") && + !isTypeRegisteredForReflection(providerClass); + } + + private static boolean isTypeRegisteredForReflection(Class clazz) { + ReflectionDataBuilder reflectionData = (ReflectionDataBuilder) ImageSingletons.lookup(RuntimeReflectionSupport.class); + return reflectionData.isTypeRegisteredForReflection(clazz); + } + private static void registerProviderClassForReflection(Class providerClass) { RuntimeReflection.register(providerClass); Constructor constructor = findDeclaredNullaryConstructor(providerClass); diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/ReflectionDataBuilder.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/ReflectionDataBuilder.java index 4c72dd7dad59..1ba01f5af1de 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/ReflectionDataBuilder.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/ReflectionDataBuilder.java @@ -1419,6 +1419,12 @@ public RuntimeDynamicAccessMetadata getTypeMetadata(Class clazz) { return types.get(analysisType).dynamicAccess; } + public boolean isTypeRegisteredForReflection(Class clazz) { + AnalysisType analysisType = metaAccess.lookupJavaType(clazz); + TypeData data = types.get(analysisType); + return data != null && data.isRegisteredAs(ACCESSED); + } + public RuntimeDynamicAccessMetadata getUnsafeAllocationMetadata(Class clazz) { guaranteeAnalysisFinishedAndRuntimeMetadataEncodingNotComplete(); return types.get(metaAccess.lookupJavaType(clazz)).unsafeAllocatedDynamicAccess; From 5defc773e7a5b1d589588e25d7883d5348ecd5e5 Mon Sep 17 00:00:00 2001 From: Vojin Jovanovic Date: Mon, 29 Jun 2026 17:13:47 +0200 Subject: [PATCH 11/63] [GR-69858] Gate provider inclusion on reflection metadata --- .../native-image/JCASecurityServices.md | 5 +- .../svm/hosted/SecurityServicesFeature.java | 83 ++++++++++++------- .../hosted/reflect/ReflectionDataBuilder.java | 6 ++ .../reachability-metadata.json | 3 + .../test/services/SecurityServiceTest.java | 58 +++++++++++++ 5 files changed, 125 insertions(+), 30 deletions(-) diff --git a/docs/reference-manual/native-image/JCASecurityServices.md b/docs/reference-manual/native-image/JCASecurityServices.md index 94a23f488891..8a4420dc7271 100644 --- a/docs/reference-manual/native-image/JCASecurityServices.md +++ b/docs/reference-manual/native-image/JCASecurityServices.md @@ -30,6 +30,8 @@ When a specific algorithm is requested, the framework searches the registered pr The `native-image` builder uses static analysis to discover which of these services are used. It does so by registering reachability handlers for each of the `getInstance()` factory methods. When it determines that a `getInstance()` method is reachable at run time, it automatically performs the reflection registration for all the concrete implementations of the corresponding service type. +Provider classes discovered as reachable subtypes of `java.security.Provider` are treated only as candidates for provider inclusion. +The builder includes such a provider and all of its services only when the provider class is registered for reflection, either by type access, its declared nullary constructor, or its static `provider()` method. Tracing of the security services automatic registration can be enabled with `-H:+TraceSecurityServices`. The report will detail all registered service classes, the API methods that triggered registration, and the parsing context for each reachable API method. @@ -40,7 +42,8 @@ The report will detail all registered service classes, the API methods that trig Currently, security providers are initialized at build time. To move their initialization to run time, use the option `--future-defaults=run-time-initialize-security-providers`, `--future-defaults=all`, or `--future-defaults=run-time-initialize-jdk`. -Provider verification will still occur at build time. +Providers listed in the build-time `java.security` configuration are still verified at build time. +Providers included only through reflection metadata are treated as explicitly configured, since run-time codebase verification is not available in Native Image. Run-time initialization of security providers helps reduce image heap size. ## Provider Registration diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SecurityServicesFeature.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SecurityServicesFeature.java index b48ea7d7a078..f2ab0ab72b58 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SecurityServicesFeature.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SecurityServicesFeature.java @@ -249,6 +249,8 @@ public static class Options { /** All providers deemed to be used by this feature. */ private final Set usedProviders = ConcurrentHashMap.newKeySet(); + private final Set> candidateProviderClasses = ConcurrentHashMap.newKeySet(); + private final Set> includedProviderClasses = ConcurrentHashMap.newKeySet(); private Field verificationResultsField; private Field providerListField; @@ -392,7 +394,7 @@ public void beforeAnalysis(BeforeAnalysisAccess a) { access.ensureInitialized("sun.security.util.AnchorCertificates"); initializeServiceRegistrationData(); - access.registerSubtypeReachabilityHandler((analysisAccess, providerClass) -> includeProviderClass(analysisAccess, providerClass), Provider.class); + access.registerSubtypeReachabilityHandler((_, providerClass) -> candidateProviderClasses.add(providerClass), Provider.class); registerManuallyConfiguredProvidersForReflection(access); if (Options.EnableSecurityServicesFeature.getValue()) { registerServiceReachabilityHandlers(access); @@ -863,35 +865,39 @@ private void registerProvider(Provider provider) { registerForReflection(provider.getClass()); /* Trigger initialization of lazy field java.security.Provider.entrySet. */ provider.entrySet(); - try { - Method getVerificationResult = ReflectionUtil.lookupMethod(jceSecurityClass, "getVerificationResult", Provider.class); - /* - * Trigger initialization of JceSecurity.verificationResults used by - * JceSecurity.canUseProvider() at runtime to check whether a provider is properly - * signed and can be used by JCE. It does that via jar verification which we cannot - * support. See also Target_javax_crypto_JceSecurity. - */ - Object result = getVerificationResult.invoke(null, provider); - /* - * Note that after verification, we move the result to a separate structure since we - * don't want to keep provider objects that were only instantiated for verification in - * the image heap. - * - * The verification result can be either null, in case of success, or an Exception, - * in case of failure. Null is interpreted as Boolean.TRUE at runtime, signifying - * successful verification. - */ - SecurityProvidersSupport.singleton().addVerifiedSecurityProvider(provider.getName(), provider.getClass().getName(), result instanceof Exception ? result : Boolean.TRUE); - } catch (ReflectiveOperationException ex) { - throw VMError.shouldNotReachHere(ex); - } + SecurityProvidersSupport.singleton().addVerifiedSecurityProvider(provider.getName(), provider.getClass().getName(), getProviderVerificationResult(provider)); } } - private void includeProviderClass(DuringAnalysisAccess access, Class providerClass) { - if (shouldSkipOmittedSunECProviderClass(providerClass)) { - return; + private Object getProviderVerificationResult(Provider provider) { + if (!buildTimeProvidersByClassName.containsKey(provider.getClass().getName())) { + return Boolean.TRUE; + } + try { + Method getVerificationResult = ReflectionUtil.lookupMethod(jceSecurityClass, "getVerificationResult", Provider.class); + /* + * Trigger initialization of JceSecurity.verificationResults used by + * JceSecurity.canUseProvider() at runtime to check whether a provider is properly + * signed and can be used by JCE. It does that via jar verification which we cannot + * support. See also Target_javax_crypto_JceSecurity. + */ + Object result = getVerificationResult.invoke(null, provider); + /* + * Note that after verification, we move the result to a separate structure since we + * don't want to keep provider objects that were only instantiated for verification in + * the image heap. + * + * The verification result can be either null, in case of success, or an Exception, in + * case of failure. Null is interpreted as Boolean.TRUE at runtime, signifying successful + * verification. + */ + return result instanceof Exception ? result : Boolean.TRUE; + } catch (ReflectiveOperationException ex) { + throw VMError.shouldNotReachHere(ex); } + } + + private void includeProviderClass(DuringAnalysisAccess access, Class providerClass) { if (!isLoadableProviderClass(access, providerClass)) { return; } @@ -982,12 +988,20 @@ private static boolean shouldSkipOmittedBuiltInProviderService(Service service) private static boolean shouldSkipOmittedSunECProviderClass(Class providerClass) { return MissingRegistrationUtils.throwMissingRegistrationErrors() && providerClass.getName().equals("sun.security.ec.SunEC") && - !isTypeRegisteredForReflection(providerClass); + !isProviderRegisteredForReflection(providerClass); } - private static boolean isTypeRegisteredForReflection(Class clazz) { + private static boolean isProviderRegisteredForReflection(Class providerClass) { ReflectionDataBuilder reflectionData = (ReflectionDataBuilder) ImageSingletons.lookup(RuntimeReflectionSupport.class); - return reflectionData.isTypeRegisteredForReflection(clazz); + if (reflectionData.isTypeRegisteredForReflection(providerClass)) { + return true; + } + Constructor constructor = findDeclaredNullaryConstructor(providerClass); + if (constructor != null && reflectionData.isMethodRegisteredForReflection(constructor)) { + return true; + } + Method providerMethod = findProviderMethod(providerClass); + return providerMethod != null && reflectionData.isMethodRegisteredForReflection(providerMethod); } private static void registerProviderClassForReflection(Class providerClass) { @@ -1077,6 +1091,17 @@ private void registerX509Extensions(DuringAnalysisAccess a) { @Override public void duringAnalysis(DuringAnalysisAccess a) { DuringAnalysisAccessImpl access = (DuringAnalysisAccessImpl) a; + boolean includedProvider = false; + for (Class providerClass : candidateProviderClasses) { + if (!includedProviderClasses.contains(providerClass) && isProviderRegisteredForReflection(providerClass)) { + includedProviderClasses.add(providerClass); + includeProviderClass(access, providerClass); + includedProvider = true; + } + } + if (includedProvider) { + access.requireAnalysisIteration(); + } access.rescanRoot(oidTableField, scanReason); if (!FutureDefaultsOptions.securityProvidersInitializedAtRunTime()) { maybeScanVerificationResultsField(access); diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/ReflectionDataBuilder.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/ReflectionDataBuilder.java index 1ba01f5af1de..96a8852df5c2 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/ReflectionDataBuilder.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/ReflectionDataBuilder.java @@ -1425,6 +1425,12 @@ public boolean isTypeRegisteredForReflection(Class clazz) { return data != null && data.isRegisteredAs(ACCESSED); } + public boolean isMethodRegisteredForReflection(Executable method) { + AnalysisMethod analysisMethod = metaAccess.lookupJavaMethod(method); + ElementData data = methods.get(analysisMethod); + return data != null && data.isRegisteredAs(ACCESSED); + } + public RuntimeDynamicAccessMetadata getUnsafeAllocationMetadata(Class clazz) { guaranteeAnalysisFinishedAndRuntimeMetadataEncodingNotComplete(); return types.get(metaAccess.lookupJavaType(clazz)).unsafeAllocatedDynamicAccess; diff --git a/substratevm/src/com.oracle.svm.test/src/META-INF/native-image/com.oracle.svm.test/reachability-metadata.json b/substratevm/src/com.oracle.svm.test/src/META-INF/native-image/com.oracle.svm.test/reachability-metadata.json index d12c49d3918f..0ed080789a31 100644 --- a/substratevm/src/com.oracle.svm.test/src/META-INF/native-image/com.oracle.svm.test/reachability-metadata.json +++ b/substratevm/src/com.oracle.svm.test/src/META-INF/native-image/com.oracle.svm.test/reachability-metadata.json @@ -8,6 +8,9 @@ "parameterTypes": [] } ] + }, + { + "type": "com.oracle.svm.test.services.SecurityServiceTest$TypeMetadataProvider" } ] } diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/services/SecurityServiceTest.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/services/SecurityServiceTest.java index 1ee84e812d6b..b793828a1a10 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/services/SecurityServiceTest.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/services/SecurityServiceTest.java @@ -71,6 +71,10 @@ public class SecurityServiceTest { private static final String REFLECTION_METADATA_PROVIDER_MAC_ALGORITHM = "reflection-metadata-mac"; private static final String SERVICE_LOADED_PROVIDER_CLASS_NAME = "com.oracle.svm.test.services.SecurityServiceTest$ServiceLoadedProvider"; private static final String SERVICE_LOADED_PROVIDER_ALGORITHM = "service-loaded-provider-algo"; + private static final String TYPE_METADATA_PROVIDER_NAME = "type-metadata-provider"; + private static final String TYPE_METADATA_PROVIDER_ALGORITHM = "type-metadata-algo"; + private static final String REACHABLE_PROVIDER_WITHOUT_METADATA_NAME = "reachable-provider-without-metadata"; + private static final String REACHABLE_PROVIDER_WITHOUT_METADATA_ALGORITHM = "reachable-without-metadata-algo"; public static class TestFeature implements Feature { @Override @@ -164,6 +168,32 @@ public void testReflectionMetadataProviderRegistration() throws Exception { } } + @Test + public void testTypeMetadataProviderRegistration() throws Exception { + Provider provider = new TypeMetadataProvider(); + int position = Security.addProvider(provider); + try { + Assert.assertTrue("Provider should be registered.", position > 0); + JCACompliantNoOpService service = JCACompliantNoOpService.getInstance(TYPE_METADATA_PROVIDER_ALGORITHM); + Assert.assertNotNull("No service instance was created", service); + Assert.assertEquals("Unexpected service implementation class", TypeMetadataNoOpServiceImpl.class.getName(), service.getClass().getName()); + } finally { + Security.removeProvider(TYPE_METADATA_PROVIDER_NAME); + } + } + + @Test + public void testReachableProviderWithoutMetadataDoesNotRegisterServices() { + Provider provider = new ReachableProviderWithoutMetadata(); + int position = Security.addProvider(provider); + try { + Assert.assertTrue("Provider should be registered.", position > 0); + Assert.assertThrows(NoSuchAlgorithmException.class, () -> JCACompliantNoOpService.getInstance(REACHABLE_PROVIDER_WITHOUT_METADATA_ALGORITHM)); + } finally { + Security.removeProvider(REACHABLE_PROVIDER_WITHOUT_METADATA_NAME); + } + } + @Test public void testServiceLoaderProviderWithoutMetadataUsesReflectionLookupFailure() { Assume.assumeTrue("native image runtime only", ImageInfo.inImageRuntimeCode()); @@ -325,6 +355,12 @@ public static class JcaCompliantNoOpServiceImpl extends JCACompliantNoOpService public static final class ReflectionMetadataNoOpServiceImpl extends JCACompliantNoOpService { } + public static final class TypeMetadataNoOpServiceImpl extends JCACompliantNoOpService { + } + + public static final class ReachableNoOpServiceImpl extends JCACompliantNoOpService { + } + public static final class ReflectionMetadataProvider extends Provider { static final long serialVersionUID = 1234L; @@ -337,6 +373,28 @@ public ReflectionMetadataProvider() { } } + public static final class TypeMetadataProvider extends Provider { + static final long serialVersionUID = 1234L; + + @SuppressWarnings("deprecation") + public TypeMetadataProvider() { + super(TYPE_METADATA_PROVIDER_NAME, 1.0, "Provider registered through type-level reflection metadata"); + putService(new Service(this, "JCACompliantNoOpService", TYPE_METADATA_PROVIDER_ALGORITHM, + TypeMetadataNoOpServiceImpl.class.getName(), null, null)); + } + } + + public static final class ReachableProviderWithoutMetadata extends Provider { + static final long serialVersionUID = 1234L; + + @SuppressWarnings("deprecation") + public ReachableProviderWithoutMetadata() { + super(REACHABLE_PROVIDER_WITHOUT_METADATA_NAME, 1.0, "Reachable provider without reflection metadata"); + putService(new Service(this, "JCACompliantNoOpService", REACHABLE_PROVIDER_WITHOUT_METADATA_ALGORITHM, + ReachableNoOpServiceImpl.class.getName(), null, null)); + } + } + public static final class ReflectionMetadataMacSpi extends MacSpi { @Override protected int engineGetMacLength() { From 4be22a077873c7800f23177de7d5f5102461984b Mon Sep 17 00:00:00 2001 From: Vojin Jovanovic Date: Tue, 30 Jun 2026 11:32:11 +0200 Subject: [PATCH 12/63] [GR-69858] Allow security provider support in layered builds --- .../src/com/oracle/svm/core/jdk/SecurityProvidersSupport.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SecurityProvidersSupport.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SecurityProvidersSupport.java index 43848a9f5574..5ed573a16178 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SecurityProvidersSupport.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SecurityProvidersSupport.java @@ -42,8 +42,8 @@ import com.oracle.svm.core.metadata.MetadataTracer; import com.oracle.svm.guest.staging.util.ImageHeapMap; import com.oracle.svm.shared.singletons.traits.BuiltinTraits.AllAccess; -import com.oracle.svm.shared.singletons.traits.BuiltinTraits.DisallowLayered; import com.oracle.svm.shared.singletons.traits.BuiltinTraits.NoLayeredCallbacks; +import com.oracle.svm.shared.singletons.traits.BuiltinTraits.PartiallyLayerAware; import com.oracle.svm.shared.singletons.traits.SingletonLayeredInstallationKind.Duplicable; import com.oracle.svm.shared.singletons.traits.SingletonTraits; import com.oracle.svm.shared.util.VMError; @@ -57,7 +57,7 @@ * "../../../../../../../../../../../../docs/reference-manual/native-image/JCASecurityServices.md"> * JCA Security Services documentation for details). */ -@SingletonTraits(access = AllAccess.class, layeredCallbacks = NoLayeredCallbacks.class, layeredInstallationKind = Duplicable.class, other = DisallowLayered.class) +@SingletonTraits(access = AllAccess.class, layeredCallbacks = NoLayeredCallbacks.class, layeredInstallationKind = Duplicable.class, other = PartiallyLayerAware.class) public final class SecurityProvidersSupport { private static final Class[] NO_PARAMETERS = new Class[0]; From 4d16591955d859f07abe5d13464158e511dcf8ef Mon Sep 17 00:00:00 2001 From: Vojin Jovanovic Date: Fri, 3 Jul 2026 14:51:31 +0200 Subject: [PATCH 13/63] Fix the missing metadata entry for the provider --- .../jdk/runtimeinit/SecuritySubstitutionRuntimeInit.java | 4 ++++ .../src/com/oracle/svm/hosted/ServiceLoaderFeature.java | 7 +++++++ .../com/oracle/svm/test/services/SecurityServiceTest.java | 8 ++++++++ 3 files changed, 19 insertions(+) diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/runtimeinit/SecuritySubstitutionRuntimeInit.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/runtimeinit/SecuritySubstitutionRuntimeInit.java index 453969916150..5e137fbdc864 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/runtimeinit/SecuritySubstitutionRuntimeInit.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/runtimeinit/SecuritySubstitutionRuntimeInit.java @@ -165,6 +165,10 @@ Provider getProvider() { // Create providers which are in java.base directly if (SecurityProvidersSupport.isBuiltInProvider(provName)) { provider = SecurityProvidersSupport.singleton().loadBuiltInProvider(provName, debug); + } else if (!SecurityProvidersSupport.singleton().isSecurityProviderIncluded(provName, provName)) { + // Skip omitted providers before the JDK falls back to ServiceLoader. + // \u00a7FS-001-jca-security-provider-inclusion + provider = null; } else { if (isLoading) { /* diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ServiceLoaderFeature.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ServiceLoaderFeature.java index dbded5746b73..42601ce4c097 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ServiceLoaderFeature.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ServiceLoaderFeature.java @@ -43,6 +43,7 @@ import com.oracle.graal.pointsto.constraints.UnsupportedPlatformException; import com.oracle.svm.core.FutureDefaultsOptions; import com.oracle.svm.core.feature.InternalFeature; +import com.oracle.svm.core.jdk.Resources; import com.oracle.svm.core.jdk.ServiceCatalogSupport; import com.oracle.svm.hosted.analysis.Inflation; import com.oracle.svm.hosted.substitute.DeletedElementException; @@ -86,6 +87,7 @@ */ @AutomaticallyRegisteredFeature public class ServiceLoaderFeature implements InternalFeature { + private static final String SECURITY_PROVIDER_SERVICE_RESOURCE = "META-INF/services/" + java.security.Provider.class.getName(); public static class Options { @Option(help = "Automatically register services for run-time lookup using ServiceLoader", type = OptionType.Expert) // @@ -165,6 +167,11 @@ public void afterRegistration(AfterRegistrationAccess access) { @Override public void beforeAnalysis(BeforeAnalysisAccess access) { FeatureImpl.BeforeAnalysisAccessImpl accessImpl = (FeatureImpl.BeforeAnalysisAccessImpl) access; + if (FutureDefaultsOptions.securityProvidersInitializedAtRunTime()) { + // Permit an absent class-path descriptor without including omitted providers. + // \u00a7FS-001-jca-security-provider-inclusion + Resources.currentLayer().registerNegativeQuery(access.getApplicationClassLoader().getUnnamedModule(), SECURITY_PROVIDER_SERVICE_RESOURCE); + } accessImpl.imageClassLoader.classLoaderSupport.serviceProvidersForEach((serviceName, providers) -> { Collection providersToSkip = providers; try { diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/services/SecurityServiceTest.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/services/SecurityServiceTest.java index b793828a1a10..37f2b8b7d818 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/services/SecurityServiceTest.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/services/SecurityServiceTest.java @@ -36,6 +36,7 @@ import java.util.ServiceLoader; import java.util.Set; +import javax.crypto.KeyGenerator; import javax.crypto.Mac; import javax.crypto.MacSpi; @@ -64,6 +65,7 @@ public class SecurityServiceTest { private static final String OMITTED_PROVIDER_ALGORITHM = "SHA256withECDSA"; private static final String OMITTED_PROVIDER_SERVICE = "Signature"; private static final String OMITTED_PROVIDER_ERROR = "SHA256withECDSA Signature not available"; + private static final String MISSING_KEY_GENERATOR_ALGORITHM = "GR69858DefinitelyMissing"; private static final String OMITTED_PROVIDER_HINT = "-H:Preserve=all"; private static final String REFLECTION_METADATA_PROVIDER_CLASS_NAME = "com.oracle.svm.test.services.SecurityServiceTest$ReflectionMetadataProvider"; private static final String REFLECTION_METADATA_PROVIDER_NAME = "reflection-metadata-provider"; @@ -287,6 +289,12 @@ public void testGenericMissingBuiltInProviderGetServicesReturnsEmptyIterator() { Assert.assertFalse("Generic service iteration should silently skip the omitted built-in provider.", services.hasNext()); } + @Test + public void testGenericMissingAlgorithmExhaustsProviderList() { + Assume.assumeTrue("needs runtime initialization", FutureDefaultsOptions.securityProvidersInitializedAtRunTime()); + Assert.assertThrows(NoSuchAlgorithmException.class, () -> KeyGenerator.getInstance(MISSING_KEY_GENERATOR_ALGORITHM)); + } + @Test public void testSecurityGetAlgorithmsOmitsMissingBuiltInProviderAlgorithm() { Assume.assumeTrue("needs runtime initialization", FutureDefaultsOptions.securityProvidersInitializedAtRunTime()); From aa7db56286cd06cd0b937e1f1bcbb149e9bcbd30 Mon Sep 17 00:00:00 2001 From: Vojin Jovanovic Date: Fri, 3 Jul 2026 18:02:02 +0200 Subject: [PATCH 14/63] [GR-69858] Preserve service-driven security providers --- .../core/jdk/SecurityProvidersSupport.java | 11 ++++ .../SecuritySubstitutionRuntimeInit.java | 3 + .../svm/hosted/SecurityServicesFeature.java | 55 +++++++++---------- .../svm/hosted/ServiceLoaderFeature.java | 8 +-- .../test/services/SecurityServiceTest.java | 50 +++++------------ 5 files changed, 55 insertions(+), 72 deletions(-) diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SecurityProvidersSupport.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SecurityProvidersSupport.java index 5ed573a16178..d916125bb580 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SecurityProvidersSupport.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SecurityProvidersSupport.java @@ -89,6 +89,11 @@ public void addVerifiedSecurityProvider(String providerName, String providerClas verifiedSecurityProviderClasses.put(providerClassName, verificationResult); } + @Platforms(Platform.HOSTED_ONLY.class) + public void addIncludedSecurityProviderClass(String providerClassName) { + verifiedSecurityProviderClasses.put(providerClassName, Boolean.TRUE); + } + public Object getSecurityProviderVerificationResult(Provider provider) { Object result = verifiedSecurityProviderClasses.get(provider.getClass().getName()); return result != null ? result : verifiedSecurityProviders.get(provider.getName()); @@ -170,6 +175,12 @@ public static String missingProviderMessage(String providerName, String provider " for reflection in reachability-metadata.json, or build with -H:Preserve=all to include all JDK providers."; } + public static String missingConfiguredProviderMessage(String providerName) { + return "The configured security provider '" + providerName + "' was requested at run time but was not included in the native image. " + + "Run your application with the tracing agent so the provider is recorded automatically, register its implementation class " + + "for reflection in reachability-metadata.json, or build with -H:Preserve=all to include all JDK providers."; + } + public static Provider traceProviderLookup(Provider provider) { if (provider == null || singleton().isMissingBuiltInProvider(provider.getName()) || singleton().isMissingBuiltInProvider(provider.getClass().getName())) { return null; diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/runtimeinit/SecuritySubstitutionRuntimeInit.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/runtimeinit/SecuritySubstitutionRuntimeInit.java index 5e137fbdc864..433876e29e60 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/runtimeinit/SecuritySubstitutionRuntimeInit.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/runtimeinit/SecuritySubstitutionRuntimeInit.java @@ -223,6 +223,9 @@ public Provider getProvider(String name) { if (SecurityProvidersSupport.singleton().isMissingBuiltInProvider(configuredProviderName)) { throw SecurityProvidersSupport.missingBuiltInProvider(configuredProviderName); } + if (!SecurityProvidersSupport.singleton().isSecurityProviderIncluded(configuredProviderName, configuredProviderName)) { + throw new SecurityException(SecurityProvidersSupport.missingConfiguredProviderMessage(configuredProviderName)); + } return SecurityProvidersSupport.traceProviderLookup(config.getProvider()); } } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SecurityServicesFeature.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SecurityServicesFeature.java index f2ab0ab72b58..987625699bbe 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SecurityServicesFeature.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SecurityServicesFeature.java @@ -93,7 +93,6 @@ import com.oracle.graal.pointsto.meta.AnalysisMethod; import com.oracle.graal.pointsto.reports.ReportUtils; import com.oracle.svm.core.FutureDefaultsOptions; -import com.oracle.svm.core.MissingRegistrationUtils; import com.oracle.svm.core.OS; import com.oracle.svm.core.SubstrateOptions; import com.oracle.svm.core.feature.InternalFeature; @@ -395,6 +394,7 @@ public void beforeAnalysis(BeforeAnalysisAccess a) { initializeServiceRegistrationData(); access.registerSubtypeReachabilityHandler((_, providerClass) -> candidateProviderClasses.add(providerClass), Provider.class); + registerServiceProviderCandidates(access); registerManuallyConfiguredProvidersForReflection(access); if (Options.EnableSecurityServicesFeature.getValue()) { registerServiceReachabilityHandlers(access); @@ -713,6 +713,20 @@ private void initializeServiceRegistrationData() { } } + private void registerServiceProviderCandidates(BeforeAnalysisAccess access) { + BeforeAnalysisAccessImpl accessImpl = (BeforeAnalysisAccessImpl) access; + accessImpl.imageClassLoader.classLoaderSupport.serviceProvidersForEach((serviceName, providers) -> { + if (serviceName.equals(Provider.class.getName())) { + for (String provider : providers) { + Class providerClass = access.findClassByName(provider); + if (providerClass != null) { + candidateProviderClasses.add(providerClass); + } + } + } + }); + } + private void registerServices(DuringAnalysisAccess access, Object trigger, Class serviceClass) { /* * SPI classes, i.e., base classes for concrete service implementations, such as @@ -906,6 +920,7 @@ private void includeProviderClass(DuringAnalysisAccess access, Class provider provider = instantiateProvider(providerClass); } registerProvider(provider); + SecurityProvidersSupport.singleton().addIncludedSecurityProviderClass(providerClass.getName()); for (Service service : provider.getServices()) { if (isValid(service)) { registerService(access, service); @@ -944,10 +959,6 @@ private static Provider instantiateProvider(Class providerClass) { } private void registerService(DuringAnalysisAccess a, Service service) { - if (shouldSkipOmittedBuiltInProviderService(service)) { - trace("Skipped service %s because provider %s was not included by reachability metadata.", asString(service), service.getProvider().getClass().getName()); - return; - } TypeResult> serviceClassResult = loader.findClass(service.getClassName()); if (serviceClassResult.isPresent()) { try (TracingAutoCloseable _ = trace(service)) { @@ -977,20 +988,6 @@ private void registerService(DuringAnalysisAccess a, Service service) { } } - private static boolean shouldSkipOmittedBuiltInProviderService(Service service) { - if (!MissingRegistrationUtils.throwMissingRegistrationErrors()) { - return false; - } - Provider provider = service.getProvider(); - return shouldSkipOmittedSunECProviderClass(provider.getClass()); - } - - private static boolean shouldSkipOmittedSunECProviderClass(Class providerClass) { - return MissingRegistrationUtils.throwMissingRegistrationErrors() && - providerClass.getName().equals("sun.security.ec.SunEC") && - !isProviderRegisteredForReflection(providerClass); - } - private static boolean isProviderRegisteredForReflection(Class providerClass) { ReflectionDataBuilder reflectionData = (ReflectionDataBuilder) ImageSingletons.lookup(RuntimeReflectionSupport.class); if (reflectionData.isTypeRegisteredForReflection(providerClass)) { @@ -1032,17 +1029,15 @@ private static Constructor findDeclaredNullaryConstructor(Class providerCl private static Method findProviderMethod(Class providerClass) { Method nullaryProviderMethod = null; try { - if (providerClass.getModule().isNamed() && !providerClass.getModule().getDescriptor().isAutomatic()) { - for (Method method : providerClass.getDeclaredMethods()) { - if (Modifier.isPublic(method.getModifiers()) && Modifier.isStatic(method.getModifiers()) && method.getParameterCount() == 0 && method.getName().equals("provider") && - Provider.class.isAssignableFrom(method.getReturnType())) { - if (nullaryProviderMethod == null) { - ModuleSupport.accessModuleByClass(ModuleSupport.Access.OPEN, SecurityServicesFeature.class, providerClass); - method.setAccessible(true); - nullaryProviderMethod = method; - } else { - return null; - } + for (Method method : providerClass.getDeclaredMethods()) { + if (Modifier.isPublic(method.getModifiers()) && Modifier.isStatic(method.getModifiers()) && method.getParameterCount() == 0 && method.getName().equals("provider") && + Provider.class.isAssignableFrom(method.getReturnType())) { + if (nullaryProviderMethod == null) { + ModuleSupport.accessModuleByClass(ModuleSupport.Access.OPEN, SecurityServicesFeature.class, providerClass); + method.setAccessible(true); + nullaryProviderMethod = method; + } else { + return null; } } } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ServiceLoaderFeature.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ServiceLoaderFeature.java index 42601ce4c097..3e2b03baec87 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ServiceLoaderFeature.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ServiceLoaderFeature.java @@ -167,11 +167,9 @@ public void afterRegistration(AfterRegistrationAccess access) { @Override public void beforeAnalysis(BeforeAnalysisAccess access) { FeatureImpl.BeforeAnalysisAccessImpl accessImpl = (FeatureImpl.BeforeAnalysisAccessImpl) access; - if (FutureDefaultsOptions.securityProvidersInitializedAtRunTime()) { - // Permit an absent class-path descriptor without including omitted providers. - // \u00a7FS-001-jca-security-provider-inclusion - Resources.currentLayer().registerNegativeQuery(access.getApplicationClassLoader().getUnnamedModule(), SECURITY_PROVIDER_SERVICE_RESOURCE); - } + // Permit an absent class-path descriptor without including omitted providers. + // \u00a7FS-001-jca-security-provider-inclusion.3 + Resources.currentLayer().registerNegativeQuery(access.getApplicationClassLoader().getUnnamedModule(), SECURITY_PROVIDER_SERVICE_RESOURCE); accessImpl.imageClassLoader.classLoaderSupport.serviceProvidersForEach((serviceName, providers) -> { Collection providersToSkip = providers; try { diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/services/SecurityServiceTest.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/services/SecurityServiceTest.java index 37f2b8b7d818..015d1fd2e79b 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/services/SecurityServiceTest.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/services/SecurityServiceTest.java @@ -64,9 +64,7 @@ public class SecurityServiceTest { private static final String OMITTED_PROVIDER_ALGORITHM = "SHA256withECDSA"; private static final String OMITTED_PROVIDER_SERVICE = "Signature"; - private static final String OMITTED_PROVIDER_ERROR = "SHA256withECDSA Signature not available"; private static final String MISSING_KEY_GENERATOR_ALGORITHM = "GR69858DefinitelyMissing"; - private static final String OMITTED_PROVIDER_HINT = "-H:Preserve=all"; private static final String REFLECTION_METADATA_PROVIDER_CLASS_NAME = "com.oracle.svm.test.services.SecurityServiceTest$ReflectionMetadataProvider"; private static final String REFLECTION_METADATA_PROVIDER_NAME = "reflection-metadata-provider"; private static final String REFLECTION_METADATA_PROVIDER_ALGORITHM = "reflection-metadata-algo"; @@ -242,51 +240,29 @@ public void testDeletedProvider() { } @Test - public void testMissingBuiltInProviderErrorMessage() { + public void testReachableBuiltInProviderIsIncluded() { Assume.assumeTrue("needs runtime initialization", FutureDefaultsOptions.securityProvidersInitializedAtRunTime()); - try { - Security.getProvider("SunEC"); - Assert.fail("Fetching an omitted built-in provider should fail."); - } catch (SecurityException e) { - Assert.assertTrue("Missing provider message should mention the provider name.", e.getMessage().contains("SunEC")); - Assert.assertTrue("Missing provider message should mention the provider class.", e.getMessage().contains("sun.security.ec.SunEC")); - Assert.assertTrue("Missing provider message should mention the tracing agent.", e.getMessage().contains("tracing agent")); - Assert.assertTrue("Missing provider message should mention reflection metadata.", e.getMessage().contains("reachability-metadata.json")); - Assert.assertTrue("Missing provider message should mention preserve all.", e.getMessage().contains(OMITTED_PROVIDER_HINT)); - } + Assert.assertNotNull("Service-driven registration should include SunEC.", Security.getProvider("SunEC")); } @Test - public void testGenericMissingBuiltInProviderGetServiceUsesBroadError() { + public void testReachableBuiltInProviderGetService() throws NoSuchAlgorithmException { Assume.assumeTrue("needs runtime initialization", FutureDefaultsOptions.securityProvidersInitializedAtRunTime()); - try { - GetInstance.getService(OMITTED_PROVIDER_SERVICE, OMITTED_PROVIDER_ALGORITHM); - Assert.fail("Generic provider discovery should not find an omitted built-in provider."); - } catch (NoSuchAlgorithmException e) { - Assert.assertEquals(OMITTED_PROVIDER_ERROR, e.getMessage()); - Assert.assertFalse("Generic discovery should not use the explicit-provider diagnostic yet.", - e.getMessage().contains(OMITTED_PROVIDER_HINT)); - } + Provider.Service service = GetInstance.getService(OMITTED_PROVIDER_SERVICE, OMITTED_PROVIDER_ALGORITHM); + Assert.assertEquals("SunEC", service.getProvider().getName()); } @Test - public void testGenericMissingBuiltInProviderGetInstanceUsesBroadError() { + public void testReachableBuiltInProviderGetInstance() throws NoSuchAlgorithmException { Assume.assumeTrue("needs runtime initialization", FutureDefaultsOptions.securityProvidersInitializedAtRunTime()); - try { - GetInstance.getInstance(OMITTED_PROVIDER_SERVICE, null, OMITTED_PROVIDER_ALGORITHM); - Assert.fail("Generic provider discovery should not instantiate an omitted built-in provider."); - } catch (NoSuchAlgorithmException e) { - Assert.assertEquals(OMITTED_PROVIDER_ERROR, e.getMessage()); - Assert.assertFalse("Generic discovery should not use the explicit-provider diagnostic yet.", - e.getMessage().contains(OMITTED_PROVIDER_HINT)); - } + Assert.assertNotNull(GetInstance.getInstance(OMITTED_PROVIDER_SERVICE, null, OMITTED_PROVIDER_ALGORITHM)); } @Test - public void testGenericMissingBuiltInProviderGetServicesReturnsEmptyIterator() { + public void testReachableBuiltInProviderGetServices() { Assume.assumeTrue("needs runtime initialization", FutureDefaultsOptions.securityProvidersInitializedAtRunTime()); Iterator services = GetInstance.getServices(OMITTED_PROVIDER_SERVICE, OMITTED_PROVIDER_ALGORITHM); - Assert.assertFalse("Generic service iteration should silently skip the omitted built-in provider.", services.hasNext()); + Assert.assertTrue("Generic service iteration should include the reachable built-in provider.", services.hasNext()); } @Test @@ -296,17 +272,17 @@ public void testGenericMissingAlgorithmExhaustsProviderList() { } @Test - public void testSecurityGetAlgorithmsOmitsMissingBuiltInProviderAlgorithm() { + public void testSecurityGetAlgorithmsIncludesReachableBuiltInProviderAlgorithm() { Assume.assumeTrue("needs runtime initialization", FutureDefaultsOptions.securityProvidersInitializedAtRunTime()); Set algorithms = Security.getAlgorithms(OMITTED_PROVIDER_SERVICE); - Assert.assertFalse("Generic algorithm discovery should not expose the omitted built-in provider algorithm.", + Assert.assertTrue("Generic algorithm discovery should expose the reachable built-in provider algorithm.", algorithms.contains(OMITTED_PROVIDER_ALGORITHM.toUpperCase())); } @Test - public void testSecurityGetProvidersFilterOmitsMissingBuiltInProvider() { + public void testSecurityGetProvidersFilterIncludesReachableBuiltInProvider() { Assume.assumeTrue("needs runtime initialization", FutureDefaultsOptions.securityProvidersInitializedAtRunTime()); - Assert.assertNull("Provider filtering should silently omit algorithms from the omitted built-in provider.", + Assert.assertNotNull("Provider filtering should include algorithms from the reachable built-in provider.", Security.getProviders(OMITTED_PROVIDER_SERVICE + "." + OMITTED_PROVIDER_ALGORITHM)); } From edbbb5cea5e95f1c21f40f3dd67fc86ee310518f Mon Sep 17 00:00:00 2001 From: Vojin Jovanovic Date: Fri, 3 Jul 2026 21:10:26 +0200 Subject: [PATCH 15/63] [GR-69858] Use standard reflection errors for providers --- .../svm/agent/BreakpointInterceptor.java | 8 +++ .../core/jdk/SecurityProvidersSupport.java | 59 +++++++++---------- .../svm/core/jdk/SecuritySubstitutions.java | 12 ++-- .../SecuritySubstitutionRuntimeInit.java | 22 +++---- .../svm/hosted/ServiceLoaderFeature.java | 2 +- 5 files changed, 51 insertions(+), 52 deletions(-) diff --git a/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/BreakpointInterceptor.java b/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/BreakpointInterceptor.java index 367524917c55..445798828584 100644 --- a/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/BreakpointInterceptor.java +++ b/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/BreakpointInterceptor.java @@ -728,6 +728,12 @@ private static boolean newInstance(JNIEnvironment jni, JNIObjectHandle thread, B return true; } + private static boolean addStaticSecurityProvider(JNIEnvironment jni, JNIObjectHandle thread, @SuppressWarnings("unused") Breakpoint bp, InterceptedState state) { + JNIObjectHandle provider = getObjectArgument(thread, 0); + traceSecurityProvider(jni, provider, provider.notEqual(nullHandle()), state.getDirectCallerClass(), state); + return true; + } + private static boolean getStaticSecurityProviderByName(JNIEnvironment jni, JNIObjectHandle thread, Breakpoint bp, InterceptedState state) { JNIObjectHandle callerClass = state.getDirectCallerClass(); JNIObjectHandle name = getObjectArgument(thread, 0); @@ -1843,6 +1849,8 @@ private interface BreakpointHandler { brk("java/lang/reflect/Array", "newInstance", "(Ljava/lang/Class;I)Ljava/lang/Object;", BreakpointInterceptor::newArrayInstance), brk("java/lang/reflect/Array", "newInstance", "(Ljava/lang/Class;[I)Ljava/lang/Object;", BreakpointInterceptor::newArrayInstanceMulti), + brk("java/security/Security", "addProvider", "(Ljava/security/Provider;)I", BreakpointInterceptor::addStaticSecurityProvider), + brk("java/security/Security", "insertProviderAt", "(Ljava/security/Provider;I)I", BreakpointInterceptor::addStaticSecurityProvider), brk("java/security/Security", "getProvider", "(Ljava/lang/String;)Ljava/security/Provider;", BreakpointInterceptor::getStaticSecurityProviderByName), brk("java/lang/ClassLoader", "findSystemClass", "(Ljava/lang/String;)Ljava/lang/Class;", diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SecurityProvidersSupport.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SecurityProvidersSupport.java index d916125bb580..10a47f554337 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SecurityProvidersSupport.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SecurityProvidersSupport.java @@ -48,6 +48,7 @@ import com.oracle.svm.shared.singletons.traits.SingletonTraits; import com.oracle.svm.shared.util.VMError; +import jdk.graal.compiler.api.directives.GraalDirectives; import jdk.graal.compiler.api.replacements.Fold; import sun.security.util.Debug; @@ -153,36 +154,17 @@ public static String getBuiltInProviderClassName(String provName) { }; } - public boolean isMissingBuiltInProvider(String provName) { - String providerName = getBuiltInProviderName(provName); - String providerFQName = getBuiltInProviderClassName(provName); - return providerName != null && !isSecurityProviderIncluded(providerName, providerFQName); - } - - public static SecurityException missingBuiltInProvider(String provName) { - String providerName = getBuiltInProviderName(provName); - String providerFQName = getBuiltInProviderClassName(provName); - if (providerName == null || providerFQName == null) { - throw VMError.shouldNotReachHere("Unsupported built-in provider: " + provName); + public static void reportMissingProviderRegistration(Class providerClass) { + try { + Class.forName(GraalDirectives.opaque(providerClass.getName()), false, providerClass.getClassLoader()); + } catch (ClassNotFoundException ex) { + throw VMError.shouldNotReachHere("A reachable security provider class was not found.", ex); } - return new SecurityException( - missingProviderMessage(providerName, providerFQName)); - } - - public static String missingProviderMessage(String providerName, String providerFQName) { - return "The security provider '" + providerName + "' (" + providerFQName + ") was requested at run time but was not included in the native image. " + - "Run your application with the tracing agent so the provider is recorded automatically, register " + providerFQName + - " for reflection in reachability-metadata.json, or build with -H:Preserve=all to include all JDK providers."; - } - - public static String missingConfiguredProviderMessage(String providerName) { - return "The configured security provider '" + providerName + "' was requested at run time but was not included in the native image. " + - "Run your application with the tracing agent so the provider is recorded automatically, register its implementation class " + - "for reflection in reachability-metadata.json, or build with -H:Preserve=all to include all JDK providers."; + throw VMError.shouldNotReachHere("A security provider without a verification result was registered for reflection: " + providerClass.getName()); } public static Provider traceProviderLookup(Provider provider) { - if (provider == null || singleton().isMissingBuiltInProvider(provider.getName()) || singleton().isMissingBuiltInProvider(provider.getClass().getName())) { + if (provider == null) { return null; } if (MetadataTracer.enabled()) { @@ -192,18 +174,33 @@ public static Provider traceProviderLookup(Provider provider) { return provider; } + private static Provider loadProviderReflectively(String providerClassName, Debug debug) { + try { + Class providerClass = Class.forName(GraalDirectives.opaque(providerClassName)); + return (Provider) providerClass.getDeclaredConstructor().newInstance(); + } catch (ReflectiveOperationException ex) { + if (debug != null) { + debug.println("Error loading provider " + providerClassName); + // Checkstyle: allow System.err (for JDK compatibility) + ex.printStackTrace(System.err); + // Checkstyle: disallow System.err + } + return null; + } + } + public Provider loadBuiltInProvider(String provName, Debug debug) { return switch (provName) { case "SUN", "sun.security.provider.Sun" -> - isSecurityProviderIncluded("SUN", "sun.security.provider.Sun") ? new sun.security.provider.Sun() : null; + isSecurityProviderIncluded("SUN", "sun.security.provider.Sun") ? new sun.security.provider.Sun() : loadProviderReflectively("sun.security.provider.Sun", debug); case "SunRsaSign", "sun.security.rsa.SunRsaSign" -> - isSecurityProviderIncluded("SunRsaSign", "sun.security.rsa.SunRsaSign") ? new sun.security.rsa.SunRsaSign() : null; + isSecurityProviderIncluded("SunRsaSign", "sun.security.rsa.SunRsaSign") ? new sun.security.rsa.SunRsaSign() : loadProviderReflectively("sun.security.rsa.SunRsaSign", debug); case "SunJCE", "com.sun.crypto.provider.SunJCE" -> - isSecurityProviderIncluded("SunJCE", "com.sun.crypto.provider.SunJCE") ? new com.sun.crypto.provider.SunJCE() : null; + isSecurityProviderIncluded("SunJCE", "com.sun.crypto.provider.SunJCE") ? new com.sun.crypto.provider.SunJCE() : loadProviderReflectively("com.sun.crypto.provider.SunJCE", debug); case "SunJSSE", "sun.security.ssl.SunJSSE" -> - isSecurityProviderIncluded("SunJSSE", "sun.security.ssl.SunJSSE") ? new sun.security.ssl.SunJSSE() : null; + isSecurityProviderIncluded("SunJSSE", "sun.security.ssl.SunJSSE") ? new sun.security.ssl.SunJSSE() : loadProviderReflectively("sun.security.ssl.SunJSSE", debug); case "SunEC", "sun.security.ec.SunEC" -> - isSecurityProviderIncluded("SunEC", "sun.security.ec.SunEC") ? allocateSunECProvider() : null; + isSecurityProviderIncluded("SunEC", "sun.security.ec.SunEC") ? allocateSunECProvider() : loadProviderReflectively("sun.security.ec.SunEC", debug); case "Apple", "apple.security.AppleProvider" -> { try { Class c = Class.forName("apple.security.AppleProvider"); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SecuritySubstitutions.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SecuritySubstitutions.java index 2215b9ff9c74..a36b2c02a724 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SecuritySubstitutions.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SecuritySubstitutions.java @@ -270,23 +270,25 @@ static Exception getVerificationResult(Provider p) { Object key = new Target_javax_crypto_JceSecurity_WeakIdentityWrapper(p, queue); Object o = verificationResults.get(key); if (o == PROVIDER_VERIFIED) { + SecurityProvidersSupport.traceProviderLookup(p); return null; } else if (o != null) { return (Exception) o; } o = SecurityProvidersSupport.singleton().getSecurityProviderVerificationResult(p); if (o == Boolean.TRUE) { + SecurityProvidersSupport.traceProviderLookup(p); return null; } else if (o != null) { return (Exception) o; } /* - * If the verification result is not found in the verificationResults map, HotSpot will - * attempt to verify the provider. This requires accessing the code base, which isn't - * supported in Native Image, so we need to fail. We could either fail here or substitute - * getCodeBase() and fail there, but handling it here is a cleaner approach. + * A provider without a verification result was not included by reflection metadata. + * Trigger the regular Class.forName missing-registration path so diagnostics and metadata + * tracing handle this like any other missing reflection access. */ - throw new SecurityException(SecurityProvidersSupport.missingProviderMessage(p.getName(), p.getClass().getName())); + SecurityProvidersSupport.reportMissingProviderRegistration(p.getClass()); + throw VMError.shouldNotReachHere("Security provider reflection access unexpectedly succeeded: " + p.getClass().getName()); } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/runtimeinit/SecuritySubstitutionRuntimeInit.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/runtimeinit/SecuritySubstitutionRuntimeInit.java index 433876e29e60..84153131a02b 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/runtimeinit/SecuritySubstitutionRuntimeInit.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/runtimeinit/SecuritySubstitutionRuntimeInit.java @@ -38,6 +38,7 @@ import com.oracle.svm.core.jdk.SecurityProvidersInitializedAtRunTime; import com.oracle.svm.core.jdk.SecurityProvidersSupport; import com.oracle.svm.shared.util.BasedOnJDKFile; +import com.oracle.svm.shared.util.VMError; import jdk.graal.compiler.core.common.SuppressFBWarnings; @@ -105,17 +106,18 @@ static Exception getVerificationResult(Provider p) { /* The verification results map key is an identity wrapper object. */ Object o = SecurityProvidersSupport.singleton().getSecurityProviderVerificationResult(p); if (o == Boolean.TRUE) { + SecurityProvidersSupport.traceProviderLookup(p); return null; } else if (o != null) { return (Exception) o; } /* - * If the verification result is not found in the verificationResults map, HotSpot will - * attempt to verify the provider. This requires accessing the code base, which isn't - * supported in Native Image, so we need to fail. We could either fail here or substitute - * getCodeBase() and fail there, but handling it here is a cleaner approach. + * A provider without a verification result was not included by reflection metadata. + * Trigger the regular Class.forName missing-registration path so diagnostics and metadata + * tracing handle this like any other missing reflection access. */ - throw new SecurityException(SecurityProvidersSupport.missingProviderMessage(p.getName(), p.getClass().getName())); + SecurityProvidersSupport.reportMissingProviderRegistration(p.getClass()); + throw VMError.shouldNotReachHere("Security provider reflection access unexpectedly succeeded: " + p.getClass().getName()); } } @@ -165,10 +167,6 @@ Provider getProvider() { // Create providers which are in java.base directly if (SecurityProvidersSupport.isBuiltInProvider(provName)) { provider = SecurityProvidersSupport.singleton().loadBuiltInProvider(provName, debug); - } else if (!SecurityProvidersSupport.singleton().isSecurityProviderIncluded(provName, provName)) { - // Skip omitted providers before the JDK falls back to ServiceLoader. - // \u00a7FS-001-jca-security-provider-inclusion - provider = null; } else { if (isLoading) { /* @@ -220,12 +218,6 @@ public Provider getProvider(String name) { String providerFQName = SecurityProvidersSupport.getBuiltInProviderClassName(configuredProviderName); boolean matches = configuredProviderName.equals(name) || (providerName != null && providerName.equals(name)) || (providerFQName != null && providerFQName.equals(name)); if (matches) { - if (SecurityProvidersSupport.singleton().isMissingBuiltInProvider(configuredProviderName)) { - throw SecurityProvidersSupport.missingBuiltInProvider(configuredProviderName); - } - if (!SecurityProvidersSupport.singleton().isSecurityProviderIncluded(configuredProviderName, configuredProviderName)) { - throw new SecurityException(SecurityProvidersSupport.missingConfiguredProviderMessage(configuredProviderName)); - } return SecurityProvidersSupport.traceProviderLookup(config.getProvider()); } } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ServiceLoaderFeature.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ServiceLoaderFeature.java index 3e2b03baec87..e5f9b70478e3 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ServiceLoaderFeature.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ServiceLoaderFeature.java @@ -168,7 +168,7 @@ public void afterRegistration(AfterRegistrationAccess access) { public void beforeAnalysis(BeforeAnalysisAccess access) { FeatureImpl.BeforeAnalysisAccessImpl accessImpl = (FeatureImpl.BeforeAnalysisAccessImpl) access; // Permit an absent class-path descriptor without including omitted providers. - // \u00a7FS-001-jca-security-provider-inclusion.3 + // \u00a7FS-001-jca-security-provider-inclusion.4 Resources.currentLayer().registerNegativeQuery(access.getApplicationClassLoader().getUnnamedModule(), SECURITY_PROVIDER_SERVICE_RESOURCE); accessImpl.imageClassLoader.classLoaderSupport.serviceProvidersForEach((serviceName, providers) -> { Collection providersToSkip = providers; From 3f55c0532c53d8643b00fdcd3001d4b8f84cfb7d Mon Sep 17 00:00:00 2001 From: Vojin Jovanovic Date: Fri, 3 Jul 2026 22:10:41 +0200 Subject: [PATCH 16/63] [GR-69858] Preserve GSS provider services --- substratevm/mx.substratevm/suite.py | 1 + .../oracle/svm/hosted/SecurityServicesFeature.java | 13 +++++++++++++ .../svm/test/services/SecurityServiceTest.java | 14 +++++++++++++- 3 files changed, 27 insertions(+), 1 deletion(-) diff --git a/substratevm/mx.substratevm/suite.py b/substratevm/mx.substratevm/suite.py index 98b9ea9f1c4b..e364afc9c13c 100644 --- a/substratevm/mx.substratevm/suite.py +++ b/substratevm/mx.substratevm/suite.py @@ -1274,6 +1274,7 @@ "jdk.management.jfr", "java.instrument", "java.rmi", + "java.security.jgss", ], "requiresConcealed" : { "java.base" : [ diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SecurityServicesFeature.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SecurityServicesFeature.java index 987625699bbe..95a0b1fd90d6 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SecurityServicesFeature.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SecurityServicesFeature.java @@ -197,6 +197,7 @@ public static class Options { * https://docs.oracle.com/en/java/javase/11/docs/specs/security/standard-names.html */ private static final String SECURE_RANDOM_SERVICE = "SecureRandom"; + private static final String GSS_API_MECHANISM_SERVICE = "GssApiMechanism"; private static final String SIGNATURE_SERVICE = "Signature"; private static final String CIPHER_SERVICE = "Cipher"; private static final String KEY_AGREEMENT_SERVICE = "KeyAgreement"; @@ -691,6 +692,7 @@ private void registerServiceReachabilityHandlers(BeforeAnalysisAccess access) { if (ModuleLayer.boot().findModule("java.security.sasl").isPresent()) { registerSASLReachabilityHandlers(access); } + registerGSSReachabilityHandler(access); /* * On Oracle JDK the SecureRandom service implementations are not automatically discovered @@ -703,6 +705,17 @@ private void registerServiceReachabilityHandlers(BeforeAnalysisAccess access) { defaultSecureRandomService.ifPresent(m -> access.registerMethodOverrideReachabilityHandler((a, t) -> registerServices(a, t, SECURE_RANDOM_SERVICE), OriginalMethodProvider.getJavaMethod(m))); } + private void registerGSSReachabilityHandler(BeforeAnalysisAccess access) { + Class gssManager = access.findClassByName("org.ietf.jgss.GSSManager"); + if (gssManager == null) { + return; + } + Method getInstance = ReflectionUtil.lookupMethod(gssManager, "getInstance"); + // The GSS facade uses Provider services but does not follow the JCA getInstance convention. + // \u00a7FS-001-jca-security-provider-inclusion.1 + access.registerReachabilityHandler(a -> registerServices(a, getInstance, GSS_API_MECHANISM_SERVICE), gssManager); + } + private void initializeServiceRegistrationData() { ctrParamClassAccessor = getConstructorParameterClassAccessor(loader); getSpiClassMethod = getSpiClassMethod(); diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/services/SecurityServiceTest.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/services/SecurityServiceTest.java index 015d1fd2e79b..59dab1794698 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/services/SecurityServiceTest.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/services/SecurityServiceTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -46,6 +46,9 @@ import org.graalvm.nativeimage.hosted.RuntimeReflection; import org.hamcrest.CoreMatchers; import org.hamcrest.MatcherAssert; +import org.ietf.jgss.GSSManager; +import org.ietf.jgss.GSSName; +import org.ietf.jgss.Oid; import org.junit.Assert; import org.junit.Assume; import org.junit.Test; @@ -153,6 +156,15 @@ public void testAutomaticSecurityServiceRegistration() { } } + /** Verifies service-driven GSS provider inclusion. \u00a7FS-001-jca-security-provider-inclusion.1 */ + @Test + public void testGSSProviderServiceRegistration() throws Exception { + Oid kerberosV5 = new Oid("1.2.840.113554.1.2.2"); + GSSManager manager = GSSManager.getInstance(); + Assert.assertTrue("The reachable GSS facade must preserve the Kerberos mechanism.", Set.of(manager.getMechs()).contains(kerberosV5)); + Assert.assertEquals("user@REALM", manager.createName("user@REALM", GSSName.NT_USER_NAME, kerberosV5).toString()); + } + @Test public void testReflectionMetadataProviderRegistration() throws Exception { Provider provider = (Provider) Class.forName(REFLECTION_METADATA_PROVIDER_CLASS_NAME).getDeclaredConstructor().newInstance(); From c9537b8610a8ff37f27552bffd17ac22a3db19d0 Mon Sep 17 00:00:00 2001 From: Vojin Jovanovic Date: Sat, 4 Jul 2026 10:15:08 +0200 Subject: [PATCH 17/63] [GR-69858] Preserve concurrent JNI registrations Security provider analysis changes the timing of reachability callbacks and exposed a race in JNIAccessFeature. Reachability callbacks add JNI classes, methods, and fields concurrently with duringAnalysis(), but duringAnalysis() previously iterated each positive-registration worklist and then cleared it. A registration added after the weakly consistent iterator had passed an entry, but before clear(), was silently discarded. This could drop the JNI metadata registered for java.net.NetworkInterface. Its runtime native initializer then failed FindClass("java/net/NetworkInterface") with NoClassDefFoundError in the recurring-callback SVM test image. Remove each observed positive registration atomically before processing it instead of clearing the worklists afterward. Registrations not observed by the current iteration, or added concurrently after removal, remain queued for the next analysis iteration. --- .../svm/hosted/jni/JNIAccessFeature.java | 20 +++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jni/JNIAccessFeature.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jni/JNIAccessFeature.java index 536dbef68ea0..6e645f5ee314 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jni/JNIAccessFeature.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jni/JNIAccessFeature.java @@ -428,10 +428,16 @@ public void duringAnalysis(DuringAnalysisAccess a) { return; } + /* + * Remove each positive registration before processing it instead of clearing the worklists + * afterwards. Reachability callbacks can add registrations concurrently; a final clear + * could otherwise discard an entry that the weakly consistent iterator did not observe. + */ for (var registration : newClasses) { - addClass(registration.element(), registration.preserved(), access); + if (newClasses.remove(registration)) { + addClass(registration.element(), registration.preserved(), access); + } } - newClasses.clear(); for (String className : newNegativeClassLookups) { addNegativeClassLookup(className); @@ -439,9 +445,10 @@ public void duringAnalysis(DuringAnalysisAccess a) { newNegativeClassLookups.clear(); for (var registration : newMethods) { - addMethod(registration.element(), registration.preserved(), access); + if (newMethods.remove(registration)) { + addMethod(registration.element(), registration.preserved(), access); + } } - newMethods.clear(); newNegativeMethodLookups.forEach((clazz, signatures) -> { for (Pair[]> signature : signatures) { @@ -451,9 +458,10 @@ public void duringAnalysis(DuringAnalysisAccess a) { newNegativeMethodLookups.clear(); newFields.forEach((registration, writable) -> { - addField(registration.element(), registration.preserved(), writable, access); + if (newFields.remove(registration, writable)) { + addField(registration.element(), registration.preserved(), writable, access); + } }); - newFields.clear(); newNegativeFieldLookups.forEach((clazz, fieldNames) -> { for (String fieldName : fieldNames) { From 3833fd5f47fe2a6655336dc171c280154b1f39d1 Mon Sep 17 00:00:00 2001 From: Vojin Jovanovic Date: Wed, 15 Jul 2026 09:27:16 +0200 Subject: [PATCH 18/63] [GR-69858] Preserve concurrent negative JNI registrations --- .../svm/hosted/jni/JNIAccessFeature.java | 47 ++++++++++--------- 1 file changed, 24 insertions(+), 23 deletions(-) diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jni/JNIAccessFeature.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jni/JNIAccessFeature.java index 6e645f5ee314..68927461a1bb 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jni/JNIAccessFeature.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jni/JNIAccessFeature.java @@ -30,7 +30,6 @@ import java.lang.reflect.Modifier; import java.util.ArrayList; import java.util.Collections; -import java.util.HashSet; import java.util.IdentityHashMap; import java.util.List; import java.util.Map; @@ -189,12 +188,18 @@ static final class JNICallableJavaMethod { private record RegistrationWithPreserved(T element, boolean preserved) { } + private record NegativeMethodLookup(Class clazz, String methodName, List> parameterTypes) { + } + + private record NegativeFieldLookup(Class clazz, String fieldName) { + } + private final Set>> newClasses = Collections.newSetFromMap(new ConcurrentHashMap<>()); private final Set newNegativeClassLookups = Collections.newSetFromMap(new ConcurrentHashMap<>()); private final Set> newMethods = Collections.newSetFromMap(new ConcurrentHashMap<>()); - private final Map, Set[]>>> newNegativeMethodLookups = new ConcurrentHashMap<>(); + private final Set newNegativeMethodLookups = Collections.newSetFromMap(new ConcurrentHashMap<>()); private final Map, Boolean> newFields = new ConcurrentHashMap<>(); - private final Map, Set> newNegativeFieldLookups = new ConcurrentHashMap<>(); + private final Set newNegativeFieldLookups = Collections.newSetFromMap(new ConcurrentHashMap<>()); // Needs Pair to de-duplicate linkage objects for lack of key-to-key lookups. private final Map> nativeLinkages = new ConcurrentHashMap<>(); @@ -286,7 +291,7 @@ public void registerFieldLookup(AccessCondition condition, boolean preserved, Cl try { register(condition, false, preserved, declaringClass.getDeclaredField(fieldName)); } catch (NoSuchFieldException e) { - newNegativeFieldLookups.computeIfAbsent(declaringClass, _ -> new HashSet<>()).add(fieldName); // noEconomicSet + newNegativeFieldLookups.add(new NegativeFieldLookup(declaringClass, fieldName)); } } @@ -295,7 +300,7 @@ public void registerMethodLookup(AccessCondition condition, boolean preserved, C try { register(condition, preserved, declaringClass.getDeclaredMethod(methodName, parameterTypes)); } catch (NoSuchMethodException e) { - newNegativeMethodLookups.computeIfAbsent(declaringClass, _ -> new HashSet<>()).add(Pair.create(methodName, parameterTypes)); // noEconomicSet + newNegativeMethodLookups.add(new NegativeMethodLookup(declaringClass, methodName, List.of(parameterTypes))); } } @@ -304,7 +309,7 @@ public void registerConstructorLookup(AccessCondition condition, boolean preserv try { register(condition, preserved, declaringClass.getDeclaredConstructor(parameterTypes)); } catch (NoSuchMethodException e) { - newNegativeMethodLookups.computeIfAbsent(declaringClass, _ -> new HashSet<>()).add(Pair.create("", parameterTypes)); // noEconomicSet + newNegativeMethodLookups.add(new NegativeMethodLookup(declaringClass, "", List.of(parameterTypes))); } } } @@ -428,11 +433,8 @@ public void duringAnalysis(DuringAnalysisAccess a) { return; } - /* - * Remove each positive registration before processing it instead of clearing the worklists - * afterwards. Reachability callbacks can add registrations concurrently; a final clear - * could otherwise discard an entry that the weakly consistent iterator did not observe. - */ + /* Remove observed registrations individually so concurrent additions remain pending. */ + // \u00a7FS-001-jca-security-provider-inclusion.5 for (var registration : newClasses) { if (newClasses.remove(registration)) { addClass(registration.element(), registration.preserved(), access); @@ -440,9 +442,10 @@ public void duringAnalysis(DuringAnalysisAccess a) { } for (String className : newNegativeClassLookups) { - addNegativeClassLookup(className); + if (newNegativeClassLookups.remove(className)) { + addNegativeClassLookup(className); + } } - newNegativeClassLookups.clear(); for (var registration : newMethods) { if (newMethods.remove(registration)) { @@ -450,12 +453,11 @@ public void duringAnalysis(DuringAnalysisAccess a) { } } - newNegativeMethodLookups.forEach((clazz, signatures) -> { - for (Pair[]> signature : signatures) { - addNegativeMethodLookup(clazz, signature.getLeft(), signature.getRight(), access); + for (NegativeMethodLookup lookup : newNegativeMethodLookups) { + if (newNegativeMethodLookups.remove(lookup)) { + addNegativeMethodLookup(lookup.clazz(), lookup.methodName(), lookup.parameterTypes().toArray(Class[]::new), access); } - }); - newNegativeMethodLookups.clear(); + } newFields.forEach((registration, writable) -> { if (newFields.remove(registration, writable)) { @@ -463,12 +465,11 @@ public void duringAnalysis(DuringAnalysisAccess a) { } }); - newNegativeFieldLookups.forEach((clazz, fieldNames) -> { - for (String fieldName : fieldNames) { - addNegativeFieldLookup(clazz, fieldName, access); + for (NegativeFieldLookup lookup : newNegativeFieldLookups) { + if (newNegativeFieldLookups.remove(lookup)) { + addNegativeFieldLookup(lookup.clazz(), lookup.fieldName(), access); } - }); - newNegativeFieldLookups.clear(); + } access.requireAnalysisIteration(); } From 7ef005eba9bb43a5d93c596c4b76851da6ea020f Mon Sep 17 00:00:00 2001 From: Vojin Jovanovic Date: Mon, 20 Jul 2026 13:07:16 +0200 Subject: [PATCH 19/63] [GR-69858] Fix GSS reachability registration --- .../src/com/oracle/svm/hosted/SecurityServicesFeature.java | 3 +-- .../src/com/oracle/svm/hosted/ServiceLoaderFeature.java | 1 - .../src/com/oracle/svm/hosted/jni/JNIAccessFeature.java | 1 - .../src/com/oracle/svm/test/services/SecurityServiceTest.java | 2 +- 4 files changed, 2 insertions(+), 5 deletions(-) diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SecurityServicesFeature.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SecurityServicesFeature.java index 95a0b1fd90d6..d12baff41d76 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SecurityServicesFeature.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SecurityServicesFeature.java @@ -712,8 +712,7 @@ private void registerGSSReachabilityHandler(BeforeAnalysisAccess access) { } Method getInstance = ReflectionUtil.lookupMethod(gssManager, "getInstance"); // The GSS facade uses Provider services but does not follow the JCA getInstance convention. - // \u00a7FS-001-jca-security-provider-inclusion.1 - access.registerReachabilityHandler(a -> registerServices(a, getInstance, GSS_API_MECHANISM_SERVICE), gssManager); + access.registerReachabilityHandler(a -> registerServices(a, getInstance, GSS_API_MECHANISM_SERVICE), getInstance); } private void initializeServiceRegistrationData() { diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ServiceLoaderFeature.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ServiceLoaderFeature.java index e5f9b70478e3..47f94a2c1364 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ServiceLoaderFeature.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ServiceLoaderFeature.java @@ -168,7 +168,6 @@ public void afterRegistration(AfterRegistrationAccess access) { public void beforeAnalysis(BeforeAnalysisAccess access) { FeatureImpl.BeforeAnalysisAccessImpl accessImpl = (FeatureImpl.BeforeAnalysisAccessImpl) access; // Permit an absent class-path descriptor without including omitted providers. - // \u00a7FS-001-jca-security-provider-inclusion.4 Resources.currentLayer().registerNegativeQuery(access.getApplicationClassLoader().getUnnamedModule(), SECURITY_PROVIDER_SERVICE_RESOURCE); accessImpl.imageClassLoader.classLoaderSupport.serviceProvidersForEach((serviceName, providers) -> { Collection providersToSkip = providers; diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jni/JNIAccessFeature.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jni/JNIAccessFeature.java index 68927461a1bb..bf56361906a2 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jni/JNIAccessFeature.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jni/JNIAccessFeature.java @@ -434,7 +434,6 @@ public void duringAnalysis(DuringAnalysisAccess a) { } /* Remove observed registrations individually so concurrent additions remain pending. */ - // \u00a7FS-001-jca-security-provider-inclusion.5 for (var registration : newClasses) { if (newClasses.remove(registration)) { addClass(registration.element(), registration.preserved(), access); diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/services/SecurityServiceTest.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/services/SecurityServiceTest.java index 59dab1794698..4ee4610ddbb9 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/services/SecurityServiceTest.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/services/SecurityServiceTest.java @@ -156,7 +156,7 @@ public void testAutomaticSecurityServiceRegistration() { } } - /** Verifies service-driven GSS provider inclusion. \u00a7FS-001-jca-security-provider-inclusion.1 */ + /** Verifies service-driven GSS provider inclusion. */ @Test public void testGSSProviderServiceRegistration() throws Exception { Oid kerberosV5 = new Oid("1.2.840.113554.1.2.2"); From 0ee92cd9552843a2e74cbd18828e21d9023ce9b7 Mon Sep 17 00:00:00 2001 From: Vojin Jovanovic Date: Mon, 20 Jul 2026 13:22:55 +0200 Subject: [PATCH 20/63] [GR-69858] Gate service-driven providers on metadata --- .../native-image/BuildOptions.md | 2 +- .../native-image/JCASecurityServices.md | 22 ++++++++- .../svm/core/FutureDefaultsOptions.java | 10 ++++- .../svm/core/doc-files/FutureDefaultsHelp.txt | 6 ++- .../svm/hosted/SecurityServicesFeature.java | 4 ++ ...rviceExplicitProviderRegistrationTest.java | 45 +++++++++++++++++++ 6 files changed, 85 insertions(+), 4 deletions(-) create mode 100644 substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/services/SecurityServiceExplicitProviderRegistrationTest.java diff --git a/docs/reference-manual/native-image/BuildOptions.md b/docs/reference-manual/native-image/BuildOptions.md index 4ee1a4ac9773..85dd491740da 100644 --- a/docs/reference-manual/native-image/BuildOptions.md +++ b/docs/reference-manual/native-image/BuildOptions.md @@ -51,7 +51,7 @@ These deprecated URL protocol options are omitted from the generated table; see | `--exact-reachability-metadata` | String | enables exact and user-friendly handling of reflection, resources, JNI, and serialization. | | `--exact-reachability-metadata=exact-reachability-metadata` | | `--exact-reachability-metadata-path` | String | trigger exact handling of reflection, resources, JNI, and serialization from all types in the given class-path or module-path entries. | None | `--exact-reachability-metadata-path=exact-reachability-metadata-path` | | `--features` | String | a comma-separated list of fully qualified Feature implementation classes | None | `--features=features` | -| `--future-defaults` | String | enable options that are planned to become defaults in future releases. Comma-separated list can contain 'all', 'none', 'run-time-initialize-jdk', 'class-for-name-respects-class-loader', 'run-time-initialize-file-system-providers', 'run-time-initialize-security-providers', 'run-time-initialize-resource-bundles', 'explicit-feature-singleton-registration'. The preferred usage is '--future-defaults=all'. | | `--future-defaults=future-defaults` | +| `--future-defaults` | String | enable options that are planned to become defaults in future releases. Comma-separated list can contain 'all', 'none', 'run-time-initialize-jdk', 'class-for-name-respects-class-loader', 'run-time-initialize-file-system-providers', 'run-time-initialize-security-providers', 'run-time-initialize-resource-bundles', 'explicit-feature-singleton-registration', 'explicit-security-provider-registration'. The preferred usage is '--future-defaults=all'. | | `--future-defaults=future-defaults` | | `--initialize-at-build-time` | String | a comma-separated list of packages and classes (and implicitly all of their superclasses) that are initialized during image generation. An empty string designates all packages. | | `--initialize-at-build-time=initialize-at-build-time` | | `--initialize-at-run-time` | String | a comma-separated list of packages and classes (and implicitly all of their subclasses) that must be initialized at runtime and not during image building. An empty string is currently not supported. | | `--initialize-at-run-time=initialize-at-run-time` | | `--libc` | String | selects the libc implementation to use. Available implementations: glibc, musl, bionic | None | `--libc=libc` | diff --git a/docs/reference-manual/native-image/JCASecurityServices.md b/docs/reference-manual/native-image/JCASecurityServices.md index 8a4420dc7271..dee761e5bf1c 100644 --- a/docs/reference-manual/native-image/JCASecurityServices.md +++ b/docs/reference-manual/native-image/JCASecurityServices.md @@ -18,7 +18,25 @@ By default the `native-image` builder uses static analysis to discover which of The automatic registration of security services can be disabled with `-H:-EnableSecurityServicesFeature`. Then a custom reflection configuration file or feature can be used to register the security services required by a specific application. Note that when automatic registration of security providers is disabled, all providers are, by default, filtered from special JDK caches that are necessary for security functionality. -In this case, register the provider classes for reflection, for example with reachability metadata collected by the Tracing Agent. +In this case, register the provider class and its nullary constructor for reflection in _reachability-metadata.json_, for example: + +```json +{ + "reflection": [ + { + "type": "com.example.security.CustomProvider", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + } + ] +} +``` + +Alternatively, collect the metadata by running your application on the JVM with the [Tracing Agent](AutomaticMetadataCollection.md). ## Security Services Automatic Registration @@ -32,6 +50,8 @@ It does so by registering reachability handlers for each of the `getInstance()` When it determines that a `getInstance()` method is reachable at run time, it automatically performs the reflection registration for all the concrete implementations of the corresponding service type. Provider classes discovered as reachable subtypes of `java.security.Provider` are treated only as candidates for provider inclusion. The builder includes such a provider and all of its services only when the provider class is registered for reflection, either by type access, its declared nullary constructor, or its static `provider()` method. +To apply this reflection requirement to providers selected by reachable service factories, use `--future-defaults=explicit-security-provider-registration`. +With this future default, a factory does not make an unregistered provider or its services available. Tracing of the security services automatic registration can be enabled with `-H:+TraceSecurityServices`. The report will detail all registered service classes, the API methods that triggered registration, and the parsing context for each reachable API method. diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/FutureDefaultsOptions.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/FutureDefaultsOptions.java index 65dfac27dbd1..05dcb7b02ea1 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/FutureDefaultsOptions.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/FutureDefaultsOptions.java @@ -89,8 +89,9 @@ public class FutureDefaultsOptions { private static final String CLASS_FOR_NAME_RESPECTS_CLASS_LOADER = "class-for-name-respects-class-loader"; private static final String EXACT_REFLECTION = "exact-reflection"; public static final String EXPLICIT_FEATURE_SINGLETON_REGISTRATION = "explicit-feature-singleton-registration"; + private static final String EXPLICIT_SECURITY_PROVIDER_REGISTRATION = "explicit-security-provider-registration"; private static final List ALL_FUTURE_DEFAULTS = List.of(CLASS_FOR_NAME_RESPECTS_CLASS_LOADER, EXACT_REFLECTION, RUN_TIME_INITIALIZE_FILE_SYSTEM_PROVIDERS, - RUN_TIME_INITIALIZE_SECURITY_PROVIDERS, RUN_TIME_INITIALIZE_RESOURCE_BUNDLES, EXPLICIT_FEATURE_SINGLETON_REGISTRATION); + RUN_TIME_INITIALIZE_RESOURCE_BUNDLES, EXPLICIT_FEATURE_SINGLETON_REGISTRATION, EXPLICIT_SECURITY_PROVIDER_REGISTRATION); private static final String COMPLETE_REFLECTION_TYPES = "complete-reflection-types"; private static final List RETIRED_FUTURE_DEFAULTS = List.of(COMPLETE_REFLECTION_TYPES); @@ -272,4 +273,11 @@ public static boolean exactReflection() { public static boolean explicitFeatureSingletonRegistration() { return getFutureDefaults().contains(EXPLICIT_FEATURE_SINGLETON_REGISTRATION); } + + /** + * @see FutureDefaultsOptions#FutureDefaults + */ + public static boolean explicitSecurityProviderRegistration() { + return getFutureDefaults().contains(EXPLICIT_SECURITY_PROVIDER_REGISTRATION); + } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/doc-files/FutureDefaultsHelp.txt b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/doc-files/FutureDefaultsHelp.txt index d80815266a73..09e72ced5af6 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/doc-files/FutureDefaultsHelp.txt +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/doc-files/FutureDefaultsHelp.txt @@ -1,4 +1,4 @@ -Enable options that are planned to become defaults in future releases. Comma-separated list can contain 'all', 'none', 'run-time-initialize-jdk', 'class-for-name-respects-class-loader', 'exact-reflection', 'run-time-initialize-file-system-providers', 'run-time-initialize-security-providers', 'run-time-initialize-resource-bundles', 'explicit-feature-singleton-registration'. The preferred usage is '--future-defaults=all'. +Enable options that are planned to become defaults in future releases. Comma-separated list can contain 'all', 'none', 'run-time-initialize-jdk', 'class-for-name-respects-class-loader', 'exact-reflection', 'run-time-initialize-file-system-providers', 'run-time-initialize-security-providers', 'run-time-initialize-resource-bundles', 'explicit-feature-singleton-registration', 'explicit-security-provider-registration'. The preferred usage is '--future-defaults=all'. The meaning of each possible option is as follows: 'all' - is the preferred option, and it enables all other behaviors. @@ -18,3 +18,7 @@ The meaning of each possible option is as follows: 'run-time-initialize-resource-bundles' - shifts away from build-time initialization for 'java.util.ResourceBundle'. Unless you store 'ResourceBundle'-related classes in the image heap, this option should not affect you. In case this option breaks your build, follow the suggestions in the error messages. 'explicit-feature-singleton-registration' - stops automatically publishing user features reached from '--features' as 'ImageSingletons'. If code needs feature-singleton lookup compatibility, the feature must register its feature object explicitly. + + 'explicit-security-provider-registration' - requires reflection metadata to include a security provider and its services. Reachable security-service factory methods do not include providers without reflection metadata. + + 'explicit-security-provider-registration' - requires reflection metadata to include a security provider and its services. Reachable security-service factory methods do not include providers without reflection metadata. diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SecurityServicesFeature.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SecurityServicesFeature.java index d12baff41d76..a3b29012498e 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SecurityServicesFeature.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SecurityServicesFeature.java @@ -971,6 +971,10 @@ private static Provider instantiateProvider(Class providerClass) { } private void registerService(DuringAnalysisAccess a, Service service) { + if (FutureDefaultsOptions.explicitSecurityProviderRegistration() && !isProviderRegisteredForReflection(service.getProvider().getClass())) { + trace("Skipped service %s because provider %s was not registered for reflection.", asString(service), service.getProvider().getClass().getName()); + return; + } TypeResult> serviceClassResult = loader.findClass(service.getClassName()); if (serviceClassResult.isPresent()) { try (TracingAutoCloseable _ = trace(service)) { diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/services/SecurityServiceExplicitProviderRegistrationTest.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/services/SecurityServiceExplicitProviderRegistrationTest.java new file mode 100644 index 000000000000..0f0a61cc89a9 --- /dev/null +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/services/SecurityServiceExplicitProviderRegistrationTest.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2026, 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.test.services; + +import java.security.NoSuchAlgorithmException; +import java.security.Security; +import java.security.Signature; + +import org.junit.Assert; +import org.junit.Test; + +import com.oracle.svm.test.NativeImageBuildArgs; + +@NativeImageBuildArgs({ + "--future-defaults=run-time-initialize-security-providers,explicit-security-provider-registration" +}) +public class SecurityServiceExplicitProviderRegistrationTest { + @Test + public void testReachableFactoryDoesNotIncludeUnregisteredProvider() { + Assert.assertNull("A reachable Signature factory must not include SunEC.", Security.getProvider("SunEC")); + Assert.assertThrows(NoSuchAlgorithmException.class, () -> Signature.getInstance("SHA256withECDSA")); + } +} From ae8627f14de97d36809d29912af20e4a57080453 Mon Sep 17 00:00:00 2001 From: Vojin Jovanovic Date: Mon, 20 Jul 2026 16:12:01 +0200 Subject: [PATCH 21/63] Revert "[GR-69858] Preserve concurrent JNI registrations" This reverts commit a3b5ef097c6c26a4f20d564b7923618997aa25be. --- .../oracle/svm/hosted/jni/JNIAccessFeature.java | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jni/JNIAccessFeature.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jni/JNIAccessFeature.java index bf56361906a2..0ddfc2bdcb3b 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jni/JNIAccessFeature.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jni/JNIAccessFeature.java @@ -433,12 +433,10 @@ public void duringAnalysis(DuringAnalysisAccess a) { return; } - /* Remove observed registrations individually so concurrent additions remain pending. */ for (var registration : newClasses) { - if (newClasses.remove(registration)) { - addClass(registration.element(), registration.preserved(), access); - } + addClass(registration.element(), registration.preserved(), access); } + newClasses.clear(); for (String className : newNegativeClassLookups) { if (newNegativeClassLookups.remove(className)) { @@ -447,10 +445,9 @@ public void duringAnalysis(DuringAnalysisAccess a) { } for (var registration : newMethods) { - if (newMethods.remove(registration)) { - addMethod(registration.element(), registration.preserved(), access); - } + addMethod(registration.element(), registration.preserved(), access); } + newMethods.clear(); for (NegativeMethodLookup lookup : newNegativeMethodLookups) { if (newNegativeMethodLookups.remove(lookup)) { @@ -459,10 +456,9 @@ public void duringAnalysis(DuringAnalysisAccess a) { } newFields.forEach((registration, writable) -> { - if (newFields.remove(registration, writable)) { - addField(registration.element(), registration.preserved(), writable, access); - } + addField(registration.element(), registration.preserved(), writable, access); }); + newFields.clear(); for (NegativeFieldLookup lookup : newNegativeFieldLookups) { if (newNegativeFieldLookups.remove(lookup)) { From d85fb1f99d3cdb10a62ef4bf54645cc3aa4b45e7 Mon Sep 17 00:00:00 2001 From: Vojin Jovanovic Date: Mon, 20 Jul 2026 18:18:36 +0200 Subject: [PATCH 22/63] [GR-69858] Reprocess late security provider candidates --- .../com/oracle/svm/hosted/SecurityServicesFeature.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SecurityServicesFeature.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SecurityServicesFeature.java index a3b29012498e..795ebb2962ec 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SecurityServicesFeature.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SecurityServicesFeature.java @@ -394,7 +394,13 @@ public void beforeAnalysis(BeforeAnalysisAccess a) { access.ensureInitialized("sun.security.util.AnchorCertificates"); initializeServiceRegistrationData(); - access.registerSubtypeReachabilityHandler((_, providerClass) -> candidateProviderClasses.add(providerClass), Provider.class); + access.registerSubtypeReachabilityHandler((analysisAccess, providerClass) -> { + if (candidateProviderClasses.add(providerClass)) { + // Process candidates reported after this feature's current pass. + // \u00a7FS-001-jca-security-provider-inclusion.5 + analysisAccess.requireAnalysisIteration(); + } + }, Provider.class); registerServiceProviderCandidates(access); registerManuallyConfiguredProvidersForReflection(access); if (Options.EnableSecurityServicesFeature.getValue()) { From aaec46828f65b1ec908dd72fe4cae262af7c1d65 Mon Sep 17 00:00:00 2001 From: Vojin Jovanovic Date: Mon, 20 Jul 2026 23:00:00 +0200 Subject: [PATCH 23/63] [GR-69858] Defer provider candidate reprocessing --- .../svm/hosted/SecurityServicesFeature.java | 22 +++++++++++-------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SecurityServicesFeature.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SecurityServicesFeature.java index 795ebb2962ec..4bd51fca1f28 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SecurityServicesFeature.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SecurityServicesFeature.java @@ -68,6 +68,7 @@ import java.util.Properties; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.BiConsumer; import java.util.function.Function; @@ -251,6 +252,7 @@ public static class Options { private final Set usedProviders = ConcurrentHashMap.newKeySet(); private final Set> candidateProviderClasses = ConcurrentHashMap.newKeySet(); private final Set> includedProviderClasses = ConcurrentHashMap.newKeySet(); + private final AtomicBoolean candidateProviderClassesChanged = new AtomicBoolean(); private Field verificationResultsField; private Field providerListField; @@ -394,13 +396,7 @@ public void beforeAnalysis(BeforeAnalysisAccess a) { access.ensureInitialized("sun.security.util.AnchorCertificates"); initializeServiceRegistrationData(); - access.registerSubtypeReachabilityHandler((analysisAccess, providerClass) -> { - if (candidateProviderClasses.add(providerClass)) { - // Process candidates reported after this feature's current pass. - // \u00a7FS-001-jca-security-provider-inclusion.5 - analysisAccess.requireAnalysisIteration(); - } - }, Provider.class); + access.registerSubtypeReachabilityHandler((_, providerClass) -> addCandidateProviderClass(providerClass), Provider.class); registerServiceProviderCandidates(access); registerManuallyConfiguredProvidersForReflection(access); if (Options.EnableSecurityServicesFeature.getValue()) { @@ -738,13 +734,19 @@ private void registerServiceProviderCandidates(BeforeAnalysisAccess access) { for (String provider : providers) { Class providerClass = access.findClassByName(provider); if (providerClass != null) { - candidateProviderClasses.add(providerClass); + addCandidateProviderClass(providerClass); } } } }); } + private void addCandidateProviderClass(Class providerClass) { + if (candidateProviderClasses.add(providerClass)) { + candidateProviderClassesChanged.set(true); + } + } + private void registerServices(DuringAnalysisAccess access, Object trigger, Class serviceClass) { /* * SPI classes, i.e., base classes for concrete service implementations, such as @@ -1108,6 +1110,7 @@ private void registerX509Extensions(DuringAnalysisAccess a) { @Override public void duringAnalysis(DuringAnalysisAccess a) { DuringAnalysisAccessImpl access = (DuringAnalysisAccessImpl) a; + boolean newProviderCandidate = candidateProviderClassesChanged.getAndSet(false); boolean includedProvider = false; for (Class providerClass : candidateProviderClasses) { if (!includedProviderClasses.contains(providerClass) && isProviderRegisteredForReflection(providerClass)) { @@ -1116,7 +1119,8 @@ public void duringAnalysis(DuringAnalysisAccess a) { includedProvider = true; } } - if (includedProvider) { + if (includedProvider || newProviderCandidate) { + // Request the extra pass here, not from the concurrent reachability callback. access.requireAnalysisIteration(); } access.rescanRoot(oidTableField, scanReason); From 9005d8cebc6e36ef249b39e1cf5418700bbdd7df Mon Sep 17 00:00:00 2001 From: Vojin Jovanovic Date: Tue, 21 Jul 2026 14:23:19 +0200 Subject: [PATCH 24/63] [GR-69858] Document security provider metadata behavior --- substratevm/docs/README.md | 5 ++ substratevm/docs/architecture/README.md | 5 ++ .../docs/architecture/security-providers.md | 1 + substratevm/docs/functional-spec/README.md | 5 ++ .../functional-spec/security-providers.md | 54 +++++++++++++++ .../core/jdk/SecurityProvidersSupport.java | 65 +++++++++++++++++-- .../svm/core/jdk/SecuritySubstitutions.java | 7 +- .../SecuritySubstitutionRuntimeInit.java | 7 +- 8 files changed, 135 insertions(+), 14 deletions(-) create mode 100644 substratevm/docs/architecture/README.md create mode 100644 substratevm/docs/architecture/security-providers.md create mode 100644 substratevm/docs/functional-spec/README.md create mode 100644 substratevm/docs/functional-spec/security-providers.md diff --git a/substratevm/docs/README.md b/substratevm/docs/README.md index aaae50d3e9e6..d5a78385acd7 100644 --- a/substratevm/docs/README.md +++ b/substratevm/docs/README.md @@ -43,3 +43,8 @@ Consulting the CI configurations in ci/ci.jsonnet may help understand how `mx ga ## Project Terminus - [Project Terminus](project-terminus.md): overview and design direction for self-hosting Native Image. + +## Security Providers + +- [JCA Security Provider Inclusion](functional-spec/security-providers.md): required provider inclusion and run-time behavior. +- [Security Provider Architecture](architecture/security-providers.md): provider inclusion, verification, and metadata tracing. diff --git a/substratevm/docs/architecture/README.md b/substratevm/docs/architecture/README.md new file mode 100644 index 000000000000..a77b54390cbe --- /dev/null +++ b/substratevm/docs/architecture/README.md @@ -0,0 +1,5 @@ +# Architecture + +This directory contains developer-facing architecture records for Native Image. + +- [Security Provider Architecture](security-providers.md): provider inclusion, verification, and metadata tracing. diff --git a/substratevm/docs/architecture/security-providers.md b/substratevm/docs/architecture/security-providers.md new file mode 100644 index 000000000000..66579720be9d --- /dev/null +++ b/substratevm/docs/architecture/security-providers.md @@ -0,0 +1 @@ +# AR-security-providers: [SecurityProvidersSupport](../../src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SecurityProvidersSupport.java) diff --git a/substratevm/docs/functional-spec/README.md b/substratevm/docs/functional-spec/README.md new file mode 100644 index 000000000000..701db6c86409 --- /dev/null +++ b/substratevm/docs/functional-spec/README.md @@ -0,0 +1,5 @@ +# Functional Specifications + +This directory contains functional specifications for Native Image. + +- [JCA Security Provider Inclusion](security-providers.md): provider lookup and factory-call behavior based on metadata. diff --git a/substratevm/docs/functional-spec/security-providers.md b/substratevm/docs/functional-spec/security-providers.md new file mode 100644 index 000000000000..8ad03166a5b5 --- /dev/null +++ b/substratevm/docs/functional-spec/security-providers.md @@ -0,0 +1,54 @@ +# FS-security-providers: JCA Security Provider Inclusion + +The set of security providers and services available in a native executable is determined when the executable is built. +At run time, `Security` lookups and Java Cryptography Architecture (JCA) factory calls expose only the providers and services included by the rules below. + +## 1. Provider Metadata and Lookup Results + +Registering a provider class for reflection through any of the following metadata includes the provider and all of its services: + +- access to the provider type; +- access to its declared nullary constructor; or +- access to its public static `provider()` method. + +If an included provider is present in the configured security-provider list, `Security.getProvider(String)` returns it and `Security.getProviders()` contains it. +JCA factory calls can use the services declared by that provider, subject to their normal algorithm and provider arguments. + +If exact reachability metadata checking is enabled, an operation that attempts to load an omitted provider reflectively reports `MissingReflectionRegistrationError` for the provider type. +Native Image must not replace that error with a security-provider-specific exception. + +If no reflective provider access occurs, an omitted provider remains unavailable through the normal JCA API results. +`Security.getProvider(String)` returns `null` when the provider is absent from the provider list, and a factory call for an unavailable provider or algorithm reports `NoSuchProviderException` or `NoSuchAlgorithmException`, as appropriate. + +## 2. Service Factory Calls + +By default, a reachable JCA service factory or JDK security-service facade, including GSS, can include the providers and services needed by that call even when the provider has no reflection metadata. +For example, a reachable `Signature.getInstance(String)` call can make a matching signature implementation available without separately registering its provider. + +A provider registered through a static `provider()` method has the same observable services as a provider registered through its constructor. +Metadata collected by tracing must preserve a loading path that makes subsequent `Security.getProvider(String)` and JCA factory calls behave the same way. + +### 2.1 Explicit Provider Registration Future Default + +With `--future-defaults=explicit-security-provider-registration`, service-factory reachability alone does not include a provider or its services. +The provider must have one of the reflection registrations listed in section 1. +Without that metadata, a factory call for an algorithm supplied only by that provider reports that the algorithm is unavailable. +A direct lookup that attempts to load the omitted provider follows the missing-registration behavior in section 1. + +## 3. Programmatically Supplied Providers + +An application can construct a provider and pass it directly to a JCA factory or add it with `Security.addProvider(Provider)`. +The provider's class still requires the metadata listed in section 1 before JCE can verify and use its services. + +If the metadata is present, provider-name lookups and factory calls using either the provider object or its name can use the provider's services. +If it is absent and exact reachability metadata checking is enabled, the first operation that requires JCE verification reports `MissingReflectionRegistrationError` for the provider type. +The error and tracing behavior must be the same as for other missing reflective type access. + +## 4. Run-Time Provider Initialization + +When security providers are initialized at run time, Native Image reconstructs the configured provider list using only providers included in the executable. +An omitted provider's services remain unavailable. +A lookup either returns `null` when the provider is absent from the reconstructed list or reports the missing-registration error from section 1 when it attempts reflective loading. + +Exhausting the configured provider list must not fail merely because no class-path `META-INF/services/java.security.Provider` descriptor is present. +An absent descriptor does not make an omitted provider or its services available. diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SecurityProvidersSupport.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SecurityProvidersSupport.java index 10a47f554337..14ffe1f7ecd7 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SecurityProvidersSupport.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SecurityProvidersSupport.java @@ -53,10 +53,65 @@ import sun.security.util.Debug; /** - * The class that holds various build-time and run-time structures necessary for security providers - * (see the - * JCA Security Services documentation for details). + * AR-security-providers: Security Provider Architecture + * + * This class holds the build-time and run-time structures for JCA security-provider inclusion, + * verification, and metadata tracing. The required behavior is specified separately by + * §FS-security-providers. + * See the + * JCA Security Services documentation for the user-facing configuration model. + * + * ## 1. Build-Time Inclusion and Verification + * + * {@code SecurityServicesFeature} coordinates two analysis inputs: subtype reachability discovers + * candidate {@link Provider} classes, and JCA factory reachability discovers used service types. + * For a provider candidate, the feature queries the reflection registry for type, constructor, or + * factory-method registration. It instantiates accepted candidates through the declared nullary + * constructor or static {@code provider()} method, then registers their service implementation + * classes. The service-driven path calls the same service-registration machinery independently. + * These mechanisms implement §FS-security-providers and §FS-security-providers.1. + * + * During analysis, the feature obtains each included provider's JCE verification result and stores + * it in this image singleton, keyed by provider class name and provider name. The feature removes + * those entries from the JDK's object-keyed cache so build-time provider instances do not remain + * reachable in the image heap. + * + * ## 2. Run-Time Verification-Result Lookup + * + * The {@code javax.crypto.JceSecurity} substitutions consult the maps in this singleton when the + * JDK verification cache has no entry. {@link Boolean#TRUE} encodes successful verification; an + * exception object encodes the original verification failure. This lets run-time JCE checks reuse + * the build-time result without retaining the provider instance or repeating JAR verification. + * + * ## 3. Type and Constructor Tracing + * + * The metadata tracer represents provider loading as two distinct accesses: a dynamic + * {@link Class#forName(String)} lookup records the type, and constructor access records how the + * provider is instantiated. Because the JCE lookup already has a provider instance, + * {@link #traceProviderLookup(Provider)} emits the constructor access directly instead of creating + * another provider. This is the native-image counterpart of the Tracing Agent's provider event. + * + * On a verification-result cache miss, {@link #reportMissingProviderRegistration(Class)} performs + * an opaque, non-initializing {@code Class.forName} lookup using the provider's class loader. The + * opaque name prevents image-build analysis from removing the probe. The lookup enters the regular + * missing-reflection-registration machinery required by + * §FS-security-providers.3. A successful lookup without a verification result is + * an internal invariant violation. + * + * ## 4. Run-Time Provider Construction + * + * With run-time provider initialization, the {@code ProviderConfig} substitutions ask this class + * to construct included JDK providers directly. Other configured providers follow the JDK's + * reflective loading path. The substitutions preserve the JDK's provider-list state, recursion + * guard, and retry counter, while the verification maps remain independent of provider creation. + * + * ## 5. Concurrent Analysis Registration + * + * Provider subtype callbacks add candidates to a concurrent set and mark it changed. A serialized + * security-services analysis pass consumes new candidates and requests another analysis iteration + * when processing registers new reflection or JNI metadata. The callbacks do not request analysis + * iterations themselves, so concurrent discovery cannot race with iteration scheduling or lose + * registrations pending for a later iteration. */ @SingletonTraits(access = AllAccess.class, layeredCallbacks = NoLayeredCallbacks.class, layeredInstallationKind = Duplicable.class, other = PartiallyLayerAware.class) public final class SecurityProvidersSupport { @@ -154,6 +209,7 @@ public static String getBuiltInProviderClassName(String provName) { }; } + /** §AR-security-providers.3: Cache misses probe type access for standard diagnostics. */ public static void reportMissingProviderRegistration(Class providerClass) { try { Class.forName(GraalDirectives.opaque(providerClass.getName()), false, providerClass.getClassLoader()); @@ -163,6 +219,7 @@ public static void reportMissingProviderRegistration(Class providerClass) { throw VMError.shouldNotReachHere("A security provider without a verification result was registered for reflection: " + providerClass.getName()); } + /** §AR-security-providers.3: Existing providers trace constructor access directly. */ public static Provider traceProviderLookup(Provider provider) { if (provider == null) { return null; diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SecuritySubstitutions.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SecuritySubstitutions.java index a36b2c02a724..53e9c08e4136 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SecuritySubstitutions.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SecuritySubstitutions.java @@ -282,11 +282,8 @@ static Exception getVerificationResult(Provider p) { } else if (o != null) { return (Exception) o; } - /* - * A provider without a verification result was not included by reflection metadata. - * Trigger the regular Class.forName missing-registration path so diagnostics and metadata - * tracing handle this like any other missing reflection access. - */ + /* §AR-security-providers.3: Probe the missing type through the ordinary reflection path; + * report an inconsistent success. */ SecurityProvidersSupport.reportMissingProviderRegistration(p.getClass()); throw VMError.shouldNotReachHere("Security provider reflection access unexpectedly succeeded: " + p.getClass().getName()); } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/runtimeinit/SecuritySubstitutionRuntimeInit.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/runtimeinit/SecuritySubstitutionRuntimeInit.java index 84153131a02b..5eb35f3970d5 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/runtimeinit/SecuritySubstitutionRuntimeInit.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/runtimeinit/SecuritySubstitutionRuntimeInit.java @@ -111,11 +111,8 @@ static Exception getVerificationResult(Provider p) { } else if (o != null) { return (Exception) o; } - /* - * A provider without a verification result was not included by reflection metadata. - * Trigger the regular Class.forName missing-registration path so diagnostics and metadata - * tracing handle this like any other missing reflection access. - */ + /* §AR-security-providers.3: Probe the missing type through the ordinary reflection path; + * report an inconsistent success. */ SecurityProvidersSupport.reportMissingProviderRegistration(p.getClass()); throw VMError.shouldNotReachHere("Security provider reflection access unexpectedly succeeded: " + p.getClass().getName()); } From 5d3d0f94ada00513bee5bf1ffe5c7504e8763d9a Mon Sep 17 00:00:00 2001 From: Vojin Jovanovic Date: Tue, 21 Jul 2026 14:30:11 +0200 Subject: [PATCH 25/63] [GR-69858] Fix security provider future default --- .../src/com/oracle/svm/core/FutureDefaultsOptions.java | 2 +- .../src/com/oracle/svm/core/doc-files/FutureDefaultsHelp.txt | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/FutureDefaultsOptions.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/FutureDefaultsOptions.java index 05dcb7b02ea1..c565a65457a4 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/FutureDefaultsOptions.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/FutureDefaultsOptions.java @@ -91,7 +91,7 @@ public class FutureDefaultsOptions { public static final String EXPLICIT_FEATURE_SINGLETON_REGISTRATION = "explicit-feature-singleton-registration"; private static final String EXPLICIT_SECURITY_PROVIDER_REGISTRATION = "explicit-security-provider-registration"; private static final List ALL_FUTURE_DEFAULTS = List.of(CLASS_FOR_NAME_RESPECTS_CLASS_LOADER, EXACT_REFLECTION, RUN_TIME_INITIALIZE_FILE_SYSTEM_PROVIDERS, - RUN_TIME_INITIALIZE_RESOURCE_BUNDLES, EXPLICIT_FEATURE_SINGLETON_REGISTRATION, EXPLICIT_SECURITY_PROVIDER_REGISTRATION); + RUN_TIME_INITIALIZE_SECURITY_PROVIDERS, RUN_TIME_INITIALIZE_RESOURCE_BUNDLES, EXPLICIT_FEATURE_SINGLETON_REGISTRATION, EXPLICIT_SECURITY_PROVIDER_REGISTRATION); private static final String COMPLETE_REFLECTION_TYPES = "complete-reflection-types"; private static final List RETIRED_FUTURE_DEFAULTS = List.of(COMPLETE_REFLECTION_TYPES); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/doc-files/FutureDefaultsHelp.txt b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/doc-files/FutureDefaultsHelp.txt index 09e72ced5af6..64c0488e865c 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/doc-files/FutureDefaultsHelp.txt +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/doc-files/FutureDefaultsHelp.txt @@ -20,5 +20,3 @@ The meaning of each possible option is as follows: 'explicit-feature-singleton-registration' - stops automatically publishing user features reached from '--features' as 'ImageSingletons'. If code needs feature-singleton lookup compatibility, the feature must register its feature object explicitly. 'explicit-security-provider-registration' - requires reflection metadata to include a security provider and its services. Reachable security-service factory methods do not include providers without reflection metadata. - - 'explicit-security-provider-registration' - requires reflection metadata to include a security provider and its services. Reachable security-service factory methods do not include providers without reflection metadata. From 9c4662d6c22a7848b98307254d8146bed199ecec Mon Sep 17 00:00:00 2001 From: Vojin Jovanovic Date: Tue, 21 Jul 2026 16:47:56 +0200 Subject: [PATCH 26/63] [GR-69858] Migrate security provider tests to metadata --- substratevm/docs/functional-spec/security-providers.md | 3 ++- substratevm/mx.substratevm/mx_substratevm.py | 1 - .../com.oracle.svm.test/reachability-metadata.json | 9 +++++++++ 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/substratevm/docs/functional-spec/security-providers.md b/substratevm/docs/functional-spec/security-providers.md index 8ad03166a5b5..2dbb40b2e968 100644 --- a/substratevm/docs/functional-spec/security-providers.md +++ b/substratevm/docs/functional-spec/security-providers.md @@ -38,7 +38,8 @@ A direct lookup that attempts to load the omitted provider follows the missing-r ## 3. Programmatically Supplied Providers An application can construct a provider and pass it directly to a JCA factory or add it with `Security.addProvider(Provider)`. -The provider's class still requires the metadata listed in section 1 before JCE can verify and use its services. +Although the application constructs the provider directly at run time, its class still requires the metadata listed in section 1. +The metadata tells Native Image to include the provider's services, verify the provider at build time, and retain the verification result for run-time instances. If the metadata is present, provider-name lookups and factory calls using either the provider object or its name can use the provider's services. If it is absent and exact reachability metadata checking is enabled, the first operation that requires JCE verification reports `MissingReflectionRegistrationError` for the provider type. diff --git a/substratevm/mx.substratevm/mx_substratevm.py b/substratevm/mx.substratevm/mx_substratevm.py index d3f3e84fa90f..90192f146ebc 100644 --- a/substratevm/mx.substratevm/mx_substratevm.py +++ b/substratevm/mx.substratevm/mx_substratevm.py @@ -820,7 +820,6 @@ def write_micronaut_style_service_entries(cp_entry, service_name, implementation '--add-exports=org.graalvm.nativeimage.builder/com.oracle.svm.core.libjvm=ALL-UNNAMED', '--add-exports=org.graalvm.nativeimage.builder/com.oracle.svm.core.properties=ALL-UNNAMED', '--add-opens=org.graalvm.nativeimage.builder/com.oracle.svm.core.jdk=ALL-UNNAMED', - '-H:AdditionalSecurityProviders=com.oracle.svm.test.services.SecurityServiceTest$NoOpProvider,sun.security.pkcs11.SunPKCS11', '-H:AdditionalSecurityServiceTypes=com.oracle.svm.test.services.SecurityServiceTest$JCACompliantNoOpService', ]) if extra_build_args is not None: diff --git a/substratevm/src/com.oracle.svm.test/src/META-INF/native-image/com.oracle.svm.test/reachability-metadata.json b/substratevm/src/com.oracle.svm.test/src/META-INF/native-image/com.oracle.svm.test/reachability-metadata.json index 0ed080789a31..d3e56a8692ce 100644 --- a/substratevm/src/com.oracle.svm.test/src/META-INF/native-image/com.oracle.svm.test/reachability-metadata.json +++ b/substratevm/src/com.oracle.svm.test/src/META-INF/native-image/com.oracle.svm.test/reachability-metadata.json @@ -11,6 +11,15 @@ }, { "type": "com.oracle.svm.test.services.SecurityServiceTest$TypeMetadataProvider" + }, + { + "type": "com.oracle.svm.test.services.SecurityServiceTest$NoOpProvider", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] } ] } From de7f7b17d976a2480cf3385fa7d90f6327da0ff6 Mon Sep 17 00:00:00 2001 From: Vojin Jovanovic Date: Tue, 21 Jul 2026 20:21:56 +0200 Subject: [PATCH 27/63] [GR-69858] Specify security provider behavior --- .../functional-spec/security-providers.md | 165 ++++++++++++++---- .../core/jdk/SecurityProvidersSupport.java | 4 +- 2 files changed, 133 insertions(+), 36 deletions(-) diff --git a/substratevm/docs/functional-spec/security-providers.md b/substratevm/docs/functional-spec/security-providers.md index 2dbb40b2e968..fc28ecdc6510 100644 --- a/substratevm/docs/functional-spec/security-providers.md +++ b/substratevm/docs/functional-spec/security-providers.md @@ -1,55 +1,152 @@ # FS-security-providers: JCA Security Provider Inclusion -The set of security providers and services available in a native executable is determined when the executable is built. -At run time, `Security` lookups and Java Cryptography Architecture (JCA) factory calls expose only the providers and services included by the rules below. +Native Image determines at build time which security provider classes and service implementations a native executable can use. +Including a provider does not by itself place the provider in the run-time provider list. +A provider is exposed by name only when it is included and is either present in the configured provider list or successfully added with `Security.addProvider(Provider)`. +An included provider can also be used through a Java Cryptography Architecture (JCA) factory overload that accepts a provider object, subject to the verification requirements below. -## 1. Provider Metadata and Lookup Results +## 1. Provider States and Common Rules -Registering a provider class for reflection through any of the following metadata includes the provider and all of its services: +### 1.1 Included Providers and Services + +An **included provider** is a provider whose class and supported services are retained in the native executable. +An **included service** is a service whose implementation and required reflective construction metadata are retained in the native executable. +Native Image includes providers and services according to sections 2 and 3. + +Inclusion is a build-time property. +Constructing a provider object at run time does not add omitted provider services to the executable. + +### 1.2 Run-Time Provider List + +The **run-time provider list** contains included providers selected from the configured security-provider list and reflects subsequent changes made through the standard `Security` API. +Filtering omitted providers must preserve the relative order of the remaining configured providers. +Including a provider through metadata does not insert a provider that is absent from the configured list. + +### 1.3 Service Availability + +A service is **available** when its implementation is included and the corresponding factory call can select its provider. +A name-based call can select only a provider in the run-time provider list. +A provider-object call can select the supplied included provider without adding it to that list. +Algorithm aliases and provider selection otherwise follow the standard JCA API behavior. + +## 2. Explicit Provider Inclusion + +### 2.1 Qualifying Reflection Metadata + +Registering any of the following reflection access for an eligible provider class includes that provider: - access to the provider type; - access to its declared nullary constructor; or -- access to its public static `provider()` method. +- access to its declared public static nullary `provider()` method whose return type is assignable to `Provider`. + +These alternatives are inclusion signals. +The provider class must still satisfy the construction requirements in section 2.2 regardless of which signal is registered. + +### 2.2 Eligible Provider Classes + +An eligible provider class is a concrete `Provider` subtype that Native Image can construct using either its declared nullary constructor or the `provider()` method described in section 2.1. +A declared nullary constructor does not have to be public. + +When both construction paths exist, Native Image uses the declared nullary constructor. + +### 2.3 Inclusion Effects + +Explicitly including an eligible provider includes each valid service declared by the provider whose implementation class Native Image can resolve. +JCA factory calls can use those services subject to the service-availability rules in section 1.3. +Provider metadata does not change the configured provider order or make an unconfigured provider visible by name. + +A provider registered through `provider()` must expose the same services through `Security` lookups and JCA factory calls as it exposed when inspected at build time. + +## 3. Service-Driven Inclusion + +### 3.1 Default Compatibility Behavior + +By default, reachability of a JCA service factory or JDK security-service facade can include services of the corresponding service type without reflection metadata for their providers. +This compatibility behavior applies to supported facades such as the Generic Security Services API (GSS-API). + +For example, reachability of a `Signature.getInstance` overload can cause signature services and their providers to be included. +This rule is based on reachability of the service factory and service type; it does not imply build-time evaluation of the run-time algorithm argument. + +### 3.2 Explicit Provider Registration Future Default + +With `--future-defaults=explicit-security-provider-registration`, service-factory or facade reachability alone does not include a provider or its services. +The provider must have one of the reflection registrations in section 2.1. +Without that metadata, a factory call for an algorithm supplied only by the omitted provider reports that the algorithm is unavailable as specified in section 4.2. +A lookup that reflectively loads the omitted provider follows section 4.3. + +## 4. Lookups and Errors + +### 4.1 Provider List Lookups + +`Security.getProvider(String)` returns an included provider when that provider is in the run-time provider list. +`Security.getProviders()` contains the same provider and preserves the list ordering described in section 1.2. + +If a provider is not in the run-time provider list and the lookup does not attempt to load it reflectively, `Security.getProvider(String)` returns `null`. + +### 4.2 Factory Call Results + +JCA factory calls retain their standard distinction between a missing provider and a missing algorithm: + +- a factory overload given the name of a provider that is not in the run-time provider list reports `NoSuchProviderException`; +- a factory call that can select a provider but cannot find an included implementation for the requested algorithm reports `NoSuchAlgorithmException`; and +- a factory overload given a provider object follows section 5.1 instead of requiring that provider to be in the run-time provider list. + +These results apply when no missing reflection registration is encountered first. + +### 4.3 Missing Reflection Registration + +When exact reachability metadata checking is enabled, an operation that reflectively loads an omitted provider reports `MissingReflectionRegistrationError` for the provider type. +Native Image must not replace that error with `NoSuchProviderException`, `NoSuchAlgorithmException`, or another security-provider-specific exception. + +This requirement applies both to loading a provider from the configured provider list and to JCE verification of a programmatically supplied provider. +Without exact reachability metadata checking, this specification does not guarantee a particular missing-registration diagnostic. + +## 5. Programmatically Supplied Providers + +### 5.1 Provider-Object Factory Calls + +An application can construct a provider and pass it directly to a JCA factory. +Direct construction does not waive the inclusion requirements in section 2. +If the provider is included, the factory can use its included services without the provider being in the run-time provider list. + +If the provider is omitted and the operation requires Java Cryptography Extension (JCE) verification, the operation follows the missing-registration behavior in section 4.3. -If an included provider is present in the configured security-provider list, `Security.getProvider(String)` returns it and `Security.getProviders()` contains it. -JCA factory calls can use the services declared by that provider, subject to their normal algorithm and provider arguments. +### 5.2 Programmatic Provider-List Changes -If exact reachability metadata checking is enabled, an operation that attempts to load an omitted provider reflectively reports `MissingReflectionRegistrationError` for the provider type. -Native Image must not replace that error with a security-provider-specific exception. +An application can call `Security.addProvider(Provider)` or `Security.insertProviderAt(Provider, int)` with a constructed provider. +Insertion does not include an omitted provider or any of its services. +After successful insertion of an included provider, provider-name lookups and name-based factory calls can select its included services. +Removal with `Security.removeProvider(String)` makes the provider unavailable to subsequent name-based lookups without affecting provider objects already held by the application. -If no reflective provider access occurs, an omitted provider remains unavailable through the normal JCA API results. -`Security.getProvider(String)` returns `null` when the provider is absent from the provider list, and a factory call for an unavailable provider or algorithm reports `NoSuchProviderException` or `NoSuchAlgorithmException`, as appropriate. +Adding a provider does not itself require JCE verification. +The first subsequent operation that requires JCE verification follows section 5.3. +Provider position, duplicate-name handling, insertion return values, and removal otherwise follow the standard `Security` API behavior. -## 2. Service Factory Calls +### 5.3 JCE Verification -By default, a reachable JCA service factory or JDK security-service facade, including GSS, can include the providers and services needed by that call even when the provider has no reflection metadata. -For example, a reachable `Signature.getInstance(String)` call can make a matching signature implementation available without separately registering its provider. +Native Image must preserve the JCE verification outcome established for every included provider and apply it to run-time instances of that provider class. +A run-time instance must receive the same outcome when its provider name differs from the name observed at build time. -A provider registered through a static `provider()` method has the same observable services as a provider registered through its constructor. -Metadata collected by tracing must preserve a loading path that makes subsequent `Security.getProvider(String)` and JCA factory calls behave the same way. +An operation that requires JCE verification of an omitted provider follows section 4.3. +Provider services that do not require JCE verification remain subject to the inclusion and availability rules in sections 1 and 2. -### 2.1 Explicit Provider Registration Future Default +## 6. Tracing Metadata -With `--future-defaults=explicit-security-provider-registration`, service-factory reachability alone does not include a provider or its services. -The provider must have one of the reflection registrations listed in section 1. -Without that metadata, a factory call for an algorithm supplied only by that provider reports that the algorithm is unavailable. -A direct lookup that attempts to load the omitted provider follows the missing-registration behavior in section 1. +Metadata collected by the Tracing Agent or native metadata tracing from a successful provider lookup must be sufficient for a subsequently built native executable to perform the same lookup and use the same provider services without additional provider metadata. +The collected metadata must retain a supported construction path: declared nullary constructor access or access to the static `provider()` method. -## 3. Programmatically Supplied Providers +Tracing a missing provider registration must use the ordinary reflection metadata format and diagnostics. +It must not introduce a security-provider-specific metadata category or error. -An application can construct a provider and pass it directly to a JCA factory or add it with `Security.addProvider(Provider)`. -Although the application constructs the provider directly at run time, its class still requires the metadata listed in section 1. -The metadata tells Native Image to include the provider's services, verify the provider at build time, and retain the verification result for run-time instances. +## 7. Run-Time Provider Initialization -If the metadata is present, provider-name lookups and factory calls using either the provider object or its name can use the provider's services. -If it is absent and exact reachability metadata checking is enabled, the first operation that requires JCE verification reports `MissingReflectionRegistrationError` for the provider type. -The error and tracing behavior must be the same as for other missing reflective type access. +### 7.1 Provider List Construction -## 4. Run-Time Provider Initialization +With `--future-defaults=run-time-initialize-security-providers`, Native Image initializes the run-time provider list from the configured security properties using only included providers. +An omitted provider is not added to the list, and its services remain unavailable. +Filtering omitted providers preserves the ordering and standard lookup results specified in sections 1.2 and 4. -When security providers are initialized at run time, Native Image reconstructs the configured provider list using only providers included in the executable. -An omitted provider's services remain unavailable. -A lookup either returns `null` when the provider is absent from the reconstructed list or reports the missing-registration error from section 1 when it attempts reflective loading. +### 7.2 Provider Service Descriptors -Exhausting the configured provider list must not fail merely because no class-path `META-INF/services/java.security.Provider` descriptor is present. -An absent descriptor does not make an omitted provider or its services available. +A class-path _META-INF/services/java.security.Provider_ descriptor does not by itself include the named provider. +If the provider is omitted, iterating to its descriptor can report the standard `ServiceConfigurationError` or missing-reflection error, and the provider's services remain unavailable. diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SecurityProvidersSupport.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SecurityProvidersSupport.java index 14ffe1f7ecd7..0f9abb1550c5 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SecurityProvidersSupport.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SecurityProvidersSupport.java @@ -69,7 +69,7 @@ * factory-method registration. It instantiates accepted candidates through the declared nullary * constructor or static {@code provider()} method, then registers their service implementation * classes. The service-driven path calls the same service-registration machinery independently. - * These mechanisms implement §FS-security-providers and §FS-security-providers.1. + * These mechanisms implement §FS-security-providers.2 and §FS-security-providers.3. * * During analysis, the feature obtains each included provider's JCE verification result and stores * it in this image singleton, keyed by provider class name and provider name. The feature removes @@ -95,7 +95,7 @@ * an opaque, non-initializing {@code Class.forName} lookup using the provider's class loader. The * opaque name prevents image-build analysis from removing the probe. The lookup enters the regular * missing-reflection-registration machinery required by - * §FS-security-providers.3. A successful lookup without a verification result is + * §FS-security-providers.5.3. A successful lookup without a verification result is * an internal invariant violation. * * ## 4. Run-Time Provider Construction From 363f77b86c7f1a5ecc69190a09f5122dbd4491c5 Mon Sep 17 00:00:00 2001 From: Vojin Jovanovic Date: Wed, 22 Jul 2026 15:55:14 +0200 Subject: [PATCH 28/63] [GR-69858] Tighten security provider specification --- .../functional-spec/security-providers.md | 383 ++++++++++++++---- .../.checkstyle_checks.xml | 4 +- .../core/jdk/SecurityProvidersSupport.java | 2 +- .../svm/core/jdk/SecuritySubstitutions.java | 15 + 4 files changed, 311 insertions(+), 93 deletions(-) diff --git a/substratevm/docs/functional-spec/security-providers.md b/substratevm/docs/functional-spec/security-providers.md index fc28ecdc6510..d856dbcdcbde 100644 --- a/substratevm/docs/functional-spec/security-providers.md +++ b/substratevm/docs/functional-spec/security-providers.md @@ -1,152 +1,355 @@ -# FS-security-providers: JCA Security Provider Inclusion - -Native Image determines at build time which security provider classes and service implementations a native executable can use. -Including a provider does not by itself place the provider in the run-time provider list. -A provider is exposed by name only when it is included and is either present in the configured provider list or successfully added with `Security.addProvider(Provider)`. -An included provider can also be used through a Java Cryptography Architecture (JCA) factory overload that accepts a provider object, subject to the verification requirements below. - -## 1. Provider States and Common Rules - -### 1.1 Included Providers and Services - -An **included provider** is a provider whose class and supported services are retained in the native executable. -An **included service** is a service whose implementation and required reflective construction metadata are retained in the native executable. -Native Image includes providers and services according to sections 2 and 3. +# FS-security-providers: JCA Security Provider Registration and Run-Time Access + +This specification defines the Native Image behavior for Java Cryptography Architecture +(JCA) security providers. +Under this behavior, the JDK can acquire or select a provider for JCA at run time only if the +provider class was *registered for reflection* at build time. +With `--exact-reachability-metadata`, attempted reflective acquisition of an unregistered provider +reports an error that identifies the missing provider type. +Native Image constructs the provider list at run time from the configured security properties and +the registered provider classes. + +Reflection registration determines which provider classes and services the native executable +contains. +It does not by itself add a provider to the run-time provider list. +Section 7 defines the future-default options that select this behavior during the transition from +service-driven provider inclusion and build-time provider-list initialization. + +## 1. Provider Reflection Registration + +### 1.1 Registered Providers and Services + +A provider class is **registered for reflection** for the purposes of this specification when all +the following conditions hold: + +- the class is a concrete `Provider` subtype; +- Native Image can construct it through either a declared nullary constructor or a declared public + static nullary `provider()` method whose return type is assignable to `Provider`; and +- reflection metadata registers access to at least one of the provider type, its declared nullary + constructor, or its qualifying `provider()` method. + +A **registered provider** is a provider whose class satisfies this definition. +A **registered service** is a valid service declared by a registered provider whose implementation +class and required reflective construction metadata Native Image can retain in the executable. + +Registration is a build-time property. +Constructing a provider object at run time does not register the provider or add omitted services +to the executable. + +### 1.2 JDK-Managed Providers and Acquisition + +A **JDK-managed provider** is a provider instance that the JDK creates or discovers instead of an +instance that application code constructs and supplies to the JDK. +JDK-managed acquisition paths include loading providers from the configured security properties, +discovering them through a service-provider descriptor, selecting them through a JCA factory or +security-service facade, and creating them through a JDK default or fallback path. + +The public JDK API surface covered by this definition includes: + +- provider lookup, enumeration, filtering, and algorithm discovery through + `Provider Security.getProvider(String)`, `Provider[] Security.getProviders()`, + `Provider[] Security.getProviders(String)`, + `Provider[] Security.getProviders(Map)`, and + `Set Security.getAlgorithms(String)`; +- provider-service lookup and instantiation through + `Provider.Service Provider.getService(String, String)`, + `Set Provider.getServices()`, and + `Object Provider.Service.newInstance(Object)`; +- JCA factory selection, for example through `Signature Signature.getInstance(String)`, + `Signature Signature.getInstance(String, String)`, and + `Signature Signature.getInstance(String, Provider)`, together with provider exposure through + `Provider Signature.getProvider()`; +- security-service facades, including `GSSManager GSSManager.getInstance()`, + `SaslClient Sasl.createSaslClient(String[], String, String, String, Map, CallbackHandler)`, + and + `SaslServer Sasl.createSaslServer(String, String, String, Map, CallbackHandler)`; and +- service discovery through `ServiceLoader ServiceLoader.load(Class)` and default + construction through `SecureRandom()`. + +This list identifies the principal public entry points but does not limit the acquisition rule to +those methods. +Other JCA engine factory overloads, provider-exposing engine methods, and internal paths that +implement these APIs are subject to the same rule. + +The JDK **acquires** a provider when a JDK API or implementation path does any of the following: + +- returns the provider or one of its `Provider.Service` objects to application code; +- selects the provider to back a JCA engine or security-service facade; or +- reports the provider or one of its algorithms as available. + +This definition covers public APIs and the internal JDK paths that implement them. +It does not treat an application constructing its own provider object through a statically resolved +constructor as JDK acquisition. +Reflective provider loading and construction remain subject to reflection registration and section +4.3. +Section 5 specifies the narrower operations allowed for such application-supplied objects. + +### 1.3 Run-Time Provider List + +The **run-time provider list** contains registered providers selected from the configured security +properties and reflects subsequent changes made through the standard `Security` API. +Filtering unregistered providers must preserve the relative order of the remaining configured +providers. +Registering a provider for reflection does not insert a provider that is absent from the configured +list. + +### 1.4 Service Availability + +A service is **available** when it is registered and the corresponding factory call can select its +provider. +A name-based call can select only a provider in the run-time provider list. +A provider-object call can select a supplied registered provider without adding it to that list. +Algorithm aliases and provider selection otherwise follow the standard JCA API behavior. -Inclusion is a build-time property. -Constructing a provider object at run time does not add omitted provider services to the executable. +## 2. Registration Semantics -### 1.2 Run-Time Provider List +### 2.1 Qualifying Reflection Metadata -The **run-time provider list** contains included providers selected from the configured security-provider list and reflects subsequent changes made through the standard `Security` API. -Filtering omitted providers must preserve the relative order of the remaining configured providers. -Including a provider through metadata does not insert a provider that is absent from the configured list. +Type access, declared nullary constructor access, and qualifying `provider()` method access are +alternative registration signals. +Registering any one of them is sufficient if the provider class meets all construction requirements +in section 1.1. +Registering a signal does not relax those construction requirements. -### 1.3 Service Availability +### 2.2 Provider Construction -A service is **available** when its implementation is included and the corresponding factory call can select its provider. -A name-based call can select only a provider in the run-time provider list. -A provider-object call can select the supplied included provider without adding it to that list. -Algorithm aliases and provider selection otherwise follow the standard JCA API behavior. +A declared nullary constructor does not have to be public. +When both supported construction paths exist, Native Image uses the declared nullary constructor. -## 2. Explicit Provider Inclusion +A provider registered through `provider()` must expose the same services through `Security` +lookups and JCA factory calls as it exposed when Native Image inspected it at build time. + +### 2.3 Registration Effects -### 2.1 Qualifying Reflection Metadata +Registering a provider includes every valid service declared by the provider whose implementation +class Native Image can resolve. +It must also retain the metadata required to construct those service implementations. +Provider registration does not change the configured provider order or make an unconfigured +provider visible by name. -Registering any of the following reflection access for an eligible provider class includes that provider: +## 3. Permitted Run-Time Access -- access to the provider type; -- access to its declared nullary constructor; or -- access to its declared public static nullary `provider()` method whose return type is assignable to `Provider`. +### 3.1 JDK-Managed Acquisition -These alternatives are inclusion signals. -The provider class must still satisfy the construction requirements in section 2.2 regardless of which signal is registered. +Every JDK-managed provider acquired at run time must be a registered provider. +This rule applies uniformly to: -### 2.2 Eligible Provider Classes +- direct provider APIs, including provider enumeration, name lookup, filtering, and algorithm + discovery through the `Security` methods listed in section 1.2; +- reflective provider loading or construction by class name; +- JCA factories, the `Provider` and `Provider.Service` methods listed in section 1.2, and engine + objects that expose their selected provider; +- security-service facades that select provider services without using a JCA engine factory, + including GSS-API and SASL; and +- service loading, default selection, and fallback paths that construct a provider or service + implementation directly. -An eligible provider class is a concrete `Provider` subtype that Native Image can construct using either its declared nullary constructor or the `provider()` method described in section 2.1. -A declared nullary constructor does not have to be public. +A direct JDK fallback must not bypass registration when the configured provider list contains no +matching registered provider. +For example, a default `SecureRandom` construction must not expose a fallback SUN provider unless +the SUN provider class is registered. -When both construction paths exist, Native Image uses the declared nullary constructor. +### 3.2 Provider List Lookups -### 2.3 Inclusion Effects +`Provider Security.getProvider(String)` returns a registered provider when that provider is in the +run-time provider list. +`Provider[] Security.getProviders()` contains the same provider and preserves the ordering +described in section 1.3. +`Provider[] Security.getProviders(String)` and +`Provider[] Security.getProviders(Map)` can return only registered providers from +that list. +`Set Security.getAlgorithms(String)` can report an algorithm only when at least one +registered provider in that list declares it. -Explicitly including an eligible provider includes each valid service declared by the provider whose implementation class Native Image can resolve. -JCA factory calls can use those services subject to the service-availability rules in section 1.3. -Provider metadata does not change the configured provider order or make an unconfigured provider visible by name. +If a provider is not in the run-time provider list and the lookup does not attempt to load it +reflectively, `Provider Security.getProvider(String)` returns `null`. -A provider registered through `provider()` must expose the same services through `Security` lookups and JCA factory calls as it exposed when inspected at build time. +### 3.3 JCA Factories and Security-Service Facades -## 3. Service-Driven Inclusion +A name-based JCA factory call can use the registered services of a provider in the run-time provider +list. +A factory overload that accepts a provider object can use the registered services of that provider +without requiring it to be in the list. -### 3.1 Default Compatibility Behavior +A factory call can use only registered service implementations. +It does not evaluate a run-time algorithm argument at build time or make additional providers or +services available. -By default, reachability of a JCA service factory or JDK security-service facade can include services of the corresponding service type without reflection metadata for their providers. -This compatibility behavior applies to supported facades such as the Generic Security Services API (GSS-API). +The same registration requirement applies when a facade or a JDK implementation path selects a +provider service without calling a public JCA factory. -For example, reachability of a `Signature.getInstance` overload can cause signature services and their providers to be included. -This rule is based on reachability of the service factory and service type; it does not imply build-time evaluation of the run-time algorithm argument. +### 3.4 Programmatic Access -### 3.2 Explicit Provider Registration Future Default +An application can construct a registered provider and pass it directly to a JCA factory. +It can also add the provider to the run-time provider list through +`int Security.addProvider(Provider)` or `int Security.insertProviderAt(Provider, int)`. +Section 5 specifies these operations in detail. -With `--future-defaults=explicit-security-provider-registration`, service-factory or facade reachability alone does not include a provider or its services. -The provider must have one of the reflection registrations in section 2.1. -Without that metadata, a factory call for an algorithm supplied only by the omitted provider reports that the algorithm is unavailable as specified in section 4.2. -A lookup that reflectively loads the omitted provider follows section 4.3. +## 4. Prohibited Run-Time Access and Errors -## 4. Lookups and Errors +### 4.1 Unregistered Providers -### 4.1 Provider List Lookups +An unregistered JDK-managed provider must not be returned, selected, or reported as available at +run time. +The JDK must not expose it partially by returning the provider while omitting some services, +advertising its algorithms without allowing their use, returning a `Provider.Service` without a +usable implementation, or producing an engine or facade backed by that provider. -`Security.getProvider(String)` returns an included provider when that provider is in the run-time provider list. -`Security.getProviders()` contains the same provider and preserves the list ordering described in section 1.2. +Failure must occur at the acquisition boundary before application code receives a provider, +service, engine, or facade that represents the unregistered provider as available. +When sections 4.2 and 4.3 do not specify a standard result or missing-registration diagnostic, this +specification requires the operation to fail before exposure but does not prescribe the exception +type. +Reachability of a JCA service factory or JDK security-service facade must not make that provider or +its services available. +Neither a service-provider descriptor nor a JDK default or fallback implementation can register the +provider after the executable has been built. -If a provider is not in the run-time provider list and the lookup does not attempt to load it reflectively, `Security.getProvider(String)` returns `null`. +Application-supplied provider objects follow section 5 and do not relax these requirements for +JDK-managed providers. -### 4.2 Factory Call Results +### 4.2 Standard Unavailable Results -JCA factory calls retain their standard distinction between a missing provider and a missing algorithm: +JCA factory calls retain their standard distinction between a missing provider and a missing +algorithm: -- a factory overload given the name of a provider that is not in the run-time provider list reports `NoSuchProviderException`; -- a factory call that can select a provider but cannot find an included implementation for the requested algorithm reports `NoSuchAlgorithmException`; and -- a factory overload given a provider object follows section 5.1 instead of requiring that provider to be in the run-time provider list. +- a factory overload given the name of a provider that is not in the run-time provider list reports + `NoSuchProviderException`; +- a factory call that can select a provider but cannot find a registered implementation for the + requested algorithm reports `NoSuchAlgorithmException`; and +- a factory overload given a provider object follows section 5.1 instead of requiring that provider + to be in the run-time provider list. These results apply when no missing reflection registration is encountered first. ### 4.3 Missing Reflection Registration -When exact reachability metadata checking is enabled, an operation that reflectively loads an omitted provider reports `MissingReflectionRegistrationError` for the provider type. -Native Image must not replace that error with `NoSuchProviderException`, `NoSuchAlgorithmException`, or another security-provider-specific exception. +When exact reachability metadata checking is enabled, an operation that reflectively loads an +unregistered provider reports `MissingReflectionRegistrationError` for the provider type. +Native Image must not replace that error with `NoSuchProviderException`, +`NoSuchAlgorithmException`, or another security-provider-specific exception. -This requirement applies both to loading a provider from the configured provider list and to JCE verification of a programmatically supplied provider. -Without exact reachability metadata checking, this specification does not guarantee a particular missing-registration diagnostic. +This requirement applies both to loading a provider from the configured provider list and to Java +Cryptography Extension (JCE) verification of a programmatically supplied provider. +Without exact reachability metadata checking, this specification does not guarantee a particular +missing-registration diagnostic. ## 5. Programmatically Supplied Providers ### 5.1 Provider-Object Factory Calls An application can construct a provider and pass it directly to a JCA factory. -Direct construction does not waive the inclusion requirements in section 2. -If the provider is included, the factory can use its included services without the provider being in the run-time provider list. +Because the application already possesses this object, its construction and ordinary Java method +calls are not JDK-managed provider acquisition as defined in section 1.2. +Direct construction does not waive the registration requirements in section 1.1. +If the provider is registered, the factory can use its registered services without the provider +being in the run-time provider list. -If the provider is omitted and the operation requires Java Cryptography Extension (JCE) verification, the operation follows the missing-registration behavior in section 4.3. +If the provider is unregistered and the operation requires JCE verification, the operation follows +the missing-registration behavior in section 4.3. ### 5.2 Programmatic Provider-List Changes -An application can call `Security.addProvider(Provider)` or `Security.insertProviderAt(Provider, int)` with a constructed provider. -Insertion does not include an omitted provider or any of its services. -After successful insertion of an included provider, provider-name lookups and name-based factory calls can select its included services. -Removal with `Security.removeProvider(String)` makes the provider unavailable to subsequent name-based lookups without affecting provider objects already held by the application. +An application can call `int Security.addProvider(Provider)` or +`int Security.insertProviderAt(Provider, int)` with a constructed provider. +Insertion does not register an unregistered provider or any of its services. +The insertion can still make the supplied provider object retrievable from the run-time provider +list according to the standard `Security` API behavior, but JCA factory calls cannot use its +unregistered services. +This exception applies only to the same application-supplied object; it does not allow the JDK to +create, discover, or substitute an unregistered provider. +After successful insertion of a registered provider, provider-name lookups and name-based factory +calls can select its registered services. +Removal with `void Security.removeProvider(String)` makes the provider unavailable to subsequent +name-based lookups without affecting provider objects already held by the application. Adding a provider does not itself require JCE verification. The first subsequent operation that requires JCE verification follows section 5.3. -Provider position, duplicate-name handling, insertion return values, and removal otherwise follow the standard `Security` API behavior. +Provider position, duplicate-name handling, insertion return values, and removal otherwise follow +the standard `Security` API behavior. ### 5.3 JCE Verification -Native Image must preserve the JCE verification outcome established for every included provider and apply it to run-time instances of that provider class. -A run-time instance must receive the same outcome when its provider name differs from the name observed at build time. - -An operation that requires JCE verification of an omitted provider follows section 4.3. -Provider services that do not require JCE verification remain subject to the inclusion and availability rules in sections 1 and 2. +Before a registered provider supplies a service for which the JDK requires Java Cryptography +Extension (JCE) verification, Native Image must have established a verification outcome for that +provider at build time. +Registration is necessary for such an operation, but registration is not successful verification. + +Native Image must preserve the build-time verification outcome and apply it to run-time instances +of that provider class. +A run-time instance must receive the same outcome when its provider name differs from the name +observed at build time. +A failed verification outcome must prevent every run-time JCE operation that requires verification +from using the provider. +The operation must expose that failure through the standard JCE behavior of the invoked factory +overload; it must not return an engine backed by the provider or continue with a partially usable +provider. + +An operation that requires JCE verification of an unregistered provider follows section 4.3. +Provider services that do not require JCE verification remain subject to the registration and +availability rules in sections 1 and 2. ## 6. Tracing Metadata -Metadata collected by the Tracing Agent or native metadata tracing from a successful provider lookup must be sufficient for a subsequently built native executable to perform the same lookup and use the same provider services without additional provider metadata. -The collected metadata must retain a supported construction path: declared nullary constructor access or access to the static `provider()` method. +Metadata collected by the Tracing Agent or native metadata tracing from a successful provider +lookup must be sufficient for a subsequently built native executable to perform the same lookup +and use the same provider services without additional provider metadata. +The collected metadata must retain a supported construction path: declared nullary constructor +access or access to the static `provider()` method. -Tracing a missing provider registration must use the ordinary reflection metadata format and diagnostics. +Tracing a missing provider registration must use the ordinary reflection metadata format and +diagnostics. It must not introduce a security-provider-specific metadata category or error. -## 7. Run-Time Provider Initialization +## 7. Transition to the Future Defaults + +Sections 1 through 6 specify the planned default behavior. +The following options select its two independent parts while the earlier behaviors remain available +for compatibility. -### 7.1 Provider List Construction +### 7.1 Run-Time Provider-List Initialization -With `--future-defaults=run-time-initialize-security-providers`, Native Image initializes the run-time provider list from the configured security properties using only included providers. -An omitted provider is not added to the list, and its services remain unavailable. -Filtering omitted providers preserves the ordering and standard lookup results specified in sections 1.2 and 4. +With `--future-defaults=run-time-initialize-security-providers`, Native Image constructs the +run-time provider list from the configured security properties using only registered providers. +An unregistered provider is not added to the list, and its services remain unavailable. +Filtering unregistered providers preserves the ordering and lookup results specified in sections +1.3, 3.2, and 4. ### 7.2 Provider Service Descriptors -A class-path _META-INF/services/java.security.Provider_ descriptor does not by itself include the named provider. -If the provider is omitted, iterating to its descriptor can report the standard `ServiceConfigurationError` or missing-reflection error, and the provider's services remain unavailable. +A class-path _META-INF/services/java.security.Provider_ descriptor does not register the named +provider for reflection. +If the provider is unregistered, service loading must not return a provider instance. +Iterating to its descriptor can report the standard `ServiceConfigurationError` or +missing-reflection error, and the provider's services remain unavailable. + +### 7.3 Earlier Service-Driven Inclusion Behavior + +Without `--future-defaults=explicit-security-provider-registration`, reachability of a JCA service +factory or JDK security-service facade can include services of the corresponding service type even +when their provider classes have no reflection metadata. +This compatibility behavior applies to supported facades such as the Generic Security Services API +(GSS-API). + +For example, reachability of `Signature Signature.getInstance(String)`, +`Signature Signature.getInstance(String, String)`, or +`Signature Signature.getInstance(String, Provider)` can cause signature services and their +providers to be included. +This rule is based on reachability of the service factory and service type, not on build-time +evaluation of the run-time algorithm argument. + +With `--future-defaults=explicit-security-provider-registration`, this compatibility behavior is +disabled. +A factory call for an algorithm supplied only by an unregistered provider follows section 4.2, and +a lookup that reflectively loads the provider follows section 4.3. + +### 7.4 Earlier Build-Time Initialization Behavior + +`--future-defaults=run-time-initialize-security-providers` replaces the earlier behavior in which +Native Image initializes the configured provider list at build time. +During the transition, omitting this future default retains that earlier initialization behavior. +When explicit provider registration is enabled without run-time provider initialization, Native +Image must filter the build-time provider list before storing it in the executable so that it does +not expose an unregistered JDK-managed provider. +Provider and service availability is still determined at build time according to either the +explicit registration rules in sections 1 and 2 or the compatibility rule in section 7.3. diff --git a/substratevm/src/com.oracle.svm.core/.checkstyle_checks.xml b/substratevm/src/com.oracle.svm.core/.checkstyle_checks.xml index 998af95ad78a..f431a9dd81a3 100644 --- a/substratevm/src/com.oracle.svm.core/.checkstyle_checks.xml +++ b/substratevm/src/com.oracle.svm.core/.checkstyle_checks.xml @@ -265,8 +265,8 @@ - - + + diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SecurityProvidersSupport.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SecurityProvidersSupport.java index 0f9abb1550c5..1d39be715f57 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SecurityProvidersSupport.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SecurityProvidersSupport.java @@ -69,7 +69,7 @@ * factory-method registration. It instantiates accepted candidates through the declared nullary * constructor or static {@code provider()} method, then registers their service implementation * classes. The service-driven path calls the same service-registration machinery independently. - * These mechanisms implement §FS-security-providers.2 and §FS-security-providers.3. + * These mechanisms implement §FS-security-providers.2 and §FS-security-providers.7.3. * * During analysis, the feature obtains each included provider's JCE verification result and stores * it in this image singleton, keyed by provider class name and provider name. The feature removes diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SecuritySubstitutions.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SecuritySubstitutions.java index 53e9c08e4136..7bfe204f83b8 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SecuritySubstitutions.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SecuritySubstitutions.java @@ -47,6 +47,7 @@ import org.graalvm.nativeimage.hosted.FieldValueTransformer; import org.graalvm.nativeimage.impl.InternalPlatform; +import com.oracle.svm.core.FutureDefaultsOptions; import com.oracle.svm.core.annotate.Alias; import com.oracle.svm.core.annotate.InjectAccessors; import com.oracle.svm.core.annotate.RecomputeFieldValue; @@ -61,6 +62,20 @@ import sun.security.util.SecurityConstants; +// Reject an unregistered SUN provider before the JDK SecureRandom fallback can expose it. +// §FS-security-providers.3.1 and §FS-security-providers.4.1 +@TargetClass(className = "sun.security.jca.Providers") +final class Target_sun_security_jca_Providers_ExplicitRegistration { + @Substitute + public static Provider getSunProvider() { + if (Boolean.getBoolean(FutureDefaultsOptions.SYSTEM_PROPERTY_PREFIX + "explicit-security-provider-registration") && + !SecurityProvidersSupport.singleton().isSecurityProviderIncluded("SUN", "sun.security.provider.Sun")) { + SecurityProvidersSupport.reportMissingProviderRegistration(sun.security.provider.Sun.class); + } + return new sun.security.provider.Sun(); + } +} + /* * All security checks are disabled. */ From e988b12e5b2f041c4831a3522d2c63349add5061 Mon Sep 17 00:00:00 2001 From: Vojin Jovanovic Date: Thu, 23 Jul 2026 09:59:32 +0200 Subject: [PATCH 29/63] [GR-69858] Preserve complete default SecureRandom provider --- .../functional-spec/security-providers.md | 27 +++++++++- .../svm/core/RuntimeRandomnessFeature.java | 51 ------------------- .../core/SecureRandomRuntimeRandomness.java | 5 +- .../core/jdk/SecurityProvidersSupport.java | 2 + .../RuntimeCompilationFeature.java | 10 ++++ .../svm/hosted/SecurityServicesFeature.java | 23 ++++++--- ...rviceExplicitProviderRegistrationTest.java | 16 ++++++ 7 files changed, 71 insertions(+), 63 deletions(-) delete mode 100644 substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/RuntimeRandomnessFeature.java diff --git a/substratevm/docs/functional-spec/security-providers.md b/substratevm/docs/functional-spec/security-providers.md index d856dbcdcbde..50bb114d0a87 100644 --- a/substratevm/docs/functional-spec/security-providers.md +++ b/substratevm/docs/functional-spec/security-providers.md @@ -35,6 +35,7 @@ class and required reflective construction metadata Native Image can retain in t Registration is a build-time property. Constructing a provider object at run time does not register the provider or add omitted services to the executable. +Section 2.4 defines the one platform registration rule for default `SecureRandom` acquisition. ### 1.2 JDK-Managed Providers and Acquisition @@ -126,6 +127,25 @@ class Native Image can resolve. It must also retain the metadata required to construct those service implementations. Provider registration does not change the configured provider order or make an unconfigured provider visible by name. +This behavior implements §DF-complete-security-provider-registration.2. + +### 2.4 Default SecureRandom Provider + +When the JDK default `SecureRandom` acquisition path is reachable, Native Image must register the +complete fallback SUN provider without requiring application reflection metadata for that provider. +This registration has the effects specified in section 2.3, including retention of every valid SUN +service whose implementation class Native Image can resolve. + +This rule applies to the no-argument `SecureRandom` constructor and other JDK paths that perform the +same default-provider selection. It does not apply to named algorithm or named provider acquisition. +It is a platform registration rule, not the earlier service-driven inclusion behavior described in +section 7.3. + +Native Image internal runtime randomness must cause this registration only in an executable that +includes the runtime-compilation subsystem that consumes that randomness. The presence of the +optional internal randomness implementation must not register SUN in an ordinary executable. + +This behavior implements §DF-default-secure-random-provider.2. ## 3. Permitted Run-Time Access @@ -146,8 +166,8 @@ This rule applies uniformly to: A direct JDK fallback must not bypass registration when the configured provider list contains no matching registered provider. -For example, a default `SecureRandom` construction must not expose a fallback SUN provider unless -the SUN provider class is registered. +Default `SecureRandom` construction follows the platform registration rule in section 2.4; other +fallbacks must fail before exposing an unregistered provider. ### 3.2 Provider List Lookups @@ -342,6 +362,9 @@ With `--future-defaults=explicit-security-provider-registration`, this compatibi disabled. A factory call for an algorithm supplied only by an unregistered provider follows section 4.2, and a lookup that reflectively loads the provider follows section 4.3. +The default `SecureRandom` registration rule in section 2.4 remains enabled because it supplies the +complete provider for a standard JDK default-acquisition path rather than inferring providers from a +general service factory. ### 7.4 Earlier Build-Time Initialization Behavior diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/RuntimeRandomnessFeature.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/RuntimeRandomnessFeature.java deleted file mode 100644 index ebf4732828e4..000000000000 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/RuntimeRandomnessFeature.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright (c) 2023, 2026, Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - */ - -package com.oracle.svm.core; - -import org.graalvm.nativeimage.ImageSingletons; - -import com.oracle.svm.core.feature.InternalFeature; -import com.oracle.svm.core.imagelayer.ImageLayerBuildingSupport; -import com.oracle.svm.shared.feature.AutomaticallyRegisteredFeature; - -/** - * Feature to register a default {@link RuntimeRandomness} instance. If another component has - * already registered a {@link RuntimeRandomness} instance, this feature does nothing. - */ -@AutomaticallyRegisteredFeature -public class RuntimeRandomnessFeature implements InternalFeature { - @Override - public boolean isInConfiguration(IsInConfigurationAccess access) { - return ImageLayerBuildingSupport.firstImageBuild(); - } - - @Override - public void duringSetup(DuringSetupAccess access) { - if (!ImageSingletons.contains(RuntimeRandomness.class)) { - ImageSingletons.add(RuntimeRandomness.class, new SecureRandomRuntimeRandomness()); - } - } -} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SecureRandomRuntimeRandomness.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SecureRandomRuntimeRandomness.java index 7f8d803fd39c..56fa17354f8d 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SecureRandomRuntimeRandomness.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SecureRandomRuntimeRandomness.java @@ -40,9 +40,8 @@ /** * An image singleton that provides a random number generator that is initialized at runtime with a - * {@link SecureRandom} instance. (see {@link RuntimeRandomness#getRandom()}). This is the default - * implementation if no other {@link RuntimeRandomness} is registered (see - * {@link RuntimeRandomnessFeature}). + * {@link SecureRandom} instance. (see {@link RuntimeRandomness#getRandom()}). Runtime compilation + * registers this implementation if no other {@link RuntimeRandomness} is registered. */ @SingletonTraits(access = RuntimeAccessOnly.class, layeredCallbacks = SingleLayer.class, layeredInstallationKind = InitialLayerOnly.class) public class SecureRandomRuntimeRandomness implements RuntimeRandomness { diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SecurityProvidersSupport.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SecurityProvidersSupport.java index 1d39be715f57..cad6d52dc060 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SecurityProvidersSupport.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SecurityProvidersSupport.java @@ -70,6 +70,8 @@ * constructor or static {@code provider()} method, then registers their service implementation * classes. The service-driven path calls the same service-registration machinery independently. * These mechanisms implement §FS-security-providers.2 and §FS-security-providers.7.3. + * Default {@code SecureRandom} acquisition first registers the complete fallback SUN provider as + * the narrow platform exception specified by §FS-security-providers.2.4. * * During analysis, the feature obtains each included provider's JCE verification result and stores * it in this image singleton, keyed by provider class name and provider name. The feature removes diff --git a/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/hosted/runtimecompilation/RuntimeCompilationFeature.java b/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/hosted/runtimecompilation/RuntimeCompilationFeature.java index daebb5fceb56..368d8c51890c 100644 --- a/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/hosted/runtimecompilation/RuntimeCompilationFeature.java +++ b/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/hosted/runtimecompilation/RuntimeCompilationFeature.java @@ -66,6 +66,8 @@ import com.oracle.graal.pointsto.util.ParallelExecutionException; import com.oracle.svm.common.meta.MethodVariant; import com.oracle.svm.core.ParsingReason; +import com.oracle.svm.core.RuntimeRandomness; +import com.oracle.svm.core.SecureRandomRuntimeRandomness; import com.oracle.svm.core.SubstrateOptions; import com.oracle.svm.core.SubstrateTarget; import com.oracle.svm.core.graal.RuntimeCompilation; @@ -415,6 +417,14 @@ public void duringSetup(DuringSetupAccess c) { if (SubstrateOptions.useLLVMBackend()) { throw UserError.abort("Runtime compilation is currently unimplemented on the LLVM backend (GR-43073)."); } + /* + * Runtime randomness seeds constant blinding and code-offset randomization. Register it + * only with its runtime-compilation consumer so ordinary executables do not retain JCA + * security providers. \u00A7FS-security-providers.2.4 + */ + if (!ImageSingletons.contains(RuntimeRandomness.class)) { + ImageSingletons.add(RuntimeRandomness.class, new SecureRandomRuntimeRandomness()); + } ImageSingletons.add(RuntimeCompilationSupport.class, new RuntimeCompilationSupport()); /* * Check if there is already a RuntimeCompiledMethodSupport registered. If so a dependent diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SecurityServicesFeature.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SecurityServicesFeature.java index 4bd51fca1f28..2675e3e92da4 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SecurityServicesFeature.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SecurityServicesFeature.java @@ -697,14 +697,23 @@ private void registerServiceReachabilityHandlers(BeforeAnalysisAccess access) { registerGSSReachabilityHandler(access); /* - * On Oracle JDK the SecureRandom service implementations are not automatically discovered - * by the mechanism above because SecureRandom.getInstance() is not invoked. For example - * java.security.SecureRandom.getDefaultPRNG() calls - * java.security.Provider.Service.newInstance() directly. On Open JDK - * SecureRandom.getInstance() is used instead. + * Default SecureRandom acquisition does not use SecureRandom.getInstance(). Register its + * private common path directly: on Oracle JDK the SUN fast path also bypasses + * Provider.getDefaultSecureRandomService(). */ - Optional defaultSecureRandomService = optionalMethod(access, "java.security.Provider", "getDefaultSecureRandomService"); - defaultSecureRandomService.ifPresent(m -> access.registerMethodOverrideReachabilityHandler((a, t) -> registerServices(a, t, SECURE_RANDOM_SERVICE), OriginalMethodProvider.getJavaMethod(m))); + Method getDefaultPRNG = ReflectionUtil.lookupMethod(SecureRandom.class, "getDefaultPRNG", boolean.class, byte[].class); + access.registerReachabilityHandler(a -> registerDefaultSecureRandomServices(a, getDefaultPRNG), getDefaultPRNG); + } + + private void registerDefaultSecureRandomServices(DuringAnalysisAccess access, Executable trigger) { + if (FutureDefaultsOptions.explicitSecurityProviderRegistration()) { + // Default acquisition retains the complete fallback provider as a platform dependency. + // \u00A7FS-security-providers.2.4 + Class providerClass = sun.security.provider.Sun.class; + registerProviderClassForReflection(providerClass); + addCandidateProviderClass(providerClass); + } + registerServices(access, trigger, SECURE_RANDOM_SERVICE); } private void registerGSSReachabilityHandler(BeforeAnalysisAccess access) { diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/services/SecurityServiceExplicitProviderRegistrationTest.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/services/SecurityServiceExplicitProviderRegistrationTest.java index 0f0a61cc89a9..26ce2cd57bec 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/services/SecurityServiceExplicitProviderRegistrationTest.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/services/SecurityServiceExplicitProviderRegistrationTest.java @@ -24,7 +24,10 @@ */ package com.oracle.svm.test.services; +import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; +import java.security.Provider; +import java.security.SecureRandom; import java.security.Security; import java.security.Signature; @@ -37,6 +40,19 @@ "--future-defaults=run-time-initialize-security-providers,explicit-security-provider-registration" }) public class SecurityServiceExplicitProviderRegistrationTest { + @Test + public void testDefaultSecureRandomIncludesCompleteSunProvider() throws NoSuchAlgorithmException { + SecureRandom random = new SecureRandom(); + Provider provider = random.getProvider(); + + Assert.assertEquals("SUN", provider.getName()); + Assert.assertSame(provider, Security.getProvider("SUN")); + Assert.assertNotNull("Default SecureRandom must retain its SHA dependency.", MessageDigest.getInstance("SHA", provider)); + Provider.Service jksService = provider.getService("KeyStore", "JKS"); + Assert.assertNotNull("Provider registration must retain unrelated advertised services.", jksService); + Assert.assertNotNull("An unrelated advertised service must remain usable.", jksService.newInstance(null)); + } + @Test public void testReachableFactoryDoesNotIncludeUnregisteredProvider() { Assert.assertNull("A reachable Signature factory must not include SunEC.", Security.getProvider("SunEC")); From 46ab99801b936779affbc65f901d4be57e7faa5f Mon Sep 17 00:00:00 2001 From: Vojin Jovanovic Date: Thu, 23 Jul 2026 18:19:23 +0200 Subject: [PATCH 30/63] [GR-69858] Require explicit security provider reflection metadata --- .../options/processor/OptionProcessor.java | 2 +- .../native-image/JCASecurityServices.md | 6 - substratevm/CHANGELOG.md | 1 + .../functional-spec/security-providers.md | 224 +++++++++--------- .../core/jdk/SecurityProvidersSupport.java | 220 ++++++++--------- .../svm/core/jdk/SecuritySubstitutions.java | 2 +- .../svm/hosted/SecurityServicesFeature.java | 70 +++--- ...andomExplicitProviderRegistrationTest.java | 50 ++++ ...rviceExplicitProviderRegistrationTest.java | 27 ++- 9 files changed, 326 insertions(+), 276 deletions(-) create mode 100644 substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/services/SecureRandomExplicitProviderRegistrationTest.java diff --git a/compiler/src/jdk.graal.compiler.processor/src/jdk/graal/compiler/options/processor/OptionProcessor.java b/compiler/src/jdk.graal.compiler.processor/src/jdk/graal/compiler/options/processor/OptionProcessor.java index b83cfbe1831a..ed259ed3e3f1 100644 --- a/compiler/src/jdk.graal.compiler.processor/src/jdk/graal/compiler/options/processor/OptionProcessor.java +++ b/compiler/src/jdk.graal.compiler.processor/src/jdk/graal/compiler/options/processor/OptionProcessor.java @@ -325,7 +325,7 @@ static void createOptionsDescriptorsFile(ProcessingEnvironment processingEnv, Op out.printf(" /*fieldName*/ \"%s\",\n", fieldName); out.printf(" /*stability*/ %s.%s,\n", getSimpleName(OPTION_STABILITY_CLASS_NAME), stability); out.printf(" /*deprecated*/ %b,\n", deprecated); - out.printf(" /*deprecationMessage*/ \"%s\");\n", deprecationMessage); + out.printf(" /*deprecationMessage*/ %s);\n", literal(deprecationMessage)); out.println(" }"); } out.println(" // CheckStyle: resume line length check"); diff --git a/docs/reference-manual/native-image/JCASecurityServices.md b/docs/reference-manual/native-image/JCASecurityServices.md index dee761e5bf1c..d6236633908e 100644 --- a/docs/reference-manual/native-image/JCASecurityServices.md +++ b/docs/reference-manual/native-image/JCASecurityServices.md @@ -86,12 +86,6 @@ Security.insertProviderAt(bcProvider, 1); If `--future-defaults=all` or `--future-defaults=run-time-initialize-jdk` is enabled, the list of providers is constructed at run time. The same approach to manipulating providers can then be used. -## SecureRandom - -The `SecureRandom` implementations open the `/dev/random` and `/dev/urandom` files which are used as sources. -These files are usually opened in class initializers. -To avoid capturing state from the machine that runs the `native-image` builder, these classes need to be initialized at run time. - ## Custom Service Types By default, only services specified in the JCA framework are automatically registered. To automatically register custom service types, you can use the `-H:AdditionalSecurityServiceTypes` option. diff --git a/substratevm/CHANGELOG.md b/substratevm/CHANGELOG.md index 323d90f28798..e1484d298d6b 100644 --- a/substratevm/CHANGELOG.md +++ b/substratevm/CHANGELOG.md @@ -9,6 +9,7 @@ This changelog summarizes major changes to GraalVM Native Image. * (GR-77670) Chunk up digest generation for Native Image Layers, to allow for large layer files to be checked. This makes older layer files potentially incompabile with layers created after this change. * (GR-73199) When native executables are built with `-H:-LegacyJavaOptionMode`, VM options are parsed only before the first `--` argument. Arguments after `--` are passed unchanged to the application main method. The legacy behavior remains unchanged. * (GR-77977) Added control flow integrity options, available via `-H:CFI`. Indirect branches on AMD64 can be guarded with software-based checks that ensure that they land on valid targets. On AArch64, PAC is supported to protect return addresses on the stack. +* (GR-69858) Deprecated `-H:AdditionalSecurityProviders` and `-H:AdditionalSecurityServiceTypes`. Register each security provider class for reflection in `reachability-metadata.json` using `{"reflection":[{"type":""}]}` instead. The Tracing Agent generates this metadata automatically. ## GraalVM 25.2 (Internal Version 25.2.4) * (GR-77358) Introduced compressed (32-bit) references, enabled by default. This generally improves memory usage and performance, but limits heap memory to 32 GB. Disable with `-H:-UseCompressedReferences`. diff --git a/substratevm/docs/functional-spec/security-providers.md b/substratevm/docs/functional-spec/security-providers.md index 50bb114d0a87..72b60e5cb1b1 100644 --- a/substratevm/docs/functional-spec/security-providers.md +++ b/substratevm/docs/functional-spec/security-providers.md @@ -1,17 +1,16 @@ # FS-security-providers: JCA Security Provider Registration and Run-Time Access -This specification defines the Native Image behavior for Java Cryptography Architecture -(JCA) security providers. -Under this behavior, the JDK can acquire or select a provider for JCA at run time only if the -provider class was *registered for reflection* at build time. +This specification defines the Native Image behavior for Java Cryptography Architecture (JCA) +security providers. +In Native Image, the JDK can acquire or select a JCA provider at run time only if the provider +class was *registered for reflection* at build time. With `--exact-reachability-metadata`, attempted reflective acquisition of an unregistered provider reports an error that identifies the missing provider type. -Native Image constructs the provider list at run time from the configured security properties and -the registered provider classes. +Native Image constructs the provider list at run time from the build-time configured security +properties and the registered provider classes. Reflection registration determines which provider classes and services the native executable -contains. -It does not by itself add a provider to the run-time provider list. +contains; it does not by itself add a provider to the run-time provider list. Section 7 defines the future-default options that select this behavior during the transition from service-driven provider inclusion and build-time provider-list initialization. @@ -35,12 +34,12 @@ class and required reflective construction metadata Native Image can retain in t Registration is a build-time property. Constructing a provider object at run time does not register the provider or add omitted services to the executable. -Section 2.4 defines the one platform registration rule for default `SecureRandom` acquisition. +Section 2.4 defines the one platform registration rule for `SecureRandom` acquisition. ### 1.2 JDK-Managed Providers and Acquisition -A **JDK-managed provider** is a provider instance that the JDK creates or discovers instead of an -instance that application code constructs and supplies to the JDK. +A **JDK-managed provider** is a provider instance that the JDK creates or discovers, as opposed to +an instance that application code constructs and supplies to the JDK. JDK-managed acquisition paths include loading providers from the configured security properties, discovering them through a service-provider descriptor, selecting them through a JCA factory or security-service facade, and creating them through a JDK default or fallback path. @@ -48,29 +47,20 @@ security-service facade, and creating them through a JDK default or fallback pat The public JDK API surface covered by this definition includes: - provider lookup, enumeration, filtering, and algorithm discovery through - `Provider Security.getProvider(String)`, `Provider[] Security.getProviders()`, - `Provider[] Security.getProviders(String)`, - `Provider[] Security.getProviders(Map)`, and - `Set Security.getAlgorithms(String)`; -- provider-service lookup and instantiation through - `Provider.Service Provider.getService(String, String)`, - `Set Provider.getServices()`, and - `Object Provider.Service.newInstance(Object)`; -- JCA factory selection, for example through `Signature Signature.getInstance(String)`, - `Signature Signature.getInstance(String, String)`, and - `Signature Signature.getInstance(String, Provider)`, together with provider exposure through - `Provider Signature.getProvider()`; -- security-service facades, including `GSSManager GSSManager.getInstance()`, - `SaslClient Sasl.createSaslClient(String[], String, String, String, Map, CallbackHandler)`, - and - `SaslServer Sasl.createSaslServer(String, String, String, Map, CallbackHandler)`; and -- service discovery through `ServiceLoader ServiceLoader.load(Class)` and default - construction through `SecureRandom()`. + `Security.getProvider(String)`, the `Security.getProviders` overloads, and + `Security.getAlgorithms(String)`; +- provider-service lookup and instantiation through `Provider.getService(String, String)`, + `Provider.getServices()`, and `Provider.Service.newInstance(Object)`; +- JCA factory selection, for example through the `Signature.getInstance` overloads, together with + provider exposure through `Signature.getProvider()`; +- security-service facades, including `GSSManager.getInstance()`, `Sasl.createSaslClient`, and + `Sasl.createSaslServer`; and +- service discovery through `ServiceLoader.load(Class)` and default construction through + `new SecureRandom()`. This list identifies the principal public entry points but does not limit the acquisition rule to -those methods. -Other JCA engine factory overloads, provider-exposing engine methods, and internal paths that -implement these APIs are subject to the same rule. +them: other JCA engine factory overloads, provider-exposing engine methods, and internal paths +that implement these APIs are subject to the same rule. The JDK **acquires** a provider when a JDK API or implementation path does any of the following: @@ -79,11 +69,11 @@ The JDK **acquires** a provider when a JDK API or implementation path does any o - reports the provider or one of its algorithms as available. This definition covers public APIs and the internal JDK paths that implement them. -It does not treat an application constructing its own provider object through a statically resolved -constructor as JDK acquisition. -Reflective provider loading and construction remain subject to reflection registration and section -4.3. -Section 5 specifies the narrower operations allowed for such application-supplied objects. +An application constructing its own provider object through a statically resolved constructor is +not JDK acquisition; section 5 specifies the narrower operations allowed for such +application-supplied objects. +Reflective provider loading and construction remain subject to reflection registration and +section 4.3. ### 1.3 Run-Time Provider List @@ -91,15 +81,15 @@ The **run-time provider list** contains registered providers selected from the c properties and reflects subsequent changes made through the standard `Security` API. Filtering unregistered providers must preserve the relative order of the remaining configured providers. -Registering a provider for reflection does not insert a provider that is absent from the configured -list. +Registering a provider for reflection does not insert a provider that is absent from the +configured list. ### 1.4 Service Availability A service is **available** when it is registered and the corresponding factory call can select its provider. -A name-based call can select only a provider in the run-time provider list. -A provider-object call can select a supplied registered provider without adding it to that list. +A name-based call can select only a provider in the run-time provider list; a provider-object call +can select a supplied registered provider without adding it to that list. Algorithm aliases and provider selection otherwise follow the standard JCA API behavior. ## 2. Registration Semantics @@ -108,9 +98,8 @@ Algorithm aliases and provider selection otherwise follow the standard JCA API b Type access, declared nullary constructor access, and qualifying `provider()` method access are alternative registration signals. -Registering any one of them is sufficient if the provider class meets all construction requirements -in section 1.1. -Registering a signal does not relax those construction requirements. +Registering any one of them is sufficient if the provider class meets all construction +requirements in section 1.1; registering a signal does not relax those requirements. ### 2.2 Provider Construction @@ -123,27 +112,31 @@ lookups and JCA factory calls as it exposed when Native Image inspected it at bu ### 2.3 Registration Effects Registering a provider includes every valid service declared by the provider whose implementation -class Native Image can resolve. -It must also retain the metadata required to construct those service implementations. +class Native Image can resolve, and retains the metadata required to construct those service +implementations. Provider registration does not change the configured provider order or make an unconfigured provider visible by name. This behavior implements §DF-complete-security-provider-registration.2. -### 2.4 Default SecureRandom Provider +### 2.4 SecureRandom Providers -When the JDK default `SecureRandom` acquisition path is reachable, Native Image must register the -complete fallback SUN provider without requiring application reflection metadata for that provider. -This registration has the effects specified in section 2.3, including retention of every valid SUN -service whose implementation class Native Image can resolve. +When a `SecureRandom` acquisition path is reachable, Native Image must register the complete +configured providers that declare `SecureRandom` services. +The application does not need to supply reflection metadata for those providers. +This registration has the effects specified in section 2.3, including retention of every valid +service that each registered provider declares and whose implementation class Native Image can +resolve. -This rule applies to the no-argument `SecureRandom` constructor and other JDK paths that perform the -same default-provider selection. It does not apply to named algorithm or named provider acquisition. -It is a platform registration rule, not the earlier service-driven inclusion behavior described in -section 7.3. +This rule applies to the `SecureRandom` constructors, the `SecureRandom.getInstance` overloads, +and JDK paths that perform the same default-provider selection. +It is a conditional platform registration rule: Native Image must not register these providers +when no `SecureRandom` acquisition path is reachable. +It is not the earlier service-driven inclusion behavior described in section 7.3. Native Image internal runtime randomness must cause this registration only in an executable that -includes the runtime-compilation subsystem that consumes that randomness. The presence of the -optional internal randomness implementation must not register SUN in an ordinary executable. +includes the runtime-compilation subsystem that consumes that randomness. +The presence of the optional internal randomness implementation must not register SUN in an +ordinary executable. This behavior implements §DF-default-secure-random-provider.2. @@ -171,38 +164,34 @@ fallbacks must fail before exposing an unregistered provider. ### 3.2 Provider List Lookups -`Provider Security.getProvider(String)` returns a registered provider when that provider is in the -run-time provider list. -`Provider[] Security.getProviders()` contains the same provider and preserves the ordering -described in section 1.3. -`Provider[] Security.getProviders(String)` and -`Provider[] Security.getProviders(Map)` can return only registered providers from -that list. -`Set Security.getAlgorithms(String)` can report an algorithm only when at least one -registered provider in that list declares it. +`Security.getProvider(String)` returns a registered provider when that provider is in the run-time +provider list. +If the provider is not in the list and the lookup does not attempt to load it reflectively, the +call returns `null`. -If a provider is not in the run-time provider list and the lookup does not attempt to load it -reflectively, `Provider Security.getProvider(String)` returns `null`. +`Security.getProviders()` contains the same provider and preserves the ordering described in +section 1.3. +`Security.getProviders(String)` and `Security.getProviders(Map)` can return only registered +providers from that list, and `Security.getAlgorithms(String)` can report an algorithm only when +at least one registered provider in that list declares it. ### 3.3 JCA Factories and Security-Service Facades -A name-based JCA factory call can use the registered services of a provider in the run-time provider -list. +A name-based JCA factory call can use the registered services of a provider in the run-time +provider list. A factory overload that accepts a provider object can use the registered services of that provider without requiring it to be in the list. -A factory call can use only registered service implementations. -It does not evaluate a run-time algorithm argument at build time or make additional providers or -services available. - +A factory call can use only registered service implementations; it does not evaluate a run-time +algorithm argument at build time or make additional providers or services available. The same registration requirement applies when a facade or a JDK implementation path selects a provider service without calling a public JCA factory. ### 3.4 Programmatic Access -An application can construct a registered provider and pass it directly to a JCA factory. -It can also add the provider to the run-time provider list through -`int Security.addProvider(Provider)` or `int Security.insertProviderAt(Provider, int)`. +An application can construct a registered provider and pass it directly to a JCA factory, or add +it to the run-time provider list through `Security.addProvider(Provider)` or +`Security.insertProviderAt(Provider, int)`. Section 5 specifies these operations in detail. ## 4. Prohibited Run-Time Access and Errors @@ -215,15 +204,14 @@ The JDK must not expose it partially by returning the provider while omitting so advertising its algorithms without allowing their use, returning a `Provider.Service` without a usable implementation, or producing an engine or facade backed by that provider. -Failure must occur at the acquisition boundary before application code receives a provider, +Failure must occur at the acquisition boundary, before application code receives a provider, service, engine, or facade that represents the unregistered provider as available. -When sections 4.2 and 4.3 do not specify a standard result or missing-registration diagnostic, this -specification requires the operation to fail before exposure but does not prescribe the exception -type. -Reachability of a JCA service factory or JDK security-service facade must not make that provider or -its services available. -Neither a service-provider descriptor nor a JDK default or fallback implementation can register the -provider after the executable has been built. +When sections 4.2 and 4.3 do not specify a standard result or missing-registration diagnostic, +this specification requires the operation to fail before exposure but does not prescribe the +exception type. +Reachability of a JCA service factory or JDK security-service facade must not make the provider or +its services available, and neither a service-provider descriptor nor a JDK default or fallback +implementation can register the provider after the executable has been built. Application-supplied provider objects follow section 5 and do not relax these requirements for JDK-managed providers. @@ -233,12 +221,12 @@ JDK-managed providers. JCA factory calls retain their standard distinction between a missing provider and a missing algorithm: -- a factory overload given the name of a provider that is not in the run-time provider list reports - `NoSuchProviderException`; +- a factory overload given the name of a provider that is not in the run-time provider list + reports `NoSuchProviderException`; - a factory call that can select a provider but cannot find a registered implementation for the requested algorithm reports `NoSuchAlgorithmException`; and -- a factory overload given a provider object follows section 5.1 instead of requiring that provider - to be in the run-time provider list. +- a factory overload given a provider object follows section 5.1 instead of requiring that + provider to be in the run-time provider list. These results apply when no missing reflection registration is encountered first. @@ -248,6 +236,12 @@ When exact reachability metadata checking is enabled, an operation that reflecti unregistered provider reports `MissingReflectionRegistrationError` for the provider type. Native Image must not replace that error with `NoSuchProviderException`, `NoSuchAlgorithmException`, or another security-provider-specific exception. +The diagnostic must identify the provider type and give the user all instructions needed to add +sufficient reflection metadata and rebuild the native image, including the metadata entry and the +location of `reachability-metadata.json`. +For a provider with a supported construction path, the type-only entry suggested by a missing-type +diagnostic is sufficient under section 2.1; Native Image retains the provider's construction and +service metadata during the subsequent build. This requirement applies both to loading a provider from the configured provider list and to Java Cryptography Extension (JCE) verification of a programmatically supplied provider. @@ -260,8 +254,8 @@ missing-registration diagnostic. An application can construct a provider and pass it directly to a JCA factory. Because the application already possesses this object, its construction and ordinary Java method -calls are not JDK-managed provider acquisition as defined in section 1.2. -Direct construction does not waive the registration requirements in section 1.1. +calls are not JDK-managed provider acquisition as defined in section 1.2; direct construction +does not, however, waive the registration requirements in section 1.1. If the provider is registered, the factory can use its registered services without the provider being in the run-time provider list. @@ -270,21 +264,20 @@ the missing-registration behavior in section 4.3. ### 5.2 Programmatic Provider-List Changes -An application can call `int Security.addProvider(Provider)` or -`int Security.insertProviderAt(Provider, int)` with a constructed provider. -Insertion does not register an unregistered provider or any of its services. -The insertion can still make the supplied provider object retrievable from the run-time provider -list according to the standard `Security` API behavior, but JCA factory calls cannot use its -unregistered services. +An application can call `Security.addProvider(Provider)` or +`Security.insertProviderAt(Provider, int)` with a constructed provider. +Insertion does not register an unregistered provider or any of its services: the insertion can +still make the supplied provider object retrievable from the run-time provider list according to +the standard `Security` API behavior, but JCA factory calls cannot use its unregistered services. This exception applies only to the same application-supplied object; it does not allow the JDK to create, discover, or substitute an unregistered provider. After successful insertion of a registered provider, provider-name lookups and name-based factory calls can select its registered services. -Removal with `void Security.removeProvider(String)` makes the provider unavailable to subsequent +Removal with `Security.removeProvider(String)` makes the provider unavailable to subsequent name-based lookups without affecting provider objects already held by the application. -Adding a provider does not itself require JCE verification. -The first subsequent operation that requires JCE verification follows section 5.3. +Adding a provider does not itself require JCE verification; the first subsequent operation that +requires JCE verification follows section 5.3. Provider position, duplicate-name handling, insertion return values, and removal otherwise follow the standard `Security` API behavior. @@ -296,9 +289,8 @@ provider at build time. Registration is necessary for such an operation, but registration is not successful verification. Native Image must preserve the build-time verification outcome and apply it to run-time instances -of that provider class. -A run-time instance must receive the same outcome when its provider name differs from the name -observed at build time. +of that provider class, including an instance whose provider name differs from the name observed +at build time. A failed verification outcome must prevent every run-time JCE operation that requires verification from using the provider. The operation must expose that failure through the standard JCE behavior of the invoked factory @@ -318,14 +310,13 @@ The collected metadata must retain a supported construction path: declared nulla access or access to the static `provider()` method. Tracing a missing provider registration must use the ordinary reflection metadata format and -diagnostics. -It must not introduce a security-provider-specific metadata category or error. +diagnostics; it must not introduce a security-provider-specific metadata category or error. ## 7. Transition to the Future Defaults Sections 1 through 6 specify the planned default behavior. -The following options select its two independent parts while the earlier behaviors remain available -for compatibility. +The following options select its two independent parts while the earlier behaviors remain +available for compatibility. ### 7.1 Run-Time Provider-List Initialization @@ -350,27 +341,24 @@ factory or JDK security-service facade can include services of the corresponding when their provider classes have no reflection metadata. This compatibility behavior applies to supported facades such as the Generic Security Services API (GSS-API). - -For example, reachability of `Signature Signature.getInstance(String)`, -`Signature Signature.getInstance(String, String)`, or -`Signature Signature.getInstance(String, Provider)` can cause signature services and their -providers to be included. -This rule is based on reachability of the service factory and service type, not on build-time +For example, reachability of any `Signature.getInstance` overload can cause signature services and +their providers to be included. +The rule is based on reachability of the service factory and service type, not on build-time evaluation of the run-time algorithm argument. With `--future-defaults=explicit-security-provider-registration`, this compatibility behavior is disabled. A factory call for an algorithm supplied only by an unregistered provider follows section 4.2, and a lookup that reflectively loads the provider follows section 4.3. -The default `SecureRandom` registration rule in section 2.4 remains enabled because it supplies the -complete provider for a standard JDK default-acquisition path rather than inferring providers from a -general service factory. +The `SecureRandom` registration rule in section 2.4 remains enabled because it conditionally +supplies complete providers for this commonly used JDK facility rather than inferring providers +from a general service factory. ### 7.4 Earlier Build-Time Initialization Behavior `--future-defaults=run-time-initialize-security-providers` replaces the earlier behavior in which -Native Image initializes the configured provider list at build time. -During the transition, omitting this future default retains that earlier initialization behavior. +Native Image initializes the configured provider list at build time; during the transition, +omitting this future default retains that earlier initialization behavior. When explicit provider registration is enabled without run-time provider initialization, Native Image must filter the build-time provider list before storing it in the executable so that it does not expose an unregistered JDK-managed provider. diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SecurityProvidersSupport.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SecurityProvidersSupport.java index cad6d52dc060..68ef16eacee2 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SecurityProvidersSupport.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SecurityProvidersSupport.java @@ -52,81 +52,77 @@ import jdk.graal.compiler.api.replacements.Fold; import sun.security.util.Debug; -/** - * AR-security-providers: Security Provider Architecture - * - * This class holds the build-time and run-time structures for JCA security-provider inclusion, - * verification, and metadata tracing. The required behavior is specified separately by - * §FS-security-providers. - * See the - * JCA Security Services documentation for the user-facing configuration model. - * - * ## 1. Build-Time Inclusion and Verification - * - * {@code SecurityServicesFeature} coordinates two analysis inputs: subtype reachability discovers - * candidate {@link Provider} classes, and JCA factory reachability discovers used service types. - * For a provider candidate, the feature queries the reflection registry for type, constructor, or - * factory-method registration. It instantiates accepted candidates through the declared nullary - * constructor or static {@code provider()} method, then registers their service implementation - * classes. The service-driven path calls the same service-registration machinery independently. - * These mechanisms implement §FS-security-providers.2 and §FS-security-providers.7.3. - * Default {@code SecureRandom} acquisition first registers the complete fallback SUN provider as - * the narrow platform exception specified by §FS-security-providers.2.4. - * - * During analysis, the feature obtains each included provider's JCE verification result and stores - * it in this image singleton, keyed by provider class name and provider name. The feature removes - * those entries from the JDK's object-keyed cache so build-time provider instances do not remain - * reachable in the image heap. - * - * ## 2. Run-Time Verification-Result Lookup - * - * The {@code javax.crypto.JceSecurity} substitutions consult the maps in this singleton when the - * JDK verification cache has no entry. {@link Boolean#TRUE} encodes successful verification; an - * exception object encodes the original verification failure. This lets run-time JCE checks reuse - * the build-time result without retaining the provider instance or repeating JAR verification. - * - * ## 3. Type and Constructor Tracing - * - * The metadata tracer represents provider loading as two distinct accesses: a dynamic - * {@link Class#forName(String)} lookup records the type, and constructor access records how the - * provider is instantiated. Because the JCE lookup already has a provider instance, - * {@link #traceProviderLookup(Provider)} emits the constructor access directly instead of creating - * another provider. This is the native-image counterpart of the Tracing Agent's provider event. - * - * On a verification-result cache miss, {@link #reportMissingProviderRegistration(Class)} performs - * an opaque, non-initializing {@code Class.forName} lookup using the provider's class loader. The - * opaque name prevents image-build analysis from removing the probe. The lookup enters the regular - * missing-reflection-registration machinery required by - * §FS-security-providers.5.3. A successful lookup without a verification result is - * an internal invariant violation. - * - * ## 4. Run-Time Provider Construction - * - * With run-time provider initialization, the {@code ProviderConfig} substitutions ask this class - * to construct included JDK providers directly. Other configured providers follow the JDK's - * reflective loading path. The substitutions preserve the JDK's provider-list state, recursion - * guard, and retry counter, while the verification maps remain independent of provider creation. - * - * ## 5. Concurrent Analysis Registration - * - * Provider subtype callbacks add candidates to a concurrent set and mark it changed. A serialized - * security-services analysis pass consumes new candidates and requests another analysis iteration - * when processing registers new reflection or JNI metadata. The callbacks do not request analysis - * iterations themselves, so concurrent discovery cannot race with iteration scheduling or lose - * registrations pending for a later iteration. - */ +/// AR-security-providers: Security Provider Architecture +/// +/// This class holds the build-time and run-time structures for JCA security-provider inclusion, +/// verification, and metadata tracing. The required behavior is specified separately by +/// §FS-security-providers. See the [JCA Security Services documentation](../../../../../../../../../../../../docs/reference-manual/native-image/JCASecurityServices.md) +/// for the user-facing configuration model. +/// +/// ## 1. Build-Time Inclusion and Verification +/// +/// [SecurityServicesFeature] coordinates two analysis inputs: subtype reachability discovers +/// candidate [Provider] classes, and JCA factory reachability discovers used service types. For a +/// provider candidate, the feature queries the reflection registry for type, constructor, or +/// factory-method registration. It instantiates accepted candidates through the declared nullary +/// constructor or static `provider()` method, then registers their service implementation +/// classes. The service-driven path calls the same service-registration machinery independently. +/// These mechanisms implement §FS-security-providers.2 and §FS-security-providers.7.3. +/// [SecureRandom] acquisition registers the complete configured providers that declare +/// `SecureRandom` services as the narrow platform exception specified by +/// §FS-security-providers.2.4. +/// +/// During analysis, the feature obtains each included provider's JCE verification result and stores +/// it in this image singleton, keyed by provider class name. Provider inclusion is tracked +/// separately so it cannot overwrite a failed verification result. The feature removes those +/// entries from the JDK's object-keyed cache so build-time provider instances do not remain +/// reachable in the image heap. +/// +/// ## 2. Run-Time Verification-Result Lookup +/// +/// The [javax.crypto.JceSecurity] substitutions consult the maps in this singleton when the JDK +/// verification cache has no entry. [Boolean#TRUE] encodes successful verification; an exception +/// object encodes the original verification failure. This lets run-time JCE checks reuse the +/// build-time result without retaining the provider instance or repeating JAR verification. +/// +/// ## 3. Type and Constructor Tracing +/// +/// The metadata tracer represents provider loading as two distinct accesses: a dynamic +/// [Class#forName(String)] lookup records the type, and constructor access records how the +/// provider is instantiated. Because the JCE lookup already has a provider instance, +/// [#traceProviderLookup(Provider)] emits the constructor access directly instead of creating +/// another provider. This is the native-image counterpart of the Tracing Agent's provider event. +/// +/// On a verification-result cache miss, [#reportMissingProviderRegistration(Class)] performs an +/// opaque, non-initializing `Class.forName` lookup using the provider's class loader. The opaque +/// name prevents image-build analysis from removing the probe. The lookup enters the regular +/// missing-reflection-registration machinery required by §FS-security-providers.5.3. A successful +/// lookup without a verification result is an internal invariant violation. +/// +/// ## 4. Run-Time Provider Construction +/// +/// With run-time provider initialization, the [ProviderConfig] substitutions ask this class to +/// construct included JDK providers directly. Other configured providers follow the JDK's +/// reflective loading path. The substitutions preserve the JDK's provider-list state, recursion +/// guard, and retry counter, while the verification maps remain independent of provider creation. +/// +/// ## 5. Concurrent Analysis Registration +/// +/// Provider subtype callbacks add candidates to a concurrent set and mark it changed. A serialized +/// security-services analysis pass consumes new candidates and requests another analysis iteration +/// when processing registers new reflection or JNI metadata. The callbacks do not request analysis +/// iterations themselves, so concurrent discovery cannot race with iteration scheduling or lose +/// registrations pending for a later iteration. +/// @SingletonTraits(access = AllAccess.class, layeredCallbacks = NoLayeredCallbacks.class, layeredInstallationKind = Duplicable.class, other = PartiallyLayerAware.class) public final class SecurityProvidersSupport { private static final Class[] NO_PARAMETERS = new Class[0]; - /** - * A map of providers, identified by their names (see {@link Provider#getName()}), and the - * results of their verification (see javax.crypto.JceSecurity#getVerificationResult). This - * structure is used instead of the (see javax.crypto.JceSecurity#verifyingProviders) map to - * avoid keeping provider objects in the image heap. - */ - private final EconomicMap verifiedSecurityProviders = ImageHeapMap.create("verifiedSecurityProviders"); - private final EconomicMap verifiedSecurityProviderClasses = ImageHeapMap.create("verifiedSecurityProviderClasses"); + /// Provider classes that may be constructed at run time. + private final EconomicMap includedSecurityProviderClasses = ImageHeapMap.create("includedSecurityProviderClasses"); + + /// Build-time JCE verification results keyed by the run-time provider class. + private final EconomicMap securityProviderVerificationResults = ImageHeapMap.create("securityProviderVerificationResults"); private Properties savedInitialSecurityProperties; @@ -142,27 +138,22 @@ public static SecurityProvidersSupport singleton() { } @Platforms(Platform.HOSTED_ONLY.class) - public void addVerifiedSecurityProvider(String providerName, String providerClassName, Object verificationResult) { - verifiedSecurityProviders.put(providerName, verificationResult); - verifiedSecurityProviderClasses.put(providerClassName, verificationResult); + public void addSecurityProviderVerificationResult(String providerClassName, Object verificationResult) { + securityProviderVerificationResults.put(providerClassName, verificationResult); } @Platforms(Platform.HOSTED_ONLY.class) public void addIncludedSecurityProviderClass(String providerClassName) { - verifiedSecurityProviderClasses.put(providerClassName, Boolean.TRUE); + includedSecurityProviderClasses.put(providerClassName, Boolean.TRUE); } public Object getSecurityProviderVerificationResult(Provider provider) { - Object result = verifiedSecurityProviderClasses.get(provider.getClass().getName()); - return result != null ? result : verifiedSecurityProviders.get(provider.getName()); + return securityProviderVerificationResults.get(provider.getClass().getName()); } - /** - * Returns {@code true} if the provider, identified by either its name (e.g., SUN) or fully - * qualified name (e.g., sun.security.provider.Sun), was included in the native image. - */ - public boolean isSecurityProviderIncluded(String providerName, String providerFQName) { - return verifiedSecurityProviders.containsKey(providerName) || verifiedSecurityProviderClasses.containsKey(providerFQName); + /// Returns `true` if the provider class was included in the native image. + public boolean isSecurityProviderIncluded(String providerClassName) { + return includedSecurityProviderClasses.containsKey(providerClassName); } @Platforms(Platform.HOSTED_ONLY.class) @@ -188,13 +179,17 @@ public Properties getSavedInitialSecurityProperties() { } public static String getBuiltInProviderName(String provName) { - return switch (provName) { - case "SUN", "sun.security.provider.Sun" -> "SUN"; - case "SunRsaSign", "sun.security.rsa.SunRsaSign" -> "SunRsaSign"; - case "SunJCE", "com.sun.crypto.provider.SunJCE" -> "SunJCE"; - case "SunJSSE", "sun.security.ssl.SunJSSE" -> "SunJSSE"; - case "SunEC", "sun.security.ec.SunEC" -> "SunEC"; - case "Apple", "apple.security.AppleProvider" -> "Apple"; + String providerClassName = getBuiltInProviderClassName(provName); + if (providerClassName == null) { + return null; + } + return switch (providerClassName) { + case "sun.security.provider.Sun" -> "SUN"; + case "sun.security.rsa.SunRsaSign" -> "SunRsaSign"; + case "com.sun.crypto.provider.SunJCE" -> "SunJCE"; + case "sun.security.ssl.SunJSSE" -> "SunJSSE"; + case "sun.security.ec.SunEC" -> "SunEC"; + case "apple.security.AppleProvider" -> "Apple"; default -> null; }; } @@ -211,7 +206,7 @@ public static String getBuiltInProviderClassName(String provName) { }; } - /** §AR-security-providers.3: Cache misses probe type access for standard diagnostics. */ + /// §AR-security-providers.3: Cache misses probe type access for standard diagnostics. public static void reportMissingProviderRegistration(Class providerClass) { try { Class.forName(GraalDirectives.opaque(providerClass.getName()), false, providerClass.getClassLoader()); @@ -221,7 +216,7 @@ public static void reportMissingProviderRegistration(Class providerClass) { throw VMError.shouldNotReachHere("A security provider without a verification result was registered for reflection: " + providerClass.getName()); } - /** §AR-security-providers.3: Existing providers trace constructor access directly. */ + /// §AR-security-providers.3: Existing providers trace constructor access directly. public static Provider traceProviderLookup(Provider provider) { if (provider == null) { return null; @@ -249,20 +244,24 @@ private static Provider loadProviderReflectively(String providerClassName, Debug } public Provider loadBuiltInProvider(String provName, Debug debug) { - return switch (provName) { - case "SUN", "sun.security.provider.Sun" -> - isSecurityProviderIncluded("SUN", "sun.security.provider.Sun") ? new sun.security.provider.Sun() : loadProviderReflectively("sun.security.provider.Sun", debug); - case "SunRsaSign", "sun.security.rsa.SunRsaSign" -> - isSecurityProviderIncluded("SunRsaSign", "sun.security.rsa.SunRsaSign") ? new sun.security.rsa.SunRsaSign() : loadProviderReflectively("sun.security.rsa.SunRsaSign", debug); - case "SunJCE", "com.sun.crypto.provider.SunJCE" -> - isSecurityProviderIncluded("SunJCE", "com.sun.crypto.provider.SunJCE") ? new com.sun.crypto.provider.SunJCE() : loadProviderReflectively("com.sun.crypto.provider.SunJCE", debug); - case "SunJSSE", "sun.security.ssl.SunJSSE" -> - isSecurityProviderIncluded("SunJSSE", "sun.security.ssl.SunJSSE") ? new sun.security.ssl.SunJSSE() : loadProviderReflectively("sun.security.ssl.SunJSSE", debug); - case "SunEC", "sun.security.ec.SunEC" -> - isSecurityProviderIncluded("SunEC", "sun.security.ec.SunEC") ? allocateSunECProvider() : loadProviderReflectively("sun.security.ec.SunEC", debug); - case "Apple", "apple.security.AppleProvider" -> { + String providerClassName = getBuiltInProviderClassName(provName); + if (providerClassName == null) { + return null; + } + return switch (providerClassName) { + case "sun.security.provider.Sun" -> + isSecurityProviderIncluded(providerClassName) ? new sun.security.provider.Sun() : loadProviderReflectively(providerClassName, debug); + case "sun.security.rsa.SunRsaSign" -> + isSecurityProviderIncluded(providerClassName) ? new sun.security.rsa.SunRsaSign() : loadProviderReflectively(providerClassName, debug); + case "com.sun.crypto.provider.SunJCE" -> + isSecurityProviderIncluded(providerClassName) ? new com.sun.crypto.provider.SunJCE() : loadProviderReflectively(providerClassName, debug); + case "sun.security.ssl.SunJSSE" -> + isSecurityProviderIncluded(providerClassName) ? new sun.security.ssl.SunJSSE() : loadProviderReflectively(providerClassName, debug); + case "sun.security.ec.SunEC" -> + isSecurityProviderIncluded(providerClassName) ? allocateSunECProvider() : loadProviderReflectively(providerClassName, debug); + case "apple.security.AppleProvider" -> { try { - Class c = Class.forName("apple.security.AppleProvider"); + Class c = Class.forName(providerClassName); if (Provider.class.isAssignableFrom(c)) { yield (Provider) c.getDeclaredConstructor().newInstance(); } @@ -281,15 +280,6 @@ public Provider loadBuiltInProvider(String provName, Debug debug) { } public static boolean isBuiltInProvider(String provName) { - return switch (provName) { - case "SUN", "sun.security.provider.Sun", - "SunRsaSign", "sun.security.rsa.SunRsaSign", - "SunJCE", "com.sun.crypto.provider.SunJCE", - "SunJSSE", "sun.security.ssl.SunJSSE", - "SunEC", "sun.security.ec.SunEC", - "Apple", "apple.security.AppleProvider" -> - true; - default -> false; - }; + return getBuiltInProviderClassName(provName) != null; } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SecuritySubstitutions.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SecuritySubstitutions.java index 7bfe204f83b8..e9fe26ee16b6 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SecuritySubstitutions.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SecuritySubstitutions.java @@ -69,7 +69,7 @@ final class Target_sun_security_jca_Providers_ExplicitRegistration { @Substitute public static Provider getSunProvider() { if (Boolean.getBoolean(FutureDefaultsOptions.SYSTEM_PROPERTY_PREFIX + "explicit-security-provider-registration") && - !SecurityProvidersSupport.singleton().isSecurityProviderIncluded("SUN", "sun.security.provider.Sun")) { + !SecurityProvidersSupport.singleton().isSecurityProviderIncluded("sun.security.provider.Sun")) { SecurityProvidersSupport.reportMissingProviderRegistration(sun.security.provider.Sun.class); } return new sun.security.provider.Sun(); diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SecurityServicesFeature.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SecurityServicesFeature.java index 2675e3e92da4..e0b8db18f115 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SecurityServicesFeature.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SecurityServicesFeature.java @@ -35,7 +35,6 @@ import java.io.PrintWriter; import java.lang.ref.WeakReference; import java.lang.reflect.Constructor; -import java.lang.reflect.Executable; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; @@ -69,7 +68,6 @@ import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicBoolean; -import java.util.function.BiConsumer; import java.util.function.Function; import javax.crypto.Cipher; @@ -120,7 +118,6 @@ import com.oracle.svm.shared.util.ReflectionUtil; import com.oracle.svm.shared.util.SubstrateUtil; import com.oracle.svm.shared.util.VMError; -import com.oracle.svm.util.GuestAccess; import com.oracle.svm.util.JVMCIReflectionUtil; import com.oracle.svm.util.JVMCIRuntimeClassInitializationSupport; import com.oracle.svm.util.OriginalMethodProvider; @@ -167,9 +164,12 @@ public class SecurityServicesFeature extends JNIRegistrationUtil implements InternalFeature { public static class Options { + private static final String ADDITIONAL_SECURITY_SERVICE_TYPES_DEPRECATION_HELP = "Deprecated. Register the security provider class for reflection instead."; private static final String ADDITIONAL_SECURITY_PROVIDERS_DEPRECATION_HELP = "Deprecated. Security providers are now detected automatically (use the tracing agent, register the provider class " + "for reflection, or build with -H:Preserve=all)."; - private static final String ADDITIONAL_SECURITY_PROVIDERS_DEPRECATION_MESSAGE = "Register the security provider class for reflection instead; the tracing agent does this automatically."; + private static final String ADDITIONAL_SECURITY_PROVIDERS_DEPRECATION_MESSAGE = "Register each security provider class for reflection in reachability-metadata.json using " + + "{\"reflection\":[{\"type\":\"\"}]}; the Tracing Agent generates this metadata automatically."; + private static final String ADDITIONAL_SECURITY_SERVICE_TYPES_DEPRECATION_MESSAGE = ADDITIONAL_SECURITY_PROVIDERS_DEPRECATION_MESSAGE; @Option(help = "Enable automatic registration of security services.")// public static final HostedOptionKey EnableSecurityServicesFeature = new HostedOptionKey<>(true); @@ -177,7 +177,7 @@ public static class Options { @Option(help = "Enable tracing of security services automatic registration.")// public static final HostedOptionKey TraceSecurityServices = new HostedOptionKey<>(false); - @Option(help = "Comma-separated list of additional security service types (fully qualified class names) for automatic registration. Note that these must be JCA compliant.")// + @Option(help = ADDITIONAL_SECURITY_SERVICE_TYPES_DEPRECATION_HELP, deprecated = true, deprecationMessage = ADDITIONAL_SECURITY_SERVICE_TYPES_DEPRECATION_MESSAGE)// public static final HostedOptionKey AdditionalSecurityServiceTypes = new HostedOptionKey<>( AccumulatingLocatableMultiOptionValue.Strings.build()); @@ -533,16 +533,7 @@ private boolean shouldRegisterProviderClassForReflection(BeforeAnalysisAccessImp } public boolean shouldRemoveProvider(Provider p) { - if (p == null) { - return true; - } - if (usedProviders.contains(p)) { - return false; - } - if (substitutionProcessor.isDeleted(GuestAccess.get().lookupType(p.getClass()))) { - return true; - } - return true; + return p == null || !usedProviders.contains(p); } private static void traceRemovedProviders(List removedProviders) { @@ -681,12 +672,11 @@ private void registerServiceReachabilityHandlers(BeforeAnalysisAccess access) { */ for (Class serviceClass : computeKnownServices(access)) { - BiConsumer handler = (a, t) -> registerServices(a, t, serviceClass); for (Method method : serviceClass.getMethods()) { if (method.getName().equals("getInstance")) { checkGetInstanceMethod(method); /* The handler will be executed only once if any of the methods is triggered. */ - access.registerMethodOverrideReachabilityHandler(handler, method); + access.registerMethodOverrideReachabilityHandler((a, t) -> registerServices(a, t, serviceClass), method); } } } @@ -702,18 +692,7 @@ private void registerServiceReachabilityHandlers(BeforeAnalysisAccess access) { * Provider.getDefaultSecureRandomService(). */ Method getDefaultPRNG = ReflectionUtil.lookupMethod(SecureRandom.class, "getDefaultPRNG", boolean.class, byte[].class); - access.registerReachabilityHandler(a -> registerDefaultSecureRandomServices(a, getDefaultPRNG), getDefaultPRNG); - } - - private void registerDefaultSecureRandomServices(DuringAnalysisAccess access, Executable trigger) { - if (FutureDefaultsOptions.explicitSecurityProviderRegistration()) { - // Default acquisition retains the complete fallback provider as a platform dependency. - // \u00A7FS-security-providers.2.4 - Class providerClass = sun.security.provider.Sun.class; - registerProviderClassForReflection(providerClass); - addCandidateProviderClass(providerClass); - } - registerServices(access, trigger, SECURE_RANDOM_SERVICE); + access.registerReachabilityHandler(a -> registerServices(a, getDefaultPRNG, SecureRandom.class), getDefaultPRNG); } private void registerGSSReachabilityHandler(BeforeAnalysisAccess access) { @@ -781,7 +760,7 @@ private String getServiceType(Class serviceClass) { // Checkstyle: disallow Class.getSimpleName } - ConcurrentHashMap processedServiceClasses = new ConcurrentHashMap<>(); + private final Set processedServiceTypes = ConcurrentHashMap.newKeySet(); private void registerServices(DuringAnalysisAccess access, Object trigger, String serviceType) { /* @@ -790,10 +769,30 @@ private void registerServices(DuringAnalysisAccess access, Object trigger, Strin * reachable at run time", therefore we need to make sure that each serviceClass is * processed only once. */ - processedServiceClasses.computeIfAbsent(serviceType, _ -> { + if (processedServiceTypes.add(serviceType)) { + if (FutureDefaultsOptions.explicitSecurityProviderRegistration() && serviceType.equals(SECURE_RANDOM_SERVICE)) { + registerSecureRandomProviders(); + } doRegisterServices(access, trigger, serviceType); - return true; - }); + } + } + + /** + * SecureRandom acquisition conditionally retains its complete configured providers as platform + * dependencies. The application does not need provider reflection metadata. + */ + private void registerSecureRandomProviders() { + // \u00A7FS-security-providers.2.4 + EconomicSet services = availableServices.get(SECURE_RANDOM_SERVICE); + VMError.guarantee(services != null); + EconomicSet> providerClasses = EconomicSet.create(); + for (Service service : services) { + providerClasses.add(service.getProvider().getClass()); + } + for (Class providerClass : providerClasses) { + registerProviderClassForReflection(providerClass); + addCandidateProviderClass(providerClass); + } } private void doRegisterServices(DuringAnalysisAccess access, Object trigger, String serviceType) { @@ -908,7 +907,10 @@ private void registerProvider(Provider provider) { registerForReflection(provider.getClass()); /* Trigger initialization of lazy field java.security.Provider.entrySet. */ provider.entrySet(); - SecurityProvidersSupport.singleton().addVerifiedSecurityProvider(provider.getName(), provider.getClass().getName(), getProviderVerificationResult(provider)); + SecurityProvidersSupport support = SecurityProvidersSupport.singleton(); + String providerClassName = provider.getClass().getName(); + support.addIncludedSecurityProviderClass(providerClassName); + support.addSecurityProviderVerificationResult(providerClassName, getProviderVerificationResult(provider)); } } diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/services/SecureRandomExplicitProviderRegistrationTest.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/services/SecureRandomExplicitProviderRegistrationTest.java new file mode 100644 index 000000000000..fd40fe0a987b --- /dev/null +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/services/SecureRandomExplicitProviderRegistrationTest.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2026, 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.test.services; + +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; + +import org.junit.Assert; +import org.junit.Test; + +import com.oracle.svm.test.NativeImageBuildArgs; + +@NativeImageBuildArgs({ + "--future-defaults=run-time-initialize-security-providers,explicit-security-provider-registration", + "--exact-reachability-metadata=com.oracle.svm.test.services", + "-Dcom.oracle.svm.test.services.SecureRandomExplicitProviderRegistrationTest=true" +}) +public class SecureRandomExplicitProviderRegistrationTest { + /** Tests \u00A7FS-security-providers.2.4. */ + @Test + public void testNamedSecureRandomNeedsNoProviderReflectionMetadata() throws NoSuchAlgorithmException { + SecureRandom random = SecureRandom.getInstance("DRBG"); + + Assert.assertEquals("SUN", random.getProvider().getName()); + Assert.assertEquals("Named SecureRandom acquisition must retain its implementation.", 1, + random.generateSeed(1).length); + } +} diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/services/SecurityServiceExplicitProviderRegistrationTest.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/services/SecurityServiceExplicitProviderRegistrationTest.java index 26ce2cd57bec..6e5ba4ecd711 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/services/SecurityServiceExplicitProviderRegistrationTest.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/services/SecurityServiceExplicitProviderRegistrationTest.java @@ -31,15 +31,21 @@ import java.security.Security; import java.security.Signature; +import org.graalvm.nativeimage.ImageInfo; import org.junit.Assert; +import org.junit.Assume; import org.junit.Test; +import com.oracle.svm.core.jdk.SecurityProvidersSupport; import com.oracle.svm.test.NativeImageBuildArgs; @NativeImageBuildArgs({ - "--future-defaults=run-time-initialize-security-providers,explicit-security-provider-registration" + "--future-defaults=run-time-initialize-security-providers,explicit-security-provider-registration", + "--exact-reachability-metadata=com.oracle.svm.test.services" }) public class SecurityServiceExplicitProviderRegistrationTest { + private static final String REGISTERED_PROVIDER_NAME = "reflection-metadata-provider"; + @Test public void testDefaultSecureRandomIncludesCompleteSunProvider() throws NoSuchAlgorithmException { SecureRandom random = new SecureRandom(); @@ -58,4 +64,23 @@ public void testReachableFactoryDoesNotIncludeUnregisteredProvider() { Assert.assertNull("A reachable Signature factory must not include SunEC.", Security.getProvider("SunEC")); Assert.assertThrows(NoSuchAlgorithmException.class, () -> Signature.getInstance("SHA256withECDSA")); } + + /** Tests \u00A7FS-security-providers.5.3. */ + @Test + public void testUnregisteredProviderCannotReuseVerificationByName() { + Assume.assumeTrue("native image runtime only", ImageInfo.inImageRuntimeCode()); + SecurityProvidersSupport support = SecurityProvidersSupport.singleton(); + + Assert.assertEquals(Boolean.TRUE, + support.getSecurityProviderVerificationResult(new SecurityServiceTest.ReflectionMetadataProvider())); + Assert.assertNull(support.getSecurityProviderVerificationResult(new SameNameUnregisteredProvider())); + } + + public static final class SameNameUnregisteredProvider extends Provider { + private static final long serialVersionUID = 1L; + + public SameNameUnregisteredProvider() { + super(REGISTERED_PROVIDER_NAME, "1.0", "Unregistered provider with a registered provider's name"); + } + } } From 5f449843ba44738cd6e0ecae0baa52b5d3e42142 Mon Sep 17 00:00:00 2001 From: Vojin Jovanovic Date: Thu, 23 Jul 2026 18:42:28 +0200 Subject: [PATCH 31/63] [GR-69858] Fix security provider Javadoc links --- .../svm/core/jdk/SecurityProvidersSupport.java | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SecurityProvidersSupport.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SecurityProvidersSupport.java index 68ef16eacee2..83f8f3e78dc1 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SecurityProvidersSupport.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SecurityProvidersSupport.java @@ -56,19 +56,20 @@ /// /// This class holds the build-time and run-time structures for JCA security-provider inclusion, /// verification, and metadata tracing. The required behavior is specified separately by -/// §FS-security-providers. See the [JCA Security Services documentation](../../../../../../../../../../../../docs/reference-manual/native-image/JCASecurityServices.md) -/// for the user-facing configuration model. +/// §FS-security-providers. See the +/// JCA +/// Security Services documentation for the user-facing configuration model. /// /// ## 1. Build-Time Inclusion and Verification /// -/// [SecurityServicesFeature] coordinates two analysis inputs: subtype reachability discovers +/// `SecurityServicesFeature` coordinates two analysis inputs: subtype reachability discovers /// candidate [Provider] classes, and JCA factory reachability discovers used service types. For a /// provider candidate, the feature queries the reflection registry for type, constructor, or /// factory-method registration. It instantiates accepted candidates through the declared nullary /// constructor or static `provider()` method, then registers their service implementation /// classes. The service-driven path calls the same service-registration machinery independently. /// These mechanisms implement §FS-security-providers.2 and §FS-security-providers.7.3. -/// [SecureRandom] acquisition registers the complete configured providers that declare +/// `SecureRandom` acquisition registers the complete configured providers that declare /// `SecureRandom` services as the narrow platform exception specified by /// §FS-security-providers.2.4. /// @@ -80,7 +81,7 @@ /// /// ## 2. Run-Time Verification-Result Lookup /// -/// The [javax.crypto.JceSecurity] substitutions consult the maps in this singleton when the JDK +/// The `javax.crypto.JceSecurity` substitutions consult the maps in this singleton when the JDK /// verification cache has no entry. [Boolean#TRUE] encodes successful verification; an exception /// object encodes the original verification failure. This lets run-time JCE checks reuse the /// build-time result without retaining the provider instance or repeating JAR verification. @@ -101,7 +102,7 @@ /// /// ## 4. Run-Time Provider Construction /// -/// With run-time provider initialization, the [ProviderConfig] substitutions ask this class to +/// With run-time provider initialization, the `ProviderConfig` substitutions ask this class to /// construct included JDK providers directly. Other configured providers follow the JDK's /// reflective loading path. The substitutions preserve the JDK's provider-list state, recursion /// guard, and retry counter, while the verification maps remain independent of provider creation. From a668e8535c882c776dc65bb476589ce4ddfa8800 Mon Sep 17 00:00:00 2001 From: Vojin Jovanovic Date: Thu, 23 Jul 2026 19:05:50 +0200 Subject: [PATCH 32/63] [GR-69858] Validate grund references in Checkstyle --- substratevm/src/com.oracle.svm.core/.checkstyle_checks.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/substratevm/src/com.oracle.svm.core/.checkstyle_checks.xml b/substratevm/src/com.oracle.svm.core/.checkstyle_checks.xml index f431a9dd81a3..77143c13c1f6 100644 --- a/substratevm/src/com.oracle.svm.core/.checkstyle_checks.xml +++ b/substratevm/src/com.oracle.svm.core/.checkstyle_checks.xml @@ -265,8 +265,8 @@ - - + + From 3a289e6c10aa7250746adc565906814cb21091fc Mon Sep 17 00:00:00 2001 From: Vojin Jovanovic Date: Fri, 24 Jul 2026 07:38:46 +0200 Subject: [PATCH 33/63] GR-69858: Support reflective security providers --- .../functional-spec/security-providers.md | 90 ++++++++++++------- .../svm/agent/BreakpointInterceptor.java | 3 +- .../.checkstyle_checks.xml | 4 +- .../core/jdk/SecurityProvidersSupport.java | 37 ++++---- .../RuntimeCompilationFeature.java | 2 +- .../.checkstyle_checks.xml | 4 +- .../svm/hosted/SecurityServicesFeature.java | 35 +++++--- .../.checkstyle_checks.xml | 4 +- ...andomExplicitProviderRegistrationTest.java | 6 +- ...rviceExplicitProviderRegistrationTest.java | 2 +- 10 files changed, 110 insertions(+), 77 deletions(-) diff --git a/substratevm/docs/functional-spec/security-providers.md b/substratevm/docs/functional-spec/security-providers.md index 72b60e5cb1b1..44a401fe9bbc 100644 --- a/substratevm/docs/functional-spec/security-providers.md +++ b/substratevm/docs/functional-spec/security-providers.md @@ -18,23 +18,29 @@ service-driven provider inclusion and build-time provider-list initialization. ### 1.1 Registered Providers and Services -A provider class is **registered for reflection** for the purposes of this specification when all -the following conditions hold: - -- the class is a concrete `Provider` subtype; -- Native Image can construct it through either a declared nullary constructor or a declared public - static nullary `provider()` method whose return type is assignable to `Provider`; and -- reflection metadata registers access to at least one of the provider type, its declared nullary - constructor, or its qualifying `provider()` method. - -A **registered provider** is a provider whose class satisfies this definition. -A **registered service** is a valid service declared by a registered provider whose implementation -class and required reflective construction metadata Native Image can retain in the executable. +A provider class is **registered for reflection** for the purposes of this specification when it +is a concrete `Provider` subtype and reflection metadata registers access to the provider type, a +declared nullary constructor, or a qualifying `provider()` method. + +A registered provider class is **JDK-constructible** when Native Image can construct it through +either a declared nullary constructor or a declared public static nullary `provider()` method whose +return type is assignable to `Provider`. +A **registered provider** is an instance of a provider class registered for reflection. +A **registered service** is a valid service whose implementation class and required reflective +construction metadata Native Image can retain in the executable. + +JDK-managed acquisition requires a JDK-constructible registered provider. +An application-supplied provider does not need to be JDK-constructible because the application +already possesses its instance. +For such a provider, Native Image does not inspect the provider at build time; each service used at +run time must be retained independently, for example through metadata collected while tracing the +corresponding factory call. Registration is a build-time property. Constructing a provider object at run time does not register the provider or add omitted services to the executable. -Section 2.4 defines the one platform registration rule for `SecureRandom` acquisition. +Section 2.4 defines the platform-owned conditional registration signal for `SecureRandom` +acquisition. ### 1.2 JDK-Managed Providers and Acquisition @@ -98,8 +104,8 @@ Algorithm aliases and provider selection otherwise follow the standard JCA API b Type access, declared nullary constructor access, and qualifying `provider()` method access are alternative registration signals. -Registering any one of them is sufficient if the provider class meets all construction -requirements in section 1.1; registering a signal does not relax those requirements. +Registering any one of them is sufficient to register the provider class. +Type access alone does not make a provider JDK-constructible. ### 2.2 Provider Construction @@ -111,9 +117,11 @@ lookups and JCA factory calls as it exposed when Native Image inspected it at bu ### 2.3 Registration Effects -Registering a provider includes every valid service declared by the provider whose implementation -class Native Image can resolve, and retains the metadata required to construct those service -implementations. +Registering a JDK-constructible provider includes every valid service declared by the provider +whose implementation class Native Image can resolve, and retains the metadata required to +construct those service implementations. +Registering a provider class that is not JDK-constructible does not include its services; services +used through an application-supplied instance must be retained independently. Provider registration does not change the configured provider order or make an unconfigured provider visible by name. This behavior implements §DF-complete-security-provider-registration.2. @@ -122,16 +130,19 @@ This behavior implements §DF-complete-security-provider-registration.2. When a `SecureRandom` acquisition path is reachable, Native Image must register the complete configured providers that declare `SecureRandom` services. -The application does not need to supply reflection metadata for those providers. +The acquisition path is a platform-owned conditional provider-registration signal, so the +application does not need to supply reflection metadata for those providers. This registration has the effects specified in section 2.3, including retention of every valid service that each registered provider declares and whose implementation class Native Image can resolve. This rule applies to the `SecureRandom` constructors, the `SecureRandom.getInstance` overloads, and JDK paths that perform the same default-provider selection. -It is a conditional platform registration rule: Native Image must not register these providers -when no `SecureRandom` acquisition path is reachable. -It is not the earlier service-driven inclusion behavior described in section 7.3. +It is not the earlier service-driven inclusion behavior described in section 7.3: the platform +supplies a provider-registration signal, and the ordinary complete-provider semantics in section +2.3 apply. +Native Image must not register these providers when no `SecureRandom` acquisition path is +reachable. Native Image internal runtime randomness must cause this registration only in an executable that includes the runtime-compilation subsystem that consumes that randomness. @@ -144,7 +155,7 @@ This behavior implements §DF-default-secure-random-provider.2. ### 3.1 JDK-Managed Acquisition -Every JDK-managed provider acquired at run time must be a registered provider. +Every JDK-managed provider acquired at run time must be a JDK-constructible registered provider. This rule applies uniformly to: - direct provider APIs, including provider enumeration, name lookup, filtering, and algorithm @@ -159,8 +170,8 @@ This rule applies uniformly to: A direct JDK fallback must not bypass registration when the configured provider list contains no matching registered provider. -Default `SecureRandom` construction follows the platform registration rule in section 2.4; other -fallbacks must fail before exposing an unregistered provider. +Default `SecureRandom` construction follows the platform-owned conditional registration signal in +section 2.4; other fallbacks must fail before exposing an unregistered provider. ### 3.2 Provider List Lookups @@ -242,6 +253,8 @@ location of `reachability-metadata.json`. For a provider with a supported construction path, the type-only entry suggested by a missing-type diagnostic is sufficient under section 2.1; Native Image retains the provider's construction and service metadata during the subsequent build. +For an application-supplied provider without a supported construction path, the type-only entry +registers the provider class but does not retain its service implementations. This requirement applies both to loading a provider from the configured provider list and to Java Cryptography Extension (JCE) verification of a programmatically supplied provider. @@ -254,10 +267,10 @@ missing-registration diagnostic. An application can construct a provider and pass it directly to a JCA factory. Because the application already possesses this object, its construction and ordinary Java method -calls are not JDK-managed provider acquisition as defined in section 1.2; direct construction -does not, however, waive the registration requirements in section 1.1. -If the provider is registered, the factory can use its registered services without the provider -being in the run-time provider list. +calls are not JDK-managed provider acquisition as defined in section 1.2. +The provider class must be registered for reflection, but it does not need to be JDK-constructible. +The factory can use its registered services without the provider being in the run-time provider +list. If the provider is unregistered and the operation requires JCE verification, the operation follows the missing-registration behavior in section 4.3. @@ -288,6 +301,12 @@ Extension (JCE) verification, Native Image must have established a verification provider at build time. Registration is necessary for such an operation, but registration is not successful verification. +For a registered application-supplied provider class that is not one of the build-time configured +providers, Native Image establishes the successful verification outcome from the class +registration without constructing a provider instance. +This permits JCE use of an existing application-supplied instance while avoiding an unsupported +attempt to reconstruct it. + Native Image must preserve the build-time verification outcome and apply it to run-time instances of that provider class, including an instance whose provider name differs from the name observed at build time. @@ -306,8 +325,11 @@ availability rules in sections 1 and 2. Metadata collected by the Tracing Agent or native metadata tracing from a successful provider lookup must be sufficient for a subsequently built native executable to perform the same lookup and use the same provider services without additional provider metadata. -The collected metadata must retain a supported construction path: declared nullary constructor -access or access to the static `provider()` method. +For a JDK-managed provider, the collected metadata must retain a supported construction path: +declared nullary constructor access or access to the static `provider()` method. +For an application-supplied provider, tracing must register the provider type without inventing a +constructor access and must independently retain each service implementation exercised by the +traced factory calls. Tracing a missing provider registration must use the ordinary reflection metadata format and diagnostics; it must not introduce a security-provider-specific metadata category or error. @@ -350,9 +372,9 @@ With `--future-defaults=explicit-security-provider-registration`, this compatibi disabled. A factory call for an algorithm supplied only by an unregistered provider follows section 4.2, and a lookup that reflectively loads the provider follows section 4.3. -The `SecureRandom` registration rule in section 2.4 remains enabled because it conditionally -supplies complete providers for this commonly used JDK facility rather than inferring providers -from a general service factory. +The platform-owned `SecureRandom` registration signal in section 2.4 remains enabled. +It registers complete providers for this commonly used JDK facility rather than inferring partial +provider support from a general service factory. ### 7.4 Earlier Build-Time Initialization Behavior diff --git a/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/BreakpointInterceptor.java b/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/BreakpointInterceptor.java index 445798828584..1341ea21ee8d 100644 --- a/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/BreakpointInterceptor.java +++ b/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/BreakpointInterceptor.java @@ -751,7 +751,8 @@ private static void traceSecurityProvider(JNIEnvironment jni, JNIObjectHandle pr if (clearException(jni)) { providerClass = nullHandle(); } - traceReflectBreakpoint(jni, providerClass, providerClass, callerClass, "invokeConstructor", providerClass.notEqual(nullHandle()), state.getFullStackTraceOrNull(), Arrays.asList()); + String providerClassName = getClassNameOrNull(jni, providerClass); + traceReflectBreakpoint(jni, agent.handles().javaLangClass, nullHandle(), callerClass, "forName", providerClassName != null, state.getFullStackTraceOrNull(), providerClassName); } private static boolean newArrayInstance(JNIEnvironment jni, JNIObjectHandle thread, Breakpoint bp, InterceptedState state) { diff --git a/substratevm/src/com.oracle.svm.core/.checkstyle_checks.xml b/substratevm/src/com.oracle.svm.core/.checkstyle_checks.xml index 77143c13c1f6..f431a9dd81a3 100644 --- a/substratevm/src/com.oracle.svm.core/.checkstyle_checks.xml +++ b/substratevm/src/com.oracle.svm.core/.checkstyle_checks.xml @@ -265,8 +265,8 @@ - - + + diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SecurityProvidersSupport.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SecurityProvidersSupport.java index 83f8f3e78dc1..e49aa5960282 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SecurityProvidersSupport.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SecurityProvidersSupport.java @@ -25,8 +25,6 @@ package com.oracle.svm.core.jdk; -import static com.oracle.svm.core.annotate.TargetElement.CONSTRUCTOR_NAME; - import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.security.Provider; @@ -37,8 +35,6 @@ import org.graalvm.nativeimage.Platform; import org.graalvm.nativeimage.Platforms; -import com.oracle.svm.configure.config.ConfigurationMemberInfo; -import com.oracle.svm.configure.config.SignatureUtil; import com.oracle.svm.core.metadata.MetadataTracer; import com.oracle.svm.guest.staging.util.ImageHeapMap; import com.oracle.svm.shared.singletons.traits.BuiltinTraits.AllAccess; @@ -67,11 +63,14 @@ /// provider candidate, the feature queries the reflection registry for type, constructor, or /// factory-method registration. It instantiates accepted candidates through the declared nullary /// constructor or static `provider()` method, then registers their service implementation -/// classes. The service-driven path calls the same service-registration machinery independently. -/// These mechanisms implement §FS-security-providers.2 and §FS-security-providers.7.3. -/// `SecureRandom` acquisition registers the complete configured providers that declare -/// `SecureRandom` services as the narrow platform exception specified by -/// §FS-security-providers.2.4. +/// classes. A registered application-supplied provider without either construction path receives a +/// class-based JCE verification result but no automatically registered services. Its service +/// implementations must be retained independently. The service-driven path calls the same +/// service-registration machinery independently. These mechanisms implement +/// §FS-security-providers.2, §FS-security-providers.5.3, and §FS-security-providers.7.3. +/// `SecureRandom` acquisition supplies the platform-owned conditional registration signal +/// specified by §FS-security-providers.2.4. The registered providers then follow the same complete +/// provider-processing path as providers registered through application metadata. /// /// During analysis, the feature obtains each included provider's JCE verification result and stores /// it in this image singleton, keyed by provider class name. Provider inclusion is tracked @@ -86,13 +85,14 @@ /// object encodes the original verification failure. This lets run-time JCE checks reuse the /// build-time result without retaining the provider instance or repeating JAR verification. /// -/// ## 3. Type and Constructor Tracing +/// ## 3. Provider Type Tracing /// -/// The metadata tracer represents provider loading as two distinct accesses: a dynamic -/// [Class#forName(String)] lookup records the type, and constructor access records how the -/// provider is instantiated. Because the JCE lookup already has a provider instance, -/// [#traceProviderLookup(Provider)] emits the constructor access directly instead of creating -/// another provider. This is the native-image counterpart of the Tracing Agent's provider event. +/// The metadata tracer records the type of a provider instance returned by a lookup or supplied to +/// JCE. It does not invent constructor access because an application-supplied provider can have no +/// JDK-supported construction path, for example when it is a non-static inner class. JDK-managed +/// construction paths are traced separately at their actual reflective access sites. This is the +/// native-image counterpart of the Tracing Agent's provider event and implements +/// §FS-security-providers.6. /// /// On a verification-result cache miss, [#reportMissingProviderRegistration(Class)] performs an /// opaque, non-initializing `Class.forName` lookup using the provider's class loader. The opaque @@ -117,8 +117,6 @@ /// @SingletonTraits(access = AllAccess.class, layeredCallbacks = NoLayeredCallbacks.class, layeredInstallationKind = Duplicable.class, other = PartiallyLayerAware.class) public final class SecurityProvidersSupport { - private static final Class[] NO_PARAMETERS = new Class[0]; - /// Provider classes that may be constructed at run time. private final EconomicMap includedSecurityProviderClasses = ImageHeapMap.create("includedSecurityProviderClasses"); @@ -217,14 +215,13 @@ public static void reportMissingProviderRegistration(Class providerClass) { throw VMError.shouldNotReachHere("A security provider without a verification result was registered for reflection: " + providerClass.getName()); } - /// §AR-security-providers.3: Existing providers trace constructor access directly. + /// §AR-security-providers.3: Existing provider instances trace type access. public static Provider traceProviderLookup(Provider provider) { if (provider == null) { return null; } if (MetadataTracer.enabled()) { - MetadataTracer.singleton().traceMethodAccess(provider.getClass(), CONSTRUCTOR_NAME, SignatureUtil.toInternalSignature(NO_PARAMETERS), - ConfigurationMemberInfo.ConfigurationMemberDeclaration.DECLARED); + MetadataTracer.singleton().traceReflectionType(provider.getClass()); } return provider; } diff --git a/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/hosted/runtimecompilation/RuntimeCompilationFeature.java b/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/hosted/runtimecompilation/RuntimeCompilationFeature.java index 368d8c51890c..29633b1e0605 100644 --- a/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/hosted/runtimecompilation/RuntimeCompilationFeature.java +++ b/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/hosted/runtimecompilation/RuntimeCompilationFeature.java @@ -420,7 +420,7 @@ public void duringSetup(DuringSetupAccess c) { /* * Runtime randomness seeds constant blinding and code-offset randomization. Register it * only with its runtime-compilation consumer so ordinary executables do not retain JCA - * security providers. \u00A7FS-security-providers.2.4 + * security providers. §FS-security-providers.2.4 */ if (!ImageSingletons.contains(RuntimeRandomness.class)) { ImageSingletons.add(RuntimeRandomness.class, new SecureRandomRuntimeRandomness()); diff --git a/substratevm/src/com.oracle.svm.hosted/.checkstyle_checks.xml b/substratevm/src/com.oracle.svm.hosted/.checkstyle_checks.xml index 204927b85c52..b544df84dd31 100644 --- a/substratevm/src/com.oracle.svm.hosted/.checkstyle_checks.xml +++ b/substratevm/src/com.oracle.svm.hosted/.checkstyle_checks.xml @@ -225,8 +225,8 @@ - - + + diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SecurityServicesFeature.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SecurityServicesFeature.java index e0b8db18f115..cefb950dc02a 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SecurityServicesFeature.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SecurityServicesFeature.java @@ -251,7 +251,7 @@ public static class Options { /** All providers deemed to be used by this feature. */ private final Set usedProviders = ConcurrentHashMap.newKeySet(); private final Set> candidateProviderClasses = ConcurrentHashMap.newKeySet(); - private final Set> includedProviderClasses = ConcurrentHashMap.newKeySet(); + private final Set> processedProviderClasses = ConcurrentHashMap.newKeySet(); private final AtomicBoolean candidateProviderClassesChanged = new AtomicBoolean(); private Field verificationResultsField; @@ -771,18 +771,18 @@ private void registerServices(DuringAnalysisAccess access, Object trigger, Strin */ if (processedServiceTypes.add(serviceType)) { if (FutureDefaultsOptions.explicitSecurityProviderRegistration() && serviceType.equals(SECURE_RANDOM_SERVICE)) { - registerSecureRandomProviders(); + registerSecureRandomProvidersFromPlatformSignal(); } doRegisterServices(access, trigger, serviceType); } } /** - * SecureRandom acquisition conditionally retains its complete configured providers as platform - * dependencies. The application does not need provider reflection metadata. + * SecureRandom acquisition supplies a platform-owned conditional registration signal. The + * registered providers follow the ordinary complete-provider processing path. */ - private void registerSecureRandomProviders() { - // \u00A7FS-security-providers.2.4 + private void registerSecureRandomProvidersFromPlatformSignal() { + // §FS-security-providers.2.4 EconomicSet services = availableServices.get(SECURE_RANDOM_SERVICE); VMError.guarantee(services != null); EconomicSet> providerClasses = EconomicSet.create(); @@ -944,6 +944,7 @@ private Object getProviderVerificationResult(Provider provider) { private void includeProviderClass(DuringAnalysisAccess access, Class providerClass) { if (!isLoadableProviderClass(access, providerClass)) { + registerApplicationSuppliedProviderClass(providerClass); return; } Provider provider = buildTimeProvidersByClassName.get(providerClass.getName()); @@ -959,6 +960,18 @@ private void includeProviderClass(DuringAnalysisAccess access, Class provider } } + /** + * An application-supplied provider does not need a construction path because the application + * already owns its instance. Preserve only its class-based JCE verification result; its service + * implementations must be registered independently. + */ + private void registerApplicationSuppliedProviderClass(Class providerClass) { + // §FS-security-providers.5.3: Preserve verification without reconstructing the provider. + Provider buildTimeProvider = buildTimeProvidersByClassName.get(providerClass.getName()); + Object verificationResult = buildTimeProvider == null ? Boolean.TRUE : getProviderVerificationResult(buildTimeProvider); + SecurityProvidersSupport.singleton().addSecurityProviderVerificationResult(providerClass.getName(), verificationResult); + } + private boolean isLoadableProviderClass(DuringAnalysisAccess access, Class providerClass) { if (providerClass == null || providerClass.isArray() || providerClass.isPrimitive() || Modifier.isAbstract(providerClass.getModifiers())) { return false; @@ -1122,15 +1135,15 @@ private void registerX509Extensions(DuringAnalysisAccess a) { public void duringAnalysis(DuringAnalysisAccess a) { DuringAnalysisAccessImpl access = (DuringAnalysisAccessImpl) a; boolean newProviderCandidate = candidateProviderClassesChanged.getAndSet(false); - boolean includedProvider = false; + boolean processedProvider = false; for (Class providerClass : candidateProviderClasses) { - if (!includedProviderClasses.contains(providerClass) && isProviderRegisteredForReflection(providerClass)) { - includedProviderClasses.add(providerClass); + if (!processedProviderClasses.contains(providerClass) && isProviderRegisteredForReflection(providerClass)) { + processedProviderClasses.add(providerClass); includeProviderClass(access, providerClass); - includedProvider = true; + processedProvider = true; } } - if (includedProvider || newProviderCandidate) { + if (processedProvider || newProviderCandidate) { // Request the extra pass here, not from the concurrent reachability callback. access.requireAnalysisIteration(); } diff --git a/substratevm/src/com.oracle.svm.test/.checkstyle_checks.xml b/substratevm/src/com.oracle.svm.test/.checkstyle_checks.xml index 90f9b4042d4b..1b039d018479 100644 --- a/substratevm/src/com.oracle.svm.test/.checkstyle_checks.xml +++ b/substratevm/src/com.oracle.svm.test/.checkstyle_checks.xml @@ -187,8 +187,8 @@ - - + + diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/services/SecureRandomExplicitProviderRegistrationTest.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/services/SecureRandomExplicitProviderRegistrationTest.java index fd40fe0a987b..e5cd81393e9e 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/services/SecureRandomExplicitProviderRegistrationTest.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/services/SecureRandomExplicitProviderRegistrationTest.java @@ -38,13 +38,13 @@ "-Dcom.oracle.svm.test.services.SecureRandomExplicitProviderRegistrationTest=true" }) public class SecureRandomExplicitProviderRegistrationTest { - /** Tests \u00A7FS-security-providers.2.4. */ + /** Tests §FS-security-providers.2.4. */ @Test - public void testNamedSecureRandomNeedsNoProviderReflectionMetadata() throws NoSuchAlgorithmException { + public void testNamedSecureRandomUsesPlatformRegistrationSignal() throws NoSuchAlgorithmException { SecureRandom random = SecureRandom.getInstance("DRBG"); Assert.assertEquals("SUN", random.getProvider().getName()); - Assert.assertEquals("Named SecureRandom acquisition must retain its implementation.", 1, + Assert.assertEquals("The platform registration signal must retain the implementation.", 1, random.generateSeed(1).length); } } diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/services/SecurityServiceExplicitProviderRegistrationTest.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/services/SecurityServiceExplicitProviderRegistrationTest.java index 6e5ba4ecd711..c45e9a7e95dd 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/services/SecurityServiceExplicitProviderRegistrationTest.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/services/SecurityServiceExplicitProviderRegistrationTest.java @@ -65,7 +65,7 @@ public void testReachableFactoryDoesNotIncludeUnregisteredProvider() { Assert.assertThrows(NoSuchAlgorithmException.class, () -> Signature.getInstance("SHA256withECDSA")); } - /** Tests \u00A7FS-security-providers.5.3. */ + /** Tests §FS-security-providers.5.3. */ @Test public void testUnregisteredProviderCannotReuseVerificationByName() { Assume.assumeTrue("native image runtime only", ImageInfo.inImageRuntimeCode()); From f1d5462772743d7b71a5d91533ce3293e0d5c9ec Mon Sep 17 00:00:00 2001 From: Vojin Jovanovic Date: Fri, 24 Jul 2026 07:48:33 +0200 Subject: [PATCH 34/63] GR-69858: Revert unnecessary JNI access changes --- .../svm/hosted/jni/JNIAccessFeature.java | 40 +++++++++---------- 1 file changed, 18 insertions(+), 22 deletions(-) diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jni/JNIAccessFeature.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jni/JNIAccessFeature.java index 0ddfc2bdcb3b..536dbef68ea0 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jni/JNIAccessFeature.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jni/JNIAccessFeature.java @@ -30,6 +30,7 @@ import java.lang.reflect.Modifier; import java.util.ArrayList; import java.util.Collections; +import java.util.HashSet; import java.util.IdentityHashMap; import java.util.List; import java.util.Map; @@ -188,18 +189,12 @@ static final class JNICallableJavaMethod { private record RegistrationWithPreserved(T element, boolean preserved) { } - private record NegativeMethodLookup(Class clazz, String methodName, List> parameterTypes) { - } - - private record NegativeFieldLookup(Class clazz, String fieldName) { - } - private final Set>> newClasses = Collections.newSetFromMap(new ConcurrentHashMap<>()); private final Set newNegativeClassLookups = Collections.newSetFromMap(new ConcurrentHashMap<>()); private final Set> newMethods = Collections.newSetFromMap(new ConcurrentHashMap<>()); - private final Set newNegativeMethodLookups = Collections.newSetFromMap(new ConcurrentHashMap<>()); + private final Map, Set[]>>> newNegativeMethodLookups = new ConcurrentHashMap<>(); private final Map, Boolean> newFields = new ConcurrentHashMap<>(); - private final Set newNegativeFieldLookups = Collections.newSetFromMap(new ConcurrentHashMap<>()); + private final Map, Set> newNegativeFieldLookups = new ConcurrentHashMap<>(); // Needs Pair to de-duplicate linkage objects for lack of key-to-key lookups. private final Map> nativeLinkages = new ConcurrentHashMap<>(); @@ -291,7 +286,7 @@ public void registerFieldLookup(AccessCondition condition, boolean preserved, Cl try { register(condition, false, preserved, declaringClass.getDeclaredField(fieldName)); } catch (NoSuchFieldException e) { - newNegativeFieldLookups.add(new NegativeFieldLookup(declaringClass, fieldName)); + newNegativeFieldLookups.computeIfAbsent(declaringClass, _ -> new HashSet<>()).add(fieldName); // noEconomicSet } } @@ -300,7 +295,7 @@ public void registerMethodLookup(AccessCondition condition, boolean preserved, C try { register(condition, preserved, declaringClass.getDeclaredMethod(methodName, parameterTypes)); } catch (NoSuchMethodException e) { - newNegativeMethodLookups.add(new NegativeMethodLookup(declaringClass, methodName, List.of(parameterTypes))); + newNegativeMethodLookups.computeIfAbsent(declaringClass, _ -> new HashSet<>()).add(Pair.create(methodName, parameterTypes)); // noEconomicSet } } @@ -309,7 +304,7 @@ public void registerConstructorLookup(AccessCondition condition, boolean preserv try { register(condition, preserved, declaringClass.getDeclaredConstructor(parameterTypes)); } catch (NoSuchMethodException e) { - newNegativeMethodLookups.add(new NegativeMethodLookup(declaringClass, "", List.of(parameterTypes))); + newNegativeMethodLookups.computeIfAbsent(declaringClass, _ -> new HashSet<>()).add(Pair.create("", parameterTypes)); // noEconomicSet } } } @@ -439,32 +434,33 @@ public void duringAnalysis(DuringAnalysisAccess a) { newClasses.clear(); for (String className : newNegativeClassLookups) { - if (newNegativeClassLookups.remove(className)) { - addNegativeClassLookup(className); - } + addNegativeClassLookup(className); } + newNegativeClassLookups.clear(); for (var registration : newMethods) { addMethod(registration.element(), registration.preserved(), access); } newMethods.clear(); - for (NegativeMethodLookup lookup : newNegativeMethodLookups) { - if (newNegativeMethodLookups.remove(lookup)) { - addNegativeMethodLookup(lookup.clazz(), lookup.methodName(), lookup.parameterTypes().toArray(Class[]::new), access); + newNegativeMethodLookups.forEach((clazz, signatures) -> { + for (Pair[]> signature : signatures) { + addNegativeMethodLookup(clazz, signature.getLeft(), signature.getRight(), access); } - } + }); + newNegativeMethodLookups.clear(); newFields.forEach((registration, writable) -> { addField(registration.element(), registration.preserved(), writable, access); }); newFields.clear(); - for (NegativeFieldLookup lookup : newNegativeFieldLookups) { - if (newNegativeFieldLookups.remove(lookup)) { - addNegativeFieldLookup(lookup.clazz(), lookup.fieldName(), access); + newNegativeFieldLookups.forEach((clazz, fieldNames) -> { + for (String fieldName : fieldNames) { + addNegativeFieldLookup(clazz, fieldName, access); } - } + }); + newNegativeFieldLookups.clear(); access.requireAnalysisIteration(); } From 5350d935602e12ea4697cac9ad816d9c60274544 Mon Sep 17 00:00:00 2001 From: Vojin Jovanovic Date: Fri, 24 Jul 2026 15:52:36 +0200 Subject: [PATCH 35/63] GR-69858: Complete security provider registration handling --- .../functional-spec/security-providers.md | 3 + .../svm/agent/BreakpointInterceptor.java | 13 ++++ .../core/jdk/SecurityProvidersSupport.java | 2 + .../SecuritySubstitutionRuntimeInit.java | 1 + .../RuntimeCompilationFeature.java | 3 +- .../svm/hosted/SecurityServicesFeature.java | 59 +++++++++++++++---- .../svm/hosted/ServiceLoaderFeature.java | 3 +- ...rviceExplicitProviderRegistrationTest.java | 2 + .../test/services/SecurityServiceTest.java | 16 ++++- 9 files changed, 86 insertions(+), 16 deletions(-) diff --git a/substratevm/docs/functional-spec/security-providers.md b/substratevm/docs/functional-spec/security-providers.md index 44a401fe9bbc..a54ecc6c73e2 100644 --- a/substratevm/docs/functional-spec/security-providers.md +++ b/substratevm/docs/functional-spec/security-providers.md @@ -120,6 +120,9 @@ lookups and JCA factory calls as it exposed when Native Image inspected it at bu Registering a JDK-constructible provider includes every valid service declared by the provider whose implementation class Native Image can resolve, and retains the metadata required to construct those service implementations. +When the configured provider list contains multiple instances of the same registered provider +class, Native Image retains every instance and the valid, resolvable services declared by each +instance. Registering a provider class that is not JDK-constructible does not include its services; services used through an application-supplied instance must be retained independently. Provider registration does not change the configured provider order or make an unconfigured diff --git a/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/BreakpointInterceptor.java b/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/BreakpointInterceptor.java index 1341ea21ee8d..9f44bb71e3de 100644 --- a/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/BreakpointInterceptor.java +++ b/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/BreakpointInterceptor.java @@ -743,6 +743,17 @@ private static boolean getStaticSecurityProviderByName(JNIEnvironment jni, JNIOb return true; } + private static boolean getSecurityServiceForProvider(JNIEnvironment jni, JNIObjectHandle thread, Breakpoint bp, InterceptedState state) { + JNIObjectHandle type = getObjectArgument(thread, 0); + JNIObjectHandle algorithm = getObjectArgument(thread, 1); + JNIObjectHandle provider = getObjectArgument(thread, 2); + JNIObjectHandle service = Support.callStaticObjectMethodLLL(jni, bp.clazz, bp.method, type, algorithm, provider); + boolean validResult = !clearException(jni) && service.notEqual(nullHandle()); + traceSecurityProvider(jni, provider, validResult, state.getDirectCallerClass(), state); + return true; + } + + /** §FS-security-providers.6: Provider insertion and lookup trace provider type access. */ private static void traceSecurityProvider(JNIEnvironment jni, JNIObjectHandle provider, boolean validResult, JNIObjectHandle callerClass, InterceptedState state) { if (!validResult) { return; @@ -1853,6 +1864,8 @@ private interface BreakpointHandler { brk("java/security/Security", "addProvider", "(Ljava/security/Provider;)I", BreakpointInterceptor::addStaticSecurityProvider), brk("java/security/Security", "insertProviderAt", "(Ljava/security/Provider;I)I", BreakpointInterceptor::addStaticSecurityProvider), brk("java/security/Security", "getProvider", "(Ljava/lang/String;)Ljava/security/Provider;", BreakpointInterceptor::getStaticSecurityProviderByName), + brk("sun/security/jca/GetInstance", "getService", "(Ljava/lang/String;Ljava/lang/String;Ljava/security/Provider;)Ljava/security/Provider$Service;", + BreakpointInterceptor::getSecurityServiceForProvider), brk("java/lang/ClassLoader", "findSystemClass", "(Ljava/lang/String;)Ljava/lang/Class;", BreakpointInterceptor::findSystemClass), diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SecurityProvidersSupport.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SecurityProvidersSupport.java index e49aa5960282..743f4d09cc83 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SecurityProvidersSupport.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SecurityProvidersSupport.java @@ -241,6 +241,8 @@ private static Provider loadProviderReflectively(String providerClassName, Debug } } + /// §FS-security-providers.3.1, §FS-security-providers.4.3, and + /// §FS-security-providers.7.1: Construct included providers; probe omitted providers normally. public Provider loadBuiltInProvider(String provName, Debug debug) { String providerClassName = getBuiltInProviderClassName(provName); if (providerClassName == null) { diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/runtimeinit/SecuritySubstitutionRuntimeInit.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/runtimeinit/SecuritySubstitutionRuntimeInit.java index 5eb35f3970d5..65e53338574f 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/runtimeinit/SecuritySubstitutionRuntimeInit.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/runtimeinit/SecuritySubstitutionRuntimeInit.java @@ -52,6 +52,7 @@ final class Target_java_security_Security { @TargetClass(java.security.Security.class) final class Target_java_security_Security_ProviderLookup { + /** §FS-security-providers.6: Successful name-based lookup traces provider type access. */ @Substitute public static Provider getProvider(String name) { return SecurityProvidersSupport.traceProviderLookup(sun.security.jca.Providers.getProviderList().getProvider(name)); diff --git a/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/hosted/runtimecompilation/RuntimeCompilationFeature.java b/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/hosted/runtimecompilation/RuntimeCompilationFeature.java index 29633b1e0605..ee50b2c7cb3f 100644 --- a/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/hosted/runtimecompilation/RuntimeCompilationFeature.java +++ b/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/hosted/runtimecompilation/RuntimeCompilationFeature.java @@ -420,8 +420,9 @@ public void duringSetup(DuringSetupAccess c) { /* * Runtime randomness seeds constant blinding and code-offset randomization. Register it * only with its runtime-compilation consumer so ordinary executables do not retain JCA - * security providers. §FS-security-providers.2.4 + * security providers. */ + // §FS-security-providers.2.4 if (!ImageSingletons.contains(RuntimeRandomness.class)) { ImageSingletons.add(RuntimeRandomness.class, new SecureRandomRuntimeRandomness()); } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SecurityServicesFeature.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SecurityServicesFeature.java index cefb950dc02a..4302d5456bfd 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SecurityServicesFeature.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SecurityServicesFeature.java @@ -60,6 +60,7 @@ import java.util.Collection; import java.util.Collections; import java.util.HashMap; +import java.util.IdentityHashMap; import java.util.List; import java.util.Map; import java.util.Objects; @@ -249,11 +250,14 @@ public static class Options { private Map> availableServices; /** All providers deemed to be used by this feature. */ - private final Set usedProviders = ConcurrentHashMap.newKeySet(); + private final Set usedProviders = Collections.synchronizedSet(Collections.newSetFromMap(new IdentityHashMap<>())); private final Set> candidateProviderClasses = ConcurrentHashMap.newKeySet(); private final Set> processedProviderClasses = ConcurrentHashMap.newKeySet(); private final AtomicBoolean candidateProviderClassesChanged = new AtomicBoolean(); + /** Providers marked as used by the deprecated compatibility option. */ + private final EconomicSet manuallyMarkedUsedProviderClassNames = EconomicSet.create(); + private Field verificationResultsField; private Field providerListField; private Field oidTableField; @@ -270,7 +274,7 @@ public static class Options { private final ScanReason scanReason = new OtherReason("Manual rescan triggered from " + SecurityServicesFeature.class); - private final Map buildTimeProvidersByClassName = new HashMap<>(); + private final Map> buildTimeProvidersByClassName = new HashMap<>(); @Override public void afterRegistration(AfterRegistrationAccess a) { @@ -502,6 +506,7 @@ private boolean shouldRemoveVerificationResult(Provider provider) { } private List filterProviderList(Object originalValue) { + // §FS-security-providers.7.4: The build-time list must not expose omitted providers. return ((ProviderList) originalValue).providers().stream().filter(p -> !shouldRemoveProvider(p)).toList(); } @@ -512,6 +517,7 @@ private void registerManuallyConfiguredProvidersForReflection(BeforeAnalysisAcce Class classByName = access.findClassByName(className); UserError.guarantee(classByName != null, "Manually marked security provider class doesn't exist: %s. Make sure that the class name is correct and that the class is on the image builder classpath.", className); + manuallyMarkedUsedProviderClassNames.add(className); if (shouldRegisterProviderClassForReflection(accessImpl, classByName)) { registerProviderClassForReflection(classByName); } @@ -533,7 +539,16 @@ private boolean shouldRegisterProviderClassForReflection(BeforeAnalysisAccessImp } public boolean shouldRemoveProvider(Provider p) { - return p == null || !usedProviders.contains(p); + if (p == null) { + return true; + } + if (usedProviders.contains(p)) { + return false; + } + if (substitutionProcessor.isDeleted(p.getClass())) { + return true; + } + return !manuallyMarkedUsedProviderClassNames.contains(p.getClass().getName()); } private static void traceRemovedProviders(List removedProviders) { @@ -711,7 +726,10 @@ private void initializeServiceRegistrationData() { availableServices = computeAvailableServices(); buildTimeProvidersByClassName.clear(); for (Provider provider : Security.getProviders()) { - buildTimeProvidersByClassName.put(provider.getClass().getName(), provider); + buildTimeProvidersByClassName.computeIfAbsent(provider.getClass().getName(), _ -> new ArrayList<>()).add(provider); + // Configured providers can predate the subtype handler. + // Reflection registration still controls inclusion. §FS-security-providers.2.1 + addCandidateProviderClass(provider.getClass()); } } @@ -719,6 +737,8 @@ private void registerServiceProviderCandidates(BeforeAnalysisAccess access) { BeforeAnalysisAccessImpl accessImpl = (BeforeAnalysisAccessImpl) access; accessImpl.imageClassLoader.classLoaderSupport.serviceProvidersForEach((serviceName, providers) -> { if (serviceName.equals(Provider.class.getName())) { + // Descriptors discover candidates without registering them. + // §FS-security-providers.7.2 for (String provider : providers) { Class providerClass = access.findClassByName(provider); if (providerClass != null) { @@ -773,6 +793,8 @@ private void registerServices(DuringAnalysisAccess access, Object trigger, Strin if (FutureDefaultsOptions.explicitSecurityProviderRegistration() && serviceType.equals(SECURE_RANDOM_SERVICE)) { registerSecureRandomProvidersFromPlatformSignal(); } + // Service reachability is a compatibility inclusion signal. + // §FS-security-providers.7.3 doRegisterServices(access, trigger, serviceType); } } @@ -915,6 +937,7 @@ private void registerProvider(Provider provider) { } private Object getProviderVerificationResult(Provider provider) { + // §FS-security-providers.5.3: Preserve the build-time outcome by provider class. if (!buildTimeProvidersByClassName.containsKey(provider.getClass().getName())) { return Boolean.TRUE; } @@ -942,20 +965,26 @@ private Object getProviderVerificationResult(Provider provider) { } } + // Use the preferred construction path and retain the complete valid, resolvable catalog. + // §FS-security-providers.2.2 and §FS-security-providers.2.3 private void includeProviderClass(DuringAnalysisAccess access, Class providerClass) { if (!isLoadableProviderClass(access, providerClass)) { registerApplicationSuppliedProviderClass(providerClass); return; } - Provider provider = buildTimeProvidersByClassName.get(providerClass.getName()); - if (provider == null) { - provider = instantiateProvider(providerClass); + List providers = buildTimeProvidersByClassName.get(providerClass.getName()); + if (providers == null) { + providers = List.of(instantiateProvider(providerClass)); } - registerProvider(provider); SecurityProvidersSupport.singleton().addIncludedSecurityProviderClass(providerClass.getName()); - for (Service service : provider.getServices()) { - if (isValid(service)) { - registerService(access, service); + // Register every configured instance and the union of their service metadata. + // §FS-security-providers.2.3 + for (Provider provider : providers) { + registerProvider(provider); + for (Service service : provider.getServices()) { + if (isValid(service)) { + registerService(access, service); + } } } } @@ -967,8 +996,8 @@ private void includeProviderClass(DuringAnalysisAccess access, Class provider */ private void registerApplicationSuppliedProviderClass(Class providerClass) { // §FS-security-providers.5.3: Preserve verification without reconstructing the provider. - Provider buildTimeProvider = buildTimeProvidersByClassName.get(providerClass.getName()); - Object verificationResult = buildTimeProvider == null ? Boolean.TRUE : getProviderVerificationResult(buildTimeProvider); + List buildTimeProviders = buildTimeProvidersByClassName.get(providerClass.getName()); + Object verificationResult = buildTimeProviders == null ? Boolean.TRUE : getProviderVerificationResult(buildTimeProviders.getFirst()); SecurityProvidersSupport.singleton().addSecurityProviderVerificationResult(providerClass.getName(), verificationResult); } @@ -1003,6 +1032,7 @@ private static Provider instantiateProvider(Class providerClass) { } private void registerService(DuringAnalysisAccess a, Service service) { + // §FS-security-providers.7.3: Explicit mode disables service-driven provider inclusion. if (FutureDefaultsOptions.explicitSecurityProviderRegistration() && !isProviderRegisteredForReflection(service.getProvider().getClass())) { trace("Skipped service %s because provider %s was not registered for reflection.", asString(service), service.getProvider().getClass().getName()); return; @@ -1036,6 +1066,8 @@ private void registerService(DuringAnalysisAccess a, Service service) { } } + // Recognize every qualifying reflection-registration signal. + // §FS-security-providers.1.1 and §FS-security-providers.2.1 private static boolean isProviderRegisteredForReflection(Class providerClass) { ReflectionDataBuilder reflectionData = (ReflectionDataBuilder) ImageSingletons.lookup(RuntimeReflectionSupport.class); if (reflectionData.isTypeRegisteredForReflection(providerClass)) { @@ -1134,6 +1166,7 @@ private void registerX509Extensions(DuringAnalysisAccess a) { @Override public void duringAnalysis(DuringAnalysisAccess a) { DuringAnalysisAccessImpl access = (DuringAnalysisAccessImpl) a; + // §AR-security-providers.5: Consume concurrent candidates in the serialized feature pass. boolean newProviderCandidate = candidateProviderClassesChanged.getAndSet(false); boolean processedProvider = false; for (Class providerClass : candidateProviderClasses) { diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ServiceLoaderFeature.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ServiceLoaderFeature.java index 47f94a2c1364..a4301804f9c3 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ServiceLoaderFeature.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ServiceLoaderFeature.java @@ -167,7 +167,7 @@ public void afterRegistration(AfterRegistrationAccess access) { @Override public void beforeAnalysis(BeforeAnalysisAccess access) { FeatureImpl.BeforeAnalysisAccessImpl accessImpl = (FeatureImpl.BeforeAnalysisAccessImpl) access; - // Permit an absent class-path descriptor without including omitted providers. + // §FS-security-providers.7.2: Permit an absent descriptor without including its providers. Resources.currentLayer().registerNegativeQuery(access.getApplicationClassLoader().getUnnamedModule(), SECURITY_PROVIDER_SERVICE_RESOURCE); accessImpl.imageClassLoader.classLoaderSupport.serviceProvidersForEach((serviceName, providers) -> { Collection providersToSkip = providers; @@ -219,6 +219,7 @@ void handleServiceClassIsReachable(DuringAnalysisAccess access, ResolvedJavaType * the regular Class.forName failure at run time. */ if (isSecurityProviderService) { + // §FS-security-providers.7.2 registerProviderForRuntimeResourceAccess(access, provider, registeredProviders); continue; } diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/services/SecurityServiceExplicitProviderRegistrationTest.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/services/SecurityServiceExplicitProviderRegistrationTest.java index c45e9a7e95dd..35a4ce9bf681 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/services/SecurityServiceExplicitProviderRegistrationTest.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/services/SecurityServiceExplicitProviderRegistrationTest.java @@ -46,6 +46,7 @@ public class SecurityServiceExplicitProviderRegistrationTest { private static final String REGISTERED_PROVIDER_NAME = "reflection-metadata-provider"; + /** Tests §FS-security-providers.2.3 and §FS-security-providers.2.4. */ @Test public void testDefaultSecureRandomIncludesCompleteSunProvider() throws NoSuchAlgorithmException { SecureRandom random = new SecureRandom(); @@ -59,6 +60,7 @@ public void testDefaultSecureRandomIncludesCompleteSunProvider() throws NoSuchAl Assert.assertNotNull("An unrelated advertised service must remain usable.", jksService.newInstance(null)); } + /** Tests §FS-security-providers.4.2 and §FS-security-providers.7.3. */ @Test public void testReachableFactoryDoesNotIncludeUnregisteredProvider() { Assert.assertNull("A reachable Signature factory must not include SunEC.", Security.getProvider("SunEC")); diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/services/SecurityServiceTest.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/services/SecurityServiceTest.java index 4ee4610ddbb9..7264f1f2b4f6 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/services/SecurityServiceTest.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/services/SecurityServiceTest.java @@ -156,7 +156,7 @@ public void testAutomaticSecurityServiceRegistration() { } } - /** Verifies service-driven GSS provider inclusion. */ + /** Tests service-driven GSS provider inclusion from §FS-security-providers.7.3. */ @Test public void testGSSProviderServiceRegistration() throws Exception { Oid kerberosV5 = new Oid("1.2.840.113554.1.2.2"); @@ -165,6 +165,8 @@ public void testGSSProviderServiceRegistration() throws Exception { Assert.assertEquals("user@REALM", manager.createName("user@REALM", GSSName.NT_USER_NAME, kerberosV5).toString()); } + // Tests provider registration, complete services, and provider-object factory calls. + // §FS-security-providers.2.1, §FS-security-providers.2.3, and §FS-security-providers.5.1 @Test public void testReflectionMetadataProviderRegistration() throws Exception { Provider provider = (Provider) Class.forName(REFLECTION_METADATA_PROVIDER_CLASS_NAME).getDeclaredConstructor().newInstance(); @@ -180,6 +182,8 @@ public void testReflectionMetadataProviderRegistration() throws Exception { } } + // Tests type-only registration, complete services, and provider-object factory calls. + // §FS-security-providers.2.1, §FS-security-providers.2.3, and §FS-security-providers.5.1 @Test public void testTypeMetadataProviderRegistration() throws Exception { Provider provider = new TypeMetadataProvider(); @@ -194,6 +198,7 @@ public void testTypeMetadataProviderRegistration() throws Exception { } } + /** Tests §FS-security-providers.4.1. */ @Test public void testReachableProviderWithoutMetadataDoesNotRegisterServices() { Provider provider = new ReachableProviderWithoutMetadata(); @@ -206,6 +211,7 @@ public void testReachableProviderWithoutMetadataDoesNotRegisterServices() { } } + /** Tests §FS-security-providers.7.2. */ @Test public void testServiceLoaderProviderWithoutMetadataUsesReflectionLookupFailure() { Assume.assumeTrue("native image runtime only", ImageInfo.inImageRuntimeCode()); @@ -220,6 +226,7 @@ public void testServiceLoaderProviderWithoutMetadataUsesReflectionLookupFailure( Assert.assertThrows(NoSuchAlgorithmException.class, () -> JCACompliantNoOpService.getInstance(SERVICE_LOADED_PROVIDER_ALGORITHM)); } + /** Tests §FS-security-providers.7.2. */ @Test public void testServiceLoaderProviderWithMetadataIsPreserved() { Assume.assumeTrue("native image runtime only", ImageInfo.inImageRuntimeCode()); @@ -251,12 +258,14 @@ public void testDeletedProvider() { Assert.assertNull("Provider should not be present.", registered); } + /** Tests the compatibility behavior in §FS-security-providers.7.3. */ @Test public void testReachableBuiltInProviderIsIncluded() { Assume.assumeTrue("needs runtime initialization", FutureDefaultsOptions.securityProvidersInitializedAtRunTime()); Assert.assertNotNull("Service-driven registration should include SunEC.", Security.getProvider("SunEC")); } + /** Tests the compatibility behavior in §FS-security-providers.7.3. */ @Test public void testReachableBuiltInProviderGetService() throws NoSuchAlgorithmException { Assume.assumeTrue("needs runtime initialization", FutureDefaultsOptions.securityProvidersInitializedAtRunTime()); @@ -264,12 +273,14 @@ public void testReachableBuiltInProviderGetService() throws NoSuchAlgorithmExcep Assert.assertEquals("SunEC", service.getProvider().getName()); } + /** Tests the compatibility behavior in §FS-security-providers.7.3. */ @Test public void testReachableBuiltInProviderGetInstance() throws NoSuchAlgorithmException { Assume.assumeTrue("needs runtime initialization", FutureDefaultsOptions.securityProvidersInitializedAtRunTime()); Assert.assertNotNull(GetInstance.getInstance(OMITTED_PROVIDER_SERVICE, null, OMITTED_PROVIDER_ALGORITHM)); } + /** Tests the compatibility behavior in §FS-security-providers.7.3. */ @Test public void testReachableBuiltInProviderGetServices() { Assume.assumeTrue("needs runtime initialization", FutureDefaultsOptions.securityProvidersInitializedAtRunTime()); @@ -277,12 +288,14 @@ public void testReachableBuiltInProviderGetServices() { Assert.assertTrue("Generic service iteration should include the reachable built-in provider.", services.hasNext()); } + /** Tests the standard unavailable result from §FS-security-providers.4.2. */ @Test public void testGenericMissingAlgorithmExhaustsProviderList() { Assume.assumeTrue("needs runtime initialization", FutureDefaultsOptions.securityProvidersInitializedAtRunTime()); Assert.assertThrows(NoSuchAlgorithmException.class, () -> KeyGenerator.getInstance(MISSING_KEY_GENERATOR_ALGORITHM)); } + /** Tests the compatibility behavior in §FS-security-providers.7.3. */ @Test public void testSecurityGetAlgorithmsIncludesReachableBuiltInProviderAlgorithm() { Assume.assumeTrue("needs runtime initialization", FutureDefaultsOptions.securityProvidersInitializedAtRunTime()); @@ -291,6 +304,7 @@ public void testSecurityGetAlgorithmsIncludesReachableBuiltInProviderAlgorithm() algorithms.contains(OMITTED_PROVIDER_ALGORITHM.toUpperCase())); } + /** Tests the compatibility behavior in §FS-security-providers.7.3. */ @Test public void testSecurityGetProvidersFilterIncludesReachableBuiltInProvider() { Assume.assumeTrue("needs runtime initialization", FutureDefaultsOptions.securityProvidersInitializedAtRunTime()); From c8745d18ec471c0e65897541d37629ae23c04e96 Mon Sep 17 00:00:00 2001 From: Vojin Jovanovic Date: Fri, 24 Jul 2026 23:56:43 +0200 Subject: [PATCH 36/63] GR-69858: Fix security provider tracing regressions --- .../functional-spec/security-providers.md | 11 ++++ .../svm/agent/BreakpointInterceptor.java | 58 ++++++++++++++----- .../agent/NativeImageAgentJNIHandleSet.java | 19 ++++++ .../core/jdk/SecurityProvidersSupport.java | 6 +- ...rviceExplicitProviderRegistrationTest.java | 7 ++- 5 files changed, 84 insertions(+), 17 deletions(-) diff --git a/substratevm/docs/functional-spec/security-providers.md b/substratevm/docs/functional-spec/security-providers.md index a54ecc6c73e2..3a7c951992a2 100644 --- a/substratevm/docs/functional-spec/security-providers.md +++ b/substratevm/docs/functional-spec/security-providers.md @@ -325,6 +325,8 @@ availability rules in sections 1 and 2. ## 6. Tracing Metadata +### 6.1 Provider and Service Coverage + Metadata collected by the Tracing Agent or native metadata tracing from a successful provider lookup must be sufficient for a subsequently built native executable to perform the same lookup and use the same provider services without additional provider metadata. @@ -337,6 +339,15 @@ traced factory calls. Tracing a missing provider registration must use the ordinary reflection metadata format and diagnostics; it must not introduce a security-provider-specific metadata category or error. +### 6.2 Observational Transparency + +Tracing must observe the application's provider lookup, service lookup, and service instantiation +without invoking any of those operations an additional time. +It must not initialize or cache a provider, service, implementation class, or resource while +recursive tracing is suppressed. +Metadata must include the nested reflection and resource accesses performed by the application's +actual operation. + ## 7. Transition to the Future Defaults Sections 1 through 6 specify the planned default behavior. diff --git a/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/BreakpointInterceptor.java b/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/BreakpointInterceptor.java index 9f44bb71e3de..2b4b04201bf9 100644 --- a/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/BreakpointInterceptor.java +++ b/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/BreakpointInterceptor.java @@ -734,26 +734,53 @@ private static boolean addStaticSecurityProvider(JNIEnvironment jni, JNIObjectHa return true; } - private static boolean getStaticSecurityProviderByName(JNIEnvironment jni, JNIObjectHandle thread, Breakpoint bp, InterceptedState state) { - JNIObjectHandle callerClass = state.getDirectCallerClass(); - JNIObjectHandle name = getObjectArgument(thread, 0); - JNIObjectHandle provider = Support.callStaticObjectMethodL(jni, bp.clazz, bp.method, name); - boolean validResult = !clearException(jni) && provider.notEqual(nullHandle()); - traceSecurityProvider(jni, provider, validResult, callerClass, state); + /** §FS-security-providers.6.2: Observe an explicit-provider lookup without invoking it. */ + private static boolean getSecurityServiceForProvider(JNIEnvironment jni, JNIObjectHandle thread, @SuppressWarnings("unused") Breakpoint bp, InterceptedState state) { + JNIObjectHandle provider = getObjectArgument(thread, 2); + /* + * Do not invoke GetInstance.getService from its entry breakpoint. Doing so can initialize + * and cache the service while recursive tracing is suppressed, preventing the real call + * from recording its implementation metadata and resource accesses. + */ + traceSecurityProvider(jni, provider, provider.notEqual(nullHandle()), state.getDirectCallerClass(), state); return true; } - private static boolean getSecurityServiceForProvider(JNIEnvironment jni, JNIObjectHandle thread, Breakpoint bp, InterceptedState state) { - JNIObjectHandle type = getObjectArgument(thread, 0); - JNIObjectHandle algorithm = getObjectArgument(thread, 1); - JNIObjectHandle provider = getObjectArgument(thread, 2); - JNIObjectHandle service = Support.callStaticObjectMethodLLL(jni, bp.clazz, bp.method, type, algorithm, provider); - boolean validResult = !clearException(jni) && service.notEqual(nullHandle()); + /** §FS-security-providers.6.1: Trace the provider selected for service instantiation. */ + private static boolean newSecurityServiceInstance(JNIEnvironment jni, JNIObjectHandle thread, @SuppressWarnings("unused") Breakpoint bp, InterceptedState state) { + JNIObjectHandle service = getReceiver(thread); + JNIObjectHandle provider = Support.callObjectMethod(jni, service, agent.handles().javaSecurityProviderServiceGetProvider); + boolean validResult = !clearException(jni) && provider.notEqual(nullHandle()); traceSecurityProvider(jni, provider, validResult, state.getDirectCallerClass(), state); return true; } - /** §FS-security-providers.6: Provider insertion and lookup trace provider type access. */ + /** §FS-security-providers.6.1 and §FS-security-providers.6.2: Trace lookup metadata only. */ + private static boolean getStaticSecurityProviderByName(JNIEnvironment jni, JNIObjectHandle thread, @SuppressWarnings("unused") Breakpoint bp, InterceptedState state) { + JNIObjectHandle providerName = getObjectArgument(thread, 0); + if (providerName.equal(nullHandle())) { + return true; + } + JNIObjectHandle providerClass = switch (fromJniString(jni, providerName)) { + case "SUN", "sun.security.provider.Sun" -> agent.handles().sunSecurityProviderSun; + case "SunRsaSign", "sun.security.rsa.SunRsaSign" -> agent.handles().sunSecurityRsaSunRsaSign; + case "SunEC", "sun.security.ec.SunEC" -> agent.handles().sunSecurityEcSunEC; + case "SunJSSE", "sun.security.ssl.SunJSSE" -> agent.handles().sunSecuritySslSunJSSE; + case "SunJCE", "com.sun.crypto.provider.SunJCE" -> agent.handles().comSunCryptoProviderSunJCE; + case "Apple", "apple.security.AppleProvider" -> agent.handles().appleSecurityAppleProvider; + default -> nullHandle(); + }; + if (providerClass.notEqual(nullHandle())) { + /* + * Record the implicit no-argument construction without eagerly invoking getProvider, + * which would cache the provider while recursive tracing is suppressed. + */ + traceReflectBreakpoint(jni, providerClass, providerClass, state.getDirectCallerClass(), "invokeConstructor", true, state.getFullStackTraceOrNull(), (Object) new String[0]); + } + return true; + } + + /** §FS-security-providers.6.1: Provider insertion and lookup trace provider type access. */ private static void traceSecurityProvider(JNIEnvironment jni, JNIObjectHandle provider, boolean validResult, JNIObjectHandle callerClass, InterceptedState state) { if (!validResult) { return; @@ -1863,9 +1890,12 @@ private interface BreakpointHandler { brk("java/security/Security", "addProvider", "(Ljava/security/Provider;)I", BreakpointInterceptor::addStaticSecurityProvider), brk("java/security/Security", "insertProviderAt", "(Ljava/security/Provider;I)I", BreakpointInterceptor::addStaticSecurityProvider), - brk("java/security/Security", "getProvider", "(Ljava/lang/String;)Ljava/security/Provider;", BreakpointInterceptor::getStaticSecurityProviderByName), + brk("java/security/Security", "getProvider", "(Ljava/lang/String;)Ljava/security/Provider;", + BreakpointInterceptor::getStaticSecurityProviderByName), brk("sun/security/jca/GetInstance", "getService", "(Ljava/lang/String;Ljava/lang/String;Ljava/security/Provider;)Ljava/security/Provider$Service;", BreakpointInterceptor::getSecurityServiceForProvider), + brk("java/security/Provider$Service", "newInstance", "(Ljava/lang/Object;)Ljava/lang/Object;", + BreakpointInterceptor::newSecurityServiceInstance), brk("java/lang/ClassLoader", "findSystemClass", "(Ljava/lang/String;)Ljava/lang/Class;", BreakpointInterceptor::findSystemClass), diff --git a/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/NativeImageAgentJNIHandleSet.java b/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/NativeImageAgentJNIHandleSet.java index 17af9a7948eb..d94cee36a8b0 100644 --- a/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/NativeImageAgentJNIHandleSet.java +++ b/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/NativeImageAgentJNIHandleSet.java @@ -57,6 +57,14 @@ public class NativeImageAgentJNIHandleSet extends JNIHandleSet { final JNIMethodId javaLangObjectGetClass; final JNIMethodId javaLangObjectToString; + final JNIMethodId javaSecurityProviderServiceGetProvider; + final JNIObjectHandle sunSecurityProviderSun; + final JNIObjectHandle sunSecurityRsaSunRsaSign; + final JNIObjectHandle sunSecurityEcSunEC; + final JNIObjectHandle sunSecuritySslSunJSSE; + final JNIObjectHandle comSunCryptoProviderSunJCE; + final JNIObjectHandle appleSecurityAppleProvider; + final JNIObjectHandle javaLangStackOverflowError; private JNIMethodId javaLangInvokeMethodTypeParameterArray = WordFactory.nullPointer(); @@ -163,6 +171,17 @@ public class NativeImageAgentJNIHandleSet extends JNIHandleSet { javaLangObjectGetClass = getMethodId(env, javaLangObject, "getClass", "()Ljava/lang/Class;", false); javaLangObjectToString = getMethodId(env, javaLangObject, "toString", "()Ljava/lang/String;", false); + JNIObjectHandle javaSecurityProviderService = findClass(env, "java/security/Provider$Service"); + javaSecurityProviderServiceGetProvider = getMethodId(env, javaSecurityProviderService, "getProvider", "()Ljava/security/Provider;", false); + + sunSecurityProviderSun = newClassGlobalRef(env, "sun/security/provider/Sun"); + sunSecurityRsaSunRsaSign = newClassGlobalRef(env, "sun/security/rsa/SunRsaSign"); + sunSecurityEcSunEC = newClassGlobalRef(env, "sun/security/ec/SunEC"); + sunSecuritySslSunJSSE = newClassGlobalRef(env, "sun/security/ssl/SunJSSE"); + comSunCryptoProviderSunJCE = newClassGlobalRef(env, "com/sun/crypto/provider/SunJCE"); + JNIObjectHandle appleProvider = findClassOptional(env, "apple/security/AppleProvider"); + appleSecurityAppleProvider = appleProvider.equal(nullHandle()) ? nullHandle() : newTrackedGlobalRef(env, appleProvider); + javaLangStackOverflowError = newClassGlobalRef(env, "java/lang/StackOverflowError"); javaLangIllegalAccessException = newClassGlobalRef(env, "java/lang/IllegalAccessException"); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SecurityProvidersSupport.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SecurityProvidersSupport.java index 743f4d09cc83..c6335d910d60 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SecurityProvidersSupport.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SecurityProvidersSupport.java @@ -37,6 +37,7 @@ import com.oracle.svm.core.metadata.MetadataTracer; import com.oracle.svm.guest.staging.util.ImageHeapMap; +import com.oracle.svm.shared.NeverInline; import com.oracle.svm.shared.singletons.traits.BuiltinTraits.AllAccess; import com.oracle.svm.shared.singletons.traits.BuiltinTraits.NoLayeredCallbacks; import com.oracle.svm.shared.singletons.traits.BuiltinTraits.PartiallyLayerAware; @@ -92,7 +93,7 @@ /// JDK-supported construction path, for example when it is a non-static inner class. JDK-managed /// construction paths are traced separately at their actual reflective access sites. This is the /// native-image counterpart of the Tracing Agent's provider event and implements -/// §FS-security-providers.6. +/// §FS-security-providers.6.1. /// /// On a verification-result cache miss, [#reportMissingProviderRegistration(Class)] performs an /// opaque, non-initializing `Class.forName` lookup using the provider's class loader. The opaque @@ -206,9 +207,10 @@ public static String getBuiltInProviderClassName(String provName) { } /// §AR-security-providers.3: Cache misses probe type access for standard diagnostics. + @NeverInline("Keep the provider class name unknown to static analysis without an opaque compiler node.") public static void reportMissingProviderRegistration(Class providerClass) { try { - Class.forName(GraalDirectives.opaque(providerClass.getName()), false, providerClass.getClassLoader()); + Class.forName(providerClass.getName(), false, providerClass.getClassLoader()); } catch (ClassNotFoundException ex) { throw VMError.shouldNotReachHere("A reachable security provider class was not found.", ex); } diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/services/SecurityServiceExplicitProviderRegistrationTest.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/services/SecurityServiceExplicitProviderRegistrationTest.java index 35a4ce9bf681..6572c6367d05 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/services/SecurityServiceExplicitProviderRegistrationTest.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/services/SecurityServiceExplicitProviderRegistrationTest.java @@ -64,7 +64,12 @@ public void testDefaultSecureRandomIncludesCompleteSunProvider() throws NoSuchAl @Test public void testReachableFactoryDoesNotIncludeUnregisteredProvider() { Assert.assertNull("A reachable Signature factory must not include SunEC.", Security.getProvider("SunEC")); - Assert.assertThrows(NoSuchAlgorithmException.class, () -> Signature.getInstance("SHA256withECDSA")); + try { + Signature signature = Signature.getInstance("SHA256withECDSA"); + Assert.assertNotEquals("A different platform provider may supply the same algorithm.", "SunEC", signature.getProvider().getName()); + } catch (NoSuchAlgorithmException expected) { + /* The algorithm is unavailable when no other platform provider supplies it. */ + } } /** Tests §FS-security-providers.5.3. */ From 82a887072bfbcfcd62c1b2aa4cb54718e1718480 Mon Sep 17 00:00:00 2001 From: Vojin Jovanovic Date: Sat, 25 Jul 2026 17:48:15 +0200 Subject: [PATCH 37/63] GR-69858: Fix security provider agent tracing --- .../functional-spec/security-providers.md | 2 + substratevm/mx.substratevm/mx_substratevm.py | 19 +++++ .../svm/agent/BreakpointInterceptor.java | 30 +++++++ .../agent/NativeImageAgentJNIHandleSet.java | 4 + .../config/SecurityProviderAgentTest.java | 56 +++++++++++++ .../SecurityProviderAgentVerifierTest.java | 79 +++++++++++++++++++ 6 files changed, 190 insertions(+) create mode 100644 substratevm/src/com.oracle.svm.configure.test/src/com/oracle/svm/configure/test/config/SecurityProviderAgentTest.java create mode 100644 substratevm/src/com.oracle.svm.configure.test/src/com/oracle/svm/configure/test/config/SecurityProviderAgentVerifierTest.java diff --git a/substratevm/docs/functional-spec/security-providers.md b/substratevm/docs/functional-spec/security-providers.md index 3a7c951992a2..3491a8b8cfc6 100644 --- a/substratevm/docs/functional-spec/security-providers.md +++ b/substratevm/docs/functional-spec/security-providers.md @@ -330,6 +330,8 @@ availability rules in sections 1 and 2. Metadata collected by the Tracing Agent or native metadata tracing from a successful provider lookup must be sufficient for a subsequently built native executable to perform the same lookup and use the same provider services without additional provider metadata. +This includes provider enumeration and filtering through the `Security` APIs when the JDK loaded +and cached a returned provider before the traced operation. For a JDK-managed provider, the collected metadata must retain a supported construction path: declared nullary constructor access or access to the static `provider()` method. For an application-supplied provider, tracing must register the provider type without inventing a diff --git a/substratevm/mx.substratevm/mx_substratevm.py b/substratevm/mx.substratevm/mx_substratevm.py index 90192f146ebc..a6eb9631c110 100644 --- a/substratevm/mx.substratevm/mx_substratevm.py +++ b/substratevm/mx.substratevm/mx_substratevm.py @@ -925,6 +925,7 @@ def java_desktop_integration_task(native_image, extra_build_args=None): def conditional_config_task(native_image): agent_path = build_native_image_agent(native_image) run_agent_jar_url_protocol_config_test(agent_path) + run_agent_security_provider_config_test(agent_path) conditional_config_filter_path = join(svmbuild_dir(), 'conditional-config-filter.json') with open(conditional_config_filter_path, 'w', encoding='utf-8') as conditional_config_filter: conditional_config_filter.write(''' @@ -938,6 +939,24 @@ def conditional_config_task(native_image): run_nic_conditional_config_test(agent_path, conditional_config_filter_path) +def run_agent_security_provider_config_test(agent_path): + config_dir = join(svmbuild_dir(), 'security-provider-agent-test-config') + if exists(config_dir): + mx.rmtree(config_dir) + + generator_class = 'com.oracle.svm.configure.test.config.SecurityProviderAgentTest' + verifier_class = 'com.oracle.svm.configure.test.config.SecurityProviderAgentVerifierTest' + agent_opts = ['config-output-dir=' + config_dir] + jvm_unittest(['-agentpath:' + agent_path + '=' + ','.join(agent_opts), + '-D' + generator_class + '.generator.enabled=true'] + + _configure_test_jvmci_exports() + + [generator_class + '#enumerateSecurityProviders']) + jvm_unittest(['-D' + verifier_class + '.verifier.enabled=true', + '-D' + verifier_class + '.configpath=' + config_dir] + + _configure_test_jvmci_exports() + + [verifier_class + '#verifyEnumeratedProvidersWereRecorded']) + + def run_agent_jar_url_protocol_config_test(agent_path): config_dir = join(svmbuild_dir(), 'jar-url-protocol-agent-test-config') if exists(config_dir): diff --git a/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/BreakpointInterceptor.java b/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/BreakpointInterceptor.java index 2b4b04201bf9..3d3138d98fae 100644 --- a/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/BreakpointInterceptor.java +++ b/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/BreakpointInterceptor.java @@ -755,6 +755,34 @@ private static boolean newSecurityServiceInstance(JNIEnvironment jni, JNIObjectH return true; } + /** + * §FS-security-providers.6.1: Trace a provider that was cached before a Security API lookup. + */ + private static boolean getCachedSecurityProvider(JNIEnvironment jni, JNIObjectHandle thread, @SuppressWarnings("unused") Breakpoint bp, InterceptedState state) { + JNIObjectHandle securityApiCaller = findSecurityApiCaller(jni, state); + if (securityApiCaller.equal(nullHandle())) { + return true; + } + JNIObjectHandle providerConfig = getReceiver(thread); + JNIObjectHandle provider = jniFunctions().getGetObjectField().invoke(jni, providerConfig, agent.handles().sunSecurityJcaProviderConfigProvider); + boolean validResult = !clearException(jni) && provider.notEqual(nullHandle()); + traceSecurityProvider(jni, provider, validResult, securityApiCaller, state); + return true; + } + + private static JNIObjectHandle findSecurityApiCaller(JNIEnvironment jni, InterceptedState state) { + JNIObjectHandle securityApiCaller = nullHandle(); + for (int depth = 1;; depth++) { + JNIObjectHandle callerClass = state.getCallerClass(depth); + if (callerClass.equal(nullHandle())) { + return securityApiCaller; + } + if ("java.security.Security".equals(getClassNameOrNull(jni, callerClass))) { + securityApiCaller = state.getCallerClass(depth + 1); + } + } + } + /** §FS-security-providers.6.1 and §FS-security-providers.6.2: Trace lookup metadata only. */ private static boolean getStaticSecurityProviderByName(JNIEnvironment jni, JNIObjectHandle thread, @SuppressWarnings("unused") Breakpoint bp, InterceptedState state) { JNIObjectHandle providerName = getObjectArgument(thread, 0); @@ -1896,6 +1924,8 @@ private interface BreakpointHandler { BreakpointInterceptor::getSecurityServiceForProvider), brk("java/security/Provider$Service", "newInstance", "(Ljava/lang/Object;)Ljava/lang/Object;", BreakpointInterceptor::newSecurityServiceInstance), + brk("sun/security/jca/ProviderConfig", "getProvider", "()Ljava/security/Provider;", + BreakpointInterceptor::getCachedSecurityProvider), brk("java/lang/ClassLoader", "findSystemClass", "(Ljava/lang/String;)Ljava/lang/Class;", BreakpointInterceptor::findSystemClass), diff --git a/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/NativeImageAgentJNIHandleSet.java b/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/NativeImageAgentJNIHandleSet.java index d94cee36a8b0..b9768d885dae 100644 --- a/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/NativeImageAgentJNIHandleSet.java +++ b/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/NativeImageAgentJNIHandleSet.java @@ -58,6 +58,7 @@ public class NativeImageAgentJNIHandleSet extends JNIHandleSet { final JNIMethodId javaLangObjectToString; final JNIMethodId javaSecurityProviderServiceGetProvider; + final JNIFieldId sunSecurityJcaProviderConfigProvider; final JNIObjectHandle sunSecurityProviderSun; final JNIObjectHandle sunSecurityRsaSunRsaSign; final JNIObjectHandle sunSecurityEcSunEC; @@ -174,6 +175,9 @@ public class NativeImageAgentJNIHandleSet extends JNIHandleSet { JNIObjectHandle javaSecurityProviderService = findClass(env, "java/security/Provider$Service"); javaSecurityProviderServiceGetProvider = getMethodId(env, javaSecurityProviderService, "getProvider", "()Ljava/security/Provider;", false); + JNIObjectHandle sunSecurityJcaProviderConfig = findClass(env, "sun/security/jca/ProviderConfig"); + sunSecurityJcaProviderConfigProvider = getFieldId(env, sunSecurityJcaProviderConfig, "provider", "Ljava/security/Provider;", false); + sunSecurityProviderSun = newClassGlobalRef(env, "sun/security/provider/Sun"); sunSecurityRsaSunRsaSign = newClassGlobalRef(env, "sun/security/rsa/SunRsaSign"); sunSecurityEcSunEC = newClassGlobalRef(env, "sun/security/ec/SunEC"); diff --git a/substratevm/src/com.oracle.svm.configure.test/src/com/oracle/svm/configure/test/config/SecurityProviderAgentTest.java b/substratevm/src/com.oracle.svm.configure.test/src/com/oracle/svm/configure/test/config/SecurityProviderAgentTest.java new file mode 100644 index 000000000000..bf4fafcf6a70 --- /dev/null +++ b/substratevm/src/com.oracle.svm.configure.test/src/com/oracle/svm/configure/test/config/SecurityProviderAgentTest.java @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2026, 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.configure.test.config; + +import static org.junit.Assume.assumeTrue; + +import java.security.Provider; +import java.security.Security; + +import org.junit.Assert; +import org.junit.Test; + +/** + * Exercises provider enumeration under the native-image agent. The JCK harness and other + * applications can initialize some providers before their test code enumerates the provider list, + * so the agent must record both newly loaded and already cached providers. + */ +public class SecurityProviderAgentTest { + private static final String GENERATOR_ENABLED_PROPERTY = SecurityProviderAgentTest.class.getName() + ".generator.enabled"; + + /** Tests §FS-security-providers.6.1. */ + @Test + public void enumerateSecurityProviders() throws Exception { + assumeTrue("Test must be explicitly enabled because it is designed to run under the agent", + Boolean.getBoolean(GENERATOR_ENABLED_PROPERTY)); + + Provider[] providers = Security.getProviders(); + Assert.assertTrue("The JDK must have at least one configured security provider", providers.length > 0); + Assert.assertSame(ReflectiveProbe.class, Class.forName(ReflectiveProbe.class.getName())); + } + + static final class ReflectiveProbe { + } +} diff --git a/substratevm/src/com.oracle.svm.configure.test/src/com/oracle/svm/configure/test/config/SecurityProviderAgentVerifierTest.java b/substratevm/src/com.oracle.svm.configure.test/src/com/oracle/svm/configure/test/config/SecurityProviderAgentVerifierTest.java new file mode 100644 index 000000000000..6fdfd72e2b8b --- /dev/null +++ b/substratevm/src/com.oracle.svm.configure.test/src/com/oracle/svm/configure/test/config/SecurityProviderAgentVerifierTest.java @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2026, 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.configure.test.config; + +import static org.junit.Assume.assumeTrue; + +import java.nio.file.Paths; +import java.security.Provider; +import java.security.Security; + +import org.junit.Assert; +import org.junit.Test; + +import com.oracle.svm.configure.NamedConfigurationTypeDescriptor; +import com.oracle.svm.configure.UnresolvedAccessCondition; +import com.oracle.svm.configure.config.ConfigurationFileCollection; +import com.oracle.svm.configure.config.ConfigurationSet; +import com.oracle.svm.configure.config.TypeConfiguration; +import com.oracle.svm.configure.test.AddExports; + +/** + * Verifies the metadata generated by {@link SecurityProviderAgentTest}. + */ +@AddExports({"org.graalvm.nativeimage/org.graalvm.nativeimage.impl", + "jdk.graal.compiler/jdk.graal.compiler.util", + "jdk.graal.compiler/jdk.graal.compiler.util.json"}) +public class SecurityProviderAgentVerifierTest { + private static final String VERIFIER_ENABLED_PROPERTY = SecurityProviderAgentVerifierTest.class.getName() + ".verifier.enabled"; + private static final String CONFIG_PATH_PROPERTY = SecurityProviderAgentVerifierTest.class.getName() + ".configpath"; + + @Test + public void verifyEnumeratedProvidersWereRecorded() throws Exception { + assumeTrue("Test must be explicitly enabled because it verifies a previous agent run", + Boolean.getBoolean(VERIFIER_ENABLED_PROPERTY)); + + TypeConfiguration reflectionConfiguration = loadActualConfig().getReflectionConfiguration(); + Assert.assertNotNull(""" + The agent did not record the reflection probe, so this verifier is not \ + checking an agent-generated configuration.""", + reflectionConfiguration.get(UnresolvedAccessCondition.unconditional(), + NamedConfigurationTypeDescriptor.fromReflectionName(SecurityProviderAgentTest.ReflectiveProbe.class.getName()))); + for (Provider provider : Security.getProviders()) { + String providerClassName = provider.getClass().getName(); + Assert.assertNotNull("Missing reflection metadata for enumerated provider " + providerClassName, + reflectionConfiguration.get(UnresolvedAccessCondition.unconditional(), + NamedConfigurationTypeDescriptor.fromReflectionName(providerClassName))); + } + } + + private static ConfigurationSet loadActualConfig() throws Exception { + String configurationPath = System.getProperty(CONFIG_PATH_PROPERTY); + Assert.assertNotNull("Missing generated configuration path", configurationPath); + ConfigurationFileCollection configurationFileCollection = new ConfigurationFileCollection(); + configurationFileCollection.addDirectory(Paths.get(configurationPath)); + return configurationFileCollection.loadConfigurationSet(e -> e, null, null); + } +} From f11af0f6621451aca7857c750bc2f3f7f6bdaa06 Mon Sep 17 00:00:00 2001 From: Vojin Jovanovic Date: Sun, 26 Jul 2026 09:46:22 +0200 Subject: [PATCH 38/63] Fix security provider tracing metadata --- .../functional-spec/security-providers.md | 9 +- substratevm/mx.substratevm/mx_substratevm.py | 31 +++--- .../svm/agent/BreakpointInterceptor.java | 102 ++++++++++++++++-- .../agent/NativeImageAgentJNIHandleSet.java | 23 ++++ .../config/SecurityProviderAgentTest.java | 25 +++++ .../SecurityProviderAgentVerifierTest.java | 22 ++++ .../oracle/svm/jvmtiagentbase/Support.java | 6 +- 7 files changed, 197 insertions(+), 21 deletions(-) diff --git a/substratevm/docs/functional-spec/security-providers.md b/substratevm/docs/functional-spec/security-providers.md index 3491a8b8cfc6..ad4d3c5ca800 100644 --- a/substratevm/docs/functional-spec/security-providers.md +++ b/substratevm/docs/functional-spec/security-providers.md @@ -332,11 +332,18 @@ lookup must be sufficient for a subsequently built native executable to perform and use the same provider services without additional provider metadata. This includes provider enumeration and filtering through the `Security` APIs when the JDK loaded and cached a returned provider before the traced operation. +Provider-list mutation through `Security.addProvider`, `Security.insertProviderAt`, or +`Security.removeProvider` is not provider enumeration or lookup. Tracing such a mutation must +register a supplied provider that the operation observes, but it must not register unrelated +configured providers loaded or inspected by the JDK while maintaining the provider list. For a JDK-managed provider, the collected metadata must retain a supported construction path: declared nullary constructor access or access to the static `provider()` method. For an application-supplied provider, tracing must register the provider type without inventing a constructor access and must independently retain each service implementation exercised by the -traced factory calls. +traced factory calls. This includes a service implementation named only by +`Provider.Service.getClassName()`: the caller-filtered trace must retain the construction access +performed inside `Provider.Service.newInstance` and attribute it to the application operation that +selected the service. Tracing a missing provider registration must use the ordinary reflection metadata format and diagnostics; it must not introduce a security-provider-specific metadata category or error. diff --git a/substratevm/mx.substratevm/mx_substratevm.py b/substratevm/mx.substratevm/mx_substratevm.py index a6eb9631c110..0774c64c30dc 100644 --- a/substratevm/mx.substratevm/mx_substratevm.py +++ b/substratevm/mx.substratevm/mx_substratevm.py @@ -940,21 +940,26 @@ def conditional_config_task(native_image): def run_agent_security_provider_config_test(agent_path): - config_dir = join(svmbuild_dir(), 'security-provider-agent-test-config') - if exists(config_dir): - mx.rmtree(config_dir) - generator_class = 'com.oracle.svm.configure.test.config.SecurityProviderAgentTest' verifier_class = 'com.oracle.svm.configure.test.config.SecurityProviderAgentVerifierTest' - agent_opts = ['config-output-dir=' + config_dir] - jvm_unittest(['-agentpath:' + agent_path + '=' + ','.join(agent_opts), - '-D' + generator_class + '.generator.enabled=true'] + - _configure_test_jvmci_exports() + - [generator_class + '#enumerateSecurityProviders']) - jvm_unittest(['-D' + verifier_class + '.verifier.enabled=true', - '-D' + verifier_class + '.configpath=' + config_dir] + - _configure_test_jvmci_exports() + - [verifier_class + '#verifyEnumeratedProvidersWereRecorded']) + cases = [ + ('enumeration', 'enumerateSecurityProviders', 'verifyEnumeratedProvidersWereRecorded'), + ('mutation', 'programmaticProviderMutationDoesNotTraceConfiguredProviders', + 'verifyMutationRecordedOnlySuppliedProvider'), + ] + for name, generator_method, verifier_method in cases: + config_dir = join(svmbuild_dir(), 'security-provider-agent-' + name + '-test-config') + if exists(config_dir): + mx.rmtree(config_dir) + agent_opts = ['config-output-dir=' + config_dir] + jvm_unittest(['-agentpath:' + agent_path + '=' + ','.join(agent_opts), + '-D' + generator_class + '.generator.enabled=true'] + + _configure_test_jvmci_exports() + + [generator_class + '#' + generator_method]) + jvm_unittest(['-D' + verifier_class + '.verifier.enabled=true', + '-D' + verifier_class + '.configpath=' + config_dir] + + _configure_test_jvmci_exports() + + [verifier_class + '#' + verifier_method]) def run_agent_jar_url_protocol_config_test(agent_path): diff --git a/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/BreakpointInterceptor.java b/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/BreakpointInterceptor.java index 3d3138d98fae..36f66a254fc3 100644 --- a/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/BreakpointInterceptor.java +++ b/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/BreakpointInterceptor.java @@ -705,6 +705,16 @@ private static boolean unreflectConstructor(JNIEnvironment jni, JNIObjectHandle private static boolean handleInvokeConstructor(JNIEnvironment jni, @SuppressWarnings("unused") Breakpoint bp, InterceptedState state, JNIObjectHandle constructor) { JNIObjectHandle callerClass = state.getDirectCallerClass(); + JNIObjectHandle providerServiceCaller = findProviderServiceCaller(jni, state); + if (providerServiceCaller.notEqual(nullHandle())) { + /* + * Provider.Service performs the reflective construction on behalf of the application. + * Attribute that access to the application operation so caller filters do not discard + * the service implementation metadata merely because the immediate caller is in the + * JDK. + */ + callerClass = providerServiceCaller; + } JNIObjectHandle declaring = Support.callObjectMethod(jni, constructor, agent.handles().javaLangReflectMemberGetDeclaringClass); if (clearException(jni)) { @@ -751,7 +761,9 @@ private static boolean newSecurityServiceInstance(JNIEnvironment jni, JNIObjectH JNIObjectHandle service = getReceiver(thread); JNIObjectHandle provider = Support.callObjectMethod(jni, service, agent.handles().javaSecurityProviderServiceGetProvider); boolean validResult = !clearException(jni) && provider.notEqual(nullHandle()); - traceSecurityProvider(jni, provider, validResult, state.getDirectCallerClass(), state); + JNIObjectHandle callerClass = findExternalSecurityCaller(jni, state, 1); + traceSecurityProvider(jni, provider, validResult, + callerClass.notEqual(nullHandle()) ? callerClass : state.getDirectCallerClass(), state); return true; } @@ -759,30 +771,108 @@ private static boolean newSecurityServiceInstance(JNIEnvironment jni, JNIObjectH * §FS-security-providers.6.1: Trace a provider that was cached before a Security API lookup. */ private static boolean getCachedSecurityProvider(JNIEnvironment jni, JNIObjectHandle thread, @SuppressWarnings("unused") Breakpoint bp, InterceptedState state) { - JNIObjectHandle securityApiCaller = findSecurityApiCaller(jni, state); + JNIObjectHandle securityApiCaller = findSecurityAcquisitionCaller(state); if (securityApiCaller.equal(nullHandle())) { return true; } JNIObjectHandle providerConfig = getReceiver(thread); JNIObjectHandle provider = jniFunctions().getGetObjectField().invoke(jni, providerConfig, agent.handles().sunSecurityJcaProviderConfigProvider); boolean validResult = !clearException(jni) && provider.notEqual(nullHandle()); + JNIObjectHandle requestedProviderName = findRequestedSecurityProviderName(thread, state); + if (validResult && requestedProviderName.notEqual(nullHandle())) { + JNIObjectHandle providerName = Support.callObjectMethod(jni, provider, agent.handles().javaSecurityProviderGetName); + validResult = !clearException(jni) && + fromJniString(jni, requestedProviderName).equals(fromJniString(jni, providerName)); + } traceSecurityProvider(jni, provider, validResult, securityApiCaller, state); return true; } - private static JNIObjectHandle findSecurityApiCaller(JNIEnvironment jni, InterceptedState state) { + private static JNIObjectHandle findSecurityAcquisitionCaller(InterceptedState state) { JNIObjectHandle securityApiCaller = nullHandle(); for (int depth = 1;; depth++) { - JNIObjectHandle callerClass = state.getCallerClass(depth); - if (callerClass.equal(nullHandle())) { + JNIMethodId callerMethod = state.getCallerMethod(depth); + if (callerMethod.isNull()) { return securityApiCaller; } - if ("java.security.Security".equals(getClassNameOrNull(jni, callerClass))) { + if (isSecurityMutationMethod(callerMethod)) { + return nullHandle(); + } + if (isSecurityAcquisitionMethod(callerMethod)) { securityApiCaller = state.getCallerClass(depth + 1); } } } + private static JNIObjectHandle findRequestedSecurityProviderName(JNIObjectHandle thread, InterceptedState state) { + JNIObjectHandle requestedProviderName = nullHandle(); + for (int depth = 1;; depth++) { + JNIMethodId callerMethod = state.getCallerMethod(depth); + if (callerMethod.isNull()) { + return requestedProviderName; + } + if (isSecurityMutationMethod(callerMethod)) { + return nullHandle(); + } + if (isSecurityAcquisitionMethod(callerMethod)) { + requestedProviderName = callerMethod.equal(agent.handles().javaSecurityGetProvider) + ? Support.getObjectArgument(thread, depth, 0) + : nullHandle(); + } + } + } + + private static boolean isSecurityAcquisitionMethod(JNIMethodId method) { + NativeImageAgentJNIHandleSet handles = agent.handles(); + return method.equal(handles.javaSecurityGetProvider) || + method.equal(handles.javaSecurityGetProviders) || + method.equal(handles.javaSecurityGetProvidersString) || + method.equal(handles.javaSecurityGetProvidersMap) || + method.equal(handles.javaSecurityGetAlgorithms); + } + + private static boolean isSecurityMutationMethod(JNIMethodId method) { + NativeImageAgentJNIHandleSet handles = agent.handles(); + return method.equal(handles.javaSecurityAddProvider) || + method.equal(handles.javaSecurityInsertProviderAt) || + method.equal(handles.javaSecurityRemoveProvider); + } + + private static JNIObjectHandle findProviderServiceCaller(JNIEnvironment jni, InterceptedState state) { + for (int depth = 1;; depth++) { + JNIMethodId callerMethod = state.getCallerMethod(depth); + if (callerMethod.isNull()) { + return nullHandle(); + } + if (callerMethod.equal(agent.handles().javaSecurityProviderServiceNewInstance)) { + return findExternalSecurityCaller(jni, state, depth + 1); + } + } + } + + private static JNIObjectHandle findExternalSecurityCaller(JNIEnvironment jni, InterceptedState state, int startDepth) { + for (int depth = startDepth;; depth++) { + JNIMethodId callerMethod = state.getCallerMethod(depth); + if (callerMethod.isNull()) { + return nullHandle(); + } + JNIObjectHandle callerClass = getMethodDeclaringClass(callerMethod); + String callerClassName = getClassNameOrNull(jni, callerClass); + if (callerClassName != null && !isJdkSecurityImplementation(callerClassName)) { + return callerClass; + } + } + } + + private static boolean isJdkSecurityImplementation(String className) { + return className.startsWith("java.security.") || + className.startsWith("javax.crypto.") || + className.startsWith("javax.net.ssl.") || + className.startsWith("javax.security.") || + className.startsWith("sun.security.") || + className.startsWith("com.sun.crypto.provider."); + } + /** §FS-security-providers.6.1 and §FS-security-providers.6.2: Trace lookup metadata only. */ private static boolean getStaticSecurityProviderByName(JNIEnvironment jni, JNIObjectHandle thread, @SuppressWarnings("unused") Breakpoint bp, InterceptedState state) { JNIObjectHandle providerName = getObjectArgument(thread, 0); diff --git a/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/NativeImageAgentJNIHandleSet.java b/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/NativeImageAgentJNIHandleSet.java index b9768d885dae..ee2e0a10cf45 100644 --- a/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/NativeImageAgentJNIHandleSet.java +++ b/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/NativeImageAgentJNIHandleSet.java @@ -58,6 +58,16 @@ public class NativeImageAgentJNIHandleSet extends JNIHandleSet { final JNIMethodId javaLangObjectToString; final JNIMethodId javaSecurityProviderServiceGetProvider; + final JNIMethodId javaSecurityProviderServiceNewInstance; + final JNIMethodId javaSecurityProviderGetName; + final JNIMethodId javaSecurityGetProvider; + final JNIMethodId javaSecurityGetProviders; + final JNIMethodId javaSecurityGetProvidersString; + final JNIMethodId javaSecurityGetProvidersMap; + final JNIMethodId javaSecurityGetAlgorithms; + final JNIMethodId javaSecurityAddProvider; + final JNIMethodId javaSecurityInsertProviderAt; + final JNIMethodId javaSecurityRemoveProvider; final JNIFieldId sunSecurityJcaProviderConfigProvider; final JNIObjectHandle sunSecurityProviderSun; final JNIObjectHandle sunSecurityRsaSunRsaSign; @@ -174,6 +184,19 @@ public class NativeImageAgentJNIHandleSet extends JNIHandleSet { JNIObjectHandle javaSecurityProviderService = findClass(env, "java/security/Provider$Service"); javaSecurityProviderServiceGetProvider = getMethodId(env, javaSecurityProviderService, "getProvider", "()Ljava/security/Provider;", false); + javaSecurityProviderServiceNewInstance = getMethodId(env, javaSecurityProviderService, "newInstance", "(Ljava/lang/Object;)Ljava/lang/Object;", false); + JNIObjectHandle javaSecurityProvider = findClass(env, "java/security/Provider"); + javaSecurityProviderGetName = getMethodId(env, javaSecurityProvider, "getName", "()Ljava/lang/String;", false); + + JNIObjectHandle javaSecuritySecurity = findClass(env, "java/security/Security"); + javaSecurityGetProvider = getMethodId(env, javaSecuritySecurity, "getProvider", "(Ljava/lang/String;)Ljava/security/Provider;", true); + javaSecurityGetProviders = getMethodId(env, javaSecuritySecurity, "getProviders", "()[Ljava/security/Provider;", true); + javaSecurityGetProvidersString = getMethodId(env, javaSecuritySecurity, "getProviders", "(Ljava/lang/String;)[Ljava/security/Provider;", true); + javaSecurityGetProvidersMap = getMethodId(env, javaSecuritySecurity, "getProviders", "(Ljava/util/Map;)[Ljava/security/Provider;", true); + javaSecurityGetAlgorithms = getMethodId(env, javaSecuritySecurity, "getAlgorithms", "(Ljava/lang/String;)Ljava/util/Set;", true); + javaSecurityAddProvider = getMethodId(env, javaSecuritySecurity, "addProvider", "(Ljava/security/Provider;)I", true); + javaSecurityInsertProviderAt = getMethodId(env, javaSecuritySecurity, "insertProviderAt", "(Ljava/security/Provider;I)I", true); + javaSecurityRemoveProvider = getMethodId(env, javaSecuritySecurity, "removeProvider", "(Ljava/lang/String;)V", true); JNIObjectHandle sunSecurityJcaProviderConfig = findClass(env, "sun/security/jca/ProviderConfig"); sunSecurityJcaProviderConfigProvider = getFieldId(env, sunSecurityJcaProviderConfig, "provider", "Ljava/security/Provider;", false); diff --git a/substratevm/src/com.oracle.svm.configure.test/src/com/oracle/svm/configure/test/config/SecurityProviderAgentTest.java b/substratevm/src/com.oracle.svm.configure.test/src/com/oracle/svm/configure/test/config/SecurityProviderAgentTest.java index bf4fafcf6a70..a3b6cb431ace 100644 --- a/substratevm/src/com.oracle.svm.configure.test/src/com/oracle/svm/configure/test/config/SecurityProviderAgentTest.java +++ b/substratevm/src/com.oracle.svm.configure.test/src/com/oracle/svm/configure/test/config/SecurityProviderAgentTest.java @@ -51,6 +51,31 @@ public void enumerateSecurityProviders() throws Exception { Assert.assertSame(ReflectiveProbe.class, Class.forName(ReflectiveProbe.class.getName())); } + /** Tests §FS-security-providers.6.1. */ + @Test + public void programmaticProviderMutationDoesNotTraceConfiguredProviders() throws Exception { + assumeTrue("Test must be explicitly enabled because it is designed to run under the agent", + Boolean.getBoolean(GENERATOR_ENABLED_PROPERTY)); + + Provider provider = new ProgrammaticallyAddedProvider(); + int position = Security.addProvider(provider); + try { + Assert.assertTrue("The test provider must be added", position > 0); + Assert.assertSame(ReflectiveProbe.class, Class.forName(ReflectiveProbe.class.getName())); + } finally { + Security.removeProvider(provider.getName()); + } + } + static final class ReflectiveProbe { } + + static final class ProgrammaticallyAddedProvider extends Provider { + private static final long serialVersionUID = 1L; + + @SuppressWarnings("deprecation") + ProgrammaticallyAddedProvider() { + super("AgentMutationProvider", 1.0, "Provider used to verify mutation tracing"); + } + } } diff --git a/substratevm/src/com.oracle.svm.configure.test/src/com/oracle/svm/configure/test/config/SecurityProviderAgentVerifierTest.java b/substratevm/src/com.oracle.svm.configure.test/src/com/oracle/svm/configure/test/config/SecurityProviderAgentVerifierTest.java index 6fdfd72e2b8b..69c444c2ed35 100644 --- a/substratevm/src/com.oracle.svm.configure.test/src/com/oracle/svm/configure/test/config/SecurityProviderAgentVerifierTest.java +++ b/substratevm/src/com.oracle.svm.configure.test/src/com/oracle/svm/configure/test/config/SecurityProviderAgentVerifierTest.java @@ -69,6 +69,28 @@ public void verifyEnumeratedProvidersWereRecorded() throws Exception { } } + @Test + public void verifyMutationRecordedOnlySuppliedProvider() throws Exception { + assumeTrue("Test must be explicitly enabled because it verifies a previous agent run", + Boolean.getBoolean(VERIFIER_ENABLED_PROPERTY)); + + TypeConfiguration reflectionConfiguration = loadActualConfig().getReflectionConfiguration(); + assertRecorded(reflectionConfiguration, SecurityProviderAgentTest.ReflectiveProbe.class.getName()); + assertRecorded(reflectionConfiguration, SecurityProviderAgentTest.ProgrammaticallyAddedProvider.class.getName()); + for (Provider provider : Security.getProviders()) { + String providerClassName = provider.getClass().getName(); + Assert.assertNull("Provider-list mutation unexpectedly recorded configured provider " + providerClassName, + reflectionConfiguration.get(UnresolvedAccessCondition.unconditional(), + NamedConfigurationTypeDescriptor.fromReflectionName(providerClassName))); + } + } + + private static void assertRecorded(TypeConfiguration reflectionConfiguration, String className) { + Assert.assertNotNull("Missing reflection metadata for " + className, + reflectionConfiguration.get(UnresolvedAccessCondition.unconditional(), + NamedConfigurationTypeDescriptor.fromReflectionName(className))); + } + private static ConfigurationSet loadActualConfig() throws Exception { String configurationPath = System.getProperty(CONFIG_PATH_PROPERTY); Assert.assertNotNull("Missing generated configuration path", configurationPath); diff --git a/substratevm/src/com.oracle.svm.jvmtiagentbase/src/com/oracle/svm/jvmtiagentbase/Support.java b/substratevm/src/com.oracle.svm.jvmtiagentbase/src/com/oracle/svm/jvmtiagentbase/Support.java index 55d2ac04a762..351401b7a5a5 100644 --- a/substratevm/src/com.oracle.svm.jvmtiagentbase/src/com/oracle/svm/jvmtiagentbase/Support.java +++ b/substratevm/src/com.oracle.svm.jvmtiagentbase/src/com/oracle/svm/jvmtiagentbase/Support.java @@ -185,9 +185,13 @@ public static JNIMethodId getCallerMethod(int depth) { } public static JNIObjectHandle getObjectArgument(JNIObjectHandle thread, int slot) { + return getObjectArgument(thread, 0, slot); + } + + public static JNIObjectHandle getObjectArgument(JNIObjectHandle thread, int depth, int slot) { assert thread.notEqual(nullHandle()) || jvmtiVersion() != JvmtiInterface.JVMTI_VERSION_19 : "JDK-8292657"; WordPointer handlePtr = StackValue.get(WordPointer.class); - if (jvmtiFunctions().GetLocalObject().invoke(jvmtiEnv(), thread, 0, slot, handlePtr) != JvmtiError.JVMTI_ERROR_NONE) { + if (jvmtiFunctions().GetLocalObject().invoke(jvmtiEnv(), thread, depth, slot, handlePtr) != JvmtiError.JVMTI_ERROR_NONE) { return nullHandle(); } return handlePtr.read(); From f823d5cb77f2a93a6038aea3dd39e40cb27b5ea6 Mon Sep 17 00:00:00 2001 From: Vojin Jovanovic Date: Sun, 26 Jul 2026 17:12:18 +0200 Subject: [PATCH 39/63] GR-69858: Refactor security provider architecture --- substratevm/docs/architecture/README.md | 3 +- .../docs/architecture/security-providers.md | 2 +- .../functional-spec/security-providers.md | 4 + .../jdk/BuiltInSecurityProviderLoader.java | 116 +++++++ .../ExplicitSecurityProviderRegistration.java | 36 +++ .../jdk/JceProviderVerificationSupport.java | 48 +++ ...ityProviderBuildTimeInitSubstitutions.java | 51 ++++ .../jdk/SecurityProviderRuntimeAccess.java | 55 ++++ .../jdk/SecurityProviderRuntimeState.java | 183 +++++++++++ .../SecurityProviderTracingSubstitutions.java | 43 +++ .../core/jdk/SecurityProvidersSupport.java | 287 ------------------ .../svm/core/jdk/SecuritySubstitutions.java | 47 +-- .../SecuritySubstitutionRuntimeInit.java | 45 +-- .../hosted/test/VerifyReflectionUsage.java | 3 +- .../LegacySecurityProviderCompatibility.java | 63 ++++ .../hosted/ReflectionRegistrationView.java | 57 ++++ .../SecurityProviderCatalogRegistrar.java | 113 +++++++ .../svm/hosted/SecurityProviderMode.java | 58 ++++ .../SecurityProviderRegistrationPlanner.java | 96 ++++++ .../svm/hosted/SecurityServicesFeature.java | 183 ++++------- .../svm/hosted/ServiceLoaderFeature.java | 30 +- ...rviceExplicitProviderRegistrationTest.java | 11 +- 22 files changed, 1046 insertions(+), 488 deletions(-) create mode 100644 substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/BuiltInSecurityProviderLoader.java create mode 100644 substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/ExplicitSecurityProviderRegistration.java create mode 100644 substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/JceProviderVerificationSupport.java create mode 100644 substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SecurityProviderBuildTimeInitSubstitutions.java create mode 100644 substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SecurityProviderRuntimeAccess.java create mode 100644 substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SecurityProviderRuntimeState.java create mode 100644 substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SecurityProviderTracingSubstitutions.java delete mode 100644 substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SecurityProvidersSupport.java create mode 100644 substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/LegacySecurityProviderCompatibility.java create mode 100644 substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ReflectionRegistrationView.java create mode 100644 substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SecurityProviderCatalogRegistrar.java create mode 100644 substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SecurityProviderMode.java create mode 100644 substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SecurityProviderRegistrationPlanner.java diff --git a/substratevm/docs/architecture/README.md b/substratevm/docs/architecture/README.md index a77b54390cbe..058dab662942 100644 --- a/substratevm/docs/architecture/README.md +++ b/substratevm/docs/architecture/README.md @@ -2,4 +2,5 @@ This directory contains developer-facing architecture records for Native Image. -- [Security Provider Architecture](security-providers.md): provider inclusion, verification, and metadata tracing. +- [Security Provider Architecture](security-providers.md): provider inclusion, verification, and + metadata tracing (§AR-security-providers). diff --git a/substratevm/docs/architecture/security-providers.md b/substratevm/docs/architecture/security-providers.md index 66579720be9d..f6dfcedd56d3 100644 --- a/substratevm/docs/architecture/security-providers.md +++ b/substratevm/docs/architecture/security-providers.md @@ -1 +1 @@ -# AR-security-providers: [SecurityProvidersSupport](../../src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SecurityProvidersSupport.java) +# AR-security-providers: [SecurityProviderRuntimeState](../../src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SecurityProviderRuntimeState.java) diff --git a/substratevm/docs/functional-spec/security-providers.md b/substratevm/docs/functional-spec/security-providers.md index ad4d3c5ca800..6d96d66305b0 100644 --- a/substratevm/docs/functional-spec/security-providers.md +++ b/substratevm/docs/functional-spec/security-providers.md @@ -39,6 +39,8 @@ corresponding factory call. Registration is a build-time property. Constructing a provider object at run time does not register the provider or add omitted services to the executable. +Run-time changes to system properties, including properties that report enabled future defaults, +must not change the provider-registration policy selected while building the executable. Section 2.4 defines the platform-owned conditional registration signal for `SecureRandom` acquisition. @@ -362,6 +364,8 @@ actual operation. Sections 1 through 6 specify the planned default behavior. The following options select its two independent parts while the earlier behaviors remain available for compatibility. +Every combination of provider-inclusion policy and provider-list initialization must preserve the +applicable behavior below; selecting one part must not implicitly select or disable the other. ### 7.1 Run-Time Provider-List Initialization diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/BuiltInSecurityProviderLoader.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/BuiltInSecurityProviderLoader.java new file mode 100644 index 000000000000..a5601f40e7eb --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/BuiltInSecurityProviderLoader.java @@ -0,0 +1,116 @@ +/* + * Copyright (c) 2026, 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.core.jdk; + +import java.lang.reflect.InvocationTargetException; +import java.security.Provider; + +import com.oracle.svm.shared.util.VMError; + +import jdk.graal.compiler.api.directives.GraalDirectives; +import sun.security.util.Debug; + +public final class BuiltInSecurityProviderLoader { + private BuiltInSecurityProviderLoader() { + } + + public static String getProviderName(String providerNameOrClassName) { + String providerClassName = getProviderClassName(providerNameOrClassName); + if (providerClassName == null) { + return null; + } + return switch (providerClassName) { + case "sun.security.provider.Sun" -> "SUN"; + case "sun.security.rsa.SunRsaSign" -> "SunRsaSign"; + case "com.sun.crypto.provider.SunJCE" -> "SunJCE"; + case "sun.security.ssl.SunJSSE" -> "SunJSSE"; + case "sun.security.ec.SunEC" -> "SunEC"; + case "apple.security.AppleProvider" -> "Apple"; + default -> null; + }; + } + + public static String getProviderClassName(String providerNameOrClassName) { + return switch (providerNameOrClassName) { + case "SUN", "sun.security.provider.Sun" -> "sun.security.provider.Sun"; + case "SunRsaSign", "sun.security.rsa.SunRsaSign" -> "sun.security.rsa.SunRsaSign"; + case "SunJCE", "com.sun.crypto.provider.SunJCE" -> "com.sun.crypto.provider.SunJCE"; + case "SunJSSE", "sun.security.ssl.SunJSSE" -> "sun.security.ssl.SunJSSE"; + case "SunEC", "sun.security.ec.SunEC" -> "sun.security.ec.SunEC"; + case "Apple", "apple.security.AppleProvider" -> "apple.security.AppleProvider"; + default -> null; + }; + } + + public static boolean isBuiltIn(String providerNameOrClassName) { + return getProviderClassName(providerNameOrClassName) != null; + } + + /** §FS-security-providers.3.1, §FS-security-providers.4.3, and §FS-security-providers.7.1. */ + public static Provider load(String providerNameOrClassName, Debug debug) { + String providerClassName = getProviderClassName(providerNameOrClassName); + if (providerClassName == null) { + return null; + } + SecurityProviderRuntimeState state = SecurityProviderRuntimeState.singleton(); + return switch (providerClassName) { + case "sun.security.provider.Sun" -> + state.isJdkConstructible(providerClassName) ? new sun.security.provider.Sun() : loadReflectively(providerClassName, debug); + case "sun.security.rsa.SunRsaSign" -> + state.isJdkConstructible(providerClassName) ? new sun.security.rsa.SunRsaSign() : loadReflectively(providerClassName, debug); + case "com.sun.crypto.provider.SunJCE" -> + state.isJdkConstructible(providerClassName) ? new com.sun.crypto.provider.SunJCE() : loadReflectively(providerClassName, debug); + case "sun.security.ssl.SunJSSE" -> + state.isJdkConstructible(providerClassName) ? new sun.security.ssl.SunJSSE() : loadReflectively(providerClassName, debug); + case "sun.security.ec.SunEC" -> + state.isJdkConstructible(providerClassName) ? allocateSunECProvider(state) : loadReflectively(providerClassName, debug); + case "apple.security.AppleProvider" -> loadReflectively(providerClassName, debug); + default -> null; + }; + } + + private static Provider allocateSunECProvider(SecurityProviderRuntimeState state) { + try { + return (Provider) state.getSunECConstructor().newInstance(); + } catch (InstantiationException | IllegalAccessException | InvocationTargetException e) { + throw VMError.shouldNotReachHere("The SunEC constructor is not present."); + } + } + + private static Provider loadReflectively(String providerClassName, Debug debug) { + try { + Class providerClass = Class.forName(GraalDirectives.opaque(providerClassName)); + return (Provider) providerClass.getDeclaredConstructor().newInstance(); + } catch (ReflectiveOperationException ex) { + if (debug != null) { + debug.println("Error loading provider " + providerClassName); + // Checkstyle: allow System.err (for JDK compatibility) + ex.printStackTrace(System.err); + // Checkstyle: disallow System.err + } + return null; + } + } +} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/ExplicitSecurityProviderRegistration.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/ExplicitSecurityProviderRegistration.java new file mode 100644 index 000000000000..5c6b90efb2b3 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/ExplicitSecurityProviderRegistration.java @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2026, 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.core.jdk; + +import java.util.function.BooleanSupplier; + +import com.oracle.svm.core.FutureDefaultsOptions; + +public final class ExplicitSecurityProviderRegistration implements BooleanSupplier { + @Override + public boolean getAsBoolean() { + return FutureDefaultsOptions.explicitSecurityProviderRegistration(); + } +} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/JceProviderVerificationSupport.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/JceProviderVerificationSupport.java new file mode 100644 index 000000000000..3fef6d3adca0 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/JceProviderVerificationSupport.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2026, 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.core.jdk; + +import java.security.Provider; + +import com.oracle.svm.core.jdk.SecurityProviderRuntimeState.ProviderInfo; +import com.oracle.svm.shared.util.VMError; + +public final class JceProviderVerificationSupport { + private JceProviderVerificationSupport() { + } + + public static Exception getVerificationResult(Provider provider) { + ProviderInfo info = SecurityProviderRuntimeState.singleton().getProviderInfo(provider); + if (info == null) { + SecurityProviderRuntimeAccess.reportMissingRegistration(provider.getClass()); + throw VMError.shouldNotReachHere("Security provider reflection access unexpectedly succeeded: " + provider.getClass().getName()); + } + if (info.verificationFailure() != null) { + return info.verificationFailure(); + } + SecurityProviderRuntimeAccess.traceLookup(provider); + return null; + } +} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SecurityProviderBuildTimeInitSubstitutions.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SecurityProviderBuildTimeInitSubstitutions.java new file mode 100644 index 000000000000..57065af3e2fc --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SecurityProviderBuildTimeInitSubstitutions.java @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2026, 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.core.jdk; + +import java.security.Provider; + +import com.oracle.svm.core.annotate.Alias; +import com.oracle.svm.core.annotate.Substitute; +import com.oracle.svm.core.annotate.TargetClass; +import com.oracle.svm.shared.util.VMError; + +@TargetClass(className = "sun.security.jca.ProviderConfig", onlyWith = SecurityProvidersInitializedAtBuildTime.class) +@SuppressWarnings({"unused", "static-method"}) +final class Target_sun_security_jca_ProviderConfig_BuildTimeInit { + + @Alias // + private String provName; + + /** + * The legacy build-time provider list cannot load a new provider after image generation. + */ + @Substitute + private Provider doLoadProvider() { + throw VMError.unsupportedFeature("Cannot load new security provider at runtime: " + provName + "."); + } +} + +public final class SecurityProviderBuildTimeInitSubstitutions { +} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SecurityProviderRuntimeAccess.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SecurityProviderRuntimeAccess.java new file mode 100644 index 000000000000..bd98ca59de24 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SecurityProviderRuntimeAccess.java @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2026, 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.core.jdk; + +import java.security.Provider; + +import com.oracle.svm.core.metadata.MetadataTracer; +import com.oracle.svm.shared.NeverInline; +import com.oracle.svm.shared.util.VMError; + +public final class SecurityProviderRuntimeAccess { + private SecurityProviderRuntimeAccess() { + } + + /** §FS-security-providers.4.3: Cache misses probe type access for standard diagnostics. */ + @NeverInline("Keep the provider class name unknown to static analysis without an opaque compiler node.") + public static void reportMissingRegistration(Class providerClass) { + try { + Class.forName(providerClass.getName(), false, providerClass.getClassLoader()); + } catch (ClassNotFoundException ex) { + throw VMError.shouldNotReachHere("A reachable security provider class was not found.", ex); + } + throw VMError.shouldNotReachHere("A security provider without a verification result was registered for reflection: " + providerClass.getName()); + } + + /** §FS-security-providers.6.1: Existing provider instances trace type access. */ + public static Provider traceLookup(Provider provider) { + if (provider != null && MetadataTracer.enabled()) { + MetadataTracer.singleton().traceReflectionType(provider.getClass()); + } + return provider; + } +} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SecurityProviderRuntimeState.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SecurityProviderRuntimeState.java new file mode 100644 index 000000000000..a29d7e393f59 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SecurityProviderRuntimeState.java @@ -0,0 +1,183 @@ +/* + * Copyright (c) 2026, 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.core.jdk; + +import java.lang.reflect.Constructor; +import java.security.Provider; +import java.util.Properties; + +import org.graalvm.collections.EconomicMap; +import org.graalvm.nativeimage.ImageSingletons; +import org.graalvm.nativeimage.Platform; +import org.graalvm.nativeimage.Platforms; + +import com.oracle.svm.guest.staging.util.ImageHeapMap; +import com.oracle.svm.shared.singletons.traits.BuiltinTraits.AllAccess; +import com.oracle.svm.shared.singletons.traits.BuiltinTraits.NoLayeredCallbacks; +import com.oracle.svm.shared.singletons.traits.BuiltinTraits.PartiallyLayerAware; +import com.oracle.svm.shared.singletons.traits.SingletonLayeredInstallationKind.Duplicable; +import com.oracle.svm.shared.singletons.traits.SingletonTraits; + +import jdk.graal.compiler.api.replacements.Fold; + +/// AR-security-providers: Security Provider Architecture +/// +/// The security-provider implementation separates build-time policy from run-time enforcement. +/// Reflection metadata, platform rules, and compatibility inputs are build-time registration +/// signals; the reflection registry is not itself the provider-policy model. This architecture +/// implements §FS-security-providers. +/// +/// ## 1. Independent Transition Axes +/// +/// `SecurityProviderMode` represents provider inclusion and provider-list initialization as +/// independent axes. Hosted components query this mode instead of reading future-default options +/// independently. Substitutions whose implementation differs by mode use build-time predicates, so +/// an application cannot change image-build policy through a run-time system property. This +/// realizes §FS-security-providers.7. +/// +/// ## 2. Registration Signals and Plans +/// +/// The hosted registration planner records provider candidates together with the provenance of the +/// signal that requests them: application reflection metadata, the platform-owned `SecureRandom` +/// rule, a deprecated provider option, or legacy service-type reachability. It produces an explicit +/// provider plan. Metadata emitted while realizing that plan is an output and is not reinterpreted +/// as a new application signal. This realizes §FS-security-providers.2 and +/// §FS-security-providers.7.3. +/// +/// ## 3. Hosted Registration Components +/// +/// `SecurityServicesFeature` coordinates the feature lifecycle. The registration planner owns +/// provider intent and iteration-safe candidate processing. The catalog registrar constructs +/// eligible providers and registers their service catalogs. `LegacySecurityProviderCompatibility` +/// owns deprecated options and service-driven inclusion. Provider code accesses reflection +/// registrations through a narrow query rather than the concrete metadata builder. +/// +/// ## 4. Run-Time Manifest +/// +/// Hosted registration writes one typed manifest entry per provider class. The entry combines +/// whether the JDK may construct the provider with the preserved JCE verification outcome. An +/// application-supplied provider can carry verification information without being marked as +/// JDK-constructible. The manifest is keyed by provider class name, as required by +/// §FS-security-providers.5.3. +/// +/// ## 5. Run-Time Access Services +/// +/// This class owns the manifest. `BuiltInSecurityProviderLoader` owns JDK aliases and construction, +/// `SecurityProviderRuntimeAccess` owns tracing and missing-registration diagnostics, and +/// `JceProviderVerificationSupport` translates manifest outcomes to the JDK contract. The two +/// provider-list initialization modes share these services. +/// +/// ## 6. Service Descriptors +/// +/// Explicit provider registration preserves `java.security.Provider` descriptors without treating +/// them as provider-registration signals, independently of provider-list initialization. Legacy +/// suppression remains part of the compatibility policy. This realizes +/// §FS-security-providers.7.2. +/// +/// ## 7. Concurrent Analysis +/// +/// Provider subtype callbacks add candidates to concurrent collections. A serialized feature pass +/// consumes signals, realizes plans, and requests additional analysis iterations. Callbacks do not +/// schedule iterations directly. +/// +/// ## 8. Retirement Boundary +/// +/// Deprecated provider options and service-reachability inclusion are confined to +/// `LegacySecurityProviderCompatibility`. Removing compatibility behavior does not change the +/// planner, catalog registrar, run-time manifest, or planned-default substitutions. +@SingletonTraits(access = AllAccess.class, layeredCallbacks = NoLayeredCallbacks.class, layeredInstallationKind = Duplicable.class, other = PartiallyLayerAware.class) +public final class SecurityProviderRuntimeState { + public enum AcquisitionKind { + APPLICATION_SUPPLIED_ONLY, + JDK_CONSTRUCTIBLE + } + + public record ProviderInfo(AcquisitionKind acquisitionKind, Exception verificationFailure) { + } + + private final EconomicMap providerInfos = ImageHeapMap.create("securityProviderInfos"); + + private Properties savedInitialSecurityProperties; + private Constructor sunECConstructor; + + @Platforms(Platform.HOSTED_ONLY.class) + public SecurityProviderRuntimeState() { + } + + @Fold + public static SecurityProviderRuntimeState singleton() { + return ImageSingletons.lookup(SecurityProviderRuntimeState.class); + } + + @Platforms(Platform.HOSTED_ONLY.class) + public void registerJdkConstructibleProvider(String providerClassName, Object verificationResult) { + registerProvider(providerClassName, AcquisitionKind.JDK_CONSTRUCTIBLE, verificationResult); + } + + @Platforms(Platform.HOSTED_ONLY.class) + public void registerApplicationSuppliedProvider(String providerClassName, Object verificationResult) { + registerProvider(providerClassName, AcquisitionKind.APPLICATION_SUPPLIED_ONLY, verificationResult); + } + + @Platforms(Platform.HOSTED_ONLY.class) + private void registerProvider(String providerClassName, AcquisitionKind acquisitionKind, Object verificationResult) { + Exception verificationFailure = verificationResult instanceof Exception exception ? exception : null; + ProviderInfo previous = providerInfos.get(providerClassName); + if (previous != null && previous.acquisitionKind() == AcquisitionKind.JDK_CONSTRUCTIBLE) { + acquisitionKind = AcquisitionKind.JDK_CONSTRUCTIBLE; + } + if (previous != null && previous.verificationFailure() != null) { + verificationFailure = previous.verificationFailure(); + } + providerInfos.put(providerClassName, new ProviderInfo(acquisitionKind, verificationFailure)); + } + + public ProviderInfo getProviderInfo(Provider provider) { + return providerInfos.get(provider.getClass().getName()); + } + + public boolean isJdkConstructible(String providerClassName) { + ProviderInfo info = providerInfos.get(providerClassName); + return info != null && info.acquisitionKind() == AcquisitionKind.JDK_CONSTRUCTIBLE; + } + + @Platforms(Platform.HOSTED_ONLY.class) + public void setSunECConstructor(Constructor constructor) { + sunECConstructor = constructor; + } + + Constructor getSunECConstructor() { + return sunECConstructor; + } + + @Platforms(Platform.HOSTED_ONLY.class) + public void setSavedInitialSecurityProperties(Properties properties) { + savedInitialSecurityProperties = properties; + } + + public Properties getSavedInitialSecurityProperties() { + return savedInitialSecurityProperties; + } +} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SecurityProviderTracingSubstitutions.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SecurityProviderTracingSubstitutions.java new file mode 100644 index 000000000000..94d342c31a4d --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SecurityProviderTracingSubstitutions.java @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2026, 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.core.jdk; + +import java.security.Provider; + +import com.oracle.svm.core.annotate.Substitute; +import com.oracle.svm.core.annotate.TargetClass; + +@TargetClass(java.security.Security.class) +final class Target_java_security_Security_ProviderLookup { + + /** §FS-security-providers.6: Successful name-based lookup traces provider type access. */ + @Substitute + public static Provider getProvider(String name) { + return SecurityProviderRuntimeAccess.traceLookup(sun.security.jca.Providers.getProviderList().getProvider(name)); + } +} + +public final class SecurityProviderTracingSubstitutions { +} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SecurityProvidersSupport.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SecurityProvidersSupport.java deleted file mode 100644 index c6335d910d60..000000000000 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SecurityProvidersSupport.java +++ /dev/null @@ -1,287 +0,0 @@ -/* - * Copyright (c) 2024, 2024, Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - */ - -package com.oracle.svm.core.jdk; - -import java.lang.reflect.Constructor; -import java.lang.reflect.InvocationTargetException; -import java.security.Provider; -import java.util.Properties; - -import org.graalvm.collections.EconomicMap; -import org.graalvm.nativeimage.ImageSingletons; -import org.graalvm.nativeimage.Platform; -import org.graalvm.nativeimage.Platforms; - -import com.oracle.svm.core.metadata.MetadataTracer; -import com.oracle.svm.guest.staging.util.ImageHeapMap; -import com.oracle.svm.shared.NeverInline; -import com.oracle.svm.shared.singletons.traits.BuiltinTraits.AllAccess; -import com.oracle.svm.shared.singletons.traits.BuiltinTraits.NoLayeredCallbacks; -import com.oracle.svm.shared.singletons.traits.BuiltinTraits.PartiallyLayerAware; -import com.oracle.svm.shared.singletons.traits.SingletonLayeredInstallationKind.Duplicable; -import com.oracle.svm.shared.singletons.traits.SingletonTraits; -import com.oracle.svm.shared.util.VMError; - -import jdk.graal.compiler.api.directives.GraalDirectives; -import jdk.graal.compiler.api.replacements.Fold; -import sun.security.util.Debug; - -/// AR-security-providers: Security Provider Architecture -/// -/// This class holds the build-time and run-time structures for JCA security-provider inclusion, -/// verification, and metadata tracing. The required behavior is specified separately by -/// §FS-security-providers. See the -/// JCA -/// Security Services documentation for the user-facing configuration model. -/// -/// ## 1. Build-Time Inclusion and Verification -/// -/// `SecurityServicesFeature` coordinates two analysis inputs: subtype reachability discovers -/// candidate [Provider] classes, and JCA factory reachability discovers used service types. For a -/// provider candidate, the feature queries the reflection registry for type, constructor, or -/// factory-method registration. It instantiates accepted candidates through the declared nullary -/// constructor or static `provider()` method, then registers their service implementation -/// classes. A registered application-supplied provider without either construction path receives a -/// class-based JCE verification result but no automatically registered services. Its service -/// implementations must be retained independently. The service-driven path calls the same -/// service-registration machinery independently. These mechanisms implement -/// §FS-security-providers.2, §FS-security-providers.5.3, and §FS-security-providers.7.3. -/// `SecureRandom` acquisition supplies the platform-owned conditional registration signal -/// specified by §FS-security-providers.2.4. The registered providers then follow the same complete -/// provider-processing path as providers registered through application metadata. -/// -/// During analysis, the feature obtains each included provider's JCE verification result and stores -/// it in this image singleton, keyed by provider class name. Provider inclusion is tracked -/// separately so it cannot overwrite a failed verification result. The feature removes those -/// entries from the JDK's object-keyed cache so build-time provider instances do not remain -/// reachable in the image heap. -/// -/// ## 2. Run-Time Verification-Result Lookup -/// -/// The `javax.crypto.JceSecurity` substitutions consult the maps in this singleton when the JDK -/// verification cache has no entry. [Boolean#TRUE] encodes successful verification; an exception -/// object encodes the original verification failure. This lets run-time JCE checks reuse the -/// build-time result without retaining the provider instance or repeating JAR verification. -/// -/// ## 3. Provider Type Tracing -/// -/// The metadata tracer records the type of a provider instance returned by a lookup or supplied to -/// JCE. It does not invent constructor access because an application-supplied provider can have no -/// JDK-supported construction path, for example when it is a non-static inner class. JDK-managed -/// construction paths are traced separately at their actual reflective access sites. This is the -/// native-image counterpart of the Tracing Agent's provider event and implements -/// §FS-security-providers.6.1. -/// -/// On a verification-result cache miss, [#reportMissingProviderRegistration(Class)] performs an -/// opaque, non-initializing `Class.forName` lookup using the provider's class loader. The opaque -/// name prevents image-build analysis from removing the probe. The lookup enters the regular -/// missing-reflection-registration machinery required by §FS-security-providers.5.3. A successful -/// lookup without a verification result is an internal invariant violation. -/// -/// ## 4. Run-Time Provider Construction -/// -/// With run-time provider initialization, the `ProviderConfig` substitutions ask this class to -/// construct included JDK providers directly. Other configured providers follow the JDK's -/// reflective loading path. The substitutions preserve the JDK's provider-list state, recursion -/// guard, and retry counter, while the verification maps remain independent of provider creation. -/// -/// ## 5. Concurrent Analysis Registration -/// -/// Provider subtype callbacks add candidates to a concurrent set and mark it changed. A serialized -/// security-services analysis pass consumes new candidates and requests another analysis iteration -/// when processing registers new reflection or JNI metadata. The callbacks do not request analysis -/// iterations themselves, so concurrent discovery cannot race with iteration scheduling or lose -/// registrations pending for a later iteration. -/// -@SingletonTraits(access = AllAccess.class, layeredCallbacks = NoLayeredCallbacks.class, layeredInstallationKind = Duplicable.class, other = PartiallyLayerAware.class) -public final class SecurityProvidersSupport { - /// Provider classes that may be constructed at run time. - private final EconomicMap includedSecurityProviderClasses = ImageHeapMap.create("includedSecurityProviderClasses"); - - /// Build-time JCE verification results keyed by the run-time provider class. - private final EconomicMap securityProviderVerificationResults = ImageHeapMap.create("securityProviderVerificationResults"); - - private Properties savedInitialSecurityProperties; - - private Constructor sunECConstructor; - - @Platforms(Platform.HOSTED_ONLY.class) - public SecurityProvidersSupport() { - } - - @Fold - public static SecurityProvidersSupport singleton() { - return ImageSingletons.lookup(SecurityProvidersSupport.class); - } - - @Platforms(Platform.HOSTED_ONLY.class) - public void addSecurityProviderVerificationResult(String providerClassName, Object verificationResult) { - securityProviderVerificationResults.put(providerClassName, verificationResult); - } - - @Platforms(Platform.HOSTED_ONLY.class) - public void addIncludedSecurityProviderClass(String providerClassName) { - includedSecurityProviderClasses.put(providerClassName, Boolean.TRUE); - } - - public Object getSecurityProviderVerificationResult(Provider provider) { - return securityProviderVerificationResults.get(provider.getClass().getName()); - } - - /// Returns `true` if the provider class was included in the native image. - public boolean isSecurityProviderIncluded(String providerClassName) { - return includedSecurityProviderClasses.containsKey(providerClassName); - } - - @Platforms(Platform.HOSTED_ONLY.class) - public void setSunECConstructor(Constructor sunECConstructor) { - this.sunECConstructor = sunECConstructor; - } - - public Provider allocateSunECProvider() { - try { - return (Provider) sunECConstructor.newInstance(); - } catch (InstantiationException | IllegalAccessException | InvocationTargetException e) { - throw VMError.shouldNotReachHere("The SunEC constructor is not present."); - } - } - - @Platforms(Platform.HOSTED_ONLY.class) - public void setSavedInitialSecurityProperties(Properties savedSecurityProperties) { - this.savedInitialSecurityProperties = savedSecurityProperties; - } - - public Properties getSavedInitialSecurityProperties() { - return savedInitialSecurityProperties; - } - - public static String getBuiltInProviderName(String provName) { - String providerClassName = getBuiltInProviderClassName(provName); - if (providerClassName == null) { - return null; - } - return switch (providerClassName) { - case "sun.security.provider.Sun" -> "SUN"; - case "sun.security.rsa.SunRsaSign" -> "SunRsaSign"; - case "com.sun.crypto.provider.SunJCE" -> "SunJCE"; - case "sun.security.ssl.SunJSSE" -> "SunJSSE"; - case "sun.security.ec.SunEC" -> "SunEC"; - case "apple.security.AppleProvider" -> "Apple"; - default -> null; - }; - } - - public static String getBuiltInProviderClassName(String provName) { - return switch (provName) { - case "SUN", "sun.security.provider.Sun" -> "sun.security.provider.Sun"; - case "SunRsaSign", "sun.security.rsa.SunRsaSign" -> "sun.security.rsa.SunRsaSign"; - case "SunJCE", "com.sun.crypto.provider.SunJCE" -> "com.sun.crypto.provider.SunJCE"; - case "SunJSSE", "sun.security.ssl.SunJSSE" -> "sun.security.ssl.SunJSSE"; - case "SunEC", "sun.security.ec.SunEC" -> "sun.security.ec.SunEC"; - case "Apple", "apple.security.AppleProvider" -> "apple.security.AppleProvider"; - default -> null; - }; - } - - /// §AR-security-providers.3: Cache misses probe type access for standard diagnostics. - @NeverInline("Keep the provider class name unknown to static analysis without an opaque compiler node.") - public static void reportMissingProviderRegistration(Class providerClass) { - try { - Class.forName(providerClass.getName(), false, providerClass.getClassLoader()); - } catch (ClassNotFoundException ex) { - throw VMError.shouldNotReachHere("A reachable security provider class was not found.", ex); - } - throw VMError.shouldNotReachHere("A security provider without a verification result was registered for reflection: " + providerClass.getName()); - } - - /// §AR-security-providers.3: Existing provider instances trace type access. - public static Provider traceProviderLookup(Provider provider) { - if (provider == null) { - return null; - } - if (MetadataTracer.enabled()) { - MetadataTracer.singleton().traceReflectionType(provider.getClass()); - } - return provider; - } - - private static Provider loadProviderReflectively(String providerClassName, Debug debug) { - try { - Class providerClass = Class.forName(GraalDirectives.opaque(providerClassName)); - return (Provider) providerClass.getDeclaredConstructor().newInstance(); - } catch (ReflectiveOperationException ex) { - if (debug != null) { - debug.println("Error loading provider " + providerClassName); - // Checkstyle: allow System.err (for JDK compatibility) - ex.printStackTrace(System.err); - // Checkstyle: disallow System.err - } - return null; - } - } - - /// §FS-security-providers.3.1, §FS-security-providers.4.3, and - /// §FS-security-providers.7.1: Construct included providers; probe omitted providers normally. - public Provider loadBuiltInProvider(String provName, Debug debug) { - String providerClassName = getBuiltInProviderClassName(provName); - if (providerClassName == null) { - return null; - } - return switch (providerClassName) { - case "sun.security.provider.Sun" -> - isSecurityProviderIncluded(providerClassName) ? new sun.security.provider.Sun() : loadProviderReflectively(providerClassName, debug); - case "sun.security.rsa.SunRsaSign" -> - isSecurityProviderIncluded(providerClassName) ? new sun.security.rsa.SunRsaSign() : loadProviderReflectively(providerClassName, debug); - case "com.sun.crypto.provider.SunJCE" -> - isSecurityProviderIncluded(providerClassName) ? new com.sun.crypto.provider.SunJCE() : loadProviderReflectively(providerClassName, debug); - case "sun.security.ssl.SunJSSE" -> - isSecurityProviderIncluded(providerClassName) ? new sun.security.ssl.SunJSSE() : loadProviderReflectively(providerClassName, debug); - case "sun.security.ec.SunEC" -> - isSecurityProviderIncluded(providerClassName) ? allocateSunECProvider() : loadProviderReflectively(providerClassName, debug); - case "apple.security.AppleProvider" -> { - try { - Class c = Class.forName(providerClassName); - if (Provider.class.isAssignableFrom(c)) { - yield (Provider) c.getDeclaredConstructor().newInstance(); - } - } catch (Exception ex) { - if (debug != null) { - debug.println("Error loading provider Apple"); - // Checkstyle: allow System.err (for JDK compatibility) - ex.printStackTrace(System.err); - // Checkstyle: disallow System.err - } - } - yield null; - } - default -> null; - }; - } - - public static boolean isBuiltInProvider(String provName) { - return getBuiltInProviderClassName(provName) != null; - } -} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SecuritySubstitutions.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SecuritySubstitutions.java index e9fe26ee16b6..2cf6e45c1c16 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SecuritySubstitutions.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SecuritySubstitutions.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2013, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -47,7 +47,6 @@ import org.graalvm.nativeimage.hosted.FieldValueTransformer; import org.graalvm.nativeimage.impl.InternalPlatform; -import com.oracle.svm.core.FutureDefaultsOptions; import com.oracle.svm.core.annotate.Alias; import com.oracle.svm.core.annotate.InjectAccessors; import com.oracle.svm.core.annotate.RecomputeFieldValue; @@ -64,13 +63,12 @@ // Reject an unregistered SUN provider before the JDK SecureRandom fallback can expose it. // §FS-security-providers.3.1 and §FS-security-providers.4.1 -@TargetClass(className = "sun.security.jca.Providers") +@TargetClass(className = "sun.security.jca.Providers", onlyWith = ExplicitSecurityProviderRegistration.class) final class Target_sun_security_jca_Providers_ExplicitRegistration { @Substitute public static Provider getSunProvider() { - if (Boolean.getBoolean(FutureDefaultsOptions.SYSTEM_PROPERTY_PREFIX + "explicit-security-provider-registration") && - !SecurityProvidersSupport.singleton().isSecurityProviderIncluded("sun.security.provider.Sun")) { - SecurityProvidersSupport.reportMissingProviderRegistration(sun.security.provider.Sun.class); + if (!SecurityProviderRuntimeState.singleton().isJdkConstructible("sun.security.provider.Sun")) { + SecurityProviderRuntimeAccess.reportMissingRegistration(sun.security.provider.Sun.class); } return new sun.security.provider.Sun(); } @@ -285,22 +283,12 @@ static Exception getVerificationResult(Provider p) { Object key = new Target_javax_crypto_JceSecurity_WeakIdentityWrapper(p, queue); Object o = verificationResults.get(key); if (o == PROVIDER_VERIFIED) { - SecurityProvidersSupport.traceProviderLookup(p); + SecurityProviderRuntimeAccess.traceLookup(p); return null; } else if (o != null) { return (Exception) o; } - o = SecurityProvidersSupport.singleton().getSecurityProviderVerificationResult(p); - if (o == Boolean.TRUE) { - SecurityProvidersSupport.traceProviderLookup(p); - return null; - } else if (o != null) { - return (Exception) o; - } - /* §AR-security-providers.3: Probe the missing type through the ordinary reflection path; - * report an inconsistent success. */ - SecurityProvidersSupport.reportMissingProviderRegistration(p.getClass()); - throw VMError.shouldNotReachHere("Security provider reflection access unexpectedly succeeded: " + p.getClass().getName()); + return JceProviderVerificationSupport.getVerificationResult(p); } } @@ -378,29 +366,6 @@ public boolean implies(ProtectionDomain domain, Permission permission) { } } -@TargetClass(className = "sun.security.jca.ProviderConfig", onlyWith = SecurityProvidersInitializedAtBuildTime.class) -@SuppressWarnings({"unused", "static-method"}) -final class Target_sun_security_jca_ProviderConfig { - - @Alias // - private String provName; - - /** - * All security providers used in a native-image must be registered during image build time. At - * runtime, we shouldn't have a call to doLoadProvider. However, this method is still reachable - * at runtime, and transitively includes other types in the image, among which is - * sun.security.jca.ProviderConfig.ProviderLoader. This class contains a static field with a - * cache of providers loaded during the image build. The contents of this cache can vary even - * when building the same image due to the way services are loaded on Java 11. This cache can - * increase the final image size substantially (if it contains, for example, - * {@code org.jcp.xml.dsig.internal.dom.XMLDSigRI}. - */ - @Substitute - private Provider doLoadProvider() { - throw VMError.unsupportedFeature("Cannot load new security provider at runtime: " + provName + "."); - } -} - @SuppressWarnings("unused") @TargetClass(className = "sun.security.jca.ProviderConfig", innerClass = "ProviderLoader") final class Target_sun_security_jca_ProviderConfig_ProviderLoader { diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/runtimeinit/SecuritySubstitutionRuntimeInit.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/runtimeinit/SecuritySubstitutionRuntimeInit.java index 65e53338574f..24fc7f2b9e3f 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/runtimeinit/SecuritySubstitutionRuntimeInit.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/runtimeinit/SecuritySubstitutionRuntimeInit.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2025, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2025, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -35,10 +35,12 @@ import com.oracle.svm.core.annotate.RecomputeFieldValue; import com.oracle.svm.core.annotate.Substitute; import com.oracle.svm.core.annotate.TargetClass; +import com.oracle.svm.core.jdk.BuiltInSecurityProviderLoader; +import com.oracle.svm.core.jdk.JceProviderVerificationSupport; +import com.oracle.svm.core.jdk.SecurityProviderRuntimeAccess; +import com.oracle.svm.core.jdk.SecurityProviderRuntimeState; import com.oracle.svm.core.jdk.SecurityProvidersInitializedAtRunTime; -import com.oracle.svm.core.jdk.SecurityProvidersSupport; import com.oracle.svm.shared.util.BasedOnJDKFile; -import com.oracle.svm.shared.util.VMError; import jdk.graal.compiler.core.common.SuppressFBWarnings; @@ -49,16 +51,6 @@ final class Target_java_security_Security { static Properties props; } -@TargetClass(java.security.Security.class) -final class Target_java_security_Security_ProviderLookup { - - /** §FS-security-providers.6: Successful name-based lookup traces provider type access. */ - @Substitute - public static Provider getProvider(String name) { - return SecurityProvidersSupport.traceProviderLookup(sun.security.jca.Providers.getProviderList().getProvider(name)); - } -} - @TargetClass(value = java.security.Security.class, innerClass = "SecPropLoader", onlyWith = SecurityProvidersInitializedAtRunTime.class) final class Target_java_security_Security_SecPropLoader { @@ -69,7 +61,7 @@ final class Target_java_security_Security_SecPropLoader { */ @Substitute private static void loadMaster() { - Target_java_security_Security.props = SecurityProvidersSupport.singleton().getSavedInitialSecurityProperties(); + Target_java_security_Security.props = SecurityProviderRuntimeState.singleton().getSavedInitialSecurityProperties(); } } @@ -104,18 +96,7 @@ final class Target_javax_crypto_JceSecurity { @Substitute static Exception getVerificationResult(Provider p) { - /* The verification results map key is an identity wrapper object. */ - Object o = SecurityProvidersSupport.singleton().getSecurityProviderVerificationResult(p); - if (o == Boolean.TRUE) { - SecurityProvidersSupport.traceProviderLookup(p); - return null; - } else if (o != null) { - return (Exception) o; - } - /* §AR-security-providers.3: Probe the missing type through the ordinary reflection path; - * report an inconsistent success. */ - SecurityProvidersSupport.reportMissingProviderRegistration(p.getClass()); - throw VMError.shouldNotReachHere("Security provider reflection access unexpectedly succeeded: " + p.getClass().getName()); + return JceProviderVerificationSupport.getVerificationResult(p); } } @@ -163,8 +144,8 @@ Provider getProvider() { return null; } // Create providers which are in java.base directly - if (SecurityProvidersSupport.isBuiltInProvider(provName)) { - provider = SecurityProvidersSupport.singleton().loadBuiltInProvider(provName, debug); + if (BuiltInSecurityProviderLoader.isBuiltIn(provName)) { + provider = BuiltInSecurityProviderLoader.load(provName, debug); } else { if (isLoading) { /* @@ -208,15 +189,15 @@ final class Target_sun_security_jca_ProviderList { public Provider getProvider(String name) { int index = getIndex(name); if (index >= 0) { - return SecurityProvidersSupport.traceProviderLookup(getProvider(index)); + return SecurityProviderRuntimeAccess.traceLookup(getProvider(index)); } for (Target_sun_security_jca_ProviderConfig config : configs) { String configuredProviderName = config.provName; - String providerName = SecurityProvidersSupport.getBuiltInProviderName(configuredProviderName); - String providerFQName = SecurityProvidersSupport.getBuiltInProviderClassName(configuredProviderName); + String providerName = BuiltInSecurityProviderLoader.getProviderName(configuredProviderName); + String providerFQName = BuiltInSecurityProviderLoader.getProviderClassName(configuredProviderName); boolean matches = configuredProviderName.equals(name) || (providerName != null && providerName.equals(name)) || (providerFQName != null && providerFQName.equals(name)); if (matches) { - return SecurityProvidersSupport.traceProviderLookup(config.getProvider()); + return SecurityProviderRuntimeAccess.traceLookup(config.getProvider()); } } return null; diff --git a/substratevm/src/com.oracle.svm.hosted.test/src/com/oracle/svm/hosted/test/VerifyReflectionUsage.java b/substratevm/src/com.oracle.svm.hosted.test/src/com/oracle/svm/hosted/test/VerifyReflectionUsage.java index f481ce0363e8..f1921c064643 100644 --- a/substratevm/src/com.oracle.svm.hosted.test/src/com/oracle/svm/hosted/test/VerifyReflectionUsage.java +++ b/substratevm/src/com.oracle.svm.hosted.test/src/com/oracle/svm/hosted/test/VerifyReflectionUsage.java @@ -154,7 +154,8 @@ public interface Provider { clazz("com.oracle.svm.core.jdk.Resources$ModuleInstanceResourceKey"), clazz("com.oracle.svm.core.jdk.Resources$ModuleNameResourceKey"), clazz("com.oracle.svm.core.jdk.resources.MissingResourceRegistrationUtils"), - clazz("com.oracle.svm.core.jdk.SecurityProvidersSupport"), + clazz("com.oracle.svm.core.jdk.BuiltInSecurityProviderLoader"), + clazz("com.oracle.svm.core.jdk.SecurityProviderRuntimeAccess"), clazz("com.oracle.svm.core.jdk.StackAccessControlContextVisitor"), clazz("com.oracle.svm.core.jfr.events.ThreadParkEvent"), clazz("com.oracle.svm.core.jfr.traceid.JfrTraceId"), diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/LegacySecurityProviderCompatibility.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/LegacySecurityProviderCompatibility.java new file mode 100644 index 000000000000..28a386166ad3 --- /dev/null +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/LegacySecurityProviderCompatibility.java @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2026, 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.hosted; + +import java.util.function.Consumer; + +import org.graalvm.collections.EconomicSet; +import org.graalvm.nativeimage.hosted.Feature.BeforeAnalysisAccess; + +import com.oracle.svm.core.util.UserError; + +/** + * Retirement boundary for deprecated security-provider options and service-driven inclusion. + */ +final class LegacySecurityProviderCompatibility { + private LegacySecurityProviderCompatibility() { + } + + static void registerAdditionalProviders(BeforeAnalysisAccess access, Consumer> registerProvider) { + for (String value : SecurityServicesFeature.Options.AdditionalSecurityProviders.getValue().values()) { + for (String className : value.split(",")) { + Class providerClass = access.findClassByName(className); + UserError.guarantee(providerClass != null, + "Manually marked security provider class doesn't exist: %s. Make sure that the class name is correct and that the class is on the image builder classpath.", className); + registerProvider.accept(providerClass); + } + } + } + + static Iterable> additionalServiceTypes(BeforeAnalysisAccess access, Iterable> knownServices) { + EconomicSet> services = EconomicSet.create(knownServices); + for (String value : SecurityServicesFeature.Options.AdditionalSecurityServiceTypes.getValue().values()) { + for (String className : value.split(",")) { + Class serviceClass = access.findClassByName(className); + UserError.guarantee(serviceClass != null, "Unable to find additional security service class %s", className); + services.add(serviceClass); + } + } + return services; + } +} diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ReflectionRegistrationView.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ReflectionRegistrationView.java new file mode 100644 index 000000000000..2b3101591b60 --- /dev/null +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ReflectionRegistrationView.java @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2026, 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.hosted; + +import java.lang.reflect.Executable; + +import org.graalvm.nativeimage.ImageSingletons; +import org.graalvm.nativeimage.impl.RuntimeReflectionSupport; + +import com.oracle.svm.hosted.reflect.ReflectionDataBuilder; + +/** + * Narrow hosted query used to recognize provider registration signals without exposing the + * concrete reflection builder to provider-policy code. + */ +interface ReflectionRegistrationView { + boolean hasTypeAccess(Class type); + + boolean hasExecutableAccess(Executable executable); + + static ReflectionRegistrationView singleton() { + ReflectionDataBuilder builder = (ReflectionDataBuilder) ImageSingletons.lookup(RuntimeReflectionSupport.class); + return new ReflectionRegistrationView() { + @Override + public boolean hasTypeAccess(Class type) { + return builder.isTypeRegisteredForReflection(type); + } + + @Override + public boolean hasExecutableAccess(Executable executable) { + return builder.isMethodRegisteredForReflection(executable); + } + }; + } +} diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SecurityProviderCatalogRegistrar.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SecurityProviderCatalogRegistrar.java new file mode 100644 index 000000000000..a3872b504e2c --- /dev/null +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SecurityProviderCatalogRegistrar.java @@ -0,0 +1,113 @@ +/* + * Copyright (c) 2026, 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.hosted; + +import java.security.Provider; +import java.security.Provider.Service; +import java.util.Collections; +import java.util.IdentityHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.graalvm.nativeimage.hosted.Feature.DuringAnalysisAccess; +import org.graalvm.nativeimage.hosted.RuntimeReflection; + +import com.oracle.svm.core.jdk.SecurityProviderRuntimeState; + +/** + * Realizes complete provider plans and writes their service catalog and run-time manifest. + */ +final class SecurityProviderCatalogRegistrar { + interface Host { + boolean isLoadableProviderClass(DuringAnalysisAccess access, Class providerClass); + + Provider instantiateProvider(Class providerClass); + + boolean isValidService(Service service); + + void registerService(DuringAnalysisAccess access, Service service); + + Object getProviderVerificationResult(Provider provider); + } + + private final Host host; + private final Map> buildTimeProvidersByClassName; + private final Set usedProviders = Collections.synchronizedSet(Collections.newSetFromMap(new IdentityHashMap<>())); + + SecurityProviderCatalogRegistrar(Host host, Map> buildTimeProvidersByClassName) { + this.host = host; + this.buildTimeProvidersByClassName = buildTimeProvidersByClassName; + } + + boolean isUsed(Provider provider) { + return usedProviders.contains(provider); + } + + void includeProviderClass(DuringAnalysisAccess access, Class providerClass) { + if (!host.isLoadableProviderClass(access, providerClass)) { + registerApplicationSuppliedProviderClass(providerClass); + return; + } + List providers = buildTimeProvidersByClassName.get(providerClass.getName()); + if (providers == null) { + providers = List.of(host.instantiateProvider(providerClass)); + } + // Register every configured instance and the union of their service metadata. + // §FS-security-providers.2.3 + for (Provider provider : providers) { + registerProvider(access, provider); + for (Service service : provider.getServices()) { + if (host.isValidService(service)) { + host.registerService(access, service); + } + } + } + } + + void registerProvider(DuringAnalysisAccess access, Provider provider) { + if (usedProviders.add(provider)) { + RuntimeReflection.register(provider.getClass()); + RuntimeReflection.register(provider.getClass().getConstructors()); + /* Trigger initialization of lazy field java.security.Provider.entrySet. */ + provider.entrySet(); + String providerClassName = provider.getClass().getName(); + Object verificationResult = host.getProviderVerificationResult(provider); + SecurityProviderRuntimeState state = SecurityProviderRuntimeState.singleton(); + if (host.isLoadableProviderClass(access, provider.getClass())) { + state.registerJdkConstructibleProvider(providerClassName, verificationResult); + } else { + state.registerApplicationSuppliedProvider(providerClassName, verificationResult); + } + } + } + + private void registerApplicationSuppliedProviderClass(Class providerClass) { + // §FS-security-providers.5.3: Preserve verification without reconstructing the provider. + List buildTimeProviders = buildTimeProvidersByClassName.get(providerClass.getName()); + Object verificationResult = buildTimeProviders == null ? Boolean.TRUE : host.getProviderVerificationResult(buildTimeProviders.getFirst()); + SecurityProviderRuntimeState.singleton().registerApplicationSuppliedProvider(providerClass.getName(), verificationResult); + } +} diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SecurityProviderMode.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SecurityProviderMode.java new file mode 100644 index 000000000000..cd2645299cca --- /dev/null +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SecurityProviderMode.java @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2026, 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.hosted; + +import com.oracle.svm.core.FutureDefaultsOptions; + +/** The two independent transition axes from §FS-security-providers.7. */ +record SecurityProviderMode(InclusionPolicy inclusionPolicy, ProviderListInitialization listInitialization) { + enum InclusionPolicy { + LEGACY_SERVICE_REACHABILITY, + EXPLICIT_METADATA + } + + enum ProviderListInitialization { + BUILD_TIME, + RUN_TIME + } + + static SecurityProviderMode current() { + InclusionPolicy inclusion = FutureDefaultsOptions.explicitSecurityProviderRegistration() + ? InclusionPolicy.EXPLICIT_METADATA + : InclusionPolicy.LEGACY_SERVICE_REACHABILITY; + ProviderListInitialization initialization = FutureDefaultsOptions.securityProvidersInitializedAtRunTime() + ? ProviderListInitialization.RUN_TIME + : ProviderListInitialization.BUILD_TIME; + return new SecurityProviderMode(inclusion, initialization); + } + + boolean explicitRegistration() { + return inclusionPolicy == InclusionPolicy.EXPLICIT_METADATA; + } + + boolean runtimeProviderList() { + return listInitialization == ProviderListInitialization.RUN_TIME; + } +} diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SecurityProviderRegistrationPlanner.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SecurityProviderRegistrationPlanner.java new file mode 100644 index 000000000000..dd527451579c --- /dev/null +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SecurityProviderRegistrationPlanner.java @@ -0,0 +1,96 @@ +/* + * Copyright (c) 2026, 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.hosted; + +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.function.Consumer; +import java.util.function.Predicate; + +/** + * Tracks provider-registration intent separately from reflection metadata emitted to realize it. + */ +final class SecurityProviderRegistrationPlanner { + enum Source { + APPLICATION_METADATA, + SECURE_RANDOM_PLATFORM, + LEGACY_ADDITIONAL_PROVIDER, + LEGACY_SERVICE_REACHABILITY + } + + private final Set> candidates = ConcurrentHashMap.newKeySet(); + private final Set> completed = ConcurrentHashMap.newKeySet(); + private final Set> completePlans = ConcurrentHashMap.newKeySet(); + private final Set> legacyGeneratedReflection = ConcurrentHashMap.newKeySet(); + private final ConcurrentHashMap, Set> sources = new ConcurrentHashMap<>(); + private final AtomicBoolean changed = new AtomicBoolean(); + + void addCandidate(Class providerClass) { + if (candidates.add(providerClass)) { + changed.set(true); + } + } + + private void recordSource(Class providerClass, Source source) { + sources.computeIfAbsent(providerClass, _ -> ConcurrentHashMap.newKeySet()).add(source); + } + + void requestCompleteProvider(Class providerClass, Source source) { + addCandidate(providerClass); + recordSource(providerClass, source); + if (completePlans.add(providerClass)) { + changed.set(true); + } + } + + void beforeLegacyReflectionRegistration(Class providerClass, Predicate> hasRegistrationSignal) { + /* + * Capture metadata that already exists before marking reflection emitted by compatibility + * processing. This prevents our own output from feeding back as application intent. + */ + if (hasRegistrationSignal.test(providerClass)) { + requestCompleteProvider(providerClass, Source.APPLICATION_METADATA); + } + recordSource(providerClass, Source.LEGACY_SERVICE_REACHABILITY); + legacyGeneratedReflection.add(providerClass); + } + + boolean processNewCompleteProviders(Predicate> hasRegistrationSignal, Consumer> includeProvider) { + boolean discoveredCandidate = changed.getAndSet(false); + boolean processed = false; + for (Class providerClass : candidates) { + boolean applicationMetadata = !legacyGeneratedReflection.contains(providerClass) && hasRegistrationSignal.test(providerClass); + if (applicationMetadata) { + requestCompleteProvider(providerClass, Source.APPLICATION_METADATA); + } + if (completePlans.contains(providerClass) && completed.add(providerClass)) { + includeProvider.accept(providerClass); + processed = true; + } + } + return processed || discoveredCandidate; + } +} diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SecurityServicesFeature.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SecurityServicesFeature.java index 4302d5456bfd..02fc925b97b9 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SecurityServicesFeature.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SecurityServicesFeature.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2018, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -60,7 +60,6 @@ import java.util.Collection; import java.util.Collections; import java.util.HashMap; -import java.util.IdentityHashMap; import java.util.List; import java.util.Map; import java.util.Objects; @@ -68,7 +67,6 @@ import java.util.Properties; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.Function; import javax.crypto.Cipher; @@ -87,7 +85,6 @@ import org.graalvm.nativeimage.ImageSingletons; import org.graalvm.nativeimage.hosted.RuntimeReflection; import org.graalvm.nativeimage.impl.RuntimeClassInitializationSupport; -import org.graalvm.nativeimage.impl.RuntimeReflectionSupport; import com.oracle.graal.pointsto.constraints.UnsupportedPlatformException; import com.oracle.graal.pointsto.meta.AnalysisMethod; @@ -100,15 +97,13 @@ import com.oracle.svm.core.jdk.JNIRegistrationUtil; import com.oracle.svm.core.jdk.NativeLibrarySupport; import com.oracle.svm.core.jdk.PlatformNativeLibrarySupport; -import com.oracle.svm.core.jdk.SecurityProvidersSupport; +import com.oracle.svm.core.jdk.SecurityProviderRuntimeState; import com.oracle.svm.core.jdk.SecuritySubstitutions; -import com.oracle.svm.core.util.UserError; import com.oracle.svm.hosted.FeatureImpl.BeforeAnalysisAccessImpl; import com.oracle.svm.hosted.FeatureImpl.DuringAnalysisAccessImpl; import com.oracle.svm.hosted.FeatureImpl.DuringSetupAccessImpl; import com.oracle.svm.hosted.analysis.Inflation; import com.oracle.svm.hosted.c.NativeLibraries; -import com.oracle.svm.hosted.reflect.ReflectionDataBuilder; import com.oracle.svm.hosted.substitute.DeletedElementException; import com.oracle.svm.hosted.substitute.AnnotationSubstitutionProcessor; import com.oracle.svm.shared.BuildPhaseProvider; @@ -249,14 +244,8 @@ public static class Options { /** All available services, organized by service type. */ private Map> availableServices; - /** All providers deemed to be used by this feature. */ - private final Set usedProviders = Collections.synchronizedSet(Collections.newSetFromMap(new IdentityHashMap<>())); - private final Set> candidateProviderClasses = ConcurrentHashMap.newKeySet(); - private final Set> processedProviderClasses = ConcurrentHashMap.newKeySet(); - private final AtomicBoolean candidateProviderClassesChanged = new AtomicBoolean(); - - /** Providers marked as used by the deprecated compatibility option. */ - private final EconomicSet manuallyMarkedUsedProviderClassNames = EconomicSet.create(); + private final SecurityProviderRegistrationPlanner providerPlanner = new SecurityProviderRegistrationPlanner(); + private SecurityProviderMode mode; private Field verificationResultsField; private Field providerListField; @@ -275,10 +264,38 @@ public static class Options { private final ScanReason scanReason = new OtherReason("Manual rescan triggered from " + SecurityServicesFeature.class); private final Map> buildTimeProvidersByClassName = new HashMap<>(); + private SecurityProviderCatalogRegistrar catalogRegistrar; @Override public void afterRegistration(AfterRegistrationAccess a) { - ImageSingletons.add(SecurityProvidersSupport.class, new SecurityProvidersSupport()); + mode = SecurityProviderMode.current(); + catalogRegistrar = new SecurityProviderCatalogRegistrar(new SecurityProviderCatalogRegistrar.Host() { + @Override + public boolean isLoadableProviderClass(DuringAnalysisAccess access, Class providerClass) { + return SecurityServicesFeature.this.isLoadableProviderClass(access, providerClass); + } + + @Override + public Provider instantiateProvider(Class providerClass) { + return SecurityServicesFeature.instantiateProvider(providerClass); + } + + @Override + public boolean isValidService(Service service) { + return isValid(service); + } + + @Override + public void registerService(DuringAnalysisAccess access, Service service) { + SecurityServicesFeature.this.registerService(access, service); + } + + @Override + public Object getProviderVerificationResult(Provider provider) { + return SecurityServicesFeature.this.getProviderVerificationResult(provider); + } + }, buildTimeProvidersByClassName); + ImageSingletons.add(SecurityProviderRuntimeState.class, new SecurityProviderRuntimeState()); ModuleSupport.accessPackagesToClass(ModuleSupport.Access.OPEN, getClass(), false, "java.base", "sun.security.x509"); ModuleSupport.accessModuleByClass(ModuleSupport.Access.OPEN, getClass(), Security.class); @@ -292,13 +309,13 @@ public void duringSetup(DuringSetupAccess a) { JVMCIRuntimeClassInitializationSupport rci = JVMCIRuntimeClassInitializationSupport.singleton(); oidTableField = access.findField("sun.security.util.ObjectIdentifier", "oidTable"); oidMapField = access.findField(OIDMap.class, "oidMap"); - if (!FutureDefaultsOptions.securityProvidersInitializedAtRunTime()) { + if (!mode.runtimeProviderList()) { verificationResultsField = access.findField("javax.crypto.JceSecurity", "verificationResults"); providerListField = access.findField("sun.security.jca.Providers", "providerList"); classCacheField = access.findField(Service.class, "classCache"); constructorCacheField = access.findField(Service.class, "constructorCache"); } else { - SecurityProvidersSupport support = SecurityProvidersSupport.singleton(); + SecurityProviderRuntimeState support = SecurityProviderRuntimeState.singleton(); ModuleSupport.accessPackagesToClass(ModuleSupport.Access.OPEN, SecuritySubstitutions.class, false, "java.base", "sun.security.ec"); ResolvedJavaMethod sunECConstructor = constructor(a, "sun.security.ec.SunEC"); support.setSunECConstructor((Constructor) OriginalMethodProvider.getJavaMethod(sunECConstructor)); @@ -402,7 +419,12 @@ public void beforeAnalysis(BeforeAnalysisAccess a) { initializeServiceRegistrationData(); access.registerSubtypeReachabilityHandler((_, providerClass) -> addCandidateProviderClass(providerClass), Provider.class); registerServiceProviderCandidates(access); - registerManuallyConfiguredProvidersForReflection(access); + LegacySecurityProviderCompatibility.registerAdditionalProviders(access, providerClass -> { + if (shouldRegisterProviderClassForReflection(access, providerClass)) { + providerPlanner.requestCompleteProvider(providerClass, SecurityProviderRegistrationPlanner.Source.LEGACY_ADDITIONAL_PROVIDER); + registerProviderClassForReflection(providerClass); + } + }); if (Options.EnableSecurityServicesFeature.getValue()) { registerServiceReachabilityHandlers(access); } @@ -422,7 +444,7 @@ public void beforeAnalysis(BeforeAnalysisAccess a) { PlatformNativeLibrarySupport.singleton().addBuiltinNativePrefix("sun_security_mscapi"); } - if (!FutureDefaultsOptions.securityProvidersInitializedAtRunTime()) { + if (!mode.runtimeProviderList()) { access.registerFieldValueTransformer(providerListField, new FieldValueTransformerWithAvailability() { // JVMCI migration blocked by GR-72131: Refactor security service code for project // Terminus. @@ -498,11 +520,11 @@ private ConcurrentHashMap, Object> filterVerificationCac private boolean shouldRemoveVerificationResult(Provider provider) { /* - * Verification results for used providers are copied into SecurityProvidersSupport, keyed by - * provider name and class name. Keep them out of JceSecurity.verificationResults so the weak + * Verification results for used providers are copied into SecurityProviderRuntimeState, + * keyed by provider class name. Keep them out of JceSecurity.verificationResults so the weak * cache keys do not keep build-time provider objects reachable in the image heap. */ - return provider == null || usedProviders.contains(provider) || shouldRemoveProvider(provider); + return provider == null || catalogRegistrar.isUsed(provider) || shouldRemoveProvider(provider); } private List filterProviderList(Object originalValue) { @@ -510,21 +532,6 @@ private List filterProviderList(Object originalValue) { return ((ProviderList) originalValue).providers().stream().filter(p -> !shouldRemoveProvider(p)).toList(); } - private void registerManuallyConfiguredProvidersForReflection(BeforeAnalysisAccess access) { - BeforeAnalysisAccessImpl accessImpl = (BeforeAnalysisAccessImpl) access; - for (String value : Options.AdditionalSecurityProviders.getValue().values()) { - for (String className : value.split(",")) { - Class classByName = access.findClassByName(className); - UserError.guarantee(classByName != null, - "Manually marked security provider class doesn't exist: %s. Make sure that the class name is correct and that the class is on the image builder classpath.", className); - manuallyMarkedUsedProviderClassNames.add(className); - if (shouldRegisterProviderClassForReflection(accessImpl, classByName)) { - registerProviderClassForReflection(classByName); - } - } - } - } - private boolean shouldRegisterProviderClassForReflection(BeforeAnalysisAccessImpl access, Class providerClass) { if (!Provider.class.isAssignableFrom(providerClass)) { return false; @@ -542,13 +549,13 @@ public boolean shouldRemoveProvider(Provider p) { if (p == null) { return true; } - if (usedProviders.contains(p)) { + if (catalogRegistrar.isUsed(p)) { return false; } if (substitutionProcessor.isDeleted(p.getClass())) { return true; } - return !manuallyMarkedUsedProviderClassNames.contains(p.getClass().getName()); + return true; } private static void traceRemovedProviders(List removedProviders) { @@ -627,15 +634,7 @@ private static void linkJaas(DuringAnalysisAccess a) { } private static Iterable> computeKnownServices(BeforeAnalysisAccess access) { - EconomicSet> allKnownServices = EconomicSet.create(knownServices); - for (String value : Options.AdditionalSecurityServiceTypes.getValue().values()) { - for (String serviceClazzName : value.split(",")) { - Class serviceClazz = access.findClassByName(serviceClazzName); - UserError.guarantee(serviceClazz != null, "Unable to find additional security service class %s", serviceClazzName); - allKnownServices.add(serviceClazz); - } - } - return allKnownServices; + return LegacySecurityProviderCompatibility.additionalServiceTypes(access, knownServices); } private Class classSaslClient; @@ -750,9 +749,7 @@ private void registerServiceProviderCandidates(BeforeAnalysisAccess access) { } private void addCandidateProviderClass(Class providerClass) { - if (candidateProviderClasses.add(providerClass)) { - candidateProviderClassesChanged.set(true); - } + providerPlanner.addCandidate(providerClass); } private void registerServices(DuringAnalysisAccess access, Object trigger, Class serviceClass) { @@ -790,7 +787,7 @@ private void registerServices(DuringAnalysisAccess access, Object trigger, Strin * processed only once. */ if (processedServiceTypes.add(serviceType)) { - if (FutureDefaultsOptions.explicitSecurityProviderRegistration() && serviceType.equals(SECURE_RANDOM_SERVICE)) { + if (mode.explicitRegistration() && serviceType.equals(SECURE_RANDOM_SERVICE)) { registerSecureRandomProvidersFromPlatformSignal(); } // Service reachability is a compatibility inclusion signal. @@ -812,8 +809,8 @@ private void registerSecureRandomProvidersFromPlatformSignal() { providerClasses.add(service.getProvider().getClass()); } for (Class providerClass : providerClasses) { + providerPlanner.requestCompleteProvider(providerClass, SecurityProviderRegistrationPlanner.Source.SECURE_RANDOM_PLATFORM); registerProviderClassForReflection(providerClass); - addCandidateProviderClass(providerClass); } } @@ -924,18 +921,6 @@ private static void registerSpiClass(Method getSpiClassMethod, String serviceTyp } } - private void registerProvider(Provider provider) { - if (usedProviders.add(provider)) { - registerForReflection(provider.getClass()); - /* Trigger initialization of lazy field java.security.Provider.entrySet. */ - provider.entrySet(); - SecurityProvidersSupport support = SecurityProvidersSupport.singleton(); - String providerClassName = provider.getClass().getName(); - support.addIncludedSecurityProviderClass(providerClassName); - support.addSecurityProviderVerificationResult(providerClassName, getProviderVerificationResult(provider)); - } - } - private Object getProviderVerificationResult(Provider provider) { // §FS-security-providers.5.3: Preserve the build-time outcome by provider class. if (!buildTimeProvidersByClassName.containsKey(provider.getClass().getName())) { @@ -967,40 +952,6 @@ private Object getProviderVerificationResult(Provider provider) { // Use the preferred construction path and retain the complete valid, resolvable catalog. // §FS-security-providers.2.2 and §FS-security-providers.2.3 - private void includeProviderClass(DuringAnalysisAccess access, Class providerClass) { - if (!isLoadableProviderClass(access, providerClass)) { - registerApplicationSuppliedProviderClass(providerClass); - return; - } - List providers = buildTimeProvidersByClassName.get(providerClass.getName()); - if (providers == null) { - providers = List.of(instantiateProvider(providerClass)); - } - SecurityProvidersSupport.singleton().addIncludedSecurityProviderClass(providerClass.getName()); - // Register every configured instance and the union of their service metadata. - // §FS-security-providers.2.3 - for (Provider provider : providers) { - registerProvider(provider); - for (Service service : provider.getServices()) { - if (isValid(service)) { - registerService(access, service); - } - } - } - } - - /** - * An application-supplied provider does not need a construction path because the application - * already owns its instance. Preserve only its class-based JCE verification result; its service - * implementations must be registered independently. - */ - private void registerApplicationSuppliedProviderClass(Class providerClass) { - // §FS-security-providers.5.3: Preserve verification without reconstructing the provider. - List buildTimeProviders = buildTimeProvidersByClassName.get(providerClass.getName()); - Object verificationResult = buildTimeProviders == null ? Boolean.TRUE : getProviderVerificationResult(buildTimeProviders.getFirst()); - SecurityProvidersSupport.singleton().addSecurityProviderVerificationResult(providerClass.getName(), verificationResult); - } - private boolean isLoadableProviderClass(DuringAnalysisAccess access, Class providerClass) { if (providerClass == null || providerClass.isArray() || providerClass.isPrimitive() || Modifier.isAbstract(providerClass.getModifiers())) { return false; @@ -1033,7 +984,7 @@ private static Provider instantiateProvider(Class providerClass) { private void registerService(DuringAnalysisAccess a, Service service) { // §FS-security-providers.7.3: Explicit mode disables service-driven provider inclusion. - if (FutureDefaultsOptions.explicitSecurityProviderRegistration() && !isProviderRegisteredForReflection(service.getProvider().getClass())) { + if (mode.explicitRegistration() && !isProviderRegisteredForReflection(service.getProvider().getClass())) { trace("Skipped service %s because provider %s was not registered for reflection.", asString(service), service.getProvider().getClass().getName()); return; } @@ -1059,7 +1010,11 @@ private void registerService(DuringAnalysisAccess a, Service service) { if (isCertificateFactory(service) && service.getAlgorithm().equals(X509)) { registerX509Extensions(a); } - registerProvider(service.getProvider()); + if (!mode.explicitRegistration()) { + Class providerClass = service.getProvider().getClass(); + providerPlanner.beforeLegacyReflectionRegistration(providerClass, SecurityServicesFeature::isProviderRegisteredForReflection); + } + catalogRegistrar.registerProvider(a, service.getProvider()); } } else { trace("Cannot register service %s. Reason: %s.", asString(service), serviceClassResult.getException()); @@ -1069,16 +1024,16 @@ private void registerService(DuringAnalysisAccess a, Service service) { // Recognize every qualifying reflection-registration signal. // §FS-security-providers.1.1 and §FS-security-providers.2.1 private static boolean isProviderRegisteredForReflection(Class providerClass) { - ReflectionDataBuilder reflectionData = (ReflectionDataBuilder) ImageSingletons.lookup(RuntimeReflectionSupport.class); - if (reflectionData.isTypeRegisteredForReflection(providerClass)) { + ReflectionRegistrationView reflection = ReflectionRegistrationView.singleton(); + if (reflection.hasTypeAccess(providerClass)) { return true; } Constructor constructor = findDeclaredNullaryConstructor(providerClass); - if (constructor != null && reflectionData.isMethodRegisteredForReflection(constructor)) { + if (constructor != null && reflection.hasExecutableAccess(constructor)) { return true; } Method providerMethod = findProviderMethod(providerClass); - return providerMethod != null && reflectionData.isMethodRegisteredForReflection(providerMethod); + return providerMethod != null && reflection.hasExecutableAccess(providerMethod); } private static void registerProviderClassForReflection(Class providerClass) { @@ -1166,22 +1121,14 @@ private void registerX509Extensions(DuringAnalysisAccess a) { @Override public void duringAnalysis(DuringAnalysisAccess a) { DuringAnalysisAccessImpl access = (DuringAnalysisAccessImpl) a; - // §AR-security-providers.5: Consume concurrent candidates in the serialized feature pass. - boolean newProviderCandidate = candidateProviderClassesChanged.getAndSet(false); - boolean processedProvider = false; - for (Class providerClass : candidateProviderClasses) { - if (!processedProviderClasses.contains(providerClass) && isProviderRegisteredForReflection(providerClass)) { - processedProviderClasses.add(providerClass); - includeProviderClass(access, providerClass); - processedProvider = true; - } - } - if (processedProvider || newProviderCandidate) { + // Consume concurrent plans in the serialized feature pass. + if (providerPlanner.processNewCompleteProviders(SecurityServicesFeature::isProviderRegisteredForReflection, + providerClass -> catalogRegistrar.includeProviderClass(access, providerClass))) { // Request the extra pass here, not from the concurrent reachability callback. access.requireAnalysisIteration(); } access.rescanRoot(oidTableField, scanReason); - if (!FutureDefaultsOptions.securityProvidersInitializedAtRunTime()) { + if (!mode.runtimeProviderList()) { maybeScanVerificationResultsField(access); maybeScanProvidersField(access); if (cachedProviders != null) { diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ServiceLoaderFeature.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ServiceLoaderFeature.java index a4301804f9c3..7c2c9385cb0d 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ServiceLoaderFeature.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ServiceLoaderFeature.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -146,6 +146,8 @@ public static class Options { "jdk.jshell.execution.impl.ConsoleImpl$ConsoleProviderImpl"); private final EconomicSet serviceProvidersToSkip = EconomicSet.create(SKIPPED_PROVIDERS); + private final LinkedHashSet securityProviderDescriptors = new LinkedHashSet<>(); + private SecurityProviderMode securityProviderMode; @Override public boolean isInConfiguration(IsInConfigurationAccess access) { @@ -154,7 +156,8 @@ public boolean isInConfiguration(IsInConfigurationAccess access) { @Override public void afterRegistration(AfterRegistrationAccess access) { - if (!FutureDefaultsOptions.securityProvidersInitializedAtRunTime()) { + securityProviderMode = SecurityProviderMode.current(); + if (!securityProviderMode.runtimeProviderList() && !securityProviderMode.explicitRegistration()) { servicesToSkip.add(java.security.Provider.class.getName()); } if (!FutureDefaultsOptions.resourceBundlesInitializedAtRunTime()) { @@ -170,6 +173,10 @@ public void beforeAnalysis(BeforeAnalysisAccess access) { // §FS-security-providers.7.2: Permit an absent descriptor without including its providers. Resources.currentLayer().registerNegativeQuery(access.getApplicationClassLoader().getUnnamedModule(), SECURITY_PROVIDER_SERVICE_RESOURCE); accessImpl.imageClassLoader.classLoaderSupport.serviceProvidersForEach((serviceName, providers) -> { + if (securityProviderMode.explicitRegistration() && serviceName.equals(java.security.Provider.class.getName())) { + securityProviderDescriptors.addAll(providers); + return; + } Collection providersToSkip = providers; try { if (!servicesToSkip.contains(serviceName)) { @@ -204,6 +211,25 @@ public void beforeAnalysis(BeforeAnalysisAccess access) { }); } + @Override + public void duringAnalysis(DuringAnalysisAccess access) { + if (!securityProviderDescriptors.isEmpty()) { + preserveSecurityProviderDescriptors(access, securityProviderDescriptors); + securityProviderDescriptors.clear(); + } + } + + private void preserveSecurityProviderDescriptors(DuringAnalysisAccess access, Collection providers) { + LinkedHashSet registeredProviders = new LinkedHashSet<>(); + for (String provider : providers) { + if (!serviceProvidersToSkip.contains(provider)) { + registerProviderForRuntimeResourceAccess(access, provider, registeredProviders); + } + } + registerProviderForRuntimeResourceAccess(access.getApplicationClassLoader().getUnnamedModule(), + java.security.Provider.class.getName(), registeredProviders); + } + void handleServiceClassIsReachable(DuringAnalysisAccess access, ResolvedJavaType serviceProvider, Collection providers) { FeatureImpl.DuringAnalysisAccessImpl accessImpl = (FeatureImpl.DuringAnalysisAccessImpl) access; boolean isSecurityProviderService = serviceProvider.equals(accessImpl.getMetaAccess().lookupJavaType(java.security.Provider.class)); diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/services/SecurityServiceExplicitProviderRegistrationTest.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/services/SecurityServiceExplicitProviderRegistrationTest.java index 6572c6367d05..e316056d53be 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/services/SecurityServiceExplicitProviderRegistrationTest.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/services/SecurityServiceExplicitProviderRegistrationTest.java @@ -36,7 +36,7 @@ import org.junit.Assume; import org.junit.Test; -import com.oracle.svm.core.jdk.SecurityProvidersSupport; +import com.oracle.svm.core.jdk.SecurityProviderRuntimeState; import com.oracle.svm.test.NativeImageBuildArgs; @NativeImageBuildArgs({ @@ -76,11 +76,12 @@ public void testReachableFactoryDoesNotIncludeUnregisteredProvider() { @Test public void testUnregisteredProviderCannotReuseVerificationByName() { Assume.assumeTrue("native image runtime only", ImageInfo.inImageRuntimeCode()); - SecurityProvidersSupport support = SecurityProvidersSupport.singleton(); + SecurityProviderRuntimeState state = SecurityProviderRuntimeState.singleton(); - Assert.assertEquals(Boolean.TRUE, - support.getSecurityProviderVerificationResult(new SecurityServiceTest.ReflectionMetadataProvider())); - Assert.assertNull(support.getSecurityProviderVerificationResult(new SameNameUnregisteredProvider())); + SecurityProviderRuntimeState.ProviderInfo registered = state.getProviderInfo(new SecurityServiceTest.ReflectionMetadataProvider()); + Assert.assertNotNull(registered); + Assert.assertNull(registered.verificationFailure()); + Assert.assertNull(state.getProviderInfo(new SameNameUnregisteredProvider())); } public static final class SameNameUnregisteredProvider extends Provider { From 2b4edd1b221df6c9eafddcd19bad59802ff532ad Mon Sep 17 00:00:00 2001 From: Vojin Jovanovic Date: Sun, 26 Jul 2026 21:25:05 +0200 Subject: [PATCH 40/63] GR-69858: Avoid reassigning security provider parameter --- .../oracle/svm/core/jdk/SecurityProviderRuntimeState.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SecurityProviderRuntimeState.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SecurityProviderRuntimeState.java index a29d7e393f59..db7bf226db89 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SecurityProviderRuntimeState.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SecurityProviderRuntimeState.java @@ -144,14 +144,15 @@ public void registerApplicationSuppliedProvider(String providerClassName, Object @Platforms(Platform.HOSTED_ONLY.class) private void registerProvider(String providerClassName, AcquisitionKind acquisitionKind, Object verificationResult) { Exception verificationFailure = verificationResult instanceof Exception exception ? exception : null; + AcquisitionKind effectiveAcquisitionKind = acquisitionKind; ProviderInfo previous = providerInfos.get(providerClassName); if (previous != null && previous.acquisitionKind() == AcquisitionKind.JDK_CONSTRUCTIBLE) { - acquisitionKind = AcquisitionKind.JDK_CONSTRUCTIBLE; + effectiveAcquisitionKind = AcquisitionKind.JDK_CONSTRUCTIBLE; } if (previous != null && previous.verificationFailure() != null) { verificationFailure = previous.verificationFailure(); } - providerInfos.put(providerClassName, new ProviderInfo(acquisitionKind, verificationFailure)); + providerInfos.put(providerClassName, new ProviderInfo(effectiveAcquisitionKind, verificationFailure)); } public ProviderInfo getProviderInfo(Provider provider) { From fa2bf8f706b5eaebf1861648a2feb564c6f548f7 Mon Sep 17 00:00:00 2001 From: Vojin Jovanovic Date: Mon, 27 Jul 2026 07:15:44 +0200 Subject: [PATCH 41/63] [GR-69858] Fix reflection usage exclusions --- .../src/com/oracle/svm/hosted/test/VerifyReflectionUsage.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/substratevm/src/com.oracle.svm.hosted.test/src/com/oracle/svm/hosted/test/VerifyReflectionUsage.java b/substratevm/src/com.oracle.svm.hosted.test/src/com/oracle/svm/hosted/test/VerifyReflectionUsage.java index f1921c064643..2b50c16503c6 100644 --- a/substratevm/src/com.oracle.svm.hosted.test/src/com/oracle/svm/hosted/test/VerifyReflectionUsage.java +++ b/substratevm/src/com.oracle.svm.hosted.test/src/com/oracle/svm/hosted/test/VerifyReflectionUsage.java @@ -138,6 +138,7 @@ public interface Provider { clazz("com.oracle.svm.core.jdk.BacktraceVisitor"), clazz("com.oracle.svm.core.jdk.BufferAddressTransformer"), clazz("com.oracle.svm.core.jdk.BuildStackTraceVisitor"), + clazz("com.oracle.svm.core.jdk.BuiltInSecurityProviderLoader"), clazz("com.oracle.svm.core.jdk.ContainsVerifyJars"), clazz("com.oracle.svm.core.jdk.GetLatestUserDefinedClassLoaderVisitor"), clazz("com.oracle.svm.core.jdk.JavaIOClassCachePresent"), @@ -154,7 +155,6 @@ public interface Provider { clazz("com.oracle.svm.core.jdk.Resources$ModuleInstanceResourceKey"), clazz("com.oracle.svm.core.jdk.Resources$ModuleNameResourceKey"), clazz("com.oracle.svm.core.jdk.resources.MissingResourceRegistrationUtils"), - clazz("com.oracle.svm.core.jdk.BuiltInSecurityProviderLoader"), clazz("com.oracle.svm.core.jdk.SecurityProviderRuntimeAccess"), clazz("com.oracle.svm.core.jdk.StackAccessControlContextVisitor"), clazz("com.oracle.svm.core.jfr.events.ThreadParkEvent"), @@ -296,6 +296,7 @@ public interface Provider { clazz("com.oracle.svm.hosted.ResourcesFeature$1"), clazz("com.oracle.svm.hosted.ResourcesFeature$ResourceCollectorImpl"), clazz("com.oracle.svm.hosted.ResourcesFeature$ResourcesRegistryImpl"), + clazz("com.oracle.svm.hosted.SecurityProviderCatalogRegistrar"), clazz("com.oracle.svm.hosted.SecurityServicesFeature"), clazz("com.oracle.svm.hosted.snippets.ReflectionPlugins"), clazz("com.oracle.svm.hosted.snippets.ReflectionPlugins$3"), From c58f4afcb9d11038503c37f0fb234523e876f317 Mon Sep 17 00:00:00 2001 From: Vojin Jovanovic Date: Mon, 27 Jul 2026 08:47:42 +0200 Subject: [PATCH 42/63] [GR-69858] Refine security provider registration --- .../functional-spec/security-providers.md | 9 +- substratevm/mx.substratevm/mx_substratevm.py | 1 + .../svm/agent/BreakpointInterceptor.java | 104 +++++++++--------- .../jdk/BuiltInSecurityProviderLoader.java | 15 ++- .../jdk/JceProviderVerificationSupport.java | 2 +- .../jdk/SecurityProviderRuntimeAccess.java | 8 +- .../jdk/SecurityProviderRuntimeState.java | 62 ++++++++--- .../svm/core/jdk/SecuritySubstitutions.java | 2 +- .../SecuritySubstitutionRuntimeInit.java | 2 +- .../RuntimeCompilationFeature.java | 3 +- .../hosted/NativeImageClassLoaderSupport.java | 5 +- .../SecurityProviderCatalogRegistrar.java | 4 +- .../svm/hosted/SecurityServicesFeature.java | 46 ++++---- .../svm/hosted/ServiceLoaderFeature.java | 14 +-- .../hosted/image/PreserveOptionsSupport.java | 2 + ...rviceExplicitProviderRegistrationTest.java | 6 +- .../test/services/SecurityServiceTest.java | 28 +++++ 17 files changed, 183 insertions(+), 130 deletions(-) diff --git a/substratevm/docs/functional-spec/security-providers.md b/substratevm/docs/functional-spec/security-providers.md index 6d96d66305b0..43490488d62a 100644 --- a/substratevm/docs/functional-spec/security-providers.md +++ b/substratevm/docs/functional-spec/security-providers.md @@ -263,8 +263,8 @@ registers the provider class but does not retain its service implementations. This requirement applies both to loading a provider from the configured provider list and to Java Cryptography Extension (JCE) verification of a programmatically supplied provider. -Without exact reachability metadata checking, this specification does not guarantee a particular -missing-registration diagnostic. +Without exact reachability metadata checking, the operation reports an actionable +`SecurityException` that identifies the unregistered provider type instead of an internal error. ## 5. Programmatically Supplied Providers @@ -379,6 +379,8 @@ Filtering unregistered providers preserves the ordering and lookup results speci A class-path _META-INF/services/java.security.Provider_ descriptor does not register the named provider for reflection. +Native Image preserves the descriptor only when `ServiceLoader` access to +`java.security.Provider` is reachable. If the provider is unregistered, service loading must not return a provider instance. Iterating to its descriptor can report the standard `ServiceConfigurationError` or missing-reflection error, and the provider's services remain unavailable. @@ -388,6 +390,9 @@ missing-reflection error, and the provider's services remain unavailable. Without `--future-defaults=explicit-security-provider-registration`, reachability of a JCA service factory or JDK security-service facade can include services of the corresponding service type even when their provider classes have no reflection metadata. +In this compatibility mode, pre-existing reflection metadata for a provider remains inert unless +another compatibility registration signal includes that provider; Native Image must not construct +the provider or expand its complete service catalog merely because its type is registered. This compatibility behavior applies to supported facades such as the Generic Security Services API (GSS-API). For example, reachability of any `Signature.getInstance` overload can cause signature services and diff --git a/substratevm/mx.substratevm/mx_substratevm.py b/substratevm/mx.substratevm/mx_substratevm.py index 0774c64c30dc..a6a3e7bdf52f 100644 --- a/substratevm/mx.substratevm/mx_substratevm.py +++ b/substratevm/mx.substratevm/mx_substratevm.py @@ -820,6 +820,7 @@ def write_micronaut_style_service_entries(cp_entry, service_name, implementation '--add-exports=org.graalvm.nativeimage.builder/com.oracle.svm.core.libjvm=ALL-UNNAMED', '--add-exports=org.graalvm.nativeimage.builder/com.oracle.svm.core.properties=ALL-UNNAMED', '--add-opens=org.graalvm.nativeimage.builder/com.oracle.svm.core.jdk=ALL-UNNAMED', + '-H:AdditionalSecurityProviders=sun.security.pkcs11.SunPKCS11', '-H:AdditionalSecurityServiceTypes=com.oracle.svm.test.services.SecurityServiceTest$JCACompliantNoOpService', ]) if extra_build_args is not None: diff --git a/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/BreakpointInterceptor.java b/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/BreakpointInterceptor.java index 36f66a254fc3..ce0656b5f47b 100644 --- a/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/BreakpointInterceptor.java +++ b/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/BreakpointInterceptor.java @@ -133,6 +133,7 @@ * Therefore, we do not support this case for now. */ final class BreakpointInterceptor { + private static final int MAX_SECURITY_STACK_DEPTH = 32; private static Tracer tracer; private static NativeImageAgent agent; private static Supplier interceptedStateSupplier; @@ -771,53 +772,53 @@ private static boolean newSecurityServiceInstance(JNIEnvironment jni, JNIObjectH * §FS-security-providers.6.1: Trace a provider that was cached before a Security API lookup. */ private static boolean getCachedSecurityProvider(JNIEnvironment jni, JNIObjectHandle thread, @SuppressWarnings("unused") Breakpoint bp, InterceptedState state) { - JNIObjectHandle securityApiCaller = findSecurityAcquisitionCaller(state); - if (securityApiCaller.equal(nullHandle())) { + SecurityAcquisition acquisition = findSecurityAcquisition(jni, thread, state); + if (acquisition == null) { return true; } JNIObjectHandle providerConfig = getReceiver(thread); JNIObjectHandle provider = jniFunctions().getGetObjectField().invoke(jni, providerConfig, agent.handles().sunSecurityJcaProviderConfigProvider); boolean validResult = !clearException(jni) && provider.notEqual(nullHandle()); - JNIObjectHandle requestedProviderName = findRequestedSecurityProviderName(thread, state); + JNIObjectHandle requestedProviderName = acquisition.requestedProviderName(); if (validResult && requestedProviderName.notEqual(nullHandle())) { JNIObjectHandle providerName = Support.callObjectMethod(jni, provider, agent.handles().javaSecurityProviderGetName); validResult = !clearException(jni) && fromJniString(jni, requestedProviderName).equals(fromJniString(jni, providerName)); } - traceSecurityProvider(jni, provider, validResult, securityApiCaller, state); + if (validResult) { + traceJdkConstructibleSecurityProvider(jni, provider, acquisition.callerClass(), state); + } + traceSecurityProvider(jni, provider, validResult, acquisition.callerClass(), state); return true; } - private static JNIObjectHandle findSecurityAcquisitionCaller(InterceptedState state) { - JNIObjectHandle securityApiCaller = nullHandle(); - for (int depth = 1;; depth++) { - JNIMethodId callerMethod = state.getCallerMethod(depth); - if (callerMethod.isNull()) { - return securityApiCaller; - } - if (isSecurityMutationMethod(callerMethod)) { - return nullHandle(); - } - if (isSecurityAcquisitionMethod(callerMethod)) { - securityApiCaller = state.getCallerClass(depth + 1); - } - } + private record SecurityAcquisition(JNIObjectHandle callerClass, JNIObjectHandle requestedProviderName) { } - private static JNIObjectHandle findRequestedSecurityProviderName(JNIObjectHandle thread, InterceptedState state) { - JNIObjectHandle requestedProviderName = nullHandle(); - for (int depth = 1;; depth++) { + private static SecurityAcquisition findSecurityAcquisition(JNIEnvironment jni, JNIObjectHandle thread, InterceptedState state) { + SecurityAcquisition fallback = null; + for (int depth = 1; depth < MAX_SECURITY_STACK_DEPTH; depth++) { JNIMethodId callerMethod = state.getCallerMethod(depth); if (callerMethod.isNull()) { - return requestedProviderName; + return fallback; } if (isSecurityMutationMethod(callerMethod)) { - return nullHandle(); + return null; } if (isSecurityAcquisitionMethod(callerMethod)) { - requestedProviderName = callerMethod.equal(agent.handles().javaSecurityGetProvider) + JNIObjectHandle requestedProviderName = callerMethod.equal(agent.handles().javaSecurityGetProvider) ? Support.getObjectArgument(thread, depth, 0) : nullHandle(); + SecurityAcquisition acquisition = new SecurityAcquisition(state.getCallerClass(depth + 1), requestedProviderName); + if (requestedProviderName.notEqual(nullHandle())) { + return acquisition; + } + fallback = acquisition; + } else if (fallback != null) { + String callerClassName = getClassNameOrNull(jni, getMethodDeclaringClass(callerMethod)); + if (callerClassName != null && !isJdkSecurityImplementation(callerClassName)) { + return fallback; + } } } } @@ -839,19 +840,15 @@ private static boolean isSecurityMutationMethod(JNIMethodId method) { } private static JNIObjectHandle findProviderServiceCaller(JNIEnvironment jni, InterceptedState state) { - for (int depth = 1;; depth++) { - JNIMethodId callerMethod = state.getCallerMethod(depth); - if (callerMethod.isNull()) { - return nullHandle(); - } - if (callerMethod.equal(agent.handles().javaSecurityProviderServiceNewInstance)) { - return findExternalSecurityCaller(jni, state, depth + 1); - } + JNIMethodId directCaller = state.getCallerMethod(1); + if (directCaller.equal(agent.handles().javaSecurityProviderServiceNewInstance)) { + return findExternalSecurityCaller(jni, state, 2); } + return nullHandle(); } private static JNIObjectHandle findExternalSecurityCaller(JNIEnvironment jni, InterceptedState state, int startDepth) { - for (int depth = startDepth;; depth++) { + for (int depth = startDepth; depth < MAX_SECURITY_STACK_DEPTH; depth++) { JNIMethodId callerMethod = state.getCallerMethod(depth); if (callerMethod.isNull()) { return nullHandle(); @@ -873,29 +870,32 @@ private static boolean isJdkSecurityImplementation(String className) { className.startsWith("com.sun.crypto.provider."); } - /** §FS-security-providers.6.1 and §FS-security-providers.6.2: Trace lookup metadata only. */ - private static boolean getStaticSecurityProviderByName(JNIEnvironment jni, JNIObjectHandle thread, @SuppressWarnings("unused") Breakpoint bp, InterceptedState state) { - JNIObjectHandle providerName = getObjectArgument(thread, 0); - if (providerName.equal(nullHandle())) { - return true; + /** §FS-security-providers.6.1: Retain construction for a successfully acquired JDK provider. */ + private static void traceJdkConstructibleSecurityProvider(JNIEnvironment jni, JNIObjectHandle provider, JNIObjectHandle callerClass, InterceptedState state) { + JNIObjectHandle providerClass = Support.callObjectMethod(jni, provider, agent.handles().javaLangObjectGetClass); + if (clearException(jni)) { + return; + } + String providerClassName = getClassNameOrNull(jni, providerClass); + if (providerClassName == null) { + return; } - JNIObjectHandle providerClass = switch (fromJniString(jni, providerName)) { - case "SUN", "sun.security.provider.Sun" -> agent.handles().sunSecurityProviderSun; - case "SunRsaSign", "sun.security.rsa.SunRsaSign" -> agent.handles().sunSecurityRsaSunRsaSign; - case "SunEC", "sun.security.ec.SunEC" -> agent.handles().sunSecurityEcSunEC; - case "SunJSSE", "sun.security.ssl.SunJSSE" -> agent.handles().sunSecuritySslSunJSSE; - case "SunJCE", "com.sun.crypto.provider.SunJCE" -> agent.handles().comSunCryptoProviderSunJCE; - case "Apple", "apple.security.AppleProvider" -> agent.handles().appleSecurityAppleProvider; - default -> nullHandle(); + boolean hasNullaryConstruction = switch (providerClassName) { + case "sun.security.provider.Sun", + "sun.security.rsa.SunRsaSign", + "sun.security.ec.SunEC", + "sun.security.ssl.SunJSSE", + "com.sun.crypto.provider.SunJCE", + "apple.security.AppleProvider" -> true; + default -> false; }; - if (providerClass.notEqual(nullHandle())) { + if (hasNullaryConstruction) { /* - * Record the implicit no-argument construction without eagerly invoking getProvider, - * which would cache the provider while recursive tracing is suppressed. + * Record the implicit no-argument construction only after ProviderConfig exposed a + * non-null result. This also covers a provider cached before the agent started. */ - traceReflectBreakpoint(jni, providerClass, providerClass, state.getDirectCallerClass(), "invokeConstructor", true, state.getFullStackTraceOrNull(), (Object) new String[0]); + traceReflectBreakpoint(jni, providerClass, providerClass, callerClass, "invokeConstructor", true, state.getFullStackTraceOrNull(), (Object) new String[0]); } - return true; } /** §FS-security-providers.6.1: Provider insertion and lookup trace provider type access. */ @@ -2008,8 +2008,6 @@ private interface BreakpointHandler { brk("java/security/Security", "addProvider", "(Ljava/security/Provider;)I", BreakpointInterceptor::addStaticSecurityProvider), brk("java/security/Security", "insertProviderAt", "(Ljava/security/Provider;I)I", BreakpointInterceptor::addStaticSecurityProvider), - brk("java/security/Security", "getProvider", "(Ljava/lang/String;)Ljava/security/Provider;", - BreakpointInterceptor::getStaticSecurityProviderByName), brk("sun/security/jca/GetInstance", "getService", "(Ljava/lang/String;Ljava/lang/String;Ljava/security/Provider;)Ljava/security/Provider$Service;", BreakpointInterceptor::getSecurityServiceForProvider), brk("java/security/Provider$Service", "newInstance", "(Ljava/lang/Object;)Ljava/lang/Object;", diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/BuiltInSecurityProviderLoader.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/BuiltInSecurityProviderLoader.java index a5601f40e7eb..21be83b87402 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/BuiltInSecurityProviderLoader.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/BuiltInSecurityProviderLoader.java @@ -74,26 +74,25 @@ public static Provider load(String providerNameOrClassName, Debug debug) { if (providerClassName == null) { return null; } - SecurityProviderRuntimeState state = SecurityProviderRuntimeState.singleton(); return switch (providerClassName) { case "sun.security.provider.Sun" -> - state.isJdkConstructible(providerClassName) ? new sun.security.provider.Sun() : loadReflectively(providerClassName, debug); + SecurityProviderRuntimeState.isJdkConstructible(providerClassName) ? new sun.security.provider.Sun() : loadReflectively(providerClassName, debug); case "sun.security.rsa.SunRsaSign" -> - state.isJdkConstructible(providerClassName) ? new sun.security.rsa.SunRsaSign() : loadReflectively(providerClassName, debug); + SecurityProviderRuntimeState.isJdkConstructible(providerClassName) ? new sun.security.rsa.SunRsaSign() : loadReflectively(providerClassName, debug); case "com.sun.crypto.provider.SunJCE" -> - state.isJdkConstructible(providerClassName) ? new com.sun.crypto.provider.SunJCE() : loadReflectively(providerClassName, debug); + SecurityProviderRuntimeState.isJdkConstructible(providerClassName) ? new com.sun.crypto.provider.SunJCE() : loadReflectively(providerClassName, debug); case "sun.security.ssl.SunJSSE" -> - state.isJdkConstructible(providerClassName) ? new sun.security.ssl.SunJSSE() : loadReflectively(providerClassName, debug); + SecurityProviderRuntimeState.isJdkConstructible(providerClassName) ? new sun.security.ssl.SunJSSE() : loadReflectively(providerClassName, debug); case "sun.security.ec.SunEC" -> - state.isJdkConstructible(providerClassName) ? allocateSunECProvider(state) : loadReflectively(providerClassName, debug); + SecurityProviderRuntimeState.isJdkConstructible(providerClassName) ? allocateSunECProvider() : loadReflectively(providerClassName, debug); case "apple.security.AppleProvider" -> loadReflectively(providerClassName, debug); default -> null; }; } - private static Provider allocateSunECProvider(SecurityProviderRuntimeState state) { + private static Provider allocateSunECProvider() { try { - return (Provider) state.getSunECConstructor().newInstance(); + return (Provider) SecurityProviderRuntimeState.getSunECConstructor().newInstance(); } catch (InstantiationException | IllegalAccessException | InvocationTargetException e) { throw VMError.shouldNotReachHere("The SunEC constructor is not present."); } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/JceProviderVerificationSupport.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/JceProviderVerificationSupport.java index 3fef6d3adca0..e4d133dcf863 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/JceProviderVerificationSupport.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/JceProviderVerificationSupport.java @@ -34,7 +34,7 @@ private JceProviderVerificationSupport() { } public static Exception getVerificationResult(Provider provider) { - ProviderInfo info = SecurityProviderRuntimeState.singleton().getProviderInfo(provider); + ProviderInfo info = SecurityProviderRuntimeState.getProviderInfo(provider); if (info == null) { SecurityProviderRuntimeAccess.reportMissingRegistration(provider.getClass()); throw VMError.shouldNotReachHere("Security provider reflection access unexpectedly succeeded: " + provider.getClass().getName()); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SecurityProviderRuntimeAccess.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SecurityProviderRuntimeAccess.java index bd98ca59de24..d522c3fc7167 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SecurityProviderRuntimeAccess.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SecurityProviderRuntimeAccess.java @@ -28,7 +28,6 @@ import com.oracle.svm.core.metadata.MetadataTracer; import com.oracle.svm.shared.NeverInline; -import com.oracle.svm.shared.util.VMError; public final class SecurityProviderRuntimeAccess { private SecurityProviderRuntimeAccess() { @@ -40,9 +39,12 @@ public static void reportMissingRegistration(Class providerClass) { try { Class.forName(providerClass.getName(), false, providerClass.getClassLoader()); } catch (ClassNotFoundException ex) { - throw VMError.shouldNotReachHere("A reachable security provider class was not found.", ex); + throw new SecurityException( + "Attempted to use a security provider that was not registered for reflection at build time: " + providerClass.getName() + ". " + + "Add the provider type to reachability-metadata.json and rebuild the image.", + ex); } - throw VMError.shouldNotReachHere("A security provider without a verification result was registered for reflection: " + providerClass.getName()); + throw new SecurityException("Attempted to use a security provider without build-time verification: " + providerClass.getName()); } /** §FS-security-providers.6.1: Existing provider instances trace type access. */ diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SecurityProviderRuntimeState.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SecurityProviderRuntimeState.java index db7bf226db89..e036c0af401a 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SecurityProviderRuntimeState.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SecurityProviderRuntimeState.java @@ -29,19 +29,17 @@ import java.util.Properties; import org.graalvm.collections.EconomicMap; -import org.graalvm.nativeimage.ImageSingletons; import org.graalvm.nativeimage.Platform; import org.graalvm.nativeimage.Platforms; import com.oracle.svm.guest.staging.util.ImageHeapMap; import com.oracle.svm.shared.singletons.traits.BuiltinTraits.AllAccess; import com.oracle.svm.shared.singletons.traits.BuiltinTraits.NoLayeredCallbacks; -import com.oracle.svm.shared.singletons.traits.BuiltinTraits.PartiallyLayerAware; -import com.oracle.svm.shared.singletons.traits.SingletonLayeredInstallationKind.Duplicable; +import com.oracle.svm.shared.singletons.LayeredImageSingletonSupport; +import com.oracle.svm.shared.singletons.MultiLayeredImageSingleton; +import com.oracle.svm.shared.singletons.traits.SingletonLayeredInstallationKind.MultiLayer; import com.oracle.svm.shared.singletons.traits.SingletonTraits; -import jdk.graal.compiler.api.replacements.Fold; - /// AR-security-providers: Security Provider Architecture /// /// The security-provider implementation separates build-time policy from run-time enforcement. @@ -107,7 +105,7 @@ /// Deprecated provider options and service-reachability inclusion are confined to /// `LegacySecurityProviderCompatibility`. Removing compatibility behavior does not change the /// planner, catalog registrar, run-time manifest, or planned-default substitutions. -@SingletonTraits(access = AllAccess.class, layeredCallbacks = NoLayeredCallbacks.class, layeredInstallationKind = Duplicable.class, other = PartiallyLayerAware.class) +@SingletonTraits(access = AllAccess.class, layeredCallbacks = NoLayeredCallbacks.class, layeredInstallationKind = MultiLayer.class) public final class SecurityProviderRuntimeState { public enum AcquisitionKind { APPLICATION_SUPPLIED_ONLY, @@ -126,9 +124,13 @@ public record ProviderInfo(AcquisitionKind acquisitionKind, Exception verificati public SecurityProviderRuntimeState() { } - @Fold - public static SecurityProviderRuntimeState singleton() { - return ImageSingletons.lookup(SecurityProviderRuntimeState.class); + @Platforms(Platform.HOSTED_ONLY.class) + public static SecurityProviderRuntimeState currentLayer() { + return LayeredImageSingletonSupport.singleton().lookup(SecurityProviderRuntimeState.class, false, true); + } + + private static SecurityProviderRuntimeState[] singletons() { + return MultiLayeredImageSingleton.getAllLayers(SecurityProviderRuntimeState.class); } @Platforms(Platform.HOSTED_ONLY.class) @@ -155,13 +157,27 @@ private void registerProvider(String providerClassName, AcquisitionKind acquisit providerInfos.put(providerClassName, new ProviderInfo(effectiveAcquisitionKind, verificationFailure)); } - public ProviderInfo getProviderInfo(Provider provider) { - return providerInfos.get(provider.getClass().getName()); + public static ProviderInfo getProviderInfo(Provider provider) { + String providerClassName = provider.getClass().getName(); + SecurityProviderRuntimeState[] states = singletons(); + for (int i = states.length - 1; i >= 0; i--) { + ProviderInfo info = states[i].providerInfos.get(providerClassName); + if (info != null) { + return info; + } + } + return null; } - public boolean isJdkConstructible(String providerClassName) { - ProviderInfo info = providerInfos.get(providerClassName); - return info != null && info.acquisitionKind() == AcquisitionKind.JDK_CONSTRUCTIBLE; + public static boolean isJdkConstructible(String providerClassName) { + SecurityProviderRuntimeState[] states = singletons(); + for (int i = states.length - 1; i >= 0; i--) { + ProviderInfo info = states[i].providerInfos.get(providerClassName); + if (info != null) { + return info.acquisitionKind() == AcquisitionKind.JDK_CONSTRUCTIBLE; + } + } + return false; } @Platforms(Platform.HOSTED_ONLY.class) @@ -169,8 +185,13 @@ public void setSunECConstructor(Constructor constructor) { sunECConstructor = constructor; } - Constructor getSunECConstructor() { - return sunECConstructor; + static Constructor getSunECConstructor() { + for (SecurityProviderRuntimeState state : singletons()) { + if (state.sunECConstructor != null) { + return state.sunECConstructor; + } + } + return null; } @Platforms(Platform.HOSTED_ONLY.class) @@ -178,7 +199,12 @@ public void setSavedInitialSecurityProperties(Properties properties) { savedInitialSecurityProperties = properties; } - public Properties getSavedInitialSecurityProperties() { - return savedInitialSecurityProperties; + public static Properties getSavedInitialSecurityProperties() { + for (SecurityProviderRuntimeState state : singletons()) { + if (state.savedInitialSecurityProperties != null) { + return state.savedInitialSecurityProperties; + } + } + return null; } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SecuritySubstitutions.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SecuritySubstitutions.java index 2cf6e45c1c16..e2b84651103d 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SecuritySubstitutions.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SecuritySubstitutions.java @@ -67,7 +67,7 @@ final class Target_sun_security_jca_Providers_ExplicitRegistration { @Substitute public static Provider getSunProvider() { - if (!SecurityProviderRuntimeState.singleton().isJdkConstructible("sun.security.provider.Sun")) { + if (!SecurityProviderRuntimeState.isJdkConstructible("sun.security.provider.Sun")) { SecurityProviderRuntimeAccess.reportMissingRegistration(sun.security.provider.Sun.class); } return new sun.security.provider.Sun(); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/runtimeinit/SecuritySubstitutionRuntimeInit.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/runtimeinit/SecuritySubstitutionRuntimeInit.java index 24fc7f2b9e3f..c92dd32968de 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/runtimeinit/SecuritySubstitutionRuntimeInit.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/runtimeinit/SecuritySubstitutionRuntimeInit.java @@ -61,7 +61,7 @@ final class Target_java_security_Security_SecPropLoader { */ @Substitute private static void loadMaster() { - Target_java_security_Security.props = SecurityProviderRuntimeState.singleton().getSavedInitialSecurityProperties(); + Target_java_security_Security.props = SecurityProviderRuntimeState.getSavedInitialSecurityProperties(); } } diff --git a/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/hosted/runtimecompilation/RuntimeCompilationFeature.java b/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/hosted/runtimecompilation/RuntimeCompilationFeature.java index ee50b2c7cb3f..f4bcd6124744 100644 --- a/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/hosted/runtimecompilation/RuntimeCompilationFeature.java +++ b/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/hosted/runtimecompilation/RuntimeCompilationFeature.java @@ -71,6 +71,7 @@ import com.oracle.svm.core.SubstrateOptions; import com.oracle.svm.core.SubstrateTarget; import com.oracle.svm.core.graal.RuntimeCompilation; +import com.oracle.svm.core.imagelayer.ImageLayerBuildingSupport; import com.oracle.svm.core.graal.RuntimeCompilationCanaryFeature; import com.oracle.svm.core.graal.code.SubstrateBackend; import com.oracle.svm.core.graal.code.SubstrateMetaAccessExtensionProvider; @@ -423,7 +424,7 @@ public void duringSetup(DuringSetupAccess c) { * security providers. */ // §FS-security-providers.2.4 - if (!ImageSingletons.contains(RuntimeRandomness.class)) { + if (ImageLayerBuildingSupport.firstImageBuild() && !ImageSingletons.contains(RuntimeRandomness.class)) { ImageSingletons.add(RuntimeRandomness.class, new SecureRandomRuntimeRandomness()); } ImageSingletons.add(RuntimeCompilationSupport.class, new RuntimeCompilationSupport()); diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/NativeImageClassLoaderSupport.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/NativeImageClassLoaderSupport.java index d702ce4f521d..3ea6174edd49 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/NativeImageClassLoaderSupport.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/NativeImageClassLoaderSupport.java @@ -475,7 +475,10 @@ public void loadAllClasses(ForkJoinPool executor, ImageClassLoader imageClassLoa preserveSelectors.addModule(m.descriptor().name(), origin); } } - PreserveOptionsSupport.JDK_MODULES_TO_PRESERVE.forEach(moduleName -> preserveSelectors.addModule(moduleName, origin)); + /* Some JDK provider modules are platform-specific, for example jdk.crypto.mscapi. */ + PreserveOptionsSupport.JDK_MODULES_TO_PRESERVE.stream() + .filter(moduleName -> findModule(moduleName).isPresent()) + .forEach(moduleName -> preserveSelectors.addModule(moduleName, origin)); preserveSelectors.addModule(ALL_UNNAMED, origin); } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SecurityProviderCatalogRegistrar.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SecurityProviderCatalogRegistrar.java index a3872b504e2c..a12ae14cd60c 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SecurityProviderCatalogRegistrar.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SecurityProviderCatalogRegistrar.java @@ -95,7 +95,7 @@ void registerProvider(DuringAnalysisAccess access, Provider provider) { provider.entrySet(); String providerClassName = provider.getClass().getName(); Object verificationResult = host.getProviderVerificationResult(provider); - SecurityProviderRuntimeState state = SecurityProviderRuntimeState.singleton(); + SecurityProviderRuntimeState state = SecurityProviderRuntimeState.currentLayer(); if (host.isLoadableProviderClass(access, provider.getClass())) { state.registerJdkConstructibleProvider(providerClassName, verificationResult); } else { @@ -108,6 +108,6 @@ private void registerApplicationSuppliedProviderClass(Class providerClass) { // §FS-security-providers.5.3: Preserve verification without reconstructing the provider. List buildTimeProviders = buildTimeProvidersByClassName.get(providerClass.getName()); Object verificationResult = buildTimeProviders == null ? Boolean.TRUE : host.getProviderVerificationResult(buildTimeProviders.getFirst()); - SecurityProviderRuntimeState.singleton().registerApplicationSuppliedProvider(providerClass.getName(), verificationResult); + SecurityProviderRuntimeState.currentLayer().registerApplicationSuppliedProvider(providerClass.getName(), verificationResult); } } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SecurityServicesFeature.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SecurityServicesFeature.java index 02fc925b97b9..498dce80149c 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SecurityServicesFeature.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SecurityServicesFeature.java @@ -315,7 +315,7 @@ public void duringSetup(DuringSetupAccess a) { classCacheField = access.findField(Service.class, "classCache"); constructorCacheField = access.findField(Service.class, "constructorCache"); } else { - SecurityProviderRuntimeState support = SecurityProviderRuntimeState.singleton(); + SecurityProviderRuntimeState support = SecurityProviderRuntimeState.currentLayer(); ModuleSupport.accessPackagesToClass(ModuleSupport.Access.OPEN, SecuritySubstitutions.class, false, "java.base", "sun.security.ec"); ResolvedJavaMethod sunECConstructor = constructor(a, "sun.security.ec.SunEC"); support.setSunECConstructor((Constructor) OriginalMethodProvider.getJavaMethod(sunECConstructor)); @@ -546,16 +546,7 @@ private boolean shouldRegisterProviderClassForReflection(BeforeAnalysisAccessImp } public boolean shouldRemoveProvider(Provider p) { - if (p == null) { - return true; - } - if (catalogRegistrar.isUsed(p)) { - return false; - } - if (substitutionProcessor.isDeleted(p.getClass())) { - return true; - } - return true; + return p == null || !catalogRegistrar.isUsed(p); } private static void traceRemovedProviders(List removedProviders) { @@ -817,7 +808,10 @@ private void registerSecureRandomProvidersFromPlatformSignal() { private void doRegisterServices(DuringAnalysisAccess access, Object trigger, String serviceType) { try (TracingAutoCloseable _ = trace(access, trigger, serviceType)) { EconomicSet services = availableServices.get(serviceType); - VMError.guarantee(services != null); + if (services == null) { + trace("No provider supplies service type %s.", serviceType); + return; + } for (Service service : services) { registerService(access, service); } @@ -923,9 +917,6 @@ private static void registerSpiClass(Method getSpiClassMethod, String serviceTyp private Object getProviderVerificationResult(Provider provider) { // §FS-security-providers.5.3: Preserve the build-time outcome by provider class. - if (!buildTimeProvidersByClassName.containsKey(provider.getClass().getName())) { - return Boolean.TRUE; - } try { Method getVerificationResult = ReflectionUtil.lookupMethod(jceSecurityClass, "getVerificationResult", Provider.class); /* @@ -1024,16 +1015,20 @@ private void registerService(DuringAnalysisAccess a, Service service) { // Recognize every qualifying reflection-registration signal. // §FS-security-providers.1.1 and §FS-security-providers.2.1 private static boolean isProviderRegisteredForReflection(Class providerClass) { - ReflectionRegistrationView reflection = ReflectionRegistrationView.singleton(); - if (reflection.hasTypeAccess(providerClass)) { - return true; - } - Constructor constructor = findDeclaredNullaryConstructor(providerClass); - if (constructor != null && reflection.hasExecutableAccess(constructor)) { - return true; + try { + ReflectionRegistrationView reflection = ReflectionRegistrationView.singleton(); + if (reflection.hasTypeAccess(providerClass)) { + return true; + } + Constructor constructor = findDeclaredNullaryConstructor(providerClass); + if (constructor != null && reflection.hasExecutableAccess(constructor)) { + return true; + } + Method providerMethod = findProviderMethod(providerClass); + return providerMethod != null && reflection.hasExecutableAccess(providerMethod); + } catch (UnsupportedPlatformException | DeletedElementException e) { + return false; } - Method providerMethod = findProviderMethod(providerClass); - return providerMethod != null && reflection.hasExecutableAccess(providerMethod); } private static void registerProviderClassForReflection(Class providerClass) { @@ -1122,7 +1117,8 @@ private void registerX509Extensions(DuringAnalysisAccess a) { public void duringAnalysis(DuringAnalysisAccess a) { DuringAnalysisAccessImpl access = (DuringAnalysisAccessImpl) a; // Consume concurrent plans in the serialized feature pass. - if (providerPlanner.processNewCompleteProviders(SecurityServicesFeature::isProviderRegisteredForReflection, + if (providerPlanner.processNewCompleteProviders( + providerClass -> mode.explicitRegistration() && isProviderRegisteredForReflection(providerClass), providerClass -> catalogRegistrar.includeProviderClass(access, providerClass))) { // Request the extra pass here, not from the concurrent reachability callback. access.requireAnalysisIteration(); diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ServiceLoaderFeature.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ServiceLoaderFeature.java index 7c2c9385cb0d..67b8c46c6ca8 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ServiceLoaderFeature.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ServiceLoaderFeature.java @@ -146,7 +146,6 @@ public static class Options { "jdk.jshell.execution.impl.ConsoleImpl$ConsoleProviderImpl"); private final EconomicSet serviceProvidersToSkip = EconomicSet.create(SKIPPED_PROVIDERS); - private final LinkedHashSet securityProviderDescriptors = new LinkedHashSet<>(); private SecurityProviderMode securityProviderMode; @Override @@ -174,7 +173,10 @@ public void beforeAnalysis(BeforeAnalysisAccess access) { Resources.currentLayer().registerNegativeQuery(access.getApplicationClassLoader().getUnnamedModule(), SECURITY_PROVIDER_SERVICE_RESOURCE); accessImpl.imageClassLoader.classLoaderSupport.serviceProvidersForEach((serviceName, providers) -> { if (securityProviderMode.explicitRegistration() && serviceName.equals(java.security.Provider.class.getName())) { - securityProviderDescriptors.addAll(providers); + ResolvedJavaType serviceClass = accessImpl.findTypeByName(serviceName); + if (serviceClass != null) { + access.registerReachabilityHandler(a -> preserveSecurityProviderDescriptors(a, providers), serviceClass); + } return; } Collection providersToSkip = providers; @@ -211,14 +213,6 @@ public void beforeAnalysis(BeforeAnalysisAccess access) { }); } - @Override - public void duringAnalysis(DuringAnalysisAccess access) { - if (!securityProviderDescriptors.isEmpty()) { - preserveSecurityProviderDescriptors(access, securityProviderDescriptors); - securityProviderDescriptors.clear(); - } - } - private void preserveSecurityProviderDescriptors(DuringAnalysisAccess access, Collection providers) { LinkedHashSet registeredProviders = new LinkedHashSet<>(); for (String provider : providers) { diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/PreserveOptionsSupport.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/PreserveOptionsSupport.java index 79848a9a2ebb..05520aed3b39 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/PreserveOptionsSupport.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/PreserveOptionsSupport.java @@ -102,10 +102,12 @@ public class PreserveOptionsSupport extends IncludeOptionsSupport { "java.sql.rowset", "java.transaction.xa", "java.datatransfer", + "java.security.jgss", "java.security.sasl", "jdk.security.jgss", "jdk.security.auth", "jdk.crypto.cryptoki", + "jdk.crypto.mscapi", "java.logging", "jdk.management", "java.management", diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/services/SecurityServiceExplicitProviderRegistrationTest.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/services/SecurityServiceExplicitProviderRegistrationTest.java index e316056d53be..f39141f80ac0 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/services/SecurityServiceExplicitProviderRegistrationTest.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/services/SecurityServiceExplicitProviderRegistrationTest.java @@ -76,12 +76,10 @@ public void testReachableFactoryDoesNotIncludeUnregisteredProvider() { @Test public void testUnregisteredProviderCannotReuseVerificationByName() { Assume.assumeTrue("native image runtime only", ImageInfo.inImageRuntimeCode()); - SecurityProviderRuntimeState state = SecurityProviderRuntimeState.singleton(); - - SecurityProviderRuntimeState.ProviderInfo registered = state.getProviderInfo(new SecurityServiceTest.ReflectionMetadataProvider()); + SecurityProviderRuntimeState.ProviderInfo registered = SecurityProviderRuntimeState.getProviderInfo(new SecurityServiceTest.ReflectionMetadataProvider()); Assert.assertNotNull(registered); Assert.assertNull(registered.verificationFailure()); - Assert.assertNull(state.getProviderInfo(new SameNameUnregisteredProvider())); + Assert.assertNull(SecurityProviderRuntimeState.getProviderInfo(new SameNameUnregisteredProvider())); } public static final class SameNameUnregisteredProvider extends Provider { diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/services/SecurityServiceTest.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/services/SecurityServiceTest.java index 7264f1f2b4f6..4da94a7e3b61 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/services/SecurityServiceTest.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/services/SecurityServiceTest.java @@ -169,6 +169,7 @@ public void testGSSProviderServiceRegistration() throws Exception { // §FS-security-providers.2.1, §FS-security-providers.2.3, and §FS-security-providers.5.1 @Test public void testReflectionMetadataProviderRegistration() throws Exception { + Assume.assumeTrue("needs explicit provider registration", FutureDefaultsOptions.explicitSecurityProviderRegistration()); Provider provider = (Provider) Class.forName(REFLECTION_METADATA_PROVIDER_CLASS_NAME).getDeclaredConstructor().newInstance(); int position = Security.addProvider(provider); try { @@ -186,6 +187,7 @@ public void testReflectionMetadataProviderRegistration() throws Exception { // §FS-security-providers.2.1, §FS-security-providers.2.3, and §FS-security-providers.5.1 @Test public void testTypeMetadataProviderRegistration() throws Exception { + Assume.assumeTrue("needs explicit provider registration", FutureDefaultsOptions.explicitSecurityProviderRegistration()); Provider provider = new TypeMetadataProvider(); int position = Security.addProvider(provider); try { @@ -211,6 +213,19 @@ public void testReachableProviderWithoutMetadataDoesNotRegisterServices() { } } + /** Tests the non-exact diagnostic from §FS-security-providers.4.3. */ + @Test + public void testUnregisteredJceProviderReportsActionableDiagnostic() { + Assume.assumeTrue("native image runtime only", ImageInfo.inImageRuntimeCode()); + Assume.assumeFalse("tests the compatibility-mode fallback", FutureDefaultsOptions.explicitSecurityProviderRegistration()); + + Provider provider = new UnregisteredMacProvider(); + SecurityException error = Assert.assertThrows(SecurityException.class, + () -> Mac.getInstance("unregistered-mac", provider)); + Assert.assertTrue("The diagnostic must identify the provider type", + error.getMessage().contains(UnregisteredMacProvider.class.getName())); + } + /** Tests §FS-security-providers.7.2. */ @Test public void testServiceLoaderProviderWithoutMetadataUsesReflectionLookupFailure() { @@ -377,6 +392,9 @@ public static final class ReflectionMetadataProvider extends Provider { @SuppressWarnings("deprecation") public ReflectionMetadataProvider() { super(REFLECTION_METADATA_PROVIDER_NAME, 1.0, "Provider registered through reflection metadata"); + if (ImageInfo.inImageBuildtimeCode() && !FutureDefaultsOptions.explicitSecurityProviderRegistration()) { + throw new AssertionError("Compatibility mode must not instantiate a provider solely because it has reflection metadata."); + } putService(new Service(this, "JCACompliantNoOpService", REFLECTION_METADATA_PROVIDER_ALGORITHM, ReflectionMetadataNoOpServiceImpl.class.getName(), null, null)); putService(new Service(this, "Mac", REFLECTION_METADATA_PROVIDER_MAC_ALGORITHM, ReflectionMetadataMacSpi.class.getName(), null, null)); @@ -405,6 +423,16 @@ public ReachableProviderWithoutMetadata() { } } + public static final class UnregisteredMacProvider extends Provider { + static final long serialVersionUID = 1234L; + + @SuppressWarnings("deprecation") + public UnregisteredMacProvider() { + super("unregistered-mac-provider", 1.0, "Provider used to test missing-registration diagnostics"); + putService(new Service(this, "Mac", "unregistered-mac", ReflectionMetadataMacSpi.class.getName(), null, null)); + } + } + public static final class ReflectionMetadataMacSpi extends MacSpi { @Override protected int engineGetMacLength() { From 181210c2851122dc61933d784c254d1375024ae7 Mon Sep 17 00:00:00 2001 From: Vojin Jovanovic Date: Mon, 27 Jul 2026 10:10:36 +0200 Subject: [PATCH 43/63] GR-69858: Complete security provider fixes --- .../svm/agent/BreakpointInterceptor.java | 21 ++++++++++++++++++- .../SecurityProviderRegistrationPlanner.java | 9 +------- .../svm/hosted/SecurityServicesFeature.java | 2 +- .../test/services/SecurityServiceTest.java | 2 ++ 4 files changed, 24 insertions(+), 10 deletions(-) diff --git a/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/BreakpointInterceptor.java b/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/BreakpointInterceptor.java index ce0656b5f47b..0ea12c66f510 100644 --- a/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/BreakpointInterceptor.java +++ b/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/BreakpointInterceptor.java @@ -792,7 +792,24 @@ private static boolean getCachedSecurityProvider(JNIEnvironment jni, JNIObjectHa return true; } - private record SecurityAcquisition(JNIObjectHandle callerClass, JNIObjectHandle requestedProviderName) { + /* JNIObjectHandle is a Word type. Record-generated methods use method handles, + * which cannot use Word parameters in Native Image. */ + private static final class SecurityAcquisition { + private final JNIObjectHandle callerClass; + private final JNIObjectHandle requestedProviderName; + + SecurityAcquisition(JNIObjectHandle callerClass, JNIObjectHandle requestedProviderName) { + this.callerClass = callerClass; + this.requestedProviderName = requestedProviderName; + } + + JNIObjectHandle callerClass() { + return callerClass; + } + + JNIObjectHandle requestedProviderName() { + return requestedProviderName; + } } private static SecurityAcquisition findSecurityAcquisition(JNIEnvironment jni, JNIObjectHandle thread, InterceptedState state) { @@ -821,6 +838,7 @@ private static SecurityAcquisition findSecurityAcquisition(JNIEnvironment jni, J } } } + return fallback; } private static boolean isSecurityAcquisitionMethod(JNIMethodId method) { @@ -859,6 +877,7 @@ private static JNIObjectHandle findExternalSecurityCaller(JNIEnvironment jni, In return callerClass; } } + return nullHandle(); } private static boolean isJdkSecurityImplementation(String className) { diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SecurityProviderRegistrationPlanner.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SecurityProviderRegistrationPlanner.java index dd527451579c..6a5fc2c182e6 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SecurityProviderRegistrationPlanner.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SecurityProviderRegistrationPlanner.java @@ -66,14 +66,7 @@ void requestCompleteProvider(Class providerClass, Source source) { } } - void beforeLegacyReflectionRegistration(Class providerClass, Predicate> hasRegistrationSignal) { - /* - * Capture metadata that already exists before marking reflection emitted by compatibility - * processing. This prevents our own output from feeding back as application intent. - */ - if (hasRegistrationSignal.test(providerClass)) { - requestCompleteProvider(providerClass, Source.APPLICATION_METADATA); - } + void beforeLegacyReflectionRegistration(Class providerClass) { recordSource(providerClass, Source.LEGACY_SERVICE_REACHABILITY); legacyGeneratedReflection.add(providerClass); } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SecurityServicesFeature.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SecurityServicesFeature.java index 498dce80149c..1eebca2fa0f3 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SecurityServicesFeature.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SecurityServicesFeature.java @@ -1003,7 +1003,7 @@ private void registerService(DuringAnalysisAccess a, Service service) { } if (!mode.explicitRegistration()) { Class providerClass = service.getProvider().getClass(); - providerPlanner.beforeLegacyReflectionRegistration(providerClass, SecurityServicesFeature::isProviderRegisteredForReflection); + providerPlanner.beforeLegacyReflectionRegistration(providerClass); } catalogRegistrar.registerProvider(a, service.getProvider()); } diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/services/SecurityServiceTest.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/services/SecurityServiceTest.java index 4da94a7e3b61..fafcf10fa616 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/services/SecurityServiceTest.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/services/SecurityServiceTest.java @@ -130,6 +130,8 @@ public void testSecurityProviderRuntimeRegistration() { */ @Test public void testUnknownSecurityServices() throws Exception { + Assume.assumeTrue("needs explicit or runtime provider registration", + FutureDefaultsOptions.explicitSecurityProviderRegistration() || FutureDefaultsOptions.securityProvidersInitializedAtRunTime()); if (FutureDefaultsOptions.securityProvidersInitializedAtRunTime()) { /* Register the provider at run time. */ Security.addProvider(new NoOpProvider()); From c940f8695b62f2a6630519909f88ff25ab3fb76c Mon Sep 17 00:00:00 2001 From: Vojin Jovanovic Date: Mon, 27 Jul 2026 12:43:08 +0200 Subject: [PATCH 44/63] GR-69858: Address security provider review feedback --- .../native-image/JCASecurityServices.md | 10 ++ .../docs/architecture/security-providers.md | 2 +- substratevm/mx.substratevm/mx_substratevm.py | 1 - .../pointsto/meta/AnalysisMetaAccess.java | 4 + .../graal/pointsto/meta/AnalysisUniverse.java | 5 + .../svm/agent/BreakpointInterceptor.java | 22 +++- .../oracle/svm/agent/NativeImageAgent.java | 3 + .../agent/NativeImageAgentJNIHandleSet.java | 20 +-- .../jdk/SecurityProviderRuntimeAccess.java | 10 ++ .../jdk/SecurityProviderRuntimeState.java | 114 ++++-------------- .../SecurityProviderTracingSubstitutions.java | 12 +- .../hosted/test/VerifyReflectionUsage.java | 1 - .../svm/hosted/NamingConventionVerifier.java | 12 ++ .../src/com/oracle/svm/hosted/SVMHost.java | 22 ++-- .../SecurityProviderCatalogRegistrar.java | 6 +- .../SecurityProviderRegistrationPlanner.java | 12 +- .../svm/hosted/SecurityServicesFeature.java | 101 ++++++++++++++-- .../hosted/reflect/ReflectionDataBuilder.java | 10 +- 18 files changed, 228 insertions(+), 139 deletions(-) diff --git a/docs/reference-manual/native-image/JCASecurityServices.md b/docs/reference-manual/native-image/JCASecurityServices.md index d6236633908e..440723a29b91 100644 --- a/docs/reference-manual/native-image/JCASecurityServices.md +++ b/docs/reference-manual/native-image/JCASecurityServices.md @@ -86,6 +86,16 @@ Security.insertProviderAt(bcProvider, 1); If `--future-defaults=all` or `--future-defaults=run-time-initialize-jdk` is enabled, the list of providers is constructed at run time. The same approach to manipulating providers can then be used. +## SecureRandom + +Native Image initializes `NativePRNG`, its seed generators, and related entropy-holding classes at +run time. +This prevents `/dev/random`, `/dev/urandom`, and machine-specific seed state from being captured +on the image builder. +Class-initialization safety is separate from provider registration: a reachable `SecureRandom` +acquisition also triggers registration of the complete configured-provider set that declares +`SecureRandom` services. + ## Custom Service Types By default, only services specified in the JCA framework are automatically registered. To automatically register custom service types, you can use the `-H:AdditionalSecurityServiceTypes` option. diff --git a/substratevm/docs/architecture/security-providers.md b/substratevm/docs/architecture/security-providers.md index f6dfcedd56d3..d1d611bb5a1c 100644 --- a/substratevm/docs/architecture/security-providers.md +++ b/substratevm/docs/architecture/security-providers.md @@ -1 +1 @@ -# AR-security-providers: [SecurityProviderRuntimeState](../../src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SecurityProviderRuntimeState.java) +# AR-security-providers: [SecurityServicesFeature](../../src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SecurityServicesFeature.java) diff --git a/substratevm/mx.substratevm/mx_substratevm.py b/substratevm/mx.substratevm/mx_substratevm.py index a6a3e7bdf52f..0774c64c30dc 100644 --- a/substratevm/mx.substratevm/mx_substratevm.py +++ b/substratevm/mx.substratevm/mx_substratevm.py @@ -820,7 +820,6 @@ def write_micronaut_style_service_entries(cp_entry, service_name, implementation '--add-exports=org.graalvm.nativeimage.builder/com.oracle.svm.core.libjvm=ALL-UNNAMED', '--add-exports=org.graalvm.nativeimage.builder/com.oracle.svm.core.properties=ALL-UNNAMED', '--add-opens=org.graalvm.nativeimage.builder/com.oracle.svm.core.jdk=ALL-UNNAMED', - '-H:AdditionalSecurityProviders=sun.security.pkcs11.SunPKCS11', '-H:AdditionalSecurityServiceTypes=com.oracle.svm.test.services.SecurityServiceTest$JCACompliantNoOpService', ]) if extra_build_args is not None: diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/meta/AnalysisMetaAccess.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/meta/AnalysisMetaAccess.java index 28784e0bcad1..753f0e8b93d8 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/meta/AnalysisMetaAccess.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/meta/AnalysisMetaAccess.java @@ -101,6 +101,10 @@ public AnalysisMethod lookupJavaMethod(Executable reflectionMethod) { return (AnalysisMethod) super.lookupJavaMethod(reflectionMethod); } + public Optional optionalLookupJavaMethod(Executable reflectionMethod) { + return Optional.ofNullable(getUniverse().optionalLookup(getWrapped().lookupJavaMethod(reflectionMethod))); + } + @Override public AnalysisField lookupJavaField(Field reflectionField) { return (AnalysisField) super.lookupJavaField(reflectionField); diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/meta/AnalysisUniverse.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/meta/AnalysisUniverse.java index 8f0db49061b6..992b813a92e0 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/meta/AnalysisUniverse.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/meta/AnalysisUniverse.java @@ -410,6 +410,11 @@ public AnalysisMethod lookup(JavaMethod method) { ". Probably there are some compilation or classpath problems. "); } + public AnalysisMethod optionalLookup(ResolvedJavaMethod method) { + ResolvedJavaMethod actualMethod = substitutions.lookup(method); + return methods.get(actualMethod); + } + @Override public JavaMethod lookupAllowUnresolved(JavaMethod rawMethod) { if (rawMethod == null) { diff --git a/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/BreakpointInterceptor.java b/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/BreakpointInterceptor.java index 0ea12c66f510..a514fc1964ec 100644 --- a/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/BreakpointInterceptor.java +++ b/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/BreakpointInterceptor.java @@ -772,6 +772,9 @@ private static boolean newSecurityServiceInstance(JNIEnvironment jni, JNIObjectH * §FS-security-providers.6.1: Trace a provider that was cached before a Security API lookup. */ private static boolean getCachedSecurityProvider(JNIEnvironment jni, JNIObjectHandle thread, @SuppressWarnings("unused") Breakpoint bp, InterceptedState state) { + if (agent.handles().sunSecurityJcaProviderConfigProvider.isNull()) { + return true; + } SecurityAcquisition acquisition = findSecurityAcquisition(jni, thread, state); if (acquisition == null) { return true; @@ -1990,6 +1993,21 @@ public static void onUnload() { tracer = null; } + static boolean securityProviderHooksAvailable() { + boolean getService = false; + boolean getProvider = false; + if (installedBreakpoints != null) { + for (Breakpoint breakpoint : installedBreakpoints.values()) { + if (breakpoint.specification.className.equals("sun/security/jca/GetInstance") && breakpoint.specification.methodName.equals("getService")) { + getService = true; + } else if (breakpoint.specification.className.equals("sun/security/jca/ProviderConfig") && breakpoint.specification.methodName.equals("getProvider")) { + getProvider = true; + } + } + } + return getService && getProvider; + } + private interface BreakpointHandler { boolean dispatch(JNIEnvironment jni, JNIObjectHandle thread, Breakpoint bp, InterceptedState state); } @@ -2027,11 +2045,11 @@ private interface BreakpointHandler { brk("java/security/Security", "addProvider", "(Ljava/security/Provider;)I", BreakpointInterceptor::addStaticSecurityProvider), brk("java/security/Security", "insertProviderAt", "(Ljava/security/Provider;I)I", BreakpointInterceptor::addStaticSecurityProvider), - brk("sun/security/jca/GetInstance", "getService", "(Ljava/lang/String;Ljava/lang/String;Ljava/security/Provider;)Ljava/security/Provider$Service;", + optionalBrk("sun/security/jca/GetInstance", "getService", "(Ljava/lang/String;Ljava/lang/String;Ljava/security/Provider;)Ljava/security/Provider$Service;", BreakpointInterceptor::getSecurityServiceForProvider), brk("java/security/Provider$Service", "newInstance", "(Ljava/lang/Object;)Ljava/lang/Object;", BreakpointInterceptor::newSecurityServiceInstance), - brk("sun/security/jca/ProviderConfig", "getProvider", "()Ljava/security/Provider;", + optionalBrk("sun/security/jca/ProviderConfig", "getProvider", "()Ljava/security/Provider;", BreakpointInterceptor::getCachedSecurityProvider), brk("java/lang/ClassLoader", "findSystemClass", "(Ljava/lang/String;)Ljava/lang/Class;", diff --git a/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/NativeImageAgent.java b/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/NativeImageAgent.java index e96f6ffb4e0e..a54ae997d59f 100644 --- a/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/NativeImageAgent.java +++ b/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/NativeImageAgent.java @@ -405,6 +405,9 @@ protected int onLoadCallback(JNIJavaVM vm, JvmtiEnv jvmti, JvmtiEventCallbacks c try { BreakpointInterceptor.onLoad(jvmti, callbacks, tracer, this, interceptedStateSupplier, experimentalClassLoaderSupport, experimentalClassDefineSupport, experimentalUnsafeAllocationSupport, trackReflectionMetadata); + if (handles().sunSecurityJcaProviderConfigProvider.isNull() || !BreakpointInterceptor.securityProviderHooksAvailable()) { + warn("JDK security-provider lookup hooks are unavailable; provider lookup coverage is reduced."); + } } catch (Throwable t) { return error(AGENT_ERROR, t.toString()); } diff --git a/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/NativeImageAgentJNIHandleSet.java b/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/NativeImageAgentJNIHandleSet.java index ee2e0a10cf45..251125e55a31 100644 --- a/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/NativeImageAgentJNIHandleSet.java +++ b/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/NativeImageAgentJNIHandleSet.java @@ -69,12 +69,6 @@ public class NativeImageAgentJNIHandleSet extends JNIHandleSet { final JNIMethodId javaSecurityInsertProviderAt; final JNIMethodId javaSecurityRemoveProvider; final JNIFieldId sunSecurityJcaProviderConfigProvider; - final JNIObjectHandle sunSecurityProviderSun; - final JNIObjectHandle sunSecurityRsaSunRsaSign; - final JNIObjectHandle sunSecurityEcSunEC; - final JNIObjectHandle sunSecuritySslSunJSSE; - final JNIObjectHandle comSunCryptoProviderSunJCE; - final JNIObjectHandle appleSecurityAppleProvider; final JNIObjectHandle javaLangStackOverflowError; @@ -198,16 +192,10 @@ public class NativeImageAgentJNIHandleSet extends JNIHandleSet { javaSecurityInsertProviderAt = getMethodId(env, javaSecuritySecurity, "insertProviderAt", "(Ljava/security/Provider;I)I", true); javaSecurityRemoveProvider = getMethodId(env, javaSecuritySecurity, "removeProvider", "(Ljava/lang/String;)V", true); - JNIObjectHandle sunSecurityJcaProviderConfig = findClass(env, "sun/security/jca/ProviderConfig"); - sunSecurityJcaProviderConfigProvider = getFieldId(env, sunSecurityJcaProviderConfig, "provider", "Ljava/security/Provider;", false); - - sunSecurityProviderSun = newClassGlobalRef(env, "sun/security/provider/Sun"); - sunSecurityRsaSunRsaSign = newClassGlobalRef(env, "sun/security/rsa/SunRsaSign"); - sunSecurityEcSunEC = newClassGlobalRef(env, "sun/security/ec/SunEC"); - sunSecuritySslSunJSSE = newClassGlobalRef(env, "sun/security/ssl/SunJSSE"); - comSunCryptoProviderSunJCE = newClassGlobalRef(env, "com/sun/crypto/provider/SunJCE"); - JNIObjectHandle appleProvider = findClassOptional(env, "apple/security/AppleProvider"); - appleSecurityAppleProvider = appleProvider.equal(nullHandle()) ? nullHandle() : newTrackedGlobalRef(env, appleProvider); + JNIObjectHandle sunSecurityJcaProviderConfig = findClassOptional(env, "sun/security/jca/ProviderConfig"); + sunSecurityJcaProviderConfigProvider = sunSecurityJcaProviderConfig.equal(nullHandle()) + ? WordFactory.nullPointer() + : getFieldIdOptional(env, sunSecurityJcaProviderConfig, "provider", "Ljava/security/Provider;", false); javaLangStackOverflowError = newClassGlobalRef(env, "java/lang/StackOverflowError"); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SecurityProviderRuntimeAccess.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SecurityProviderRuntimeAccess.java index d522c3fc7167..26c6d5b7774c 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SecurityProviderRuntimeAccess.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SecurityProviderRuntimeAccess.java @@ -54,4 +54,14 @@ public static Provider traceLookup(Provider provider) { } return provider; } + + /** §FS-security-providers.6.1: Enumeration traces every provider returned by the JDK. */ + public static Provider[] traceLookups(Provider[] providers) { + if (providers != null && MetadataTracer.enabled()) { + for (Provider provider : providers) { + traceLookup(provider); + } + } + return providers; + } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SecurityProviderRuntimeState.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SecurityProviderRuntimeState.java index e036c0af401a..707d54a1b2ee 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SecurityProviderRuntimeState.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SecurityProviderRuntimeState.java @@ -32,7 +32,6 @@ import org.graalvm.nativeimage.Platform; import org.graalvm.nativeimage.Platforms; -import com.oracle.svm.guest.staging.util.ImageHeapMap; import com.oracle.svm.shared.singletons.traits.BuiltinTraits.AllAccess; import com.oracle.svm.shared.singletons.traits.BuiltinTraits.NoLayeredCallbacks; import com.oracle.svm.shared.singletons.LayeredImageSingletonSupport; @@ -40,71 +39,6 @@ import com.oracle.svm.shared.singletons.traits.SingletonLayeredInstallationKind.MultiLayer; import com.oracle.svm.shared.singletons.traits.SingletonTraits; -/// AR-security-providers: Security Provider Architecture -/// -/// The security-provider implementation separates build-time policy from run-time enforcement. -/// Reflection metadata, platform rules, and compatibility inputs are build-time registration -/// signals; the reflection registry is not itself the provider-policy model. This architecture -/// implements §FS-security-providers. -/// -/// ## 1. Independent Transition Axes -/// -/// `SecurityProviderMode` represents provider inclusion and provider-list initialization as -/// independent axes. Hosted components query this mode instead of reading future-default options -/// independently. Substitutions whose implementation differs by mode use build-time predicates, so -/// an application cannot change image-build policy through a run-time system property. This -/// realizes §FS-security-providers.7. -/// -/// ## 2. Registration Signals and Plans -/// -/// The hosted registration planner records provider candidates together with the provenance of the -/// signal that requests them: application reflection metadata, the platform-owned `SecureRandom` -/// rule, a deprecated provider option, or legacy service-type reachability. It produces an explicit -/// provider plan. Metadata emitted while realizing that plan is an output and is not reinterpreted -/// as a new application signal. This realizes §FS-security-providers.2 and -/// §FS-security-providers.7.3. -/// -/// ## 3. Hosted Registration Components -/// -/// `SecurityServicesFeature` coordinates the feature lifecycle. The registration planner owns -/// provider intent and iteration-safe candidate processing. The catalog registrar constructs -/// eligible providers and registers their service catalogs. `LegacySecurityProviderCompatibility` -/// owns deprecated options and service-driven inclusion. Provider code accesses reflection -/// registrations through a narrow query rather than the concrete metadata builder. -/// -/// ## 4. Run-Time Manifest -/// -/// Hosted registration writes one typed manifest entry per provider class. The entry combines -/// whether the JDK may construct the provider with the preserved JCE verification outcome. An -/// application-supplied provider can carry verification information without being marked as -/// JDK-constructible. The manifest is keyed by provider class name, as required by -/// §FS-security-providers.5.3. -/// -/// ## 5. Run-Time Access Services -/// -/// This class owns the manifest. `BuiltInSecurityProviderLoader` owns JDK aliases and construction, -/// `SecurityProviderRuntimeAccess` owns tracing and missing-registration diagnostics, and -/// `JceProviderVerificationSupport` translates manifest outcomes to the JDK contract. The two -/// provider-list initialization modes share these services. -/// -/// ## 6. Service Descriptors -/// -/// Explicit provider registration preserves `java.security.Provider` descriptors without treating -/// them as provider-registration signals, independently of provider-list initialization. Legacy -/// suppression remains part of the compatibility policy. This realizes -/// §FS-security-providers.7.2. -/// -/// ## 7. Concurrent Analysis -/// -/// Provider subtype callbacks add candidates to concurrent collections. A serialized feature pass -/// consumes signals, realizes plans, and requests additional analysis iterations. Callbacks do not -/// schedule iterations directly. -/// -/// ## 8. Retirement Boundary -/// -/// Deprecated provider options and service-reachability inclusion are confined to -/// `LegacySecurityProviderCompatibility`. Removing compatibility behavior does not change the -/// planner, catalog registrar, run-time manifest, or planned-default substitutions. @SingletonTraits(access = AllAccess.class, layeredCallbacks = NoLayeredCallbacks.class, layeredInstallationKind = MultiLayer.class) public final class SecurityProviderRuntimeState { public enum AcquisitionKind { @@ -115,7 +49,7 @@ public enum AcquisitionKind { public record ProviderInfo(AcquisitionKind acquisitionKind, Exception verificationFailure) { } - private final EconomicMap providerInfos = ImageHeapMap.create("securityProviderInfos"); + private final EconomicMap providerInfos = EconomicMap.create(); private Properties savedInitialSecurityProperties; private Constructor sunECConstructor; @@ -144,40 +78,38 @@ public void registerApplicationSuppliedProvider(String providerClassName, Object } @Platforms(Platform.HOSTED_ONLY.class) - private void registerProvider(String providerClassName, AcquisitionKind acquisitionKind, Object verificationResult) { + private synchronized void registerProvider(String providerClassName, AcquisitionKind acquisitionKind, Object verificationResult) { Exception verificationFailure = verificationResult instanceof Exception exception ? exception : null; - AcquisitionKind effectiveAcquisitionKind = acquisitionKind; - ProviderInfo previous = providerInfos.get(providerClassName); - if (previous != null && previous.acquisitionKind() == AcquisitionKind.JDK_CONSTRUCTIBLE) { - effectiveAcquisitionKind = AcquisitionKind.JDK_CONSTRUCTIBLE; - } - if (previous != null && previous.verificationFailure() != null) { - verificationFailure = previous.verificationFailure(); + providerInfos.put(providerClassName, merge(providerInfos.get(providerClassName), new ProviderInfo(acquisitionKind, verificationFailure))); + } + + private static ProviderInfo merge(ProviderInfo oldInfo, ProviderInfo newInfo) { + if (oldInfo == null || newInfo == null) { + return oldInfo != null ? oldInfo : newInfo; } - providerInfos.put(providerClassName, new ProviderInfo(effectiveAcquisitionKind, verificationFailure)); + AcquisitionKind acquisitionKind = oldInfo.acquisitionKind() == AcquisitionKind.JDK_CONSTRUCTIBLE || newInfo.acquisitionKind() == AcquisitionKind.JDK_CONSTRUCTIBLE + ? AcquisitionKind.JDK_CONSTRUCTIBLE + : AcquisitionKind.APPLICATION_SUPPLIED_ONLY; + Exception verificationFailure = oldInfo.verificationFailure() != null ? oldInfo.verificationFailure() : newInfo.verificationFailure(); + return new ProviderInfo(acquisitionKind, verificationFailure); } public static ProviderInfo getProviderInfo(Provider provider) { - String providerClassName = provider.getClass().getName(); + return getProviderInfo(provider.getClass().getName()); + } + + private static ProviderInfo getProviderInfo(String providerClassName) { SecurityProviderRuntimeState[] states = singletons(); - for (int i = states.length - 1; i >= 0; i--) { - ProviderInfo info = states[i].providerInfos.get(providerClassName); - if (info != null) { - return info; - } + ProviderInfo result = null; + for (SecurityProviderRuntimeState state : states) { + result = merge(result, state.providerInfos.get(providerClassName)); } - return null; + return result; } public static boolean isJdkConstructible(String providerClassName) { - SecurityProviderRuntimeState[] states = singletons(); - for (int i = states.length - 1; i >= 0; i--) { - ProviderInfo info = states[i].providerInfos.get(providerClassName); - if (info != null) { - return info.acquisitionKind() == AcquisitionKind.JDK_CONSTRUCTIBLE; - } - } - return false; + ProviderInfo info = getProviderInfo(providerClassName); + return info != null && info.acquisitionKind() == AcquisitionKind.JDK_CONSTRUCTIBLE; } @Platforms(Platform.HOSTED_ONLY.class) diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SecurityProviderTracingSubstitutions.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SecurityProviderTracingSubstitutions.java index 94d342c31a4d..29661509b8d9 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SecurityProviderTracingSubstitutions.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SecurityProviderTracingSubstitutions.java @@ -29,7 +29,7 @@ import com.oracle.svm.core.annotate.Substitute; import com.oracle.svm.core.annotate.TargetClass; -@TargetClass(java.security.Security.class) +@TargetClass(value = java.security.Security.class, onlyWith = SecurityProvidersInitializedAtBuildTime.class) final class Target_java_security_Security_ProviderLookup { /** §FS-security-providers.6: Successful name-based lookup traces provider type access. */ @@ -37,6 +37,16 @@ final class Target_java_security_Security_ProviderLookup { public static Provider getProvider(String name) { return SecurityProviderRuntimeAccess.traceLookup(sun.security.jca.Providers.getProviderList().getProvider(name)); } + +} + +/** Keeps provider enumeration tracing active in both provider-list initialization modes. */ +@TargetClass(java.security.Security.class) +final class Target_java_security_Security_ProviderEnumeration { + @Substitute + public static Provider[] getProviders() { + return SecurityProviderRuntimeAccess.traceLookups(sun.security.jca.Providers.getFullProviderList().toArray()); + } } public final class SecurityProviderTracingSubstitutions { diff --git a/substratevm/src/com.oracle.svm.hosted.test/src/com/oracle/svm/hosted/test/VerifyReflectionUsage.java b/substratevm/src/com.oracle.svm.hosted.test/src/com/oracle/svm/hosted/test/VerifyReflectionUsage.java index 2b50c16503c6..bff8e7e44ff3 100644 --- a/substratevm/src/com.oracle.svm.hosted.test/src/com/oracle/svm/hosted/test/VerifyReflectionUsage.java +++ b/substratevm/src/com.oracle.svm.hosted.test/src/com/oracle/svm/hosted/test/VerifyReflectionUsage.java @@ -296,7 +296,6 @@ public interface Provider { clazz("com.oracle.svm.hosted.ResourcesFeature$1"), clazz("com.oracle.svm.hosted.ResourcesFeature$ResourceCollectorImpl"), clazz("com.oracle.svm.hosted.ResourcesFeature$ResourcesRegistryImpl"), - clazz("com.oracle.svm.hosted.SecurityProviderCatalogRegistrar"), clazz("com.oracle.svm.hosted.SecurityServicesFeature"), clazz("com.oracle.svm.hosted.snippets.ReflectionPlugins"), clazz("com.oracle.svm.hosted.snippets.ReflectionPlugins$3"), diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/NamingConventionVerifier.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/NamingConventionVerifier.java index 6e6a9d75bbc7..b65418f8fe13 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/NamingConventionVerifier.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/NamingConventionVerifier.java @@ -99,6 +99,18 @@ static void checkName(BigBang bb, ResolvedJavaType type) { checkName(bb, null, format); } + static boolean isNameAllowed(ResolvedJavaMethod method) { + return namingConventionsViolation(method.format("%H.%n(%p)")) == null; + } + + static boolean isNameAllowed(ResolvedJavaField field) { + return namingConventionsViolation(field.format("%H.%n")) == null; + } + + static boolean isNameAllowed(ResolvedJavaType type) { + return namingConventionsViolation(type.toJavaName(true)) == null; + } + private static void checkName(BigBang bb, AnalysisMethod method, String name) { String message = namingConventionsViolation(name); if (message != null) { diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SVMHost.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SVMHost.java index 7d78103feb61..1cf344e0c27a 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SVMHost.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SVMHost.java @@ -1116,8 +1116,8 @@ public boolean isSupportedOriginalType(BigBang bb, ResolvedJavaType type) { } /* Remaining types should match the naming conventions. */ - if (verifyNamingConventions) { - NamingConventionVerifier.checkName(bb, type); + if (verifyNamingConventions && !NamingConventionVerifier.isNameAllowed(type)) { + return false; } return super.isSupportedOriginalType(bb, type); @@ -1142,8 +1142,8 @@ public boolean isSupportedAnalysisMethod(BigBang bb, AnalysisMethod method) { } /* Remaining methods should match the naming conventions. */ - if (verifyNamingConventions) { - NamingConventionVerifier.checkName(bb, method); + if (verifyNamingConventions && !NamingConventionVerifier.isNameAllowed(method)) { + return false; } return super.isSupportedAnalysisMethod(bb, method); @@ -1180,8 +1180,8 @@ public boolean isSupportedOriginalMethod(BigBang bb, ResolvedJavaMethod method) } /* Remaining methods should match the naming conventions. */ - if (verifyNamingConventions) { - NamingConventionVerifier.checkName(bb, method); + if (verifyNamingConventions && !NamingConventionVerifier.isNameAllowed(method)) { + return false; } return super.isSupportedOriginalMethod(bb, method); @@ -1222,7 +1222,7 @@ private boolean isSupportedMethod(BigBang bb, ResolvedJavaMethod method) { * they are replaced by the invocation plugin with a constant. If reachable in an extension * image, the plugin will replace it again. */ - if (GuestAnnotationAccess.isAnnotationPresent(method, Fold.class) && GuestAnnotationAccess.isAnnotationPresent(method, GuestFold.class)) { + if (GuestAnnotationAccess.isAnnotationPresent(method, Fold.class) || GuestAnnotationAccess.isAnnotationPresent(method, GuestFold.class)) { return false; } @@ -1306,8 +1306,8 @@ public boolean isSupportedAnalysisField(BigBang bb, AnalysisField field) { } /* Remaining fields should match the naming conventions. */ - if (verifyNamingConventions) { - NamingConventionVerifier.checkName(bb, field); + if (verifyNamingConventions && !NamingConventionVerifier.isNameAllowed(field)) { + return false; } return super.isSupportedAnalysisField(bb, field); @@ -1351,8 +1351,8 @@ public boolean isSupportedOriginalField(BigBang bb, ResolvedJavaField field) { } /* Remaining fields should match the naming conventions. */ - if (verifyNamingConventions) { - NamingConventionVerifier.checkName(bb, field); + if (verifyNamingConventions && !NamingConventionVerifier.isNameAllowed(field)) { + return false; } return super.isSupportedOriginalField(bb, field); diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SecurityProviderCatalogRegistrar.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SecurityProviderCatalogRegistrar.java index a12ae14cd60c..61c55be98bec 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SecurityProviderCatalogRegistrar.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SecurityProviderCatalogRegistrar.java @@ -50,6 +50,8 @@ interface Host { void registerService(DuringAnalysisAccess access, Service service); + void registerSelectedConstructionPath(Class providerClass); + Object getProviderVerificationResult(Provider provider); } @@ -90,7 +92,9 @@ void includeProviderClass(DuringAnalysisAccess access, Class providerClass) { void registerProvider(DuringAnalysisAccess access, Provider provider) { if (usedProviders.add(provider)) { RuntimeReflection.register(provider.getClass()); - RuntimeReflection.register(provider.getClass().getConstructors()); + if (host.isLoadableProviderClass(access, provider.getClass())) { + host.registerSelectedConstructionPath(provider.getClass()); + } /* Trigger initialization of lazy field java.security.Provider.entrySet. */ provider.entrySet(); String providerClassName = provider.getClass().getName(); diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SecurityProviderRegistrationPlanner.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SecurityProviderRegistrationPlanner.java index 6a5fc2c182e6..538f805dd8d2 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SecurityProviderRegistrationPlanner.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SecurityProviderRegistrationPlanner.java @@ -28,7 +28,7 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.Consumer; -import java.util.function.Predicate; +import java.util.function.Function; /** * Tracks provider-registration intent separately from reflection metadata emitted to realize it. @@ -36,6 +36,7 @@ final class SecurityProviderRegistrationPlanner { enum Source { APPLICATION_METADATA, + PRESERVE, SECURE_RANDOM_PLATFORM, LEGACY_ADDITIONAL_PROVIDER, LEGACY_SERVICE_REACHABILITY @@ -71,13 +72,14 @@ void beforeLegacyReflectionRegistration(Class providerClass) { legacyGeneratedReflection.add(providerClass); } - boolean processNewCompleteProviders(Predicate> hasRegistrationSignal, Consumer> includeProvider) { + boolean processNewCompleteProviders(Function, Source> signalSource, Consumer> includeProvider) { boolean discoveredCandidate = changed.getAndSet(false); boolean processed = false; for (Class providerClass : candidates) { - boolean applicationMetadata = !legacyGeneratedReflection.contains(providerClass) && hasRegistrationSignal.test(providerClass); - if (applicationMetadata) { - requestCompleteProvider(providerClass, Source.APPLICATION_METADATA); + Source signal = !legacyGeneratedReflection.contains(providerClass) ? signalSource.apply(providerClass) : null; + if (signal != null) { + recordSource(providerClass, signal); + completePlans.add(providerClass); } if (completePlans.contains(providerClass) && completed.add(providerClass)) { includeProvider.accept(providerClass); diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SecurityServicesFeature.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SecurityServicesFeature.java index 1eebca2fa0f3..7d79df414dce 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SecurityServicesFeature.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SecurityServicesFeature.java @@ -129,6 +129,71 @@ import sun.security.provider.NativePRNG; import sun.security.x509.OIDMap; +/// AR-security-providers: Security Provider Architecture +/// +/// The security-provider implementation separates build-time policy from run-time enforcement. +/// Reflection metadata, platform rules, and compatibility inputs are build-time registration +/// signals; the reflection registry is not itself the provider-policy model. This architecture +/// implements §FS-security-providers. +/// +/// ## 1. Independent Transition Axes +/// +/// `SecurityProviderMode` represents provider inclusion and provider-list initialization as +/// independent axes. Hosted components query this mode instead of reading future-default options +/// independently. Substitutions whose implementation differs by mode use build-time predicates, so +/// an application cannot change image-build policy through a run-time system property. This +/// realizes §FS-security-providers.7. +/// +/// ## 2. Registration Signals and Plans +/// +/// The hosted registration planner records provider candidates together with the provenance of the +/// signal that requests them: application reflection metadata, the platform-owned `SecureRandom` +/// rule, a deprecated provider option, or legacy service-type reachability. It produces an explicit +/// provider plan. Metadata emitted while realizing that plan is an output and is not reinterpreted +/// as a new application signal. This realizes §FS-security-providers.2 and +/// §FS-security-providers.7.3. +/// +/// ## 3. Hosted Registration Components +/// +/// `SecurityServicesFeature` coordinates the feature lifecycle. The registration planner owns +/// provider intent and iteration-safe candidate processing. The catalog registrar constructs +/// eligible providers and registers their service catalogs. `LegacySecurityProviderCompatibility` +/// owns deprecated options and service-driven inclusion. Provider code accesses reflection +/// registrations through a narrow query rather than the concrete metadata builder. +/// +/// ## 4. Run-Time Manifest +/// +/// `SecurityProviderRuntimeState` owns the layered, typed manifest written by hosted registration. +/// Each entry combines whether the JDK may construct the provider with the preserved JCE +/// verification outcome. An application-supplied provider can carry verification information +/// without being marked as JDK-constructible. The manifest is keyed by provider class name, as +/// required by §FS-security-providers.5.3. +/// +/// ## 5. Run-Time Access Services +/// +/// `SecurityProviderRuntimeState` owns manifest access, `BuiltInSecurityProviderLoader` owns JDK +/// aliases and construction, `SecurityProviderRuntimeAccess` owns tracing and missing-registration +/// diagnostics, and `JceProviderVerificationSupport` translates manifest outcomes to the JDK +/// contract. The two provider-list initialization modes share these services. +/// +/// ## 6. Service Descriptors +/// +/// Explicit provider registration preserves `java.security.Provider` descriptors without treating +/// them as provider-registration signals, independently of provider-list initialization. Legacy +/// suppression remains part of the compatibility policy. This realizes +/// §FS-security-providers.7.2. +/// +/// ## 7. Concurrent Analysis +/// +/// Provider subtype callbacks add candidates to concurrent collections. A serialized feature pass +/// consumes signals, realizes plans, and requests additional analysis iterations. Callbacks do not +/// schedule iterations directly. +/// +/// ## 8. Retirement Boundary +/// +/// Deprecated provider options and service-reachability inclusion are confined to +/// `LegacySecurityProviderCompatibility`. Removing compatibility behavior does not change the +/// planner, catalog registrar, run-time manifest, or planned-default substitutions. /** *

* This feature automatically registers security providers and their services for reflection and JNI @@ -155,7 +220,6 @@ * {@code EnableSecurityServicesFeature} option. For debugging or detailed inspection, tracing can * be enabled via the {@code TraceSecurityServices} option. */ - @AutomaticallyRegisteredFeature public class SecurityServicesFeature extends JNIRegistrationUtil implements InternalFeature { @@ -265,6 +329,8 @@ public static class Options { private final Map> buildTimeProvidersByClassName = new HashMap<>(); private SecurityProviderCatalogRegistrar catalogRegistrar; + private ReflectionRegistrationView reflectionRegistrationView; + private boolean preserveAll; @Override public void afterRegistration(AfterRegistrationAccess a) { @@ -290,6 +356,11 @@ public void registerService(DuringAnalysisAccess access, Service service) { SecurityServicesFeature.this.registerService(access, service); } + @Override + public void registerSelectedConstructionPath(Class providerClass) { + SecurityServicesFeature.registerSelectedConstructionPath(providerClass); + } + @Override public Object getProviderVerificationResult(Provider provider) { return SecurityServicesFeature.this.getProviderVerificationResult(provider); @@ -417,6 +488,8 @@ public void beforeAnalysis(BeforeAnalysisAccess a) { access.ensureInitialized("sun.security.util.AnchorCertificates"); initializeServiceRegistrationData(); + preserveAll = access.imageClassLoader.classLoaderSupport.isPreserveAll(); + reflectionRegistrationView = ReflectionRegistrationView.singleton(); access.registerSubtypeReachabilityHandler((_, providerClass) -> addCandidateProviderClass(providerClass), Provider.class); registerServiceProviderCandidates(access); LegacySecurityProviderCompatibility.registerAdditionalProviders(access, providerClass -> { @@ -1014,18 +1087,17 @@ private void registerService(DuringAnalysisAccess a, Service service) { // Recognize every qualifying reflection-registration signal. // §FS-security-providers.1.1 and §FS-security-providers.2.1 - private static boolean isProviderRegisteredForReflection(Class providerClass) { + private boolean isProviderRegisteredForReflection(Class providerClass) { try { - ReflectionRegistrationView reflection = ReflectionRegistrationView.singleton(); - if (reflection.hasTypeAccess(providerClass)) { + if (reflectionRegistrationView.hasTypeAccess(providerClass)) { return true; } Constructor constructor = findDeclaredNullaryConstructor(providerClass); - if (constructor != null && reflection.hasExecutableAccess(constructor)) { + if (constructor != null && reflectionRegistrationView.hasExecutableAccess(constructor)) { return true; } Method providerMethod = findProviderMethod(providerClass); - return providerMethod != null && reflection.hasExecutableAccess(providerMethod); + return providerMethod != null && reflectionRegistrationView.hasExecutableAccess(providerMethod); } catch (UnsupportedPlatformException | DeletedElementException e) { return false; } @@ -1048,6 +1120,18 @@ private static void registerProviderClassForReflection(Class providerClass) { trace("Registered provider %s for reflection", providerClass.getName()); } + private static void registerSelectedConstructionPath(Class providerClass) { + Constructor constructor = findDeclaredNullaryConstructor(providerClass); + if (constructor != null) { + RuntimeReflection.register(constructor); + } else { + Method providerMethod = findProviderMethod(providerClass); + if (providerMethod != null) { + RuntimeReflection.register(providerMethod); + } + } + } + private static boolean hasDeclaredNullaryConstructor(Class providerClass) { return findDeclaredNullaryConstructor(providerClass) != null; } @@ -1118,7 +1202,10 @@ public void duringAnalysis(DuringAnalysisAccess a) { DuringAnalysisAccessImpl access = (DuringAnalysisAccessImpl) a; // Consume concurrent plans in the serialized feature pass. if (providerPlanner.processNewCompleteProviders( - providerClass -> mode.explicitRegistration() && isProviderRegisteredForReflection(providerClass), + providerClass -> mode.explicitRegistration() && isProviderRegisteredForReflection(providerClass) + ? SecurityProviderRegistrationPlanner.Source.APPLICATION_METADATA + : preserveAll && isProviderRegisteredForReflection(providerClass) + ? SecurityProviderRegistrationPlanner.Source.PRESERVE : null, providerClass -> catalogRegistrar.includeProviderClass(access, providerClass))) { // Request the extra pass here, not from the concurrent reachability callback. access.requireAnalysisIteration(); diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/ReflectionDataBuilder.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/ReflectionDataBuilder.java index 96a8852df5c2..ac941e6e7b9b 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/ReflectionDataBuilder.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/ReflectionDataBuilder.java @@ -1420,13 +1420,19 @@ public RuntimeDynamicAccessMetadata getTypeMetadata(Class clazz) { } public boolean isTypeRegisteredForReflection(Class clazz) { - AnalysisType analysisType = metaAccess.lookupJavaType(clazz); + AnalysisType analysisType = metaAccess.optionalLookupJavaType(clazz).orElse(null); + if (analysisType == null) { + return false; + } TypeData data = types.get(analysisType); return data != null && data.isRegisteredAs(ACCESSED); } public boolean isMethodRegisteredForReflection(Executable method) { - AnalysisMethod analysisMethod = metaAccess.lookupJavaMethod(method); + AnalysisMethod analysisMethod = metaAccess.optionalLookupJavaMethod(method).orElse(null); + if (analysisMethod == null) { + return false; + } ElementData data = methods.get(analysisMethod); return data != null && data.isRegisteredAs(ACCESSED); } From efcba17a198f118c083f8bfbcc1b1f2007365f36 Mon Sep 17 00:00:00 2001 From: Vojin Jovanovic Date: Mon, 27 Jul 2026 14:23:56 +0200 Subject: [PATCH 45/63] GR-69858: Fix security provider CI regressions --- .../oracle/svm/agent/NativeImageAgent.java | 14 ++--- .../jdk/SecurityProviderRuntimeState.java | 3 +- .../svm/hosted/SecurityServicesFeature.java | 52 +++++++++---------- 3 files changed, 35 insertions(+), 34 deletions(-) diff --git a/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/NativeImageAgent.java b/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/NativeImageAgent.java index a54ae997d59f..d0e93f4bf23c 100644 --- a/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/NativeImageAgent.java +++ b/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/NativeImageAgent.java @@ -385,7 +385,7 @@ protected int onLoadCallback(JNIJavaVM vm, JvmtiEnv jvmti, JvmtiEventCallbacks c } expectedConfigModifiedBefore = getMostRecentlyModified(configOutputDirPath, getMostRecentlyModified(configOutputLockFilePath, null)); } catch (Throwable t) { - return error(AGENT_ERROR, t.toString()); + return error(AGENT_ERROR, "configuration writer initialization failed: " + t); } } else { try { @@ -394,7 +394,7 @@ protected int onLoadCallback(JNIJavaVM vm, JvmtiEnv jvmti, JvmtiEventCallbacks c tracer = writer; tracingResultWriter = writer; } catch (Throwable t) { - return error(AGENT_ERROR, t.toString()); + return error(AGENT_ERROR, "trace writer initialization failed: " + t); } } @@ -405,16 +405,13 @@ protected int onLoadCallback(JNIJavaVM vm, JvmtiEnv jvmti, JvmtiEventCallbacks c try { BreakpointInterceptor.onLoad(jvmti, callbacks, tracer, this, interceptedStateSupplier, experimentalClassLoaderSupport, experimentalClassDefineSupport, experimentalUnsafeAllocationSupport, trackReflectionMetadata); - if (handles().sunSecurityJcaProviderConfigProvider.isNull() || !BreakpointInterceptor.securityProviderHooksAvailable()) { - warn("JDK security-provider lookup hooks are unavailable; provider lookup coverage is reduced."); - } } catch (Throwable t) { - return error(AGENT_ERROR, t.toString()); + return error(AGENT_ERROR, "breakpoint interceptor initialization failed: " + t); } try { JniCallInterceptor.onLoad(tracer, this, interceptedStateSupplier); } catch (Throwable t) { - return error(AGENT_ERROR, t.toString()); + return error(AGENT_ERROR, "JNI call interceptor initialization failed: " + t); } setupExecutorServiceForPeriodicConfigurationCapture(configWritePeriod, configWritePeriodInitialDelay); @@ -567,6 +564,9 @@ private static String transformPath(String path) { @Override protected void onVMInitCallback(JvmtiEnv jvmti, JNIEnvironment jni, JNIObjectHandle thread) { BreakpointInterceptor.onVMInit(jvmti, jni); + if (handles().sunSecurityJcaProviderConfigProvider.isNull() || !BreakpointInterceptor.securityProviderHooksAvailable()) { + warn("JDK security-provider lookup hooks are unavailable; provider lookup coverage is reduced."); + } if (tracer != null) { tracer.tracePhaseChange("live"); } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SecurityProviderRuntimeState.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SecurityProviderRuntimeState.java index 707d54a1b2ee..68276938ba00 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SecurityProviderRuntimeState.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SecurityProviderRuntimeState.java @@ -32,6 +32,7 @@ import org.graalvm.nativeimage.Platform; import org.graalvm.nativeimage.Platforms; +import com.oracle.svm.guest.staging.util.ImageHeapMap; import com.oracle.svm.shared.singletons.traits.BuiltinTraits.AllAccess; import com.oracle.svm.shared.singletons.traits.BuiltinTraits.NoLayeredCallbacks; import com.oracle.svm.shared.singletons.LayeredImageSingletonSupport; @@ -49,7 +50,7 @@ public enum AcquisitionKind { public record ProviderInfo(AcquisitionKind acquisitionKind, Exception verificationFailure) { } - private final EconomicMap providerInfos = EconomicMap.create(); + private final EconomicMap providerInfos = ImageHeapMap.createNonLayeredMap(); private Properties savedInitialSecurityProperties; private Constructor sunECConstructor; diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SecurityServicesFeature.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SecurityServicesFeature.java index 7d79df414dce..07e23f98c09f 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SecurityServicesFeature.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SecurityServicesFeature.java @@ -194,32 +194,32 @@ /// Deprecated provider options and service-reachability inclusion are confined to /// `LegacySecurityProviderCompatibility`. Removing compatibility behavior does not change the /// planner, catalog registrar, run-time manifest, or planned-default substitutions. -/** - *

- * This feature automatically registers security providers and their services for reflection and JNI - * access, ensuring they are available at run time. - * - *

- * The feature distinguishes between providers that are initialized at build time and those that are - * initialized at run time. This distinction is essential because certain providers may perform - * sensitive operations. Right now, all providers are initialized build-time by default, but that - * can be changed using --future-defaults=run-time-initialize-security-providers - * - *

- * The initialization strategy is: - *

    - *
  • Build-time Initialization: Most cryptographic infrastructure is initialized at build-time. - * This includes reflection metadata and service registration.
  • - *
  • Run-time Initialization: Classes that rely on system resources (e.g., {@code /dev/urandom}, - * keystore passwords, or native Windows libraries) are marked for runtime initialization or the - * providers (if --future-defaults is used).
  • - *
- * - *

- * This feature is automatically registered, but it can be controlled via the - * {@code EnableSecurityServicesFeature} option. For debugging or detailed inspection, tracing can - * be enabled via the {@code TraceSecurityServices} option. - */ +/// +///

+/// This feature automatically registers security providers and their services for reflection and +/// JNI access, ensuring they are available at run time. +/// +///

+/// The feature distinguishes between providers that are initialized at build time and those that +/// are initialized at run time. This distinction is essential because certain providers may +/// perform sensitive operations. Right now, all providers are initialized build-time by default, +/// but that can be changed using +/// --future-defaults=run-time-initialize-security-providers +/// +///

+/// The initialization strategy is: +///

    +///
  • Build-time Initialization: Most cryptographic infrastructure is initialized at build-time. +/// This includes reflection metadata and service registration.
  • +///
  • Run-time Initialization: Classes that rely on system resources (e.g., {@code /dev/urandom}, +/// keystore passwords, or native Windows libraries) are marked for runtime initialization or the +/// providers (if --future-defaults is used).
  • +///
+/// +///

+/// This feature is automatically registered, but it can be controlled via the +/// {@code EnableSecurityServicesFeature} option. For debugging or detailed inspection, tracing can +/// be enabled via the {@code TraceSecurityServices} option. @AutomaticallyRegisteredFeature public class SecurityServicesFeature extends JNIRegistrationUtil implements InternalFeature { From 97c7c1d4bcba86f2255ab443e54bbdff877f40fc Mon Sep 17 00:00:00 2001 From: Vojin Jovanovic Date: Mon, 27 Jul 2026 16:36:01 +0200 Subject: [PATCH 46/63] [GR-69858] Preserve application provider verification metadata --- .../svm/hosted/SecurityServicesFeature.java | 21 +++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SecurityServicesFeature.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SecurityServicesFeature.java index 07e23f98c09f..a489124c857a 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SecurityServicesFeature.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SecurityServicesFeature.java @@ -1103,6 +1103,22 @@ private boolean isProviderRegisteredForReflection(Class providerClass) { } } + private SecurityProviderRegistrationPlanner.Source completeProviderSource(DuringAnalysisAccess access, Class providerClass) { + if (!isProviderRegisteredForReflection(providerClass)) { + return null; + } + if (preserveAll) { + return SecurityProviderRegistrationPlanner.Source.PRESERVE; + } + // Compatibility mode keeps constructible-provider metadata inert. Application providers + // still need class-based JCE verification. §FS-security-providers.5.3, + // §FS-security-providers.7.3 + if (mode.explicitRegistration() || !isLoadableProviderClass(access, providerClass)) { + return SecurityProviderRegistrationPlanner.Source.APPLICATION_METADATA; + } + return null; + } + private static void registerProviderClassForReflection(Class providerClass) { RuntimeReflection.register(providerClass); Constructor constructor = findDeclaredNullaryConstructor(providerClass); @@ -1202,10 +1218,7 @@ public void duringAnalysis(DuringAnalysisAccess a) { DuringAnalysisAccessImpl access = (DuringAnalysisAccessImpl) a; // Consume concurrent plans in the serialized feature pass. if (providerPlanner.processNewCompleteProviders( - providerClass -> mode.explicitRegistration() && isProviderRegisteredForReflection(providerClass) - ? SecurityProviderRegistrationPlanner.Source.APPLICATION_METADATA - : preserveAll && isProviderRegisteredForReflection(providerClass) - ? SecurityProviderRegistrationPlanner.Source.PRESERVE : null, + providerClass -> completeProviderSource(access, providerClass), providerClass -> catalogRegistrar.includeProviderClass(access, providerClass))) { // Request the extra pass here, not from the concurrent reachability callback. access.requireAnalysisIteration(); From 549d8630016ae5302523d76b482ebdf25f2d9d0a Mon Sep 17 00:00:00 2001 From: Vojin Jovanovic Date: Mon, 27 Jul 2026 19:42:34 +0200 Subject: [PATCH 47/63] Make explicit provider registration imply runtime initialization --- .../functional-spec/security-providers.md | 20 +++++------ .../svm/core/FutureDefaultsOptions.java | 3 ++ .../svm/core/doc-files/FutureDefaultsHelp.txt | 2 +- .../jdk/BuiltInSecurityProviderLoader.java | 33 ++++++++++++++++++- .../jdk/SecurityProviderRuntimeAccess.java | 23 +++++++++++++ .../SecuritySubstitutionRuntimeInit.java | 10 +++++- .../svm/hosted/SecurityProviderMode.java | 32 +++++++----------- .../svm/hosted/SecurityServicesFeature.java | 12 +++---- .../svm/hosted/ServiceLoaderFeature.java | 2 +- ...andomExplicitProviderRegistrationTest.java | 2 +- ...rviceExplicitProviderRegistrationTest.java | 10 +++++- .../test/services/SecurityServiceTest.java | 3 +- 12 files changed, 108 insertions(+), 44 deletions(-) diff --git a/substratevm/docs/functional-spec/security-providers.md b/substratevm/docs/functional-spec/security-providers.md index 43490488d62a..03bcec09f92c 100644 --- a/substratevm/docs/functional-spec/security-providers.md +++ b/substratevm/docs/functional-spec/security-providers.md @@ -362,10 +362,13 @@ actual operation. ## 7. Transition to the Future Defaults Sections 1 through 6 specify the planned default behavior. -The following options select its two independent parts while the earlier behaviors remain -available for compatibility. -Every combination of provider-inclusion policy and provider-list initialization must preserve the -applicable behavior below; selecting one part must not implicitly select or disable the other. +The following options select the transition behavior while the earlier behaviors remain available +for compatibility. +Run-time provider-list initialization can be selected independently. +Explicit provider registration depends on and implicitly enables run-time provider-list +initialization. +The supported combinations are legacy inclusion with build-time initialization, legacy inclusion +with run-time initialization, and explicit registration with run-time initialization. ### 7.1 Run-Time Provider-List Initialization @@ -374,6 +377,7 @@ run-time provider list from the configured security properties using only regist An unregistered provider is not added to the list, and its services remain unavailable. Filtering unregistered providers preserves the ordering and lookup results specified in sections 1.3, 3.2, and 4. +`--future-defaults=explicit-security-provider-registration` implicitly enables this behavior. ### 7.2 Provider Service Descriptors @@ -412,9 +416,5 @@ provider support from a general service factory. `--future-defaults=run-time-initialize-security-providers` replaces the earlier behavior in which Native Image initializes the configured provider list at build time; during the transition, -omitting this future default retains that earlier initialization behavior. -When explicit provider registration is enabled without run-time provider initialization, Native -Image must filter the build-time provider list before storing it in the executable so that it does -not expose an unregistered JDK-managed provider. -Provider and service availability is still determined at build time according to either the -explicit registration rules in sections 1 and 2 or the compatibility rule in section 7.3. +omitting this future default and explicit provider registration retains that earlier initialization +behavior. diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/FutureDefaultsOptions.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/FutureDefaultsOptions.java index c565a65457a4..623db3a78daf 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/FutureDefaultsOptions.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/FutureDefaultsOptions.java @@ -217,6 +217,9 @@ private static LinkedHashSet computeFutureDefaults(Stream "SunJCE"; case "sun.security.ssl.SunJSSE" -> "SunJSSE"; case "sun.security.ec.SunEC" -> "SunEC"; + case "sun.security.jgss.SunProvider" -> "SunJGSS"; + case "com.sun.security.sasl.Provider" -> "SunSASL"; + case "org.jcp.xml.dsig.internal.dom.XMLDSigRI" -> "XMLDSig"; + case "sun.security.smartcardio.SunPCSC" -> "SunPCSC"; + case "sun.security.provider.certpath.ldap.JdkLDAP" -> "JdkLDAP"; + case "com.sun.security.sasl.gsskerb.JdkSASL" -> "JdkSASL"; + case "sun.security.pkcs11.SunPKCS11" -> "SunPKCS11"; + case "sun.security.mscapi.SunMSCAPI" -> "SunMSCAPI"; + case "com.oracle.security.ucrypto.UcryptoProvider" -> "OracleUcrypto"; case "apple.security.AppleProvider" -> "Apple"; default -> null; }; @@ -59,13 +68,35 @@ public static String getProviderClassName(String providerNameOrClassName) { case "SunJCE", "com.sun.crypto.provider.SunJCE" -> "com.sun.crypto.provider.SunJCE"; case "SunJSSE", "sun.security.ssl.SunJSSE" -> "sun.security.ssl.SunJSSE"; case "SunEC", "sun.security.ec.SunEC" -> "sun.security.ec.SunEC"; + case "SunJGSS", "sun.security.jgss.SunProvider" -> "sun.security.jgss.SunProvider"; + case "SunSASL", "com.sun.security.sasl.Provider" -> "com.sun.security.sasl.Provider"; + case "XMLDSig", "org.jcp.xml.dsig.internal.dom.XMLDSigRI" -> "org.jcp.xml.dsig.internal.dom.XMLDSigRI"; + case "SunPCSC", "sun.security.smartcardio.SunPCSC" -> "sun.security.smartcardio.SunPCSC"; + case "JdkLDAP", "sun.security.provider.certpath.ldap.JdkLDAP" -> "sun.security.provider.certpath.ldap.JdkLDAP"; + case "JdkSASL", "com.sun.security.sasl.gsskerb.JdkSASL" -> "com.sun.security.sasl.gsskerb.JdkSASL"; + case "SunPKCS11", "sun.security.pkcs11.SunPKCS11" -> "sun.security.pkcs11.SunPKCS11"; + case "SunMSCAPI", "sun.security.mscapi.SunMSCAPI" -> "sun.security.mscapi.SunMSCAPI"; + case "OracleUcrypto", "com.oracle.security.ucrypto.UcryptoProvider" -> "com.oracle.security.ucrypto.UcryptoProvider"; case "Apple", "apple.security.AppleProvider" -> "apple.security.AppleProvider"; default -> null; }; } public static boolean isBuiltIn(String providerNameOrClassName) { - return getProviderClassName(providerNameOrClassName) != null; + String providerClassName = getProviderClassName(providerNameOrClassName); + if (providerClassName == null) { + return false; + } + return switch (providerClassName) { + case "sun.security.provider.Sun", + "sun.security.rsa.SunRsaSign", + "com.sun.crypto.provider.SunJCE", + "sun.security.ssl.SunJSSE", + "sun.security.ec.SunEC", + "apple.security.AppleProvider" -> + true; + default -> false; + }; } /** §FS-security-providers.3.1, §FS-security-providers.4.3, and §FS-security-providers.7.1. */ diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SecurityProviderRuntimeAccess.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SecurityProviderRuntimeAccess.java index 26c6d5b7774c..c98009d0b4c4 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SecurityProviderRuntimeAccess.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SecurityProviderRuntimeAccess.java @@ -25,14 +25,37 @@ package com.oracle.svm.core.jdk; import java.security.Provider; +import java.util.function.Supplier; import com.oracle.svm.core.metadata.MetadataTracer; import com.oracle.svm.shared.NeverInline; public final class SecurityProviderRuntimeAccess { + private static final ThreadLocal LOAD_UNREGISTERED_CONFIGURED_PROVIDER = new ThreadLocal<>(); + private SecurityProviderRuntimeAccess() { } + /** §FS-security-providers.4.3: Explicit configured-provider lookups retain diagnostics. */ + public static Provider loadUnregisteredConfiguredProvider(Supplier loader) { + Boolean previous = LOAD_UNREGISTERED_CONFIGURED_PROVIDER.get(); + LOAD_UNREGISTERED_CONFIGURED_PROVIDER.set(true); + try { + return loader.get(); + } finally { + if (previous == null) { + LOAD_UNREGISTERED_CONFIGURED_PROVIDER.remove(); + } else { + LOAD_UNREGISTERED_CONFIGURED_PROVIDER.set(previous); + } + } + } + + /** §FS-security-providers.7.1: Provider-list construction filters unregistered providers. */ + public static boolean shouldLoadUnregisteredConfiguredProvider() { + return Boolean.TRUE.equals(LOAD_UNREGISTERED_CONFIGURED_PROVIDER.get()); + } + /** §FS-security-providers.4.3: Cache misses probe type access for standard diagnostics. */ @NeverInline("Keep the provider class name unknown to static analysis without an opaque compiler node.") public static void reportMissingRegistration(Class providerClass) { diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/runtimeinit/SecuritySubstitutionRuntimeInit.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/runtimeinit/SecuritySubstitutionRuntimeInit.java index c92dd32968de..c803a55d44bf 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/runtimeinit/SecuritySubstitutionRuntimeInit.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/runtimeinit/SecuritySubstitutionRuntimeInit.java @@ -140,6 +140,13 @@ Provider getProvider() { if (provider != null) { return provider; } + String builtInProviderClassName = BuiltInSecurityProviderLoader.getProviderClassName(provName); + String providerClassName = builtInProviderClassName != null ? builtInProviderClassName : provName; + /* Omit unregistered providers from the run-time list. §FS-security-providers.7.1 */ + if (!SecurityProviderRuntimeAccess.shouldLoadUnregisteredConfiguredProvider() && + !SecurityProviderRuntimeState.isJdkConstructible(providerClassName)) { + return null; + } if (!shouldLoad()) { return null; } @@ -197,7 +204,8 @@ public Provider getProvider(String name) { String providerFQName = BuiltInSecurityProviderLoader.getProviderClassName(configuredProviderName); boolean matches = configuredProviderName.equals(name) || (providerName != null && providerName.equals(name)) || (providerFQName != null && providerFQName.equals(name)); if (matches) { - return SecurityProviderRuntimeAccess.traceLookup(config.getProvider()); + Provider provider = SecurityProviderRuntimeAccess.loadUnregisteredConfiguredProvider(config::getProvider); + return SecurityProviderRuntimeAccess.traceLookup(provider); } } return null; diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SecurityProviderMode.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SecurityProviderMode.java index cd2645299cca..6197d0a29c1a 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SecurityProviderMode.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SecurityProviderMode.java @@ -26,33 +26,25 @@ import com.oracle.svm.core.FutureDefaultsOptions; -/** The two independent transition axes from §FS-security-providers.7. */ -record SecurityProviderMode(InclusionPolicy inclusionPolicy, ProviderListInitialization listInitialization) { - enum InclusionPolicy { - LEGACY_SERVICE_REACHABILITY, - EXPLICIT_METADATA - } - - enum ProviderListInitialization { - BUILD_TIME, - RUN_TIME - } +/** The supported transition modes from §FS-security-providers.7. */ +enum SecurityProviderMode { + LEGACY_BUILD_TIME, + LEGACY_RUN_TIME, + EXPLICIT_RUN_TIME; static SecurityProviderMode current() { - InclusionPolicy inclusion = FutureDefaultsOptions.explicitSecurityProviderRegistration() - ? InclusionPolicy.EXPLICIT_METADATA - : InclusionPolicy.LEGACY_SERVICE_REACHABILITY; - ProviderListInitialization initialization = FutureDefaultsOptions.securityProvidersInitializedAtRunTime() - ? ProviderListInitialization.RUN_TIME - : ProviderListInitialization.BUILD_TIME; - return new SecurityProviderMode(inclusion, initialization); + if (FutureDefaultsOptions.explicitSecurityProviderRegistration()) { + assert FutureDefaultsOptions.securityProvidersInitializedAtRunTime(); + return EXPLICIT_RUN_TIME; + } + return FutureDefaultsOptions.securityProvidersInitializedAtRunTime() ? LEGACY_RUN_TIME : LEGACY_BUILD_TIME; } boolean explicitRegistration() { - return inclusionPolicy == InclusionPolicy.EXPLICIT_METADATA; + return this == EXPLICIT_RUN_TIME; } boolean runtimeProviderList() { - return listInitialization == ProviderListInitialization.RUN_TIME; + return this != LEGACY_BUILD_TIME; } } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SecurityServicesFeature.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SecurityServicesFeature.java index a489124c857a..b4fc4d824d63 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SecurityServicesFeature.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SecurityServicesFeature.java @@ -136,10 +136,11 @@ /// signals; the reflection registry is not itself the provider-policy model. This architecture /// implements §FS-security-providers. /// -/// ## 1. Independent Transition Axes +/// ## 1. Supported Transition Modes /// -/// `SecurityProviderMode` represents provider inclusion and provider-list initialization as -/// independent axes. Hosted components query this mode instead of reading future-default options +/// `SecurityProviderMode` represents the three supported combinations of provider inclusion and +/// provider-list initialization. Explicit registration depends on run-time provider-list +/// initialization. Hosted components query this mode instead of reading future-default options /// independently. Substitutions whose implementation differs by mode use build-time predicates, so /// an application cannot change image-build policy through a run-time system property. This /// realizes §FS-security-providers.7. @@ -179,9 +180,8 @@ /// ## 6. Service Descriptors /// /// Explicit provider registration preserves `java.security.Provider` descriptors without treating -/// them as provider-registration signals, independently of provider-list initialization. Legacy -/// suppression remains part of the compatibility policy. This realizes -/// §FS-security-providers.7.2. +/// them as provider-registration signals. Legacy suppression remains part of the compatibility +/// policy. This realizes §FS-security-providers.7.2. /// /// ## 7. Concurrent Analysis /// diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ServiceLoaderFeature.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ServiceLoaderFeature.java index 67b8c46c6ca8..139ba82d5437 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ServiceLoaderFeature.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ServiceLoaderFeature.java @@ -156,7 +156,7 @@ public boolean isInConfiguration(IsInConfigurationAccess access) { @Override public void afterRegistration(AfterRegistrationAccess access) { securityProviderMode = SecurityProviderMode.current(); - if (!securityProviderMode.runtimeProviderList() && !securityProviderMode.explicitRegistration()) { + if (!securityProviderMode.runtimeProviderList()) { servicesToSkip.add(java.security.Provider.class.getName()); } if (!FutureDefaultsOptions.resourceBundlesInitializedAtRunTime()) { diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/services/SecureRandomExplicitProviderRegistrationTest.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/services/SecureRandomExplicitProviderRegistrationTest.java index e5cd81393e9e..bf3bc5e8e871 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/services/SecureRandomExplicitProviderRegistrationTest.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/services/SecureRandomExplicitProviderRegistrationTest.java @@ -33,7 +33,7 @@ import com.oracle.svm.test.NativeImageBuildArgs; @NativeImageBuildArgs({ - "--future-defaults=run-time-initialize-security-providers,explicit-security-provider-registration", + "--future-defaults=explicit-security-provider-registration", "--exact-reachability-metadata=com.oracle.svm.test.services", "-Dcom.oracle.svm.test.services.SecureRandomExplicitProviderRegistrationTest=true" }) diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/services/SecurityServiceExplicitProviderRegistrationTest.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/services/SecurityServiceExplicitProviderRegistrationTest.java index f39141f80ac0..8e88edc56baa 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/services/SecurityServiceExplicitProviderRegistrationTest.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/services/SecurityServiceExplicitProviderRegistrationTest.java @@ -36,16 +36,24 @@ import org.junit.Assume; import org.junit.Test; +import com.oracle.svm.core.FutureDefaultsOptions; import com.oracle.svm.core.jdk.SecurityProviderRuntimeState; import com.oracle.svm.test.NativeImageBuildArgs; @NativeImageBuildArgs({ - "--future-defaults=run-time-initialize-security-providers,explicit-security-provider-registration", + "--future-defaults=explicit-security-provider-registration", "--exact-reachability-metadata=com.oracle.svm.test.services" }) public class SecurityServiceExplicitProviderRegistrationTest { private static final String REGISTERED_PROVIDER_NAME = "reflection-metadata-provider"; + /** Tests §FS-security-providers.7.1. */ + @Test + public void testExplicitRegistrationEnablesRuntimeProviderInitialization() { + Assert.assertTrue(FutureDefaultsOptions.explicitSecurityProviderRegistration()); + Assert.assertTrue(FutureDefaultsOptions.securityProvidersInitializedAtRunTime()); + } + /** Tests §FS-security-providers.2.3 and §FS-security-providers.2.4. */ @Test public void testDefaultSecureRandomIncludesCompleteSunProvider() throws NoSuchAlgorithmException { diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/services/SecurityServiceTest.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/services/SecurityServiceTest.java index fafcf10fa616..045299a783b3 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/services/SecurityServiceTest.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/services/SecurityServiceTest.java @@ -130,8 +130,7 @@ public void testSecurityProviderRuntimeRegistration() { */ @Test public void testUnknownSecurityServices() throws Exception { - Assume.assumeTrue("needs explicit or runtime provider registration", - FutureDefaultsOptions.explicitSecurityProviderRegistration() || FutureDefaultsOptions.securityProvidersInitializedAtRunTime()); + Assume.assumeTrue("needs runtime provider initialization", FutureDefaultsOptions.securityProvidersInitializedAtRunTime()); if (FutureDefaultsOptions.securityProvidersInitializedAtRunTime()) { /* Register the provider at run time. */ Security.addProvider(new NoOpProvider()); From 51082494b973d7b6d20a871508dd7ca41010cdb5 Mon Sep 17 00:00:00 2001 From: Vojin Jovanovic Date: Tue, 28 Jul 2026 09:29:16 +0200 Subject: [PATCH 48/63] GR-69858: Refine security provider reflection tracing --- .../functional-spec/security-providers.md | 2 + substratevm/mx.substratevm/mx_substratevm.py | 2 + .../svm/agent/BreakpointInterceptor.java | 17 +++++-- .../agent/NativeImageAgentJNIHandleSet.java | 3 +- .../config/SecurityProviderAgentTest.java | 50 +++++++++++++++++++ .../SecurityProviderAgentVerifierTest.java | 18 +++++++ 6 files changed, 88 insertions(+), 4 deletions(-) diff --git a/substratevm/docs/functional-spec/security-providers.md b/substratevm/docs/functional-spec/security-providers.md index 03bcec09f92c..66af76449e22 100644 --- a/substratevm/docs/functional-spec/security-providers.md +++ b/substratevm/docs/functional-spec/security-providers.md @@ -346,6 +346,8 @@ traced factory calls. This includes a service implementation named only by `Provider.Service.getClassName()`: the caller-filtered trace must retain the construction access performed inside `Provider.Service.newInstance` and attribute it to the application operation that selected the service. +The trace may locate `Provider.Service.newInstance` through contiguous helper frames declared by +`Provider.Service`, but it must not cross a frame declared by another class. Tracing a missing provider registration must use the ordinary reflection metadata format and diagnostics; it must not introduce a security-provider-specific metadata category or error. diff --git a/substratevm/mx.substratevm/mx_substratevm.py b/substratevm/mx.substratevm/mx_substratevm.py index 0774c64c30dc..b20a5cede2d2 100644 --- a/substratevm/mx.substratevm/mx_substratevm.py +++ b/substratevm/mx.substratevm/mx_substratevm.py @@ -946,6 +946,8 @@ def run_agent_security_provider_config_test(agent_path): ('enumeration', 'enumerateSecurityProviders', 'verifyEnumeratedProvidersWereRecorded'), ('mutation', 'programmaticProviderMutationDoesNotTraceConfiguredProviders', 'verifyMutationRecordedOnlySuppliedProvider'), + ('service-construction', 'providerServiceHelpersRetainConstructorMetadata', + 'verifyProviderServiceConstructorWasRecorded'), ] for name, generator_method, verifier_method in cases: config_dir = join(svmbuild_dir(), 'security-provider-agent-' + name + '-test-config') diff --git a/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/BreakpointInterceptor.java b/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/BreakpointInterceptor.java index a514fc1964ec..8766e0701d08 100644 --- a/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/BreakpointInterceptor.java +++ b/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/BreakpointInterceptor.java @@ -860,10 +860,21 @@ private static boolean isSecurityMutationMethod(JNIMethodId method) { method.equal(handles.javaSecurityRemoveProvider); } + /** §FS-security-providers.6.1: Cross only contiguous Provider.Service helper frames. */ private static JNIObjectHandle findProviderServiceCaller(JNIEnvironment jni, InterceptedState state) { - JNIMethodId directCaller = state.getCallerMethod(1); - if (directCaller.equal(agent.handles().javaSecurityProviderServiceNewInstance)) { - return findExternalSecurityCaller(jni, state, 2); + NativeImageAgentJNIHandleSet handles = agent.handles(); + for (int depth = 1; depth < MAX_SECURITY_STACK_DEPTH; depth++) { + JNIMethodId callerMethod = state.getCallerMethod(depth); + if (callerMethod.isNull()) { + return nullHandle(); + } + if (callerMethod.equal(handles.javaSecurityProviderServiceNewInstance)) { + return findExternalSecurityCaller(jni, state, depth + 1); + } + JNIObjectHandle callerClass = getMethodDeclaringClass(callerMethod); + if (!jniFunctions().getIsSameObject().invoke(jni, callerClass, handles.javaSecurityProviderService)) { + return nullHandle(); + } } return nullHandle(); } diff --git a/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/NativeImageAgentJNIHandleSet.java b/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/NativeImageAgentJNIHandleSet.java index 251125e55a31..94c30cef1bc0 100644 --- a/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/NativeImageAgentJNIHandleSet.java +++ b/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/NativeImageAgentJNIHandleSet.java @@ -57,6 +57,7 @@ public class NativeImageAgentJNIHandleSet extends JNIHandleSet { final JNIMethodId javaLangObjectGetClass; final JNIMethodId javaLangObjectToString; + final JNIObjectHandle javaSecurityProviderService; final JNIMethodId javaSecurityProviderServiceGetProvider; final JNIMethodId javaSecurityProviderServiceNewInstance; final JNIMethodId javaSecurityProviderGetName; @@ -176,7 +177,7 @@ public class NativeImageAgentJNIHandleSet extends JNIHandleSet { javaLangObjectGetClass = getMethodId(env, javaLangObject, "getClass", "()Ljava/lang/Class;", false); javaLangObjectToString = getMethodId(env, javaLangObject, "toString", "()Ljava/lang/String;", false); - JNIObjectHandle javaSecurityProviderService = findClass(env, "java/security/Provider$Service"); + javaSecurityProviderService = newClassGlobalRef(env, "java/security/Provider$Service"); javaSecurityProviderServiceGetProvider = getMethodId(env, javaSecurityProviderService, "getProvider", "()Ljava/security/Provider;", false); javaSecurityProviderServiceNewInstance = getMethodId(env, javaSecurityProviderService, "newInstance", "(Ljava/lang/Object;)Ljava/lang/Object;", false); JNIObjectHandle javaSecurityProvider = findClass(env, "java/security/Provider"); diff --git a/substratevm/src/com.oracle.svm.configure.test/src/com/oracle/svm/configure/test/config/SecurityProviderAgentTest.java b/substratevm/src/com.oracle.svm.configure.test/src/com/oracle/svm/configure/test/config/SecurityProviderAgentTest.java index a3b6cb431ace..5bc21aa8dacc 100644 --- a/substratevm/src/com.oracle.svm.configure.test/src/com/oracle/svm/configure/test/config/SecurityProviderAgentTest.java +++ b/substratevm/src/com.oracle.svm.configure.test/src/com/oracle/svm/configure/test/config/SecurityProviderAgentTest.java @@ -26,8 +26,17 @@ import static org.junit.Assume.assumeTrue; +import java.security.InvalidAlgorithmParameterException; +import java.security.InvalidKeyException; +import java.security.PrivateKey; import java.security.Provider; +import java.security.PublicKey; +import java.security.SecureRandom; import java.security.Security; +import java.security.spec.AlgorithmParameterSpec; + +import javax.crypto.KEM; +import javax.crypto.KEMSpi; import org.junit.Assert; import org.junit.Test; @@ -39,6 +48,7 @@ */ public class SecurityProviderAgentTest { private static final String GENERATOR_ENABLED_PROPERTY = SecurityProviderAgentTest.class.getName() + ".generator.enabled"; + private static final String KEM_ALGORITHM = "AgentKEM"; /** Tests §FS-security-providers.6.1. */ @Test @@ -67,6 +77,22 @@ public void programmaticProviderMutationDoesNotTraceConfiguredProviders() throws } } + /** Tests §FS-security-providers.6.1. */ + @Test + public void providerServiceHelpersRetainConstructorMetadata() throws Exception { + assumeTrue("Test must be explicitly enabled because it is designed to run under the agent", + Boolean.getBoolean(GENERATOR_ENABLED_PROPERTY)); + + Provider provider = new ProgrammaticallyAddedKEMProvider(); + int position = Security.addProvider(provider); + try { + Assert.assertTrue("The test provider must be added", position > 0); + Assert.assertNotNull(KEM.getInstance(KEM_ALGORITHM, provider)); + } finally { + Security.removeProvider(provider.getName()); + } + } + static final class ReflectiveProbe { } @@ -78,4 +104,28 @@ static final class ProgrammaticallyAddedProvider extends Provider { super("AgentMutationProvider", 1.0, "Provider used to verify mutation tracing"); } } + + static final class ProgrammaticallyAddedKEMProvider extends Provider { + private static final long serialVersionUID = 1L; + + @SuppressWarnings("deprecation") + ProgrammaticallyAddedKEMProvider() { + super("AgentKEMProvider", 1.0, "Provider used to verify service constructor tracing"); + put("KEM." + KEM_ALGORITHM, TestKEM.class.getName()); + } + } + + public static final class TestKEM implements KEMSpi { + @Override + public EncapsulatorSpi engineNewEncapsulator(PublicKey publicKey, AlgorithmParameterSpec spec, SecureRandom secureRandom) + throws InvalidAlgorithmParameterException, InvalidKeyException { + throw new UnsupportedOperationException(); + } + + @Override + public DecapsulatorSpi engineNewDecapsulator(PrivateKey privateKey, AlgorithmParameterSpec spec) + throws InvalidAlgorithmParameterException, InvalidKeyException { + throw new UnsupportedOperationException(); + } + } } diff --git a/substratevm/src/com.oracle.svm.configure.test/src/com/oracle/svm/configure/test/config/SecurityProviderAgentVerifierTest.java b/substratevm/src/com.oracle.svm.configure.test/src/com/oracle/svm/configure/test/config/SecurityProviderAgentVerifierTest.java index 69c444c2ed35..6f0db467690f 100644 --- a/substratevm/src/com.oracle.svm.configure.test/src/com/oracle/svm/configure/test/config/SecurityProviderAgentVerifierTest.java +++ b/substratevm/src/com.oracle.svm.configure.test/src/com/oracle/svm/configure/test/config/SecurityProviderAgentVerifierTest.java @@ -36,7 +36,10 @@ import com.oracle.svm.configure.NamedConfigurationTypeDescriptor; import com.oracle.svm.configure.UnresolvedAccessCondition; import com.oracle.svm.configure.config.ConfigurationFileCollection; +import com.oracle.svm.configure.config.ConfigurationMemberInfo; +import com.oracle.svm.configure.config.ConfigurationMethod; import com.oracle.svm.configure.config.ConfigurationSet; +import com.oracle.svm.configure.config.ConfigurationType; import com.oracle.svm.configure.config.TypeConfiguration; import com.oracle.svm.configure.test.AddExports; @@ -85,6 +88,21 @@ public void verifyMutationRecordedOnlySuppliedProvider() throws Exception { } } + @Test + public void verifyProviderServiceConstructorWasRecorded() throws Exception { + assumeTrue("Test must be explicitly enabled because it verifies a previous agent run", + Boolean.getBoolean(VERIFIER_ENABLED_PROPERTY)); + + TypeConfiguration reflectionConfiguration = loadActualConfig().getReflectionConfiguration(); + ConfigurationType kemType = reflectionConfiguration.get(UnresolvedAccessCondition.unconditional(), + NamedConfigurationTypeDescriptor.fromReflectionName(SecurityProviderAgentTest.TestKEM.class.getName())); + Assert.assertNotNull("Missing reflection metadata for the KEM service implementation", kemType); + ConfigurationMemberInfo constructorInfo = ConfigurationType.TestBackdoor.getMethodInfoIfPresent( + kemType, new ConfigurationMethod("", "()V")); + Assert.assertNotNull("Missing KEM service implementation constructor metadata", constructorInfo); + Assert.assertEquals("ACCESSED", constructorInfo.getAccessibility().toString()); + } + private static void assertRecorded(TypeConfiguration reflectionConfiguration, String className) { Assert.assertNotNull("Missing reflection metadata for " + className, reflectionConfiguration.get(UnresolvedAccessCondition.unconditional(), From 91b30ed3d0ce89778dfb16efb4a3b3f1c5ac870e Mon Sep 17 00:00:00 2001 From: Vojin Jovanovic Date: Tue, 28 Jul 2026 12:37:04 +0200 Subject: [PATCH 49/63] GR-69858: Fix security provider review findings --- .../native-image/BuildOptions.md | 2 +- .../native-image/JCASecurityServices.md | 10 +- substratevm/CHANGELOG.md | 3 +- substratevm/docs/architecture/README.md | 2 +- substratevm/docs/functional-spec/README.md | 1 + .../docs/functional-spec/decisions/README.md | 9 + ...complete-security-provider-registration.md | 325 ++++++++++++++++++ .../default-secure-random-provider.md | 65 ++++ ...chability-independent-runtime-semantics.md | 69 ++++ .../decisions/standard-jca-semantics.md | 69 ++++ .../functional-spec/security-providers.md | 23 +- substratevm/mx.substratevm/mx_substratevm.py | 1 - .../svm/agent/BreakpointInterceptor.java | 15 +- .../jdk/BuiltInSecurityProviderLoader.java | 65 +--- .../jdk/SecurityProviderRuntimeAccess.java | 25 +- .../jdk/SecurityProviderRuntimeState.java | 23 ++ .../svm/core/jdk/SecuritySubstitutions.java | 47 +-- .../SecuritySubstitutionRuntimeInit.java | 12 +- .../svm/hosted/NamingConventionVerifier.java | 6 + .../SecurityProviderCatalogRegistrar.java | 9 +- .../SecurityProviderRegistrationPlanner.java | 13 +- .../svm/hosted/SecurityServicesFeature.java | 103 +++--- .../security/SecurityProviderCatalog.java | 97 ++++++ .../reachability-metadata.json | 12 + ...untimeCompilationSecurityProviderTest.java | 68 ++++ ...rviceExplicitProviderRegistrationTest.java | 52 ++- ...urityServiceRuntimeInitializationTest.java | 154 +++++++++ .../test/services/SecurityServiceTest.java | 247 +++++-------- 28 files changed, 1182 insertions(+), 345 deletions(-) create mode 100644 substratevm/docs/functional-spec/decisions/README.md create mode 100644 substratevm/docs/functional-spec/decisions/complete-security-provider-registration.md create mode 100644 substratevm/docs/functional-spec/decisions/default-secure-random-provider.md create mode 100644 substratevm/docs/functional-spec/decisions/reachability-independent-runtime-semantics.md create mode 100644 substratevm/docs/functional-spec/decisions/standard-jca-semantics.md create mode 100644 substratevm/src/com.oracle.svm.shared/src/com/oracle/svm/shared/security/SecurityProviderCatalog.java create mode 100644 substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/services/RuntimeCompilationSecurityProviderTest.java create mode 100644 substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/services/SecurityServiceRuntimeInitializationTest.java diff --git a/docs/reference-manual/native-image/BuildOptions.md b/docs/reference-manual/native-image/BuildOptions.md index 85dd491740da..630bf06af072 100644 --- a/docs/reference-manual/native-image/BuildOptions.md +++ b/docs/reference-manual/native-image/BuildOptions.md @@ -51,7 +51,7 @@ These deprecated URL protocol options are omitted from the generated table; see | `--exact-reachability-metadata` | String | enables exact and user-friendly handling of reflection, resources, JNI, and serialization. | | `--exact-reachability-metadata=exact-reachability-metadata` | | `--exact-reachability-metadata-path` | String | trigger exact handling of reflection, resources, JNI, and serialization from all types in the given class-path or module-path entries. | None | `--exact-reachability-metadata-path=exact-reachability-metadata-path` | | `--features` | String | a comma-separated list of fully qualified Feature implementation classes | None | `--features=features` | -| `--future-defaults` | String | enable options that are planned to become defaults in future releases. Comma-separated list can contain 'all', 'none', 'run-time-initialize-jdk', 'class-for-name-respects-class-loader', 'run-time-initialize-file-system-providers', 'run-time-initialize-security-providers', 'run-time-initialize-resource-bundles', 'explicit-feature-singleton-registration', 'explicit-security-provider-registration'. The preferred usage is '--future-defaults=all'. | | `--future-defaults=future-defaults` | +| `--future-defaults` | String | enable options that are planned to become defaults in future releases. Comma-separated list can contain 'all', 'none', 'run-time-initialize-jdk', 'class-for-name-respects-class-loader', 'run-time-initialize-file-system-providers', 'run-time-initialize-security-providers', 'run-time-initialize-resource-bundles', 'explicit-feature-singleton-registration', 'explicit-security-provider-registration', 'exact-reflection'. The preferred usage is '--future-defaults=all'. | | `--future-defaults=future-defaults` | | `--initialize-at-build-time` | String | a comma-separated list of packages and classes (and implicitly all of their superclasses) that are initialized during image generation. An empty string designates all packages. | | `--initialize-at-build-time=initialize-at-build-time` | | `--initialize-at-run-time` | String | a comma-separated list of packages and classes (and implicitly all of their subclasses) that must be initialized at runtime and not during image building. An empty string is currently not supported. | | `--initialize-at-run-time=initialize-at-run-time` | | `--libc` | String | selects the libc implementation to use. Available implementations: glibc, musl, bionic | None | `--libc=libc` | diff --git a/docs/reference-manual/native-image/JCASecurityServices.md b/docs/reference-manual/native-image/JCASecurityServices.md index 440723a29b91..e1bfd8aa290b 100644 --- a/docs/reference-manual/native-image/JCASecurityServices.md +++ b/docs/reference-manual/native-image/JCASecurityServices.md @@ -98,10 +98,14 @@ acquisition also triggers registration of the complete configured-provider set t ## Custom Service Types -By default, only services specified in the JCA framework are automatically registered. To automatically register custom service types, you can use the `-H:AdditionalSecurityServiceTypes` option. -Note that for automatic registration to work, the service interface must have a `getInstance` method and have the same name as the service type. +By default, Native Image automatically detects only service types specified in the JCA framework. +The `-H:AdditionalSecurityServiceTypes` option is deprecated. +Register the provider class and its supported construction path in _reachability-metadata.json_ so +Native Image retains its complete service catalog, including custom service types. +Alternatively, collect this metadata with the Tracing Agent. +For compatibility with automatic service-driven registration, the service interface must have a +`getInstance` method and the same name as the service type. If you rely on third-party code that does not comply with these requirements, manual configuration is required. -Register the provider class for reflection in _reachability-metadata.json_ or collect the metadata with the Tracing Agent. ### Further Reading diff --git a/substratevm/CHANGELOG.md b/substratevm/CHANGELOG.md index e1484d298d6b..a48cf5ba933a 100644 --- a/substratevm/CHANGELOG.md +++ b/substratevm/CHANGELOG.md @@ -9,7 +9,8 @@ This changelog summarizes major changes to GraalVM Native Image. * (GR-77670) Chunk up digest generation for Native Image Layers, to allow for large layer files to be checked. This makes older layer files potentially incompabile with layers created after this change. * (GR-73199) When native executables are built with `-H:-LegacyJavaOptionMode`, VM options are parsed only before the first `--` argument. Arguments after `--` are passed unchanged to the application main method. The legacy behavior remains unchanged. * (GR-77977) Added control flow integrity options, available via `-H:CFI`. Indirect branches on AMD64 can be guarded with software-based checks that ensure that they land on valid targets. On AArch64, PAC is supported to protect return addresses on the stack. -* (GR-69858) Deprecated `-H:AdditionalSecurityProviders` and `-H:AdditionalSecurityServiceTypes`. Register each security provider class for reflection in `reachability-metadata.json` using `{"reflection":[{"type":""}]}` instead. The Tracing Agent generates this metadata automatically. +* (GR-69858) Deprecated `-H:AdditionalSecurityProviders` and `-H:AdditionalSecurityServiceTypes`. Register each security provider class for reflection in `reachability-metadata.json` using `{"reflection":[{"type":""}]}` instead. The Tracing Agent generates this metadata automatically. When provider initialization occurs at run time, Native Image preserves a reachable `META-INF/services/java.security.Provider` descriptor even if its provider is not registered; iterating that entry reports the standard service-loading or missing-reflection error. +* (GR-69858) Restored folded-method filtering in `SVMHost` so methods annotated with either `@Fold` or `@GuestFold` are excluded from the run-time universe. Speculative provider-candidate probing can silently reject hosted-named elements, while the final universe verifier continues to report reachable naming violations. ## GraalVM 25.2 (Internal Version 25.2.4) * (GR-77358) Introduced compressed (32-bit) references, enabled by default. This generally improves memory usage and performance, but limits heap memory to 32 GB. Disable with `-H:-UseCompressedReferences`. diff --git a/substratevm/docs/architecture/README.md b/substratevm/docs/architecture/README.md index 058dab662942..8b9c69ee40bf 100644 --- a/substratevm/docs/architecture/README.md +++ b/substratevm/docs/architecture/README.md @@ -3,4 +3,4 @@ This directory contains developer-facing architecture records for Native Image. - [Security Provider Architecture](security-providers.md): provider inclusion, verification, and - metadata tracing (§AR-security-providers). + metadata tracing ([§AR-security-providers](../../src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SecurityServicesFeature.java)). diff --git a/substratevm/docs/functional-spec/README.md b/substratevm/docs/functional-spec/README.md index 701db6c86409..4894f89b87e9 100644 --- a/substratevm/docs/functional-spec/README.md +++ b/substratevm/docs/functional-spec/README.md @@ -3,3 +3,4 @@ This directory contains functional specifications for Native Image. - [JCA Security Provider Inclusion](security-providers.md): provider lookup and factory-call behavior based on metadata. +- [Functional Decisions](decisions/): product behavior decisions and tradeoffs for the specifications. diff --git a/substratevm/docs/functional-spec/decisions/README.md b/substratevm/docs/functional-spec/decisions/README.md new file mode 100644 index 000000000000..c12a19a3a176 --- /dev/null +++ b/substratevm/docs/functional-spec/decisions/README.md @@ -0,0 +1,9 @@ +# Functional Decisions + +This directory contains product behavior decisions and tradeoffs for Native Image functional +specifications. + +- [Register Providers as Complete Service Units](complete-security-provider-registration.md) +- [Retain the Complete Default Provider for SecureRandom](default-secure-random-provider.md) +- [Keep Run-Time Semantics Independent of Reachability](reachability-independent-runtime-semantics.md) +- [Preserve Standard JCA Semantics Without Provider-Specific Flags](standard-jca-semantics.md) diff --git a/substratevm/docs/functional-spec/decisions/complete-security-provider-registration.md b/substratevm/docs/functional-spec/decisions/complete-security-provider-registration.md new file mode 100644 index 000000000000..936be41b3f43 --- /dev/null +++ b/substratevm/docs/functional-spec/decisions/complete-security-provider-registration.md @@ -0,0 +1,325 @@ +# DF-complete-security-provider-registration: Register Providers as Complete Service Units + +## 1. Context + +### 1.1 A Provider Is a Catalog Before It Is a Factory + +A Java Cryptography Architecture (JCA) provider is not merely a class that implements one +cryptographic operation. +It is a catalog of named capabilities. +When a provider is constructed, it publishes `Provider.Service` descriptors for message digests, +signatures, key stores, secure random number generators, and any other service types that it +supports. +Each descriptor names a service type, an algorithm, an implementation class, aliases, and +attributes such as supported key classes. + +The JDK exposes that catalog through several related APIs. +`Security.getProviders()` exposes the providers themselves. +`Provider.getService()` and `Provider.getServices()` expose their service descriptors. +`Security.getAlgorithms()` and the filtering overloads of `Security.getProviders()` derive +algorithm availability from those descriptors. +JCA factories such as `MessageDigest.getInstance()` and `Signature.getInstance()` search the same +catalog before constructing an engine. + +Service discovery and service construction are nevertheless different operations. +Looking up a `Provider.Service` is ordinarily a map lookup in the provider. +It does not reflectively inspect the service implementation. +Construction happens later, usually when `Provider.Service.newInstance()` loads the named +implementation class and invokes an applicable constructor. +A provider can also supply a specialized `Provider.Service` that overrides this construction path +and creates the implementation directly. + +This separation is unremarkable on the JVM because all provider implementation classes remain +available on the class path or module path. +It becomes important in Native Image because a closed-world build must decide which classes and +reflective constructors to retain. +A provider descriptor can survive in a native executable even when the implementation that it names +does not, unless Native Image deliberately keeps the two together. + +### 1.2 Two Levels of Dynamic Construction + +A JDK-managed provider can itself be constructed dynamically. +The JDK can read its class name from security properties, discover it through a service-provider +descriptor, or select it through an internal default path. +The provider therefore needs a supported reflective construction path before it can participate in +the run-time provider list. + +Once constructed, the provider introduces a second level of dynamic construction. +Its service implementation names come from the provider catalog rather than ordinary Java call +edges. +Native Image must retain the applicable implementation constructors and any auxiliary metadata +needed by the engine. +For example, some engines accept a constructor parameter, some services declare supported key +classes, and JKS and X.509 support require additional implementation metadata. + +The provider registration and service availability requirements are specified by +[§FS-security-providers.1.1](../security-providers.md#11-registered-providers-and-services) +and [§FS-security-providers.2.3](../security-providers.md#23-registration-effects). +The design question is where to place the application-controlled boundary between these two +levels. +Registering only the provider is compact but requires Native Image to complete its service catalog. +Requiring registration of the provider and every service appears more granular but creates a +partially registered object unless every discovery API applies exactly the same filter. + +### 1.3 Reflection Metadata Does Not Describe a JCA Service + +Ordinary reflection metadata identifies Java classes, constructors, methods, and fields. +It does not identify a `(provider, service type, algorithm)` tuple. +That distinction matters because a Java class is not always equivalent to one JCA service. + +Several algorithms can share one implementation class. +Aliases can name the same service through different algorithm strings. +A provider can use one service class with constructor parameters to produce several variants. +Conversely, a specialized `Provider.Service` can construct an implementation without using the +implementation constructor that ordinary reflection metadata would appear to register. + +Treating an implementation constructor as an individual-service signal would therefore be +imprecise. +Registering one constructor could enable several service entries, while failing to register a +constructor would not necessarily disable a service whose provider overrides `newInstance()`. +A separate service identity and a run-time allowlist would still be needed to define exactly what +the executable exposes. + +## 2. Decision + +### 2.1 The Provider Is the Registration Unit + +Native Image treats a registered provider as one complete service-registration unit. +Qualifying reflection metadata for the provider is the application-controlled registration signal. +The application does not enumerate reflection metadata for every implementation class named by that +provider. +This registration boundary follows [§DF-standard-jca-semantics.2](standard-jca-semantics.md#2-decision). + +At build time, Native Image constructs the registered provider and reads the provider's own catalog. +It retains every valid service whose implementation class it can resolve. +For each retained service, Native Image registers the reflective construction metadata and +auxiliary metadata required to use that implementation. +The provider remains the authority for its service types, algorithms, aliases, attributes, and +implementation mappings. + +The same rule applies when Native Image registers a provider through a platform rule rather than +application metadata. +Such a rule changes who supplies the provider-registration signal, but it does not create a +different kind of provider or a smaller service catalog. + +### 2.2 One Catalog Must Answer Every Availability Question + +At run time, provider discovery and service acquisition observe one consistent catalog. +A service reported as available can be selected and constructed. +An omitted provider cannot leak service names into algorithm discovery, and an included provider +does not advertise a supported, resolvable service whose construction metadata was intentionally +discarded. + +This invariant applies whether an application reaches a service through a named JCA factory, a +factory overload that accepts a `Provider` object, direct `Provider.Service` access, provider +enumeration, or a JDK default path. +It also avoids making success depend on whether the same service was reached through a public engine +factory or an internal JDK facade. + +### 2.3 Reachability Does Not Subdivide an Explicitly Registered Provider + +Reachability still determines whether application code and JDK paths are present in an executable. +It does not, however, redefine the contents of a provider after that provider has been explicitly +registered. +A run-time algorithm string can select any service that belongs to the registered provider without +requiring Native Image to predict that string at build time. + +Reachability of a JCA factory can continue to drive service-type registration only under the +compatibility behavior in +[§FS-security-providers.7.3](../security-providers.md#73-earlier-service-driven-inclusion-behavior). +When explicit provider registration is enabled, reaching a service factory does not register an +otherwise unregistered provider and does not select a subset of an already registered provider. + +## 3. Why Completeness Is the Stable Boundary + +### 3.1 Registration Must Mean More Than Provider Construction + +Registering only the provider constructor would answer one narrow question: whether the JDK can +create the provider object. +It would not answer whether that object tells the truth about its capabilities. +The object would still populate its normal service registry, and all discovery APIs would see that +registry before any service implementation constructor was invoked. + +Deferring the real availability check until `Provider.Service.newInstance()` would move failures +away from the acquisition boundary. +An application could discover an algorithm, select its provider, retrieve its service descriptor, +and only then receive a missing-reflection failure. +The same algorithm might instead produce `NoSuchAlgorithmException` through another entry point. +The result would depend on how far a particular JDK path progressed before it encountered the +missing constructor, rather than on one definition of service availability. + +The specification rejects this partial exposure in +[§FS-security-providers.4.1](../security-providers.md#41-unregistered-providers). +Completing the provider at build time makes provider registration a useful promise instead of merely +permission to allocate the outer object. + +### 3.2 The Provider Already Owns the Necessary Information + +The provider catalog is the most accurate source of service information. +It contains provider-specific services, aliases, attributes, and implementation mappings that a +generic Native Image configuration format should not duplicate. +Reading this catalog at build time also allows Native Image to apply existing service-specific +handling in one place. + +This approach keeps reflection metadata stable when a provider changes an internal implementation +class without changing its public algorithms. +Users register the provider that they intend to make available. +They do not need to know which internal class implements SHA-256 in a particular JDK update or which +constructor a third-party provider uses for a cipher SPI. + +### 3.3 The Cost Is Paid at an Intentional Boundary + +Complete registration can retain services that a particular execution never selects. +That is a real size cost, but it is attached to an explicit choice to include the provider. +An executable that does not register or otherwise require the provider does not pay that cost. + +The alternative is not cost-free precision. +Precise partial providers require a new service identity, filtering rules, run-time checks, and +compatibility behavior for every route through the JCA. +The resulting implementation would be larger and harder to reason about even when the retained +application code became somewhat smaller. + +Provider completeness therefore chooses a coarse but stable boundary. +It accepts a measurable inclusion cost in exchange for a single availability model and ordinary JCA +behavior after the executable is built. + +## 4. Rejected Alternatives + +### 4.1 Keep Every Descriptor and Fail During Construction + +One possible two-stage model would register the provider first and treat reflection metadata for +each implementation constructor as a second permission. +The full provider catalog would remain visible, but `Provider.Service.newInstance()` would fail when +the selected implementation lacked metadata. + +This model needs few Native Image substitutions because the existing reflection machinery can +report the missing constructor. +Its simplicity is deceptive. +`Provider.getService()` would return a descriptor that cannot provide a service. +`Provider.getServices()` and `Security.getAlgorithms()` would advertise unavailable algorithms. +Provider filtering could select a provider that later fails to instantiate its advertised +implementation. + +The failure would also expose Native Image mechanics through APIs that normally describe JCA +availability. +Exact reachability metadata could produce a missing-registration error, while a different factory +path could translate the same underlying absence into a standard JCA exception. +This alternative was rejected because it preserves the catalog structurally but breaks its meaning. + +### 4.2 Filter Services Whose Implementations Lack Metadata + +A stricter two-stage model would hide every service whose implementation constructor was not +registered. +The provider could remain in the provider list while exposing a reduced, internally consistent +catalog. +This is the strongest alternative in principle, but it requires Native Image to own a second service +registry beside the provider's registry. + +The filter would have to cover `Provider.getService()`, `Provider.getServices()`, +`Security.getAlgorithms()`, both forms of `Security.getProviders()` filtering, all JCA factories, +security-service facades, aliases, default and fallback paths, and direct service construction. +It would have to handle providers initialized at build time and providers initialized at run time. +It would also have to define what happens when application code constructs a provider, calls +`Security.addProvider()`, or supplies that provider object directly to a factory. + +Filtering only the public lookup methods would not be enough. +Some JDK paths use specialized defaults or provider-specific service construction. +A provider can override service behavior, and a custom provider can mutate its catalog after +construction. +Keeping these paths consistent would require a canonical run-time allowlist and broad interception +of JDK and provider behavior. + +The metadata signal would remain ambiguous even after that work. +An implementation class shared by several algorithms does not say which service entries to expose. +Constructor metadata does not describe aliases or a specialized `newInstance()` override. +This alternative was rejected because ordinary reflection metadata cannot precisely specify its +policy, and enforcing the policy would add substantial run-time machinery. + +### 4.3 Retain Only Reachable Service Types + +The earlier compatibility behavior registers implementations by reachable engine type. +For example, reachability of `MessageDigest.getInstance()` can retain message digest services +without retaining key store services. +This is an effective closed-world optimization when factory reachability is the intended inclusion +signal. + +It is not a complete definition of an explicitly registered provider. +Applications can call `Provider.getService()` with a run-time service type and algorithm. +They can enumerate services and select one according to configuration that Native Image cannot +evaluate at build time. +JDK facades and defaults can also acquire services without following the public engine-factory +shape. + +Using the shared methods `Provider.getService()`, `Provider.getServices()`, or +`Provider.Service.newInstance()` as conservative triggers does not restore useful precision. +Nearly every JCA factory eventually reaches `Provider.getService()` and +`Provider.Service.newInstance()`. +The first reachable engine would therefore retain every service of every included provider. +The optimization would distinguish an executable with no security use from one with some security +use, but it would no longer distinguish message digests from key stores or signatures. + +This alternative remains appropriate as transition compatibility behavior. +It was rejected as the semantics of explicit provider registration because it makes the provider's +contents depend on incidental call-graph shapes and does not cover dynamic service access. + +### 4.4 Enumerate Every Service Implementation in Reflection Metadata + +Another model would keep ordinary reflection metadata but require a provider entry followed by +entries for every service implementation constructor. +A complete provider registration would then be a long expansion of provider internals. + +This format would be verbose and fragile. +Provider upgrades could replace implementation classes or constructors without changing any public +algorithm. +Metadata copied between JDK versions could silently produce a partial provider. +Third-party provider users would need to inspect internal service mappings that the provider already +publishes programmatically. + +It would also fail to provide true per-service precision because multiple service entries can share +one implementation class. +The metadata would describe Java implementation reachability, not the service catalog that users +intend to expose. +This alternative was rejected because it transfers provider-internal maintenance to application +configuration without establishing a coherent service identity. + +### 4.5 Introduce Security-Provider-Specific Metadata + +A dedicated metadata category could directly name a provider and either select all services or list +service type and algorithm pairs. +Unlike raw reflection metadata, such a format could express the intended JCA-level identity. + +The format would still duplicate information already owned by the provider. +It would need rules for aliases, provider version changes, unknown custom service types, shared +implementations, specialized service construction, and services added programmatically. +Selective entries would still require the run-time filtering model described in section 4.2. +The Tracing Agent and missing-registration diagnostics would also need a new metadata vocabulary. + +A new metadata category is justified only if selective provider catalogs become a product feature +rather than an implementation optimization. +No size evidence currently establishes that need. +This alternative was rejected for the present design because provider reflection metadata already +supplies a stable registration signal and complete registration needs no second run-time catalog. + +## 5. Consequences + +Provider reflection metadata remains concise and independent of provider implementation details. +A supported provider constructor or `provider()` method is enough to request the provider. +Native Image derives service implementation metadata from the provider's authoritative catalog. + +Registering a provider can increase executable size because Native Image retains every resolvable +valid service and its construction metadata. +The size effect can be larger for providers that implement many unrelated engines. +This cost is visible and attributable to provider registration rather than to incidental +reachability of a shared JCA helper method. + +Users receive a simpler failure model. +An unregistered provider is unavailable. +A registered provider exposes a complete supported catalog. +Factory selection, provider enumeration, service enumeration, and direct service access do not +disagree because Native Image pruned an implementation behind an advertised descriptor. + +This decision favors consistent JCA behavior over service-level pruning. +Service-level pruning can be reconsidered if measurements show a material executable-size +regression. +Any future design must define a stable service identity and preserve one consistent catalog across +all provider discovery and service acquisition paths. diff --git a/substratevm/docs/functional-spec/decisions/default-secure-random-provider.md b/substratevm/docs/functional-spec/decisions/default-secure-random-provider.md new file mode 100644 index 000000000000..fb03daeb9432 --- /dev/null +++ b/substratevm/docs/functional-spec/decisions/default-secure-random-provider.md @@ -0,0 +1,65 @@ +# DF-default-secure-random-provider: Retain the Complete Default Provider for SecureRandom + +## 1. Context + +`SecureRandom` constructors and factories select JDK-managed providers. +Requiring application reflection metadata for those providers would make a commonly used JDK +facility fail for reasons that expose provider implementation details. + +Native Image also uses `SecureRandom` to seed runtime-compilation hardening. Registering that +internal random source in every executable makes `SecureRandom` appear application-reachable and +retains the complete SUN provider even in an otherwise empty executable. + +Provider registration is intentionally complete: exposing a provider while omitting services that +the provider advertises would create inconsistent discovery and factory results. +This constraint is specified by +[§FS-security-providers.1.1](../security-providers.md#11-registered-providers-and-services) +and [§FS-security-providers.4.1](../security-providers.md#41-unregistered-providers). + +## 2. Decision + +When a `SecureRandom` acquisition path is reachable, Native Image registers every configured +provider that declares a `SecureRandom` service. +The acquisition path is a platform-owned conditional provider-registration signal, so the +application does not need to supply reflection metadata for those implicit JDK dependencies. +Each provider is registered completely, so provider discovery and service acquisition remain +consistent. + +Native Image registers its internal secure runtime-randomness singleton only in executables that +include runtime compilation, which is the only subsystem that consumes that singleton. Ordinary +executables therefore do not retain SUN merely because Native Image has an optional internal +hardening mechanism. + +This is not service-driven inclusion. +The platform supplies the registration signal, and the ordinary complete-provider semantics apply +after registration. +Reachability of other JCA factories does not register their providers when explicit +security-provider registration is enabled. +This bounded registration condition follows +[§DF-reachability-independent-runtime-semantics.2](reachability-independent-runtime-semantics.md#2-decision). + +## 3. Rejected Alternatives + +Conditionally registering SUN whenever the `SecureRandom` type is reachable was rejected because +the Native Image runtime can make the type reachable independently of application use. It would +make the condition effectively unconditional and impose the complete provider cost on ordinary +executables. + +Retaining only the default `SecureRandom` implementation and its SHA dependency was rejected +because the resulting SUN object could advertise omitted services. Correctly supporting partial +providers would require one canonical filtered service registry across every provider-discovery and +factory API. + +Replacing internal `SecureRandom` with a non-cryptographic generator was rejected because the +generator seeds runtime constant blinding and code-offset randomization. + +## 4. Consequences + +Applications can use `SecureRandom` constructors and factories under explicit provider +registration without provider-specific metadata. +Such applications, and runtime-compilation images that use the internal secure random source, pay +the full size cost of the configured providers that declare `SecureRandom` services. +Ordinary executables that do not acquire `SecureRandom` avoid that cost. + +Provider registration remains all-or-nothing, so users do not observe algorithms that are named by +SUN but absent from the executable. diff --git a/substratevm/docs/functional-spec/decisions/reachability-independent-runtime-semantics.md b/substratevm/docs/functional-spec/decisions/reachability-independent-runtime-semantics.md new file mode 100644 index 000000000000..219db2f800d8 --- /dev/null +++ b/substratevm/docs/functional-spec/decisions/reachability-independent-runtime-semantics.md @@ -0,0 +1,69 @@ +# DF-reachability-independent-runtime-semantics: Keep Run-Time Semantics Independent of Reachability + +## 1. Context + +Native Image uses reachability analysis to determine which program elements an executable +contains. +Applications can nevertheless make choices from run-time inputs that static analysis cannot +predict. +These choices include class names, resource names, service implementations, serialization types, +security providers, and algorithm names. + +Reachability is an implementation property of the closed-world build. +If incidental call-graph reachability changes the meaning of retained operations, two applications +with the same configuration and run-time inputs can observe different Java behavior. +Adding an unused call could make a dynamic value valid, expose an additional catalog entry, or +change which implementation an existing operation selects. +Such differences would expose the mechanics of closed-world analysis as run-time semantics. + +## 2. Decision + +Given the same build configuration, registered metadata, and run-time inputs, Native Image +preserves the same observable behavior for retained operations regardless of incidental +reachability during image construction. +Reachability determines which code and explicitly conditional components enter the closed world. +It does not act as a proxy for run-time values or silently redefine the behavior of components that +the build has retained and configured. + +An explicit specification can define reachability as a platform-owned inclusion condition when the +platform itself introduces an otherwise implicit dependency. +Such a condition decides whether to include a complete component. +It does not permit reachability to select an undocumented subset of that component or alter its +behavior after inclusion. + +For security providers, reachability of a factory method, service type, algorithm constant, facade, +or fallback path does not select a subset of an explicitly registered provider's services. +It also does not register an otherwise unregistered provider, except where +[§FS-security-providers.2.4](../security-providers.md#24-securerandom-providers) defines the bounded +`SecureRandom` inclusion rule. +The earlier service-driven behavior in +[§FS-security-providers.7.3](../security-providers.md#73-earlier-service-driven-inclusion-behavior) +remains a documented transition compatibility mode, not the planned run-time semantics. + +## 3. Rejected Alternatives + +Treating every reachable operation as evidence for all dynamic values it might consume was rejected +because reachability does not identify the value selected at run time. +It can retain unrelated implementations while still omitting values supplied through external +configuration. + +Pruning an explicitly registered component according to the callers visible during analysis was +rejected because dynamic entry points do not follow one statically recognizable call shape. +The resulting component would expose a build-dependent partial interface. + +Evaluating constant arguments as an implicit permission boundary was rejected because it would make +a constant and the same value read from run-time configuration behave differently. + +## 4. Consequences + +Dynamic behavior still requires the metadata or platform registration specified for that +mechanism. +Missing registration remains a defined closed-world boundary. +After the build includes a component, incidental reachability does not create a second, +less-visible permission boundary inside it. + +This rule can retain implementations with no statically visible callers when an explicitly +registered component exposes them dynamically. +That size cost gives registration one stable meaning and prevents behavior from changing when an +application restructures equivalent calls or moves a value from source code to run-time +configuration. diff --git a/substratevm/docs/functional-spec/decisions/standard-jca-semantics.md b/substratevm/docs/functional-spec/decisions/standard-jca-semantics.md new file mode 100644 index 000000000000..31391ef9f3a0 --- /dev/null +++ b/substratevm/docs/functional-spec/decisions/standard-jca-semantics.md @@ -0,0 +1,69 @@ +# DF-standard-jca-semantics: Preserve Standard JCA Semantics Without Provider-Specific Flags + +## 1. Context + +The Java Cryptography Architecture (JCA) discovers and constructs security providers dynamically. +The JDK can read provider class names from security properties, discover providers through service +descriptors, and select providers through standard factory and fallback paths. +On the JVM, these operations can load any available class without an advance declaration. + +Native Image uses closed-world analysis and must retain every class and reflective operation that +can occur at run time. +This requirement creates an inclusion boundary, but it does not require a second, +security-provider-specific configuration model. +Ordinary reflection metadata already describes access to provider types, constructors, and +qualifying `provider()` methods. + +Provider-specific command-line options would expose Native Image implementation details in the +application's security configuration. +They would also make otherwise standard JCA behavior depend on how the native executable was built +rather than on the application's Java configuration. +The registration requirements and standard run-time behavior are specified by +[§FS-security-providers.1](../security-providers.md#1-provider-reflection-registration) +and [§FS-security-providers.3](../security-providers.md#3-permitted-run-time-access). + +## 2. Decision + +Native Image uses ordinary reflection metadata as the application-controlled registration signal +for a security provider. +When the platform must preserve an implicit JDK dependency, it supplies an equivalent registration +signal instead of requiring the application to identify the provider implementation. + +After registration, Native Image preserves the observable behavior of the standard JCA APIs for +the supported provider operations. +Applications select and configure providers through standard Java APIs, security properties, and +service descriptors. +They do not need a provider-specific Native Image command-line option. + +Options that select compatibility behavior during the transition to the planned defaults do not +become permanent provider-registration requirements. +Tracing and missing-registration diagnostics use ordinary reachability metadata rather than a +security-provider-specific metadata category. + +## 3. Rejected Alternatives + +A Native Image option that names every enabled provider was rejected because reflection metadata +already expresses the required dynamic class access. +Such an option would create a second registration mechanism and make standard Java configuration +insufficient. + +Unconditionally retaining every provider was rejected because it would increase executable size +and include provider implementations that the application did not request. + +Inferring all provider access from static reachability was rejected because provider names, +algorithms, and service selections can arrive only at run time. +Static analysis cannot reliably reconstruct the application's dynamic JCA configuration. + +## 4. Consequences + +Applications use one general Native Image mechanism for dynamic Java access. +Security-provider support does not require a permanent provider-specific enablement option or +metadata format. + +An application can still need reflection metadata when it requests a provider that Native Image +cannot infer from an implicit platform dependency. +This is the ordinary closed-world registration boundary, not a change to JCA run-time semantics. + +Once registered, a provider participates through the standard JCA APIs. +Missing registration produces the ordinary reachability-metadata diagnostic defined by the +functional specification. diff --git a/substratevm/docs/functional-spec/security-providers.md b/substratevm/docs/functional-spec/security-providers.md index 66af76449e22..95da628dae1b 100644 --- a/substratevm/docs/functional-spec/security-providers.md +++ b/substratevm/docs/functional-spec/security-providers.md @@ -108,6 +108,8 @@ Type access, declared nullary constructor access, and qualifying `provider()` me alternative registration signals. Registering any one of them is sufficient to register the provider class. Type access alone does not make a provider JDK-constructible. +Using ordinary reflection metadata as this registration signal implements +[§DF-standard-jca-semantics.2](decisions/standard-jca-semantics.md#2-decision). ### 2.2 Provider Construction @@ -129,7 +131,8 @@ Registering a provider class that is not JDK-constructible does not include its used through an application-supplied instance must be retained independently. Provider registration does not change the configured provider order or make an unconfigured provider visible by name. -This behavior implements §DF-complete-security-provider-registration.2. +This behavior implements +[§DF-complete-security-provider-registration.2](decisions/complete-security-provider-registration.md#2-decision). ### 2.4 SecureRandom Providers @@ -153,8 +156,8 @@ Native Image internal runtime randomness must cause this registration only in an includes the runtime-compilation subsystem that consumes that randomness. The presence of the optional internal randomness implementation must not register SUN in an ordinary executable. - -This behavior implements §DF-default-secure-random-provider.2. +This behavior implements +[§DF-default-secure-random-provider.2](decisions/default-secure-random-provider.md#2-decision). ## 3. Permitted Run-Time Access @@ -376,6 +379,13 @@ with run-time initialization, and explicit registration with run-time initializa With `--future-defaults=run-time-initialize-security-providers`, Native Image constructs the run-time provider list from the configured security properties using only registered providers. +When a configured entry names a provider that the JDK resolves through `ServiceLoader`, Native +Image applies the registration decision to the resolved provider class rather than treating the +provider name as a class name. +At run time, Native Image loads that resolved provider directly through its registered public +nullary construction path. +It does not scan or instantiate unrelated provider descriptors while resolving the configured +name. An unregistered provider is not added to the list, and its services remain unavailable. Filtering unregistered providers preserves the ordering and lookup results specified in sections 1.3, 3.2, and 4. @@ -399,10 +409,15 @@ when their provider classes have no reflection metadata. In this compatibility mode, pre-existing reflection metadata for a provider remains inert unless another compatibility registration signal includes that provider; Native Image must not construct the provider or expand its complete service catalog merely because its type is registered. +Type-only metadata collected for an application-supplied provider still establishes the class-based +JCE verification outcome specified in section 5.3. This verification-only effect must not cause +Native Image to construct the provider, register a construction path, or expand its service catalog. This compatibility behavior applies to supported facades such as the Generic Security Services API (GSS-API). For example, reachability of any `Signature.getInstance` overload can cause signature services and their providers to be included. +When this service-driven behavior includes a provider, it preserves the earlier compatibility +registration of every public provider constructor. The rule is based on reachability of the service factory and service type, not on build-time evaluation of the run-time algorithm argument. @@ -413,6 +428,8 @@ a lookup that reflectively loads the provider follows section 4.3. The platform-owned `SecureRandom` registration signal in section 2.4 remains enabled. It registers complete providers for this commonly used JDK facility rather than inferring partial provider support from a general service factory. +This planned behavior implements +[§DF-reachability-independent-runtime-semantics.2](decisions/reachability-independent-runtime-semantics.md#2-decision). ### 7.4 Earlier Build-Time Initialization Behavior diff --git a/substratevm/mx.substratevm/mx_substratevm.py b/substratevm/mx.substratevm/mx_substratevm.py index b20a5cede2d2..fef5975f5e35 100644 --- a/substratevm/mx.substratevm/mx_substratevm.py +++ b/substratevm/mx.substratevm/mx_substratevm.py @@ -820,7 +820,6 @@ def write_micronaut_style_service_entries(cp_entry, service_name, implementation '--add-exports=org.graalvm.nativeimage.builder/com.oracle.svm.core.libjvm=ALL-UNNAMED', '--add-exports=org.graalvm.nativeimage.builder/com.oracle.svm.core.properties=ALL-UNNAMED', '--add-opens=org.graalvm.nativeimage.builder/com.oracle.svm.core.jdk=ALL-UNNAMED', - '-H:AdditionalSecurityServiceTypes=com.oracle.svm.test.services.SecurityServiceTest$JCACompliantNoOpService', ]) if extra_build_args is not None: additional_build_args += extra_build_args diff --git a/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/BreakpointInterceptor.java b/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/BreakpointInterceptor.java index 8766e0701d08..ecc3111c1a5e 100644 --- a/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/BreakpointInterceptor.java +++ b/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/BreakpointInterceptor.java @@ -106,6 +106,7 @@ import com.oracle.svm.jvmtiagentbase.jvmti.JvmtiEventMode; import com.oracle.svm.jvmtiagentbase.jvmti.JvmtiFrameInfo; import com.oracle.svm.jvmtiagentbase.jvmti.JvmtiLocationFormat; +import com.oracle.svm.shared.security.SecurityProviderCatalog; import com.oracle.svm.shared.util.VMError; import jdk.graal.compiler.core.common.NumUtil; @@ -753,7 +754,9 @@ private static boolean getSecurityServiceForProvider(JNIEnvironment jni, JNIObje * and cache the service while recursive tracing is suppressed, preventing the real call * from recording its implementation metadata and resource accesses. */ - traceSecurityProvider(jni, provider, provider.notEqual(nullHandle()), state.getDirectCallerClass(), state); + JNIObjectHandle callerClass = findExternalSecurityCaller(jni, state, 1); + traceSecurityProvider(jni, provider, provider.notEqual(nullHandle()), + callerClass.notEqual(nullHandle()) ? callerClass : state.getDirectCallerClass(), state); return true; } @@ -913,15 +916,7 @@ private static void traceJdkConstructibleSecurityProvider(JNIEnvironment jni, JN if (providerClassName == null) { return; } - boolean hasNullaryConstruction = switch (providerClassName) { - case "sun.security.provider.Sun", - "sun.security.rsa.SunRsaSign", - "sun.security.ec.SunEC", - "sun.security.ssl.SunJSSE", - "com.sun.crypto.provider.SunJCE", - "apple.security.AppleProvider" -> true; - default -> false; - }; + boolean hasNullaryConstruction = SecurityProviderCatalog.isDirectlyConstructible(providerClassName); if (hasNullaryConstruction) { /* * Record the implicit no-argument construction only after ProviderConfig exposed a diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/BuiltInSecurityProviderLoader.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/BuiltInSecurityProviderLoader.java index eb9852137cbd..15c91c79470f 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/BuiltInSecurityProviderLoader.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/BuiltInSecurityProviderLoader.java @@ -24,9 +24,11 @@ */ package com.oracle.svm.core.jdk; +import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.security.Provider; +import com.oracle.svm.shared.security.SecurityProviderCatalog; import com.oracle.svm.shared.util.VMError; import jdk.graal.compiler.api.directives.GraalDirectives; @@ -37,66 +39,15 @@ private BuiltInSecurityProviderLoader() { } public static String getProviderName(String providerNameOrClassName) { - String providerClassName = getProviderClassName(providerNameOrClassName); - if (providerClassName == null) { - return null; - } - return switch (providerClassName) { - case "sun.security.provider.Sun" -> "SUN"; - case "sun.security.rsa.SunRsaSign" -> "SunRsaSign"; - case "com.sun.crypto.provider.SunJCE" -> "SunJCE"; - case "sun.security.ssl.SunJSSE" -> "SunJSSE"; - case "sun.security.ec.SunEC" -> "SunEC"; - case "sun.security.jgss.SunProvider" -> "SunJGSS"; - case "com.sun.security.sasl.Provider" -> "SunSASL"; - case "org.jcp.xml.dsig.internal.dom.XMLDSigRI" -> "XMLDSig"; - case "sun.security.smartcardio.SunPCSC" -> "SunPCSC"; - case "sun.security.provider.certpath.ldap.JdkLDAP" -> "JdkLDAP"; - case "com.sun.security.sasl.gsskerb.JdkSASL" -> "JdkSASL"; - case "sun.security.pkcs11.SunPKCS11" -> "SunPKCS11"; - case "sun.security.mscapi.SunMSCAPI" -> "SunMSCAPI"; - case "com.oracle.security.ucrypto.UcryptoProvider" -> "OracleUcrypto"; - case "apple.security.AppleProvider" -> "Apple"; - default -> null; - }; + return SecurityProviderCatalog.getProviderName(providerNameOrClassName); } public static String getProviderClassName(String providerNameOrClassName) { - return switch (providerNameOrClassName) { - case "SUN", "sun.security.provider.Sun" -> "sun.security.provider.Sun"; - case "SunRsaSign", "sun.security.rsa.SunRsaSign" -> "sun.security.rsa.SunRsaSign"; - case "SunJCE", "com.sun.crypto.provider.SunJCE" -> "com.sun.crypto.provider.SunJCE"; - case "SunJSSE", "sun.security.ssl.SunJSSE" -> "sun.security.ssl.SunJSSE"; - case "SunEC", "sun.security.ec.SunEC" -> "sun.security.ec.SunEC"; - case "SunJGSS", "sun.security.jgss.SunProvider" -> "sun.security.jgss.SunProvider"; - case "SunSASL", "com.sun.security.sasl.Provider" -> "com.sun.security.sasl.Provider"; - case "XMLDSig", "org.jcp.xml.dsig.internal.dom.XMLDSigRI" -> "org.jcp.xml.dsig.internal.dom.XMLDSigRI"; - case "SunPCSC", "sun.security.smartcardio.SunPCSC" -> "sun.security.smartcardio.SunPCSC"; - case "JdkLDAP", "sun.security.provider.certpath.ldap.JdkLDAP" -> "sun.security.provider.certpath.ldap.JdkLDAP"; - case "JdkSASL", "com.sun.security.sasl.gsskerb.JdkSASL" -> "com.sun.security.sasl.gsskerb.JdkSASL"; - case "SunPKCS11", "sun.security.pkcs11.SunPKCS11" -> "sun.security.pkcs11.SunPKCS11"; - case "SunMSCAPI", "sun.security.mscapi.SunMSCAPI" -> "sun.security.mscapi.SunMSCAPI"; - case "OracleUcrypto", "com.oracle.security.ucrypto.UcryptoProvider" -> "com.oracle.security.ucrypto.UcryptoProvider"; - case "Apple", "apple.security.AppleProvider" -> "apple.security.AppleProvider"; - default -> null; - }; + return SecurityProviderCatalog.getProviderClassName(providerNameOrClassName); } public static boolean isBuiltIn(String providerNameOrClassName) { - String providerClassName = getProviderClassName(providerNameOrClassName); - if (providerClassName == null) { - return false; - } - return switch (providerClassName) { - case "sun.security.provider.Sun", - "sun.security.rsa.SunRsaSign", - "com.sun.crypto.provider.SunJCE", - "sun.security.ssl.SunJSSE", - "sun.security.ec.SunEC", - "apple.security.AppleProvider" -> - true; - default -> false; - }; + return SecurityProviderCatalog.isDirectlyConstructible(providerNameOrClassName); } /** §FS-security-providers.3.1, §FS-security-providers.4.3, and §FS-security-providers.7.1. */ @@ -122,10 +73,12 @@ public static Provider load(String providerNameOrClassName, Debug debug) { } private static Provider allocateSunECProvider() { + Constructor constructor = SecurityProviderRuntimeState.getSunECConstructor(); + VMError.guarantee(constructor != null, "The SunEC constructor is not present."); try { - return (Provider) SecurityProviderRuntimeState.getSunECConstructor().newInstance(); + return (Provider) constructor.newInstance(); } catch (InstantiationException | IllegalAccessException | InvocationTargetException e) { - throw VMError.shouldNotReachHere("The SunEC constructor is not present."); + throw VMError.shouldNotReachHere("The SunEC provider cannot be constructed.", e); } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SecurityProviderRuntimeAccess.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SecurityProviderRuntimeAccess.java index c98009d0b4c4..f38a55451f5c 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SecurityProviderRuntimeAccess.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SecurityProviderRuntimeAccess.java @@ -27,6 +27,7 @@ import java.security.Provider; import java.util.function.Supplier; +import com.oracle.svm.core.FutureDefaultsOptions; import com.oracle.svm.core.metadata.MetadataTracer; import com.oracle.svm.shared.NeverInline; @@ -56,18 +57,38 @@ public static boolean shouldLoadUnregisteredConfiguredProvider() { return Boolean.TRUE.equals(LOAD_UNREGISTERED_CONFIGURED_PROVIDER.get()); } + /** §FS-security-providers.7.1: Load an already-resolved ServiceLoader provider directly. */ + public static Provider loadRegisteredConfiguredProvider(String providerName, String providerClassName) { + try { + Class providerClass = Class.forName(providerClassName, true, ClassLoader.getSystemClassLoader()); + Provider candidate = providerClass.asSubclass(Provider.class).getConstructor().newInstance(); + return providerName.equals(candidate.getName()) ? candidate : null; + } catch (ReflectiveOperationException | SecurityException ex) { + return null; + } + } + /** §FS-security-providers.4.3: Cache misses probe type access for standard diagnostics. */ @NeverInline("Keep the provider class name unknown to static analysis without an opaque compiler node.") public static void reportMissingRegistration(Class providerClass) { + String remediation = missingRegistrationRemediation(); try { Class.forName(providerClass.getName(), false, providerClass.getClassLoader()); } catch (ClassNotFoundException ex) { throw new SecurityException( "Attempted to use a security provider that was not registered for reflection at build time: " + providerClass.getName() + ". " + - "Add the provider type to reachability-metadata.json and rebuild the image.", + remediation, ex); } - throw new SecurityException("Attempted to use a security provider without build-time verification: " + providerClass.getName()); + throw new SecurityException("Attempted to use a security provider without build-time verification: " + providerClass.getName() + ". " + remediation); + } + + private static String missingRegistrationRemediation() { + if (FutureDefaultsOptions.explicitSecurityProviderRegistration()) { + return "Register the provider type or a supported construction path in reachability-metadata.json and rebuild the image."; + } + return "Provider reflection metadata does not enable provider construction or services in compatibility mode. " + + "Add qualifying metadata and rebuild with --future-defaults=explicit-security-provider-registration."; } /** §FS-security-providers.6.1: Existing provider instances trace type access. */ diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SecurityProviderRuntimeState.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SecurityProviderRuntimeState.java index 68276938ba00..ac8fc38044fc 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SecurityProviderRuntimeState.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SecurityProviderRuntimeState.java @@ -51,6 +51,7 @@ public record ProviderInfo(AcquisitionKind acquisitionKind, Exception verificati } private final EconomicMap providerInfos = ImageHeapMap.createNonLayeredMap(); + private final EconomicMap configuredProviderClassNames = ImageHeapMap.createNonLayeredMap(); private Properties savedInitialSecurityProperties; private Constructor sunECConstructor; @@ -84,6 +85,14 @@ private synchronized void registerProvider(String providerClassName, Acquisition providerInfos.put(providerClassName, merge(providerInfos.get(providerClassName), new ProviderInfo(acquisitionKind, verificationFailure))); } + @Platforms(Platform.HOSTED_ONLY.class) + public synchronized void registerConfiguredProviderName(String providerName, String providerClassName) { + String previousClassName = configuredProviderClassNames.get(providerName); + assert previousClassName == null || previousClassName.equals(providerClassName) : providerName + + " maps to both " + previousClassName + " and " + providerClassName; + configuredProviderClassNames.put(providerName, providerClassName); + } + private static ProviderInfo merge(ProviderInfo oldInfo, ProviderInfo newInfo) { if (oldInfo == null || newInfo == null) { return oldInfo != null ? oldInfo : newInfo; @@ -113,6 +122,20 @@ public static boolean isJdkConstructible(String providerClassName) { return info != null && info.acquisitionKind() == AcquisitionKind.JDK_CONSTRUCTIBLE; } + public static String getConfiguredProviderClassName(String providerName) { + String result = null; + for (SecurityProviderRuntimeState state : singletons()) { + String providerClassName = state.configuredProviderClassNames.get(providerName); + if (providerClassName != null) { + if (result != null && !result.equals(providerClassName)) { + return null; + } + result = providerClassName; + } + } + return result; + } + @Platforms(Platform.HOSTED_ONLY.class) public void setSunECConstructor(Constructor constructor) { sunECConstructor = constructor; diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SecuritySubstitutions.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SecuritySubstitutions.java index e2b84651103d..d3fc156bb576 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SecuritySubstitutions.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SecuritySubstitutions.java @@ -24,7 +24,6 @@ */ package com.oracle.svm.core.jdk; -import java.lang.ref.ReferenceQueue; import java.lang.reflect.Constructor; import java.net.URL; import java.security.CodeSource; @@ -61,8 +60,8 @@ import sun.security.util.SecurityConstants; +// §FS-security-providers.3.1 and §FS-security-providers.4.1: // Reject an unregistered SUN provider before the JDK SecureRandom fallback can expose it. -// §FS-security-providers.3.1 and §FS-security-providers.4.1 @TargetClass(className = "sun.security.jca.Providers", onlyWith = ExplicitSecurityProviderRegistration.class) final class Target_sun_security_jca_Providers_ExplicitRegistration { @Substitute @@ -241,30 +240,14 @@ private static void setJavaHome(String newJavaHome) { } /** - * The {@code javax.crypto.JceSecurity#verificationResults} cache is initialized by the - * SecurityServicesFeature at build time, for all registered providers. The cache is used by - * {@code javax.crypto.JceSecurity#canUseProvider} at run time to check whether a provider is - * properly signed and can be used by JCE. It does that via jar verification which we cannot - * support. + * JCE jar verification cannot run in the image. SecurityServicesFeature records build-time + * verification outcomes by provider class in SecurityProviderRuntimeState and clears the JDK's + * provider-instance-keyed weak cache. */ @TargetClass(className = "javax.crypto.JceSecurity", onlyWith = SecurityProvidersInitializedAtBuildTime.class) @BasedOnJDKFile("https://github.com/graalvm/labs-openjdk/blob/jdk-24+27/src/java.base/share/classes/javax/crypto/JceSecurity.java.template") @SuppressWarnings({"unused"}) final class Target_javax_crypto_JceSecurity { - - // Checkstyle: stop - @Alias // - private static Object PROVIDER_VERIFIED; - // Checkstyle: resume - - /* - * Map of providers that have already been verified. A value of PROVIDER_VERIFIED - * indicates successful verification. Otherwise, the value is the Exception that caused the - * verification to fail. - */ - @Alias // - private static Map verificationResults; - @Alias // @RecomputeFieldValue(kind = RecomputeFieldValue.Kind.Reset) // private static Map verifyingProviders; @@ -273,34 +256,12 @@ final class Target_javax_crypto_JceSecurity { @RecomputeFieldValue(kind = RecomputeFieldValue.Kind.FromAlias) // private static Map, URL> codeBaseCacheRef = new WeakHashMap<>(); - @Alias // - @TargetElement // - private static ReferenceQueue queue; - @Substitute static Exception getVerificationResult(Provider p) { - /* The verification results map key is an identity wrapper object. */ - Object key = new Target_javax_crypto_JceSecurity_WeakIdentityWrapper(p, queue); - Object o = verificationResults.get(key); - if (o == PROVIDER_VERIFIED) { - SecurityProviderRuntimeAccess.traceLookup(p); - return null; - } else if (o != null) { - return (Exception) o; - } return JceProviderVerificationSupport.getVerificationResult(p); } } -@TargetClass(className = "javax.crypto.JceSecurity", innerClass = "WeakIdentityWrapper", onlyWith = SecurityProvidersInitializedAtBuildTime.class) -@SuppressWarnings({"unused"}) -final class Target_javax_crypto_JceSecurity_WeakIdentityWrapper { - - @Alias // - Target_javax_crypto_JceSecurity_WeakIdentityWrapper(Provider obj, ReferenceQueue queue) { - } -} - /** * JDK 8 has the class `javax.crypto.JarVerifier`, but in JDK 11 and later that class is only * available in Oracle builds, and not in OpenJDK builds. diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/runtimeinit/SecuritySubstitutionRuntimeInit.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/runtimeinit/SecuritySubstitutionRuntimeInit.java index c803a55d44bf..d613adaad158 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/runtimeinit/SecuritySubstitutionRuntimeInit.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/runtimeinit/SecuritySubstitutionRuntimeInit.java @@ -140,9 +140,11 @@ Provider getProvider() { if (provider != null) { return provider; } + String configuredProviderClassName = SecurityProviderRuntimeState.getConfiguredProviderClassName(provName); String builtInProviderClassName = BuiltInSecurityProviderLoader.getProviderClassName(provName); - String providerClassName = builtInProviderClassName != null ? builtInProviderClassName : provName; - /* Omit unregistered providers from the run-time list. §FS-security-providers.7.1 */ + String providerClassName = configuredProviderClassName != null ? configuredProviderClassName + : builtInProviderClassName != null ? builtInProviderClassName : provName; + /* §FS-security-providers.7.1: Omit unregistered providers from the run-time list. */ if (!SecurityProviderRuntimeAccess.shouldLoadUnregisteredConfiguredProvider() && !SecurityProviderRuntimeState.isJdkConstructible(providerClassName)) { return null; @@ -153,6 +155,11 @@ Provider getProvider() { // Create providers which are in java.base directly if (BuiltInSecurityProviderLoader.isBuiltIn(provName)) { provider = BuiltInSecurityProviderLoader.load(provName, debug); + } else if (configuredProviderClassName != null) { + /* §FS-security-providers.7.1: + * Load the catalog-resolved provider directly, avoiding a ServiceLoader scan that + * could touch unrelated omitted descriptors. */ + provider = SecurityProviderRuntimeAccess.loadRegisteredConfiguredProvider(provName, configuredProviderClassName); } else { if (isLoading) { /* @@ -177,6 +184,7 @@ Provider getProvider() { } return provider; } + } @TargetClass(className = "sun.security.jca.ProviderList", onlyWith = SecurityProvidersInitializedAtRunTime.class) diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/NamingConventionVerifier.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/NamingConventionVerifier.java index b65418f8fe13..2c124a326feb 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/NamingConventionVerifier.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/NamingConventionVerifier.java @@ -111,6 +111,12 @@ static boolean isNameAllowed(ResolvedJavaType type) { return namingConventionsViolation(type.toJavaName(true)) == null; } + /* + * Layered-image candidate probing uses the predicates above to reject an unsupported candidate + * before analysis creates an element for it. checkUniverse remains the verification backstop: + * any forbidden element that becomes reachable through another path still fails the build. + */ + private static void checkName(BigBang bb, AnalysisMethod method, String name) { String message = namingConventionsViolation(name); if (message != null) { diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SecurityProviderCatalogRegistrar.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SecurityProviderCatalogRegistrar.java index 61c55be98bec..1e6b0f4ef3fd 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SecurityProviderCatalogRegistrar.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SecurityProviderCatalogRegistrar.java @@ -50,7 +50,7 @@ interface Host { void registerService(DuringAnalysisAccess access, Service service); - void registerSelectedConstructionPath(Class providerClass); + void registerSelectedConstructionPath(DuringAnalysisAccess access, Class providerClass); Object getProviderVerificationResult(Provider provider); } @@ -77,8 +77,8 @@ void includeProviderClass(DuringAnalysisAccess access, Class providerClass) { if (providers == null) { providers = List.of(host.instantiateProvider(providerClass)); } + // §FS-security-providers.2.3: // Register every configured instance and the union of their service metadata. - // §FS-security-providers.2.3 for (Provider provider : providers) { registerProvider(access, provider); for (Service service : provider.getServices()) { @@ -93,13 +93,14 @@ void registerProvider(DuringAnalysisAccess access, Provider provider) { if (usedProviders.add(provider)) { RuntimeReflection.register(provider.getClass()); if (host.isLoadableProviderClass(access, provider.getClass())) { - host.registerSelectedConstructionPath(provider.getClass()); + host.registerSelectedConstructionPath(access, provider.getClass()); } /* Trigger initialization of lazy field java.security.Provider.entrySet. */ provider.entrySet(); String providerClassName = provider.getClass().getName(); Object verificationResult = host.getProviderVerificationResult(provider); SecurityProviderRuntimeState state = SecurityProviderRuntimeState.currentLayer(); + state.registerConfiguredProviderName(provider.getName(), providerClassName); if (host.isLoadableProviderClass(access, provider.getClass())) { state.registerJdkConstructibleProvider(providerClassName, verificationResult); } else { @@ -108,7 +109,7 @@ void registerProvider(DuringAnalysisAccess access, Provider provider) { } } - private void registerApplicationSuppliedProviderClass(Class providerClass) { + void registerApplicationSuppliedProviderClass(Class providerClass) { // §FS-security-providers.5.3: Preserve verification without reconstructing the provider. List buildTimeProviders = buildTimeProvidersByClassName.get(providerClass.getName()); Object verificationResult = buildTimeProviders == null ? Boolean.TRUE : host.getProviderVerificationResult(buildTimeProviders.getFirst()); diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SecurityProviderRegistrationPlanner.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SecurityProviderRegistrationPlanner.java index 538f805dd8d2..76e0cb7a2e9d 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SecurityProviderRegistrationPlanner.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SecurityProviderRegistrationPlanner.java @@ -36,6 +36,7 @@ final class SecurityProviderRegistrationPlanner { enum Source { APPLICATION_METADATA, + APPLICATION_VERIFICATION_METADATA, PRESERVE, SECURE_RANDOM_PLATFORM, LEGACY_ADDITIONAL_PROVIDER, @@ -44,6 +45,7 @@ enum Source { private final Set> candidates = ConcurrentHashMap.newKeySet(); private final Set> completed = ConcurrentHashMap.newKeySet(); + private final Set> verificationCompleted = ConcurrentHashMap.newKeySet(); private final Set> completePlans = ConcurrentHashMap.newKeySet(); private final Set> legacyGeneratedReflection = ConcurrentHashMap.newKeySet(); private final ConcurrentHashMap, Set> sources = new ConcurrentHashMap<>(); @@ -72,14 +74,21 @@ void beforeLegacyReflectionRegistration(Class providerClass) { legacyGeneratedReflection.add(providerClass); } - boolean processNewCompleteProviders(Function, Source> signalSource, Consumer> includeProvider) { + boolean processNewProviders(Function, Source> signalSource, Consumer> includeProvider, Consumer> registerVerification) { boolean discoveredCandidate = changed.getAndSet(false); boolean processed = false; for (Class providerClass : candidates) { Source signal = !legacyGeneratedReflection.contains(providerClass) ? signalSource.apply(providerClass) : null; if (signal != null) { recordSource(providerClass, signal); - completePlans.add(providerClass); + if (signal == Source.APPLICATION_VERIFICATION_METADATA) { + if (verificationCompleted.add(providerClass)) { + registerVerification.accept(providerClass); + processed = true; + } + } else { + completePlans.add(providerClass); + } } if (completePlans.contains(providerClass) && completed.add(providerClass)) { includeProvider.accept(providerClass); diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SecurityServicesFeature.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SecurityServicesFeature.java index b4fc4d824d63..199b635d6558 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SecurityServicesFeature.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SecurityServicesFeature.java @@ -33,7 +33,6 @@ import java.io.FileWriter; import java.io.IOException; import java.io.PrintWriter; -import java.lang.ref.WeakReference; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; @@ -119,6 +118,7 @@ import com.oracle.svm.util.OriginalMethodProvider; import com.oracle.svm.util.TypeResult; import com.oracle.svm.util.dynamicaccess.JVMCIRuntimeJNIAccess; +import com.oracle.svm.util.dynamicaccess.JVMCIRuntimeReflection; import jdk.graal.compiler.debug.Assertions; import jdk.graal.compiler.options.Option; @@ -224,12 +224,13 @@ public class SecurityServicesFeature extends JNIRegistrationUtil implements InternalFeature { public static class Options { - private static final String ADDITIONAL_SECURITY_SERVICE_TYPES_DEPRECATION_HELP = "Deprecated. Register the security provider class for reflection instead."; + private static final String ADDITIONAL_SECURITY_SERVICE_TYPES_DEPRECATION_HELP = "Deprecated. Register the providers that implement the custom service type for reflection instead."; private static final String ADDITIONAL_SECURITY_PROVIDERS_DEPRECATION_HELP = "Deprecated. Security providers are now detected automatically (use the tracing agent, register the provider class " + "for reflection, or build with -H:Preserve=all)."; private static final String ADDITIONAL_SECURITY_PROVIDERS_DEPRECATION_MESSAGE = "Register each security provider class for reflection in reachability-metadata.json using " + "{\"reflection\":[{\"type\":\"\"}]}; the Tracing Agent generates this metadata automatically."; - private static final String ADDITIONAL_SECURITY_SERVICE_TYPES_DEPRECATION_MESSAGE = ADDITIONAL_SECURITY_PROVIDERS_DEPRECATION_MESSAGE; + private static final String ADDITIONAL_SECURITY_SERVICE_TYPES_DEPRECATION_MESSAGE = "Register each provider that implements the custom service type and its supported construction path " + + "in reachability-metadata.json, or collect the metadata with the Tracing Agent."; @Option(help = "Enable automatic registration of security services.")// public static final HostedOptionKey EnableSecurityServicesFeature = new HostedOptionKey<>(true); @@ -318,7 +319,7 @@ public static class Options { private Field classCacheField; private Field constructorCacheField; - private ConcurrentHashMap, Object> cachedVerificationCache; + private ConcurrentHashMap cachedVerificationCache; private ProviderList cachedProviders; private Class jceSecurityClass; @@ -357,8 +358,16 @@ public void registerService(DuringAnalysisAccess access, Service service) { } @Override - public void registerSelectedConstructionPath(Class providerClass) { - SecurityServicesFeature.registerSelectedConstructionPath(providerClass); + public void registerSelectedConstructionPath(DuringAnalysisAccess access, Class providerClass) { + if (mode.explicitRegistration()) { + SecurityServicesFeature.registerSelectedConstructionPath(providerClass); + } else { + /* §FS-security-providers.7.3: + * Legacy inclusion registers every public constructor; explicit mode selects + * one JDK construction path. */ + ResolvedJavaType providerType = ((DuringAnalysisAccessImpl) access).getMetaAccess().lookupJavaType(providerClass); + JVMCIRuntimeReflection.register(JVMCIReflectionUtil.getConstructors(providerType)); + } } @Override @@ -565,8 +574,8 @@ public boolean isAvailable() { public Object transform(Object receiver, Object originalValue) { if (cachedVerificationCache != null) { if (SubstrateUtil.assertionsEnabled()) { - var filteredCache = filterVerificationCache(originalValue); - assert cachedVerificationCache.equals(filteredCache) : Assertions.errorMessage(cachedVerificationCache, filteredCache); + var emptyCache = emptyVerificationCache(); + assert cachedVerificationCache.equals(emptyCache) : Assertions.errorMessage(cachedVerificationCache, emptyCache); } } /* @@ -579,25 +588,13 @@ public Object transform(Object receiver, Object originalValue) { } } - @SuppressWarnings("unchecked") - private ConcurrentHashMap, Object> filterVerificationCache(Object originalValue) { - /* - * The verification cache is an WeakIdentityWrapper -> Verification result - * ConcurrentHashMap. We do not care about the private WeakIdentityWrapper class, it extends - * WeakReference and so using WeakReference.get() is sufficient for us. - */ - var cleanedCache = new ConcurrentHashMap<>((ConcurrentHashMap, Object>) originalValue); - cleanedCache.keySet().removeIf(key -> shouldRemoveVerificationResult(key.get())); - return cleanedCache; - } - - private boolean shouldRemoveVerificationResult(Provider provider) { + private static ConcurrentHashMap emptyVerificationCache() { /* - * Verification results for used providers are copied into SecurityProviderRuntimeState, - * keyed by provider class name. Keep them out of JceSecurity.verificationResults so the weak - * cache keys do not keep build-time provider objects reachable in the image heap. + * Verification outcomes used by the image live in SecurityProviderRuntimeState, keyed by + * provider class. The JDK's weak, provider-instance-keyed cache must not retain build-time + * provider objects in the image heap. */ - return provider == null || catalogRegistrar.isUsed(provider) || shouldRemoveProvider(provider); + return new ConcurrentHashMap<>(); } private List filterProviderList(Object originalValue) { @@ -789,9 +786,14 @@ private void initializeServiceRegistrationData() { availableServices = computeAvailableServices(); buildTimeProvidersByClassName.clear(); for (Provider provider : Security.getProviders()) { - buildTimeProvidersByClassName.computeIfAbsent(provider.getClass().getName(), _ -> new ArrayList<>()).add(provider); + String providerClassName = provider.getClass().getName(); + buildTimeProvidersByClassName.computeIfAbsent(providerClassName, _ -> new ArrayList<>()).add(provider); + /* §FS-security-providers.7.1: + * Resolve configured names independently because a token may be a provider name or a + * ServiceLoader descriptor. */ + SecurityProviderRuntimeState.currentLayer().registerConfiguredProviderName(provider.getName(), providerClassName); // Configured providers can predate the subtype handler. - // Reflection registration still controls inclusion. §FS-security-providers.2.1 + // §FS-security-providers.2.1: Reflection registration still controls inclusion. addCandidateProviderClass(provider.getClass()); } } @@ -800,8 +802,8 @@ private void registerServiceProviderCandidates(BeforeAnalysisAccess access) { BeforeAnalysisAccessImpl accessImpl = (BeforeAnalysisAccessImpl) access; accessImpl.imageClassLoader.classLoaderSupport.serviceProvidersForEach((serviceName, providers) -> { if (serviceName.equals(Provider.class.getName())) { + // §FS-security-providers.7.2: // Descriptors discover candidates without registering them. - // §FS-security-providers.7.2 for (String provider : providers) { Class providerClass = access.findClassByName(provider); if (providerClass != null) { @@ -854,8 +856,8 @@ private void registerServices(DuringAnalysisAccess access, Object trigger, Strin if (mode.explicitRegistration() && serviceType.equals(SECURE_RANDOM_SERVICE)) { registerSecureRandomProvidersFromPlatformSignal(); } + // §FS-security-providers.7.3: // Service reachability is a compatibility inclusion signal. - // §FS-security-providers.7.3 doRegisterServices(access, trigger, serviceType); } } @@ -1014,8 +1016,8 @@ private Object getProviderVerificationResult(Provider provider) { } } + // §FS-security-providers.2.2 and §FS-security-providers.2.3: // Use the preferred construction path and retain the complete valid, resolvable catalog. - // §FS-security-providers.2.2 and §FS-security-providers.2.3 private boolean isLoadableProviderClass(DuringAnalysisAccess access, Class providerClass) { if (providerClass == null || providerClass.isArray() || providerClass.isPrimitive() || Modifier.isAbstract(providerClass.getModifiers())) { return false; @@ -1085,13 +1087,21 @@ private void registerService(DuringAnalysisAccess a, Service service) { } } + // §FS-security-providers.1.1 and §FS-security-providers.2.1: // Recognize every qualifying reflection-registration signal. - // §FS-security-providers.1.1 and §FS-security-providers.2.1 private boolean isProviderRegisteredForReflection(Class providerClass) { try { if (reflectionRegistrationView.hasTypeAccess(providerClass)) { return true; } + return isProviderConstructionRegisteredForReflection(providerClass); + } catch (UnsupportedPlatformException | DeletedElementException e) { + return false; + } + } + + private boolean isProviderConstructionRegisteredForReflection(Class providerClass) { + try { Constructor constructor = findDeclaredNullaryConstructor(providerClass); if (constructor != null && reflectionRegistrationView.hasExecutableAccess(constructor)) { return true; @@ -1103,19 +1113,21 @@ private boolean isProviderRegisteredForReflection(Class providerClass) { } } - private SecurityProviderRegistrationPlanner.Source completeProviderSource(DuringAnalysisAccess access, Class providerClass) { + private SecurityProviderRegistrationPlanner.Source providerRegistrationSource(Class providerClass) { if (!isProviderRegisteredForReflection(providerClass)) { return null; } if (preserveAll) { return SecurityProviderRegistrationPlanner.Source.PRESERVE; } - // Compatibility mode keeps constructible-provider metadata inert. Application providers - // still need class-based JCE verification. §FS-security-providers.5.3, - // §FS-security-providers.7.3 - if (mode.explicitRegistration() || !isLoadableProviderClass(access, providerClass)) { + if (mode.explicitRegistration()) { return SecurityProviderRegistrationPlanner.Source.APPLICATION_METADATA; } + // §FS-security-providers.7.3: Compatibility type-only metadata identifies application + // providers. It preserves JCE verification without construction or service expansion. + if (reflectionRegistrationView.hasTypeAccess(providerClass) && !isProviderConstructionRegisteredForReflection(providerClass)) { + return SecurityProviderRegistrationPlanner.Source.APPLICATION_VERIFICATION_METADATA; + } return null; } @@ -1217,9 +1229,10 @@ private void registerX509Extensions(DuringAnalysisAccess a) { public void duringAnalysis(DuringAnalysisAccess a) { DuringAnalysisAccessImpl access = (DuringAnalysisAccessImpl) a; // Consume concurrent plans in the serialized feature pass. - if (providerPlanner.processNewCompleteProviders( - providerClass -> completeProviderSource(access, providerClass), - providerClass -> catalogRegistrar.includeProviderClass(access, providerClass))) { + if (providerPlanner.processNewProviders( + this::providerRegistrationSource, + providerClass -> catalogRegistrar.includeProviderClass(access, providerClass), + catalogRegistrar::registerApplicationSuppliedProviderClass)) { // Request the extra pass here, not from the concurrent reachability callback. access.requireAnalysisIteration(); } @@ -1240,14 +1253,10 @@ public void duringAnalysis(DuringAnalysisAccess a) { private void maybeScanVerificationResultsField(DuringAnalysisAccessImpl access) { if (access.getMetaAccess().lookupJavaField(verificationResultsField).isRead()) { - try { - var filteredVerificationCache = filterVerificationCache(verificationResultsField.get(null)); - if (cachedVerificationCache == null || !cachedVerificationCache.equals(filteredVerificationCache)) { - cachedVerificationCache = filteredVerificationCache; - access.rescanObject(cachedVerificationCache, scanReason); - } - } catch (IllegalAccessException ex) { - throw VMError.shouldNotReachHere("Cannot access field: " + verificationResultsField.getName(), ex); + var emptyCache = emptyVerificationCache(); + if (cachedVerificationCache == null || !cachedVerificationCache.equals(emptyCache)) { + cachedVerificationCache = emptyCache; + access.rescanObject(cachedVerificationCache, scanReason); } } } diff --git a/substratevm/src/com.oracle.svm.shared/src/com/oracle/svm/shared/security/SecurityProviderCatalog.java b/substratevm/src/com.oracle.svm.shared/src/com/oracle/svm/shared/security/SecurityProviderCatalog.java new file mode 100644 index 000000000000..f8622a19a7a9 --- /dev/null +++ b/substratevm/src/com.oracle.svm.shared/src/com/oracle/svm/shared/security/SecurityProviderCatalog.java @@ -0,0 +1,97 @@ +/* + * Copyright (c) 2026, 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.shared.security; + +/** + * Provider names and classes that require shared handling in Native Image runtime support and the + * Tracing Agent. + */ +public final class SecurityProviderCatalog { + private SecurityProviderCatalog() { + } + + public static String getProviderName(String providerNameOrClassName) { + String providerClassName = getProviderClassName(providerNameOrClassName); + if (providerClassName == null) { + return null; + } + return switch (providerClassName) { + case "sun.security.provider.Sun" -> "SUN"; + case "sun.security.rsa.SunRsaSign" -> "SunRsaSign"; + case "com.sun.crypto.provider.SunJCE" -> "SunJCE"; + case "sun.security.ssl.SunJSSE" -> "SunJSSE"; + case "sun.security.ec.SunEC" -> "SunEC"; + case "sun.security.jgss.SunProvider" -> "SunJGSS"; + case "com.sun.security.sasl.Provider" -> "SunSASL"; + case "org.jcp.xml.dsig.internal.dom.XMLDSigRI" -> "XMLDSig"; + case "sun.security.smartcardio.SunPCSC" -> "SunPCSC"; + case "sun.security.provider.certpath.ldap.JdkLDAP" -> "JdkLDAP"; + case "com.sun.security.sasl.gsskerb.JdkSASL" -> "JdkSASL"; + case "sun.security.pkcs11.SunPKCS11" -> "SunPKCS11"; + case "sun.security.mscapi.SunMSCAPI" -> "SunMSCAPI"; + case "com.oracle.security.ucrypto.UcryptoProvider" -> "OracleUcrypto"; + case "apple.security.AppleProvider" -> "Apple"; + default -> null; + }; + } + + public static String getProviderClassName(String providerNameOrClassName) { + return switch (providerNameOrClassName) { + case "SUN", "sun.security.provider.Sun" -> "sun.security.provider.Sun"; + case "SunRsaSign", "sun.security.rsa.SunRsaSign" -> "sun.security.rsa.SunRsaSign"; + case "SunJCE", "com.sun.crypto.provider.SunJCE" -> "com.sun.crypto.provider.SunJCE"; + case "SunJSSE", "sun.security.ssl.SunJSSE" -> "sun.security.ssl.SunJSSE"; + case "SunEC", "sun.security.ec.SunEC" -> "sun.security.ec.SunEC"; + case "SunJGSS", "sun.security.jgss.SunProvider" -> "sun.security.jgss.SunProvider"; + case "SunSASL", "com.sun.security.sasl.Provider" -> "com.sun.security.sasl.Provider"; + case "XMLDSig", "org.jcp.xml.dsig.internal.dom.XMLDSigRI" -> "org.jcp.xml.dsig.internal.dom.XMLDSigRI"; + case "SunPCSC", "sun.security.smartcardio.SunPCSC" -> "sun.security.smartcardio.SunPCSC"; + case "JdkLDAP", "sun.security.provider.certpath.ldap.JdkLDAP" -> "sun.security.provider.certpath.ldap.JdkLDAP"; + case "JdkSASL", "com.sun.security.sasl.gsskerb.JdkSASL" -> "com.sun.security.sasl.gsskerb.JdkSASL"; + case "SunPKCS11", "sun.security.pkcs11.SunPKCS11" -> "sun.security.pkcs11.SunPKCS11"; + case "SunMSCAPI", "sun.security.mscapi.SunMSCAPI" -> "sun.security.mscapi.SunMSCAPI"; + case "OracleUcrypto", "com.oracle.security.ucrypto.UcryptoProvider" -> "com.oracle.security.ucrypto.UcryptoProvider"; + case "Apple", "apple.security.AppleProvider" -> "apple.security.AppleProvider"; + default -> null; + }; + } + + public static boolean isDirectlyConstructible(String providerNameOrClassName) { + String providerClassName = getProviderClassName(providerNameOrClassName); + if (providerClassName == null) { + return false; + } + return switch (providerClassName) { + case "sun.security.provider.Sun", + "sun.security.rsa.SunRsaSign", + "com.sun.crypto.provider.SunJCE", + "sun.security.ssl.SunJSSE", + "sun.security.ec.SunEC", + "apple.security.AppleProvider" -> + true; + default -> false; + }; + } +} diff --git a/substratevm/src/com.oracle.svm.test/src/META-INF/native-image/com.oracle.svm.test/reachability-metadata.json b/substratevm/src/com.oracle.svm.test/src/META-INF/native-image/com.oracle.svm.test/reachability-metadata.json index d3e56a8692ce..0afdf59c7921 100644 --- a/substratevm/src/com.oracle.svm.test/src/META-INF/native-image/com.oracle.svm.test/reachability-metadata.json +++ b/substratevm/src/com.oracle.svm.test/src/META-INF/native-image/com.oracle.svm.test/reachability-metadata.json @@ -12,6 +12,15 @@ { "type": "com.oracle.svm.test.services.SecurityServiceTest$TypeMetadataProvider" }, + { + "type": "com.oracle.svm.test.services.SecurityServiceTest$ReflectionMetadataMacSpi", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, { "type": "com.oracle.svm.test.services.SecurityServiceTest$NoOpProvider", "methods": [ @@ -20,6 +29,9 @@ "parameterTypes": [] } ] + }, + { + "type": "com.oracle.svm.test.services.SecurityServiceTest$FailedVerificationProvider" } ] } diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/services/RuntimeCompilationSecurityProviderTest.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/services/RuntimeCompilationSecurityProviderTest.java new file mode 100644 index 000000000000..4a54dd701bae --- /dev/null +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/services/RuntimeCompilationSecurityProviderTest.java @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2026, 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.test.services; + +import java.security.Provider; +import java.security.Security; +import java.util.List; + +import org.graalvm.nativeimage.hosted.Feature; +import org.junit.Assert; +import org.junit.Test; + +import com.oracle.svm.test.NativeImageBuildArgs; + +@NativeImageBuildArgs({ + "--future-defaults=explicit-security-provider-registration", + "--features=com.oracle.svm.test.services.RuntimeCompilationSecurityProviderTest$EnableRuntimeCompilationFeature" +}) +public class RuntimeCompilationSecurityProviderTest { + public static final class EnableRuntimeCompilationFeature implements Feature { + @Override + public List> getRequiredFeatures() { + return List.of(runtimeCompilationFeature()); + } + + @SuppressWarnings("unchecked") + private static Class runtimeCompilationFeature() { + try { + return (Class) Class.forName( + "com.oracle.svm.graal.hosted.runtimecompilation.RuntimeCompilationFeature"); + } catch (ClassNotFoundException e) { + throw new AssertionError("Runtime compilation feature is unavailable", e); + } + } + } + + /** §FS-security-providers.2.4: Tests the internal-runtime-randomness branch. */ + @Test + public void testRuntimeCompilationRandomnessRegistersSunProvider() { + Provider provider = Security.getProvider("SUN"); + Assert.assertNotNull("Runtime compilation randomness must retain its SecureRandom provider.", provider); + Assert.assertEquals("sun.security.provider.Sun", provider.getClass().getName()); + Assert.assertNotNull("Complete provider registration must retain unrelated services.", + provider.getService("MessageDigest", "SHA-256")); + } +} diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/services/SecurityServiceExplicitProviderRegistrationTest.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/services/SecurityServiceExplicitProviderRegistrationTest.java index 8e88edc56baa..4b064dbd6044 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/services/SecurityServiceExplicitProviderRegistrationTest.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/services/SecurityServiceExplicitProviderRegistrationTest.java @@ -31,6 +31,8 @@ import java.security.Security; import java.security.Signature; +import javax.crypto.Mac; + import org.graalvm.nativeimage.ImageInfo; import org.junit.Assert; import org.junit.Assume; @@ -47,14 +49,14 @@ public class SecurityServiceExplicitProviderRegistrationTest { private static final String REGISTERED_PROVIDER_NAME = "reflection-metadata-provider"; - /** Tests §FS-security-providers.7.1. */ + /** §FS-security-providers.7.1: Tests explicit registration with run-time initialization. */ @Test public void testExplicitRegistrationEnablesRuntimeProviderInitialization() { Assert.assertTrue(FutureDefaultsOptions.explicitSecurityProviderRegistration()); Assert.assertTrue(FutureDefaultsOptions.securityProvidersInitializedAtRunTime()); } - /** Tests §FS-security-providers.2.3 and §FS-security-providers.2.4. */ + /** §FS-security-providers.2.3 and §FS-security-providers.2.4: Tests default SecureRandom registration. */ @Test public void testDefaultSecureRandomIncludesCompleteSunProvider() throws NoSuchAlgorithmException { SecureRandom random = new SecureRandom(); @@ -68,7 +70,7 @@ public void testDefaultSecureRandomIncludesCompleteSunProvider() throws NoSuchAl Assert.assertNotNull("An unrelated advertised service must remain usable.", jksService.newInstance(null)); } - /** Tests §FS-security-providers.4.2 and §FS-security-providers.7.3. */ + /** §FS-security-providers.4.2 and §FS-security-providers.7.3: Tests omitted providers. */ @Test public void testReachableFactoryDoesNotIncludeUnregisteredProvider() { Assert.assertNull("A reachable Signature factory must not include SunEC.", Security.getProvider("SunEC")); @@ -80,7 +82,38 @@ public void testReachableFactoryDoesNotIncludeUnregisteredProvider() { } } - /** Tests §FS-security-providers.5.3. */ + // §FS-security-providers.2.1, §FS-security-providers.2.3, and + // §FS-security-providers.5.1: Tests registration, complete services, and provider-object calls. + @Test + public void testReflectionMetadataProviderRegistration() throws Exception { + Provider provider = new SecurityServiceTest.ReflectionMetadataProvider(); + int position = Security.addProvider(provider); + try { + Assert.assertTrue(position > 0); + SecurityServiceTest.JCACompliantNoOpService service = SecurityServiceTest.JCACompliantNoOpService.getInstance("reflection-metadata-algo"); + Assert.assertEquals(SecurityServiceTest.ReflectionMetadataNoOpServiceImpl.class, service.getClass()); + Assert.assertNotNull(Mac.getInstance("reflection-metadata-mac", provider)); + } finally { + Security.removeProvider(REGISTERED_PROVIDER_NAME); + } + } + + // §FS-security-providers.2.1, §FS-security-providers.2.3, and + // §FS-security-providers.5.1: Tests type-only registration, services, and provider-object calls. + @Test + public void testTypeMetadataProviderRegistration() throws Exception { + Provider provider = new SecurityServiceTest.TypeMetadataProvider(); + int position = Security.addProvider(provider); + try { + Assert.assertTrue(position > 0); + SecurityServiceTest.JCACompliantNoOpService service = SecurityServiceTest.JCACompliantNoOpService.getInstance("type-metadata-algo"); + Assert.assertEquals(SecurityServiceTest.TypeMetadataNoOpServiceImpl.class, service.getClass()); + } finally { + Security.removeProvider("type-metadata-provider"); + } + } + + /** §FS-security-providers.5.3: Tests class-based verification identity. */ @Test public void testUnregisteredProviderCannotReuseVerificationByName() { Assume.assumeTrue("native image runtime only", ImageInfo.inImageRuntimeCode()); @@ -90,6 +123,17 @@ public void testUnregisteredProviderCannotReuseVerificationByName() { Assert.assertNull(SecurityProviderRuntimeState.getProviderInfo(new SameNameUnregisteredProvider())); } + /** §FS-security-providers.4.3: Tests the explicit-mode diagnostic. */ + @Test + public void testUnregisteredProviderReportsExplicitMetadataRemediation() { + Provider provider = new SecurityServiceTest.UnregisteredMacProvider(); + SecurityException error = Assert.assertThrows(SecurityException.class, + () -> Mac.getInstance("unregistered-mac", provider)); + Assert.assertTrue(error.getMessage().contains(SecurityServiceTest.UnregisteredMacProvider.class.getName())); + Assert.assertTrue(error.getMessage().contains("supported construction path")); + Assert.assertTrue(error.getMessage().contains("reachability-metadata.json")); + } + public static final class SameNameUnregisteredProvider extends Provider { private static final long serialVersionUID = 1L; diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/services/SecurityServiceRuntimeInitializationTest.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/services/SecurityServiceRuntimeInitializationTest.java new file mode 100644 index 000000000000..7fa628b884ef --- /dev/null +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/services/SecurityServiceRuntimeInitializationTest.java @@ -0,0 +1,154 @@ +/* + * Copyright (c) 2026, 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.test.services; + +import java.security.NoSuchAlgorithmException; +import java.security.Provider; +import java.security.Security; +import java.util.Iterator; +import java.util.ServiceConfigurationError; +import java.util.ServiceLoader; +import java.util.Set; + +import javax.crypto.KeyGenerator; + +import org.graalvm.nativeimage.ImageInfo; +import org.junit.Assert; +import org.junit.Test; + +import com.oracle.svm.core.FutureDefaultsOptions; +import com.oracle.svm.test.NativeImageBuildArgs; + +import sun.security.jca.GetInstance; + +@NativeImageBuildArgs({ + "--future-defaults=run-time-initialize-security-providers", + "-H:AdditionalSecurityServiceTypes=JCACompliantNoOpService" +}) +public class SecurityServiceRuntimeInitializationTest { + private static final String OMITTED_PROVIDER_ALGORITHM = "SHA256withECDSA"; + private static final String OMITTED_PROVIDER_SERVICE = "Signature"; + private static final String MISSING_KEY_GENERATOR_ALGORITHM = "GR69858DefinitelyMissing"; + private static final String REFLECTION_METADATA_PROVIDER_CLASS_NAME = "com.oracle.svm.test.services.SecurityServiceTest$ReflectionMetadataProvider"; + private static final String REFLECTION_METADATA_PROVIDER_NAME = "reflection-metadata-provider"; + private static final String SERVICE_LOADED_PROVIDER_CLASS_NAME = "com.oracle.svm.test.services.SecurityServiceTest$ServiceLoadedProvider"; + private static final String SERVICE_LOADED_PROVIDER_ALGORITHM = "service-loaded-provider-algo"; + + @Test + public void testRuntimeInitializationModeIsActive() { + Assert.assertTrue(FutureDefaultsOptions.securityProvidersInitializedAtRunTime()); + Assert.assertFalse(FutureDefaultsOptions.explicitSecurityProviderRegistration()); + } + + @Test + public void testSecurityProviderRuntimeRegistration() { + Assert.assertNull("Provider is registered.", Security.getProvider("no-op-provider")); + Security.addProvider(new SecurityServiceTest.NoOpProvider()); + Assert.assertNotNull("Provider is not registered.", Security.getProvider("no-op-provider")); + } + + /** + * Tests the regression from issue + * 1883. + */ + @Test + public void testUnknownSecurityServices() throws Exception { + Security.addProvider(new SecurityServiceTest.NoOpProvider()); + Provider registered = Security.getProvider("no-op-provider"); + Assert.assertNotNull("Provider is not registered", registered); + Object implementation = registered.getService("NoOp", "no-op-algo").newInstance(null); + Assert.assertNotNull("No service instance was created", implementation); + Assert.assertEquals(SecurityServiceTest.NoOpImpl.class, implementation.getClass()); + } + + /** §FS-security-providers.7.2: Tests absent service-provider metadata. */ + @Test + public void testServiceLoaderProviderWithoutMetadataUsesReflectionLookupFailure() { + Assert.assertTrue(ImageInfo.inImageRuntimeCode()); + Assert.assertThrows(ClassNotFoundException.class, () -> Class.forName(SERVICE_LOADED_PROVIDER_CLASS_NAME)); + ServiceConfigurationError error = Assert.assertThrows(ServiceConfigurationError.class, () -> ServiceLoader.load(Provider.class).stream() + .anyMatch(provider -> provider.type().getName().equals(SERVICE_LOADED_PROVIDER_CLASS_NAME))); + Assert.assertTrue(error.getMessage().contains(SERVICE_LOADED_PROVIDER_CLASS_NAME)); + Assert.assertTrue(error.getCause() instanceof ClassNotFoundException); + Assert.assertThrows(NoSuchAlgorithmException.class, + () -> SecurityServiceTest.JCACompliantNoOpService.getInstance(SERVICE_LOADED_PROVIDER_ALGORITHM)); + } + + /** §FS-security-providers.7.2: Tests preserved metadata-registered service providers. */ + @Test + public void testServiceLoaderProviderWithMetadataIsPreserved() { + Provider provider = ServiceLoader.load(Provider.class).stream() + .filter(candidate -> candidate.type().getName().equals(REFLECTION_METADATA_PROVIDER_CLASS_NAME)) + .findFirst() + .orElseThrow(() -> new AssertionError("Metadata-registered security provider should be visible through ServiceLoader.")) + .get(); + Assert.assertEquals(REFLECTION_METADATA_PROVIDER_NAME, provider.getName()); + } + + /** §FS-security-providers.7.3: Tests compatibility-mode provider inclusion. */ + @Test + public void testReachableBuiltInProviderIsIncluded() { + Assert.assertNotNull("Service-driven registration should include SunEC.", Security.getProvider("SunEC")); + } + + /** §FS-security-providers.7.3: Tests compatibility-mode service lookup. */ + @Test + public void testReachableBuiltInProviderGetService() throws NoSuchAlgorithmException { + Provider.Service service = GetInstance.getService(OMITTED_PROVIDER_SERVICE, OMITTED_PROVIDER_ALGORITHM); + Assert.assertEquals("SunEC", service.getProvider().getName()); + } + + /** §FS-security-providers.7.3: Tests compatibility-mode service instantiation. */ + @Test + public void testReachableBuiltInProviderGetInstance() throws NoSuchAlgorithmException { + Assert.assertNotNull(GetInstance.getInstance(OMITTED_PROVIDER_SERVICE, null, OMITTED_PROVIDER_ALGORITHM)); + } + + /** §FS-security-providers.7.3: Tests compatibility-mode service enumeration. */ + @Test + public void testReachableBuiltInProviderGetServices() { + Iterator services = GetInstance.getServices(OMITTED_PROVIDER_SERVICE, OMITTED_PROVIDER_ALGORITHM); + Assert.assertTrue(services.hasNext()); + } + + /** §FS-security-providers.4.2: Tests the standard unavailable result. */ + @Test + public void testGenericMissingAlgorithmExhaustsProviderList() { + Assert.assertThrows(NoSuchAlgorithmException.class, () -> KeyGenerator.getInstance(MISSING_KEY_GENERATOR_ALGORITHM)); + } + + /** §FS-security-providers.7.3: Tests compatibility-mode algorithm enumeration. */ + @Test + public void testSecurityGetAlgorithmsIncludesReachableBuiltInProviderAlgorithm() { + Set algorithms = Security.getAlgorithms(OMITTED_PROVIDER_SERVICE); + Assert.assertTrue(algorithms.contains(OMITTED_PROVIDER_ALGORITHM.toUpperCase())); + } + + /** §FS-security-providers.7.3: Tests compatibility-mode provider filtering. */ + @Test + public void testSecurityGetProvidersFilterIncludesReachableBuiltInProvider() { + Assert.assertNotNull(Security.getProviders(OMITTED_PROVIDER_SERVICE + "." + OMITTED_PROVIDER_ALGORITHM)); + } +} diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/services/SecurityServiceTest.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/services/SecurityServiceTest.java index 045299a783b3..f9a4dc8d5ef4 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/services/SecurityServiceTest.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/services/SecurityServiceTest.java @@ -31,12 +31,8 @@ import java.security.Provider; import java.security.Security; import java.security.spec.AlgorithmParameterSpec; -import java.util.Iterator; -import java.util.ServiceConfigurationError; -import java.util.ServiceLoader; import java.util.Set; -import javax.crypto.KeyGenerator; import javax.crypto.Mac; import javax.crypto.MacSpi; @@ -56,26 +52,26 @@ import com.oracle.svm.core.FutureDefaultsOptions; import com.oracle.svm.core.annotate.Delete; import com.oracle.svm.core.annotate.TargetClass; +import com.oracle.svm.core.jdk.SecurityProviderRuntimeState; import com.oracle.svm.shared.util.ModuleSupport; import com.oracle.svm.shared.util.ReflectionUtil; +import com.oracle.svm.test.NativeImageBuildArgs; import sun.security.jca.GetInstance; /** * Tests the {@code SecurityServicesFeature}. */ +@NativeImageBuildArgs("-H:AdditionalSecurityServiceTypes=JCACompliantNoOpService") public class SecurityServiceTest { - private static final String OMITTED_PROVIDER_ALGORITHM = "SHA256withECDSA"; - private static final String OMITTED_PROVIDER_SERVICE = "Signature"; - private static final String MISSING_KEY_GENERATOR_ALGORITHM = "GR69858DefinitelyMissing"; - private static final String REFLECTION_METADATA_PROVIDER_CLASS_NAME = "com.oracle.svm.test.services.SecurityServiceTest$ReflectionMetadataProvider"; private static final String REFLECTION_METADATA_PROVIDER_NAME = "reflection-metadata-provider"; private static final String REFLECTION_METADATA_PROVIDER_ALGORITHM = "reflection-metadata-algo"; private static final String REFLECTION_METADATA_PROVIDER_MAC_ALGORITHM = "reflection-metadata-mac"; - private static final String SERVICE_LOADED_PROVIDER_CLASS_NAME = "com.oracle.svm.test.services.SecurityServiceTest$ServiceLoadedProvider"; private static final String SERVICE_LOADED_PROVIDER_ALGORITHM = "service-loaded-provider-algo"; private static final String TYPE_METADATA_PROVIDER_NAME = "type-metadata-provider"; private static final String TYPE_METADATA_PROVIDER_ALGORITHM = "type-metadata-algo"; + private static final String TYPE_METADATA_PROVIDER_MAC_ALGORITHM = "type-metadata-mac"; + private static final String FAILED_VERIFICATION_PROVIDER_MAC_ALGORITHM = "failed-verification-mac"; private static final String REACHABLE_PROVIDER_WITHOUT_METADATA_NAME = "reachable-provider-without-metadata"; private static final String REACHABLE_PROVIDER_WITHOUT_METADATA_ALGORITHM = "reachable-without-metadata-algo"; @@ -85,6 +81,7 @@ public void afterRegistration(AfterRegistrationAccess access) { // register the providers Security.addProvider(new NoOpProvider()); Security.addProvider(new NoOpProviderTwo()); + Security.addProvider(new LegacyConstructorProvider()); // open sun.security.jca.GetInstance ModuleSupport.accessModuleByClass(ModuleSupport.Access.EXPORT, JCACompliantNoOpService.class, ReflectionUtil.lookupClass(false, "sun.security.jca.GetInstance")); @@ -97,49 +94,24 @@ public void duringSetup(final DuringSetupAccess access) { RuntimeClassInitialization.initializeAtBuildTime(NoOpService.class); RuntimeClassInitialization.initializeAtBuildTime(NoOpProvider.class); RuntimeClassInitialization.initializeAtBuildTime(NoOpProviderTwo.class); + RuntimeClassInitialization.initializeAtBuildTime(LegacyConstructorProvider.class); } // register the service implementation for reflection explicitly, // non-standard services are not processed automatically RuntimeReflection.register(NoOpImpl.class); RuntimeReflection.register(NoOpImpl.class.getDeclaredConstructors()); } - } - - /** - * This test ensures that the list of security providers is populated at run time, and not at - * build time. - */ - @Test - public void testSecurityProviderRuntimeRegistration() { - Assume.assumeTrue("needs runtime initialization", FutureDefaultsOptions.securityProvidersInitializedAtRunTime()); - Provider notRegistered = Security.getProvider("no-op-provider"); - Assert.assertNull("Provider is registered.", notRegistered); - - Security.addProvider(new NoOpProvider()); - Provider registered = Security.getProvider("no-op-provider"); - Assert.assertNotNull("Provider is not registered.", registered); - } - - /** - * Tests that native-image generation doesn't run into an issue (like NPE) if the application - * uses a java.security.Provider.Service which isn't part of the services shipped in the JDK. - * - * @throws Exception - * @see issue-1883 - */ - @Test - public void testUnknownSecurityServices() throws Exception { - Assume.assumeTrue("needs runtime provider initialization", FutureDefaultsOptions.securityProvidersInitializedAtRunTime()); - if (FutureDefaultsOptions.securityProvidersInitializedAtRunTime()) { - /* Register the provider at run time. */ - Security.addProvider(new NoOpProvider()); + @Override + public void beforeAnalysis(BeforeAnalysisAccess access) { + /* + * Deterministically model the negative outcome produced by build-time JCE + * authentication. Registering the later successful catalog result must not erase it. + */ + SecurityProviderRuntimeState.currentLayer().registerApplicationSuppliedProvider( + FailedVerificationProvider.class.getName(), + new SecurityException("simulated build-time provider verification failure")); } - final Provider registered = Security.getProvider("no-op-provider"); - Assert.assertNotNull("Provider is not registered", registered); - final Object impl = registered.getService("NoOp", "no-op-algo").newInstance(null); - Assert.assertNotNull("No service instance was created", impl); - MatcherAssert.assertThat("Unexpected service implementation class", impl, CoreMatchers.instanceOf(NoOpImpl.class)); } @Test @@ -157,7 +129,15 @@ public void testAutomaticSecurityServiceRegistration() { } } - /** Tests service-driven GSS provider inclusion from §FS-security-providers.7.3. */ + /** §FS-security-providers.7.3: Tests the public-constructor compatibility surface. */ + @Test + public void testLegacyServiceInclusionRegistersEveryPublicProviderConstructor() throws Exception { + Assert.assertFalse(FutureDefaultsOptions.explicitSecurityProviderRegistration()); + Provider provider = LegacyConstructorProvider.class.getConstructor(String.class).newInstance("reflected"); + Assert.assertEquals("legacy-constructor-provider-reflected", provider.getName()); + } + + /** §FS-security-providers.7.3: Tests service-driven GSS provider inclusion. */ @Test public void testGSSProviderServiceRegistration() throws Exception { Oid kerberosV5 = new Oid("1.2.840.113554.1.2.2"); @@ -166,42 +146,35 @@ public void testGSSProviderServiceRegistration() throws Exception { Assert.assertEquals("user@REALM", manager.createName("user@REALM", GSSName.NT_USER_NAME, kerberosV5).toString()); } - // Tests provider registration, complete services, and provider-object factory calls. - // §FS-security-providers.2.1, §FS-security-providers.2.3, and §FS-security-providers.5.1 + /** §FS-security-providers.5.3 and §FS-security-providers.7.3: Tests compatibility-mode verification. */ @Test - public void testReflectionMetadataProviderRegistration() throws Exception { - Assume.assumeTrue("needs explicit provider registration", FutureDefaultsOptions.explicitSecurityProviderRegistration()); - Provider provider = (Provider) Class.forName(REFLECTION_METADATA_PROVIDER_CLASS_NAME).getDeclaredConstructor().newInstance(); - int position = Security.addProvider(provider); - try { - Assert.assertTrue("Provider should be registered.", position > 0); - JCACompliantNoOpService service = JCACompliantNoOpService.getInstance(REFLECTION_METADATA_PROVIDER_ALGORITHM); - Assert.assertNotNull("No service instance was created", service); - Assert.assertEquals("Unexpected service implementation class", ReflectionMetadataNoOpServiceImpl.class.getName(), service.getClass().getName()); - Assert.assertNotNull("No JCE service instance was created", Mac.getInstance(REFLECTION_METADATA_PROVIDER_MAC_ALGORITHM, provider)); - } finally { - Security.removeProvider(REFLECTION_METADATA_PROVIDER_NAME); - } + public void testTypeMetadataApplicationProviderVerification() throws Exception { + Assume.assumeTrue("native image runtime only", ImageInfo.inImageRuntimeCode()); + Assume.assumeFalse("tests compatibility-mode verification", FutureDefaultsOptions.explicitSecurityProviderRegistration()); + + Provider provider = new TypeMetadataProvider(); + SecurityProviderRuntimeState.ProviderInfo info = SecurityProviderRuntimeState.getProviderInfo(provider); + Assert.assertNotNull("Type-only provider metadata must establish a JCE verification result.", info); + Assert.assertEquals(SecurityProviderRuntimeState.AcquisitionKind.APPLICATION_SUPPLIED_ONLY, info.acquisitionKind()); + Assert.assertNull("The application-supplied provider should pass class-based verification.", info.verificationFailure()); + Assert.assertNotNull(Mac.getInstance(TYPE_METADATA_PROVIDER_MAC_ALGORITHM, provider)); } - // Tests type-only registration, complete services, and provider-object factory calls. - // §FS-security-providers.2.1, §FS-security-providers.2.3, and §FS-security-providers.5.1 + /** §FS-security-providers.5.3: Tests preservation of the failed-verification outcome. */ @Test - public void testTypeMetadataProviderRegistration() throws Exception { - Assume.assumeTrue("needs explicit provider registration", FutureDefaultsOptions.explicitSecurityProviderRegistration()); - Provider provider = new TypeMetadataProvider(); - int position = Security.addProvider(provider); - try { - Assert.assertTrue("Provider should be registered.", position > 0); - JCACompliantNoOpService service = JCACompliantNoOpService.getInstance(TYPE_METADATA_PROVIDER_ALGORITHM); - Assert.assertNotNull("No service instance was created", service); - Assert.assertEquals("Unexpected service implementation class", TypeMetadataNoOpServiceImpl.class.getName(), service.getClass().getName()); - } finally { - Security.removeProvider(TYPE_METADATA_PROVIDER_NAME); - } + public void testFailedBuildTimeProviderVerificationStaysUnusable() { + Assume.assumeTrue("native image runtime only", ImageInfo.inImageRuntimeCode()); + + Provider provider = new FailedVerificationProvider(); + SecurityProviderRuntimeState.ProviderInfo info = SecurityProviderRuntimeState.getProviderInfo(provider); + Assert.assertNotNull("The failed verification outcome must be retained.", info); + Assert.assertNotNull("A successful later catalog pass must not erase the failure.", info.verificationFailure()); + Assert.assertTrue(info.verificationFailure().getMessage().contains("simulated build-time provider verification failure")); + Assert.assertThrows(SecurityException.class, + () -> Mac.getInstance(FAILED_VERIFICATION_PROVIDER_MAC_ALGORITHM, provider)); } - /** Tests §FS-security-providers.4.1. */ + /** §FS-security-providers.4.1: Tests omission without registration metadata. */ @Test public void testReachableProviderWithoutMetadataDoesNotRegisterServices() { Provider provider = new ReachableProviderWithoutMetadata(); @@ -214,7 +187,7 @@ public void testReachableProviderWithoutMetadataDoesNotRegisterServices() { } } - /** Tests the non-exact diagnostic from §FS-security-providers.4.3. */ + /** §FS-security-providers.4.3: Tests the non-exact diagnostic. */ @Test public void testUnregisteredJceProviderReportsActionableDiagnostic() { Assume.assumeTrue("native image runtime only", ImageInfo.inImageRuntimeCode()); @@ -225,39 +198,10 @@ public void testUnregisteredJceProviderReportsActionableDiagnostic() { () -> Mac.getInstance("unregistered-mac", provider)); Assert.assertTrue("The diagnostic must identify the provider type", error.getMessage().contains(UnregisteredMacProvider.class.getName())); - } - - /** Tests §FS-security-providers.7.2. */ - @Test - public void testServiceLoaderProviderWithoutMetadataUsesReflectionLookupFailure() { - Assume.assumeTrue("native image runtime only", ImageInfo.inImageRuntimeCode()); - Assume.assumeTrue("needs runtime initialization", FutureDefaultsOptions.securityProvidersInitializedAtRunTime()); - - Assert.assertThrows(ClassNotFoundException.class, () -> Class.forName(SERVICE_LOADED_PROVIDER_CLASS_NAME)); - ServiceConfigurationError serviceLoaderError = Assert.assertThrows(ServiceConfigurationError.class, () -> ServiceLoader.load(Provider.class).stream() - .anyMatch(provider -> provider.type().getName().equals(SERVICE_LOADED_PROVIDER_CLASS_NAME))); - Assert.assertTrue("ServiceLoader should report the missing provider class.", serviceLoaderError.getMessage().contains(SERVICE_LOADED_PROVIDER_CLASS_NAME)); - Assert.assertTrue("ServiceLoader should use the standard reflection lookup failure.", serviceLoaderError.getCause() instanceof ClassNotFoundException); - - Assert.assertThrows(NoSuchAlgorithmException.class, () -> JCACompliantNoOpService.getInstance(SERVICE_LOADED_PROVIDER_ALGORITHM)); - } - - /** Tests §FS-security-providers.7.2. */ - @Test - public void testServiceLoaderProviderWithMetadataIsPreserved() { - Assume.assumeTrue("native image runtime only", ImageInfo.inImageRuntimeCode()); - Assume.assumeTrue("needs runtime initialization", FutureDefaultsOptions.securityProvidersInitializedAtRunTime()); - - try { - Provider provider = ServiceLoader.load(Provider.class).stream() - .filter(candidate -> candidate.type().getName().equals(REFLECTION_METADATA_PROVIDER_CLASS_NAME)) - .findFirst() - .orElseThrow(() -> new AssertionError("Metadata-registered security provider should be visible through ServiceLoader.")) - .get(); - Assert.assertEquals("Unexpected provider name", REFLECTION_METADATA_PROVIDER_NAME, provider.getName()); - } catch (ServiceConfigurationError e) { - Assert.fail("Metadata-registered security provider should be loadable through ServiceLoader: " + e); - } + Assert.assertTrue("The compatibility-mode diagnostic must explain that metadata is inert for services.", + error.getMessage().contains("does not enable provider construction or services in compatibility mode")); + Assert.assertTrue("The diagnostic must name the explicit registration migration.", + error.getMessage().contains("--future-defaults=explicit-security-provider-registration")); } @Delete @@ -274,61 +218,7 @@ public void testDeletedProvider() { Assert.assertNull("Provider should not be present.", registered); } - /** Tests the compatibility behavior in §FS-security-providers.7.3. */ - @Test - public void testReachableBuiltInProviderIsIncluded() { - Assume.assumeTrue("needs runtime initialization", FutureDefaultsOptions.securityProvidersInitializedAtRunTime()); - Assert.assertNotNull("Service-driven registration should include SunEC.", Security.getProvider("SunEC")); - } - - /** Tests the compatibility behavior in §FS-security-providers.7.3. */ - @Test - public void testReachableBuiltInProviderGetService() throws NoSuchAlgorithmException { - Assume.assumeTrue("needs runtime initialization", FutureDefaultsOptions.securityProvidersInitializedAtRunTime()); - Provider.Service service = GetInstance.getService(OMITTED_PROVIDER_SERVICE, OMITTED_PROVIDER_ALGORITHM); - Assert.assertEquals("SunEC", service.getProvider().getName()); - } - - /** Tests the compatibility behavior in §FS-security-providers.7.3. */ - @Test - public void testReachableBuiltInProviderGetInstance() throws NoSuchAlgorithmException { - Assume.assumeTrue("needs runtime initialization", FutureDefaultsOptions.securityProvidersInitializedAtRunTime()); - Assert.assertNotNull(GetInstance.getInstance(OMITTED_PROVIDER_SERVICE, null, OMITTED_PROVIDER_ALGORITHM)); - } - - /** Tests the compatibility behavior in §FS-security-providers.7.3. */ - @Test - public void testReachableBuiltInProviderGetServices() { - Assume.assumeTrue("needs runtime initialization", FutureDefaultsOptions.securityProvidersInitializedAtRunTime()); - Iterator services = GetInstance.getServices(OMITTED_PROVIDER_SERVICE, OMITTED_PROVIDER_ALGORITHM); - Assert.assertTrue("Generic service iteration should include the reachable built-in provider.", services.hasNext()); - } - - /** Tests the standard unavailable result from §FS-security-providers.4.2. */ - @Test - public void testGenericMissingAlgorithmExhaustsProviderList() { - Assume.assumeTrue("needs runtime initialization", FutureDefaultsOptions.securityProvidersInitializedAtRunTime()); - Assert.assertThrows(NoSuchAlgorithmException.class, () -> KeyGenerator.getInstance(MISSING_KEY_GENERATOR_ALGORITHM)); - } - - /** Tests the compatibility behavior in §FS-security-providers.7.3. */ - @Test - public void testSecurityGetAlgorithmsIncludesReachableBuiltInProviderAlgorithm() { - Assume.assumeTrue("needs runtime initialization", FutureDefaultsOptions.securityProvidersInitializedAtRunTime()); - Set algorithms = Security.getAlgorithms(OMITTED_PROVIDER_SERVICE); - Assert.assertTrue("Generic algorithm discovery should expose the reachable built-in provider algorithm.", - algorithms.contains(OMITTED_PROVIDER_ALGORITHM.toUpperCase())); - } - - /** Tests the compatibility behavior in §FS-security-providers.7.3. */ - @Test - public void testSecurityGetProvidersFilterIncludesReachableBuiltInProvider() { - Assume.assumeTrue("needs runtime initialization", FutureDefaultsOptions.securityProvidersInitializedAtRunTime()); - Assert.assertNotNull("Provider filtering should include algorithms from the reachable built-in provider.", - Security.getProviders(OMITTED_PROVIDER_SERVICE + "." + OMITTED_PROVIDER_ALGORITHM)); - } - - private static final class NoOpProvider extends Provider { + static final class NoOpProvider extends Provider { static final long serialVersionUID = 1234L; @@ -365,11 +255,26 @@ protected NoOpProviderTwo() { } } + public static final class LegacyConstructorProvider extends Provider { + static final long serialVersionUID = 1234L; + + public LegacyConstructorProvider() { + this("default"); + } + + @SuppressWarnings("deprecation") + public LegacyConstructorProvider(String configuration) { + super("legacy-constructor-provider-" + configuration, 1.0, "Provider with legacy public constructors"); + putService(new Service(this, "JCACompliantNoOpService", "legacy-constructor-algo", + JcaCompliantNoOpServiceImpl.class.getName(), null, null)); + } + } + /* * Service class' simple name must match its type. The service must also have a getInstance * method used to obtain its' instance. */ - private abstract static class JCACompliantNoOpService { + abstract static class JCACompliantNoOpService { public static JCACompliantNoOpService getInstance(String algorithm) throws NoSuchAlgorithmException { return (JCACompliantNoOpService) GetInstance.getInstance("JCACompliantNoOpService", null, algorithm).impl; } @@ -410,6 +315,7 @@ public TypeMetadataProvider() { super(TYPE_METADATA_PROVIDER_NAME, 1.0, "Provider registered through type-level reflection metadata"); putService(new Service(this, "JCACompliantNoOpService", TYPE_METADATA_PROVIDER_ALGORITHM, TypeMetadataNoOpServiceImpl.class.getName(), null, null)); + putService(new Service(this, "Mac", TYPE_METADATA_PROVIDER_MAC_ALGORITHM, ReflectionMetadataMacSpi.class.getName(), null, null)); } } @@ -434,6 +340,17 @@ public UnregisteredMacProvider() { } } + public static final class FailedVerificationProvider extends Provider { + static final long serialVersionUID = 1234L; + + @SuppressWarnings("deprecation") + public FailedVerificationProvider() { + super("failed-verification-provider", 1.0, "Provider with a preserved build-time verification failure"); + putService(new Service(this, "Mac", FAILED_VERIFICATION_PROVIDER_MAC_ALGORITHM, + ReflectionMetadataMacSpi.class.getName(), null, null)); + } + } + public static final class ReflectionMetadataMacSpi extends MacSpi { @Override protected int engineGetMacLength() { From b0958db4dfd1b4249daee0166a6aa80e0f8e4db7 Mon Sep 17 00:00:00 2001 From: Vojin Jovanovic Date: Tue, 28 Jul 2026 14:29:06 +0200 Subject: [PATCH 50/63] GR-69858: Move hosted security provider support to JCA package --- .../native-image/JCASecurityServices.md | 2 +- substratevm/docs/architecture/README.md | 2 +- substratevm/docs/architecture/security-providers.md | 2 +- .../graal/pointsto/meta/AnalysisMetaAccess.java | 4 ---- .../graal/pointsto/meta/AnalysisUniverse.java | 5 ----- .../svm/hosted/test/VerifyReflectionUsage.java | 2 +- .../svm/hosted/NativeImageClassLoaderSupport.java | 2 +- .../com/oracle/svm/hosted/ServiceLoaderFeature.java | 1 + .../LegacySecurityProviderCompatibility.java | 2 +- .../{ => jca}/ReflectionRegistrationView.java | 2 +- .../{ => jca}/SecurityProviderCatalogRegistrar.java | 2 +- .../svm/hosted/{ => jca}/SecurityProviderMode.java | 10 +++++----- .../SecurityProviderRegistrationPlanner.java | 2 +- .../hosted/{ => jca}/SecurityServicesFeature.java | 13 +++++++------ .../svm/hosted/reflect/ReflectionDataBuilder.java | 5 +---- 15 files changed, 23 insertions(+), 33 deletions(-) rename substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/{ => jca}/LegacySecurityProviderCompatibility.java (98%) rename substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/{ => jca}/ReflectionRegistrationView.java (98%) rename substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/{ => jca}/SecurityProviderCatalogRegistrar.java (99%) rename substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/{ => jca}/SecurityProviderMode.java (89%) rename substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/{ => jca}/SecurityProviderRegistrationPlanner.java (99%) rename substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/{ => jca}/SecurityServicesFeature.java (99%) diff --git a/docs/reference-manual/native-image/JCASecurityServices.md b/docs/reference-manual/native-image/JCASecurityServices.md index e1bfd8aa290b..45f4913f32a9 100644 --- a/docs/reference-manual/native-image/JCASecurityServices.md +++ b/docs/reference-manual/native-image/JCASecurityServices.md @@ -40,7 +40,7 @@ Alternatively, collect the metadata by running your application on the JVM with ## Security Services Automatic Registration -The mechanism, implemented in the `com.oracle.svm.hosted.SecurityServicesFeature` class, uses reachability of specific API methods in the JCA framework to determine which security services are used. +The mechanism, implemented in the `com.oracle.svm.hosted.jca.SecurityServicesFeature` class, uses reachability of specific API methods in the JCA framework to determine which security services are used. Each JCA provider registers concrete implementation classes for the algorithms it supports. Each of the service classes (`Signature`, `Cipher`, `Mac`, `KeyPair`, `KeyGenerator`, `KeyFactory`, `KeyStore`, etc.) declares a series of `getInstance(, ` factory methods which provide a concrete service implementation. diff --git a/substratevm/docs/architecture/README.md b/substratevm/docs/architecture/README.md index 8b9c69ee40bf..626d63297a14 100644 --- a/substratevm/docs/architecture/README.md +++ b/substratevm/docs/architecture/README.md @@ -3,4 +3,4 @@ This directory contains developer-facing architecture records for Native Image. - [Security Provider Architecture](security-providers.md): provider inclusion, verification, and - metadata tracing ([§AR-security-providers](../../src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SecurityServicesFeature.java)). + metadata tracing ([§AR-security-providers](../../src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jca/SecurityServicesFeature.java)). diff --git a/substratevm/docs/architecture/security-providers.md b/substratevm/docs/architecture/security-providers.md index d1d611bb5a1c..be3f8789bc33 100644 --- a/substratevm/docs/architecture/security-providers.md +++ b/substratevm/docs/architecture/security-providers.md @@ -1 +1 @@ -# AR-security-providers: [SecurityServicesFeature](../../src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SecurityServicesFeature.java) +# AR-security-providers: [SecurityServicesFeature](../../src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jca/SecurityServicesFeature.java) diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/meta/AnalysisMetaAccess.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/meta/AnalysisMetaAccess.java index 753f0e8b93d8..28784e0bcad1 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/meta/AnalysisMetaAccess.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/meta/AnalysisMetaAccess.java @@ -101,10 +101,6 @@ public AnalysisMethod lookupJavaMethod(Executable reflectionMethod) { return (AnalysisMethod) super.lookupJavaMethod(reflectionMethod); } - public Optional optionalLookupJavaMethod(Executable reflectionMethod) { - return Optional.ofNullable(getUniverse().optionalLookup(getWrapped().lookupJavaMethod(reflectionMethod))); - } - @Override public AnalysisField lookupJavaField(Field reflectionField) { return (AnalysisField) super.lookupJavaField(reflectionField); diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/meta/AnalysisUniverse.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/meta/AnalysisUniverse.java index 992b813a92e0..8f0db49061b6 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/meta/AnalysisUniverse.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/meta/AnalysisUniverse.java @@ -410,11 +410,6 @@ public AnalysisMethod lookup(JavaMethod method) { ". Probably there are some compilation or classpath problems. "); } - public AnalysisMethod optionalLookup(ResolvedJavaMethod method) { - ResolvedJavaMethod actualMethod = substitutions.lookup(method); - return methods.get(actualMethod); - } - @Override public JavaMethod lookupAllowUnresolved(JavaMethod rawMethod) { if (rawMethod == null) { diff --git a/substratevm/src/com.oracle.svm.hosted.test/src/com/oracle/svm/hosted/test/VerifyReflectionUsage.java b/substratevm/src/com.oracle.svm.hosted.test/src/com/oracle/svm/hosted/test/VerifyReflectionUsage.java index bff8e7e44ff3..bc219b067137 100644 --- a/substratevm/src/com.oracle.svm.hosted.test/src/com/oracle/svm/hosted/test/VerifyReflectionUsage.java +++ b/substratevm/src/com.oracle.svm.hosted.test/src/com/oracle/svm/hosted/test/VerifyReflectionUsage.java @@ -296,7 +296,7 @@ public interface Provider { clazz("com.oracle.svm.hosted.ResourcesFeature$1"), clazz("com.oracle.svm.hosted.ResourcesFeature$ResourceCollectorImpl"), clazz("com.oracle.svm.hosted.ResourcesFeature$ResourcesRegistryImpl"), - clazz("com.oracle.svm.hosted.SecurityServicesFeature"), + clazz("com.oracle.svm.hosted.jca.SecurityServicesFeature"), clazz("com.oracle.svm.hosted.snippets.ReflectionPlugins"), clazz("com.oracle.svm.hosted.snippets.ReflectionPlugins$3"), clazz("com.oracle.svm.hosted.substitute.AutomaticUnsafeTransformationSupport"), diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/NativeImageClassLoaderSupport.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/NativeImageClassLoaderSupport.java index 3ea6174edd49..82af7324c9d3 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/NativeImageClassLoaderSupport.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/NativeImageClassLoaderSupport.java @@ -626,7 +626,7 @@ private LinkedHashSet serviceProviders(String serviceName) { return serviceProviders.computeIfAbsent(serviceName, _ -> new LinkedHashSet<>()); } - void serviceProvidersForEach(BiConsumer> action) { + public void serviceProvidersForEach(BiConsumer> action) { serviceProviders.forEach((key, val) -> action.accept(key, Collections.unmodifiableCollection(val))); } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ServiceLoaderFeature.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ServiceLoaderFeature.java index 139ba82d5437..ff324ca7b88f 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ServiceLoaderFeature.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ServiceLoaderFeature.java @@ -46,6 +46,7 @@ import com.oracle.svm.core.jdk.Resources; import com.oracle.svm.core.jdk.ServiceCatalogSupport; import com.oracle.svm.hosted.analysis.Inflation; +import com.oracle.svm.hosted.jca.SecurityProviderMode; import com.oracle.svm.hosted.substitute.DeletedElementException; import com.oracle.svm.shared.feature.AutomaticallyRegisteredFeature; import com.oracle.svm.shared.option.AccumulatingLocatableMultiOptionValue; diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/LegacySecurityProviderCompatibility.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jca/LegacySecurityProviderCompatibility.java similarity index 98% rename from substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/LegacySecurityProviderCompatibility.java rename to substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jca/LegacySecurityProviderCompatibility.java index 28a386166ad3..713f45a783ff 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/LegacySecurityProviderCompatibility.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jca/LegacySecurityProviderCompatibility.java @@ -22,7 +22,7 @@ * or visit www.oracle.com if you need additional information or have any * questions. */ -package com.oracle.svm.hosted; +package com.oracle.svm.hosted.jca; import java.util.function.Consumer; diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ReflectionRegistrationView.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jca/ReflectionRegistrationView.java similarity index 98% rename from substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ReflectionRegistrationView.java rename to substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jca/ReflectionRegistrationView.java index 2b3101591b60..98f6aa1c44a9 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ReflectionRegistrationView.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jca/ReflectionRegistrationView.java @@ -22,7 +22,7 @@ * or visit www.oracle.com if you need additional information or have any * questions. */ -package com.oracle.svm.hosted; +package com.oracle.svm.hosted.jca; import java.lang.reflect.Executable; diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SecurityProviderCatalogRegistrar.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jca/SecurityProviderCatalogRegistrar.java similarity index 99% rename from substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SecurityProviderCatalogRegistrar.java rename to substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jca/SecurityProviderCatalogRegistrar.java index 1e6b0f4ef3fd..edd942e234dc 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SecurityProviderCatalogRegistrar.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jca/SecurityProviderCatalogRegistrar.java @@ -22,7 +22,7 @@ * or visit www.oracle.com if you need additional information or have any * questions. */ -package com.oracle.svm.hosted; +package com.oracle.svm.hosted.jca; import java.security.Provider; import java.security.Provider.Service; diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SecurityProviderMode.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jca/SecurityProviderMode.java similarity index 89% rename from substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SecurityProviderMode.java rename to substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jca/SecurityProviderMode.java index 6197d0a29c1a..f8c13806a149 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SecurityProviderMode.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jca/SecurityProviderMode.java @@ -22,17 +22,17 @@ * or visit www.oracle.com if you need additional information or have any * questions. */ -package com.oracle.svm.hosted; +package com.oracle.svm.hosted.jca; import com.oracle.svm.core.FutureDefaultsOptions; /** The supported transition modes from §FS-security-providers.7. */ -enum SecurityProviderMode { +public enum SecurityProviderMode { LEGACY_BUILD_TIME, LEGACY_RUN_TIME, EXPLICIT_RUN_TIME; - static SecurityProviderMode current() { + public static SecurityProviderMode current() { if (FutureDefaultsOptions.explicitSecurityProviderRegistration()) { assert FutureDefaultsOptions.securityProvidersInitializedAtRunTime(); return EXPLICIT_RUN_TIME; @@ -40,11 +40,11 @@ static SecurityProviderMode current() { return FutureDefaultsOptions.securityProvidersInitializedAtRunTime() ? LEGACY_RUN_TIME : LEGACY_BUILD_TIME; } - boolean explicitRegistration() { + public boolean explicitRegistration() { return this == EXPLICIT_RUN_TIME; } - boolean runtimeProviderList() { + public boolean runtimeProviderList() { return this != LEGACY_BUILD_TIME; } } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SecurityProviderRegistrationPlanner.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jca/SecurityProviderRegistrationPlanner.java similarity index 99% rename from substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SecurityProviderRegistrationPlanner.java rename to substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jca/SecurityProviderRegistrationPlanner.java index 76e0cb7a2e9d..0b042c3b0558 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SecurityProviderRegistrationPlanner.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jca/SecurityProviderRegistrationPlanner.java @@ -22,7 +22,7 @@ * or visit www.oracle.com if you need additional information or have any * questions. */ -package com.oracle.svm.hosted; +package com.oracle.svm.hosted.jca; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SecurityServicesFeature.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jca/SecurityServicesFeature.java similarity index 99% rename from substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SecurityServicesFeature.java rename to substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jca/SecurityServicesFeature.java index 199b635d6558..ae06c6431d84 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SecurityServicesFeature.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jca/SecurityServicesFeature.java @@ -22,12 +22,12 @@ * or visit www.oracle.com if you need additional information or have any * questions. */ -package com.oracle.svm.hosted; +package com.oracle.svm.hosted.jca; import static com.oracle.graal.pointsto.ObjectScanner.OtherReason; import static com.oracle.graal.pointsto.ObjectScanner.ScanReason; -import static com.oracle.svm.hosted.SecurityServicesFeature.SecurityServicesPrinter.dedent; -import static com.oracle.svm.hosted.SecurityServicesFeature.SecurityServicesPrinter.indent; +import static com.oracle.svm.hosted.jca.SecurityServicesFeature.SecurityServicesPrinter.dedent; +import static com.oracle.svm.hosted.jca.SecurityServicesFeature.SecurityServicesPrinter.indent; import java.io.File; import java.io.FileWriter; @@ -101,6 +101,7 @@ import com.oracle.svm.hosted.FeatureImpl.BeforeAnalysisAccessImpl; import com.oracle.svm.hosted.FeatureImpl.DuringAnalysisAccessImpl; import com.oracle.svm.hosted.FeatureImpl.DuringSetupAccessImpl; +import com.oracle.svm.hosted.ImageClassLoader; import com.oracle.svm.hosted.analysis.Inflation; import com.oracle.svm.hosted.c.NativeLibraries; import com.oracle.svm.hosted.substitute.DeletedElementException; @@ -497,7 +498,7 @@ public void beforeAnalysis(BeforeAnalysisAccess a) { access.ensureInitialized("sun.security.util.AnchorCertificates"); initializeServiceRegistrationData(); - preserveAll = access.imageClassLoader.classLoaderSupport.isPreserveAll(); + preserveAll = access.getImageClassLoader().classLoaderSupport.isPreserveAll(); reflectionRegistrationView = ReflectionRegistrationView.singleton(); access.registerSubtypeReachabilityHandler((_, providerClass) -> addCandidateProviderClass(providerClass), Provider.class); registerServiceProviderCandidates(access); @@ -634,7 +635,7 @@ private static void traceRemovedProviders(List removedProviders) { } private static void registerSunMSCAPIConfig(BeforeAnalysisAccess a) { - NativeLibraries nativeLibraries = ((FeatureImpl.DuringAnalysisAccessImpl) a).getNativeLibraries(); + NativeLibraries nativeLibraries = ((DuringAnalysisAccessImpl) a).getNativeLibraries(); /* We statically link sunmscapi thus we classify it as builtIn library */ NativeLibrarySupport.singleton().preregisterUninitializedBuiltinLibrary("sunmscapi"); @@ -800,7 +801,7 @@ private void initializeServiceRegistrationData() { private void registerServiceProviderCandidates(BeforeAnalysisAccess access) { BeforeAnalysisAccessImpl accessImpl = (BeforeAnalysisAccessImpl) access; - accessImpl.imageClassLoader.classLoaderSupport.serviceProvidersForEach((serviceName, providers) -> { + accessImpl.getImageClassLoader().classLoaderSupport.serviceProvidersForEach((serviceName, providers) -> { if (serviceName.equals(Provider.class.getName())) { // §FS-security-providers.7.2: // Descriptors discover candidates without registering them. diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/ReflectionDataBuilder.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/ReflectionDataBuilder.java index ac941e6e7b9b..5ccc0200eb7f 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/ReflectionDataBuilder.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/ReflectionDataBuilder.java @@ -1429,10 +1429,7 @@ public boolean isTypeRegisteredForReflection(Class clazz) { } public boolean isMethodRegisteredForReflection(Executable method) { - AnalysisMethod analysisMethod = metaAccess.optionalLookupJavaMethod(method).orElse(null); - if (analysisMethod == null) { - return false; - } + AnalysisMethod analysisMethod = metaAccess.lookupJavaMethod(method); ElementData data = methods.get(analysisMethod); return data != null && data.isRegisteredAs(ACCESSED); } From 4284bcbe69ea358561d8c6bfafd1db8c3fc39a71 Mon Sep 17 00:00:00 2001 From: Vojin Jovanovic Date: Tue, 28 Jul 2026 17:40:26 +0200 Subject: [PATCH 51/63] GR-69858: Fix security provider gate failures --- substratevm/CHANGELOG.md | 1 - .../src/com/oracle/svm/hosted/test/VerifyReflectionUsage.java | 2 +- .../src/com/oracle/svm/hosted/SVMHost.java | 2 +- 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/substratevm/CHANGELOG.md b/substratevm/CHANGELOG.md index a48cf5ba933a..e9baebeed665 100644 --- a/substratevm/CHANGELOG.md +++ b/substratevm/CHANGELOG.md @@ -10,7 +10,6 @@ This changelog summarizes major changes to GraalVM Native Image. * (GR-73199) When native executables are built with `-H:-LegacyJavaOptionMode`, VM options are parsed only before the first `--` argument. Arguments after `--` are passed unchanged to the application main method. The legacy behavior remains unchanged. * (GR-77977) Added control flow integrity options, available via `-H:CFI`. Indirect branches on AMD64 can be guarded with software-based checks that ensure that they land on valid targets. On AArch64, PAC is supported to protect return addresses on the stack. * (GR-69858) Deprecated `-H:AdditionalSecurityProviders` and `-H:AdditionalSecurityServiceTypes`. Register each security provider class for reflection in `reachability-metadata.json` using `{"reflection":[{"type":""}]}` instead. The Tracing Agent generates this metadata automatically. When provider initialization occurs at run time, Native Image preserves a reachable `META-INF/services/java.security.Provider` descriptor even if its provider is not registered; iterating that entry reports the standard service-loading or missing-reflection error. -* (GR-69858) Restored folded-method filtering in `SVMHost` so methods annotated with either `@Fold` or `@GuestFold` are excluded from the run-time universe. Speculative provider-candidate probing can silently reject hosted-named elements, while the final universe verifier continues to report reachable naming violations. ## GraalVM 25.2 (Internal Version 25.2.4) * (GR-77358) Introduced compressed (32-bit) references, enabled by default. This generally improves memory usage and performance, but limits heap memory to 32 GB. Disable with `-H:-UseCompressedReferences`. diff --git a/substratevm/src/com.oracle.svm.hosted.test/src/com/oracle/svm/hosted/test/VerifyReflectionUsage.java b/substratevm/src/com.oracle.svm.hosted.test/src/com/oracle/svm/hosted/test/VerifyReflectionUsage.java index bc219b067137..5b8ce619f092 100644 --- a/substratevm/src/com.oracle.svm.hosted.test/src/com/oracle/svm/hosted/test/VerifyReflectionUsage.java +++ b/substratevm/src/com.oracle.svm.hosted.test/src/com/oracle/svm/hosted/test/VerifyReflectionUsage.java @@ -252,6 +252,7 @@ public interface Provider { clazz("com.oracle.svm.hosted.imagelayer.ImageSingletonSlotData"), clazz("com.oracle.svm.hosted.InstrumentFeature"), clazz("com.oracle.svm.hosted.InternalResourceAccess"), + clazz("com.oracle.svm.hosted.jca.SecurityServicesFeature"), clazz("com.oracle.svm.hosted.jdk.AtomicFieldUpdaterFeature"), clazz("com.oracle.svm.hosted.jdk.HostedClassLoaderPackageManagement"), clazz("com.oracle.svm.hosted.jdk.JDKRegistrations"), @@ -296,7 +297,6 @@ public interface Provider { clazz("com.oracle.svm.hosted.ResourcesFeature$1"), clazz("com.oracle.svm.hosted.ResourcesFeature$ResourceCollectorImpl"), clazz("com.oracle.svm.hosted.ResourcesFeature$ResourcesRegistryImpl"), - clazz("com.oracle.svm.hosted.jca.SecurityServicesFeature"), clazz("com.oracle.svm.hosted.snippets.ReflectionPlugins"), clazz("com.oracle.svm.hosted.snippets.ReflectionPlugins$3"), clazz("com.oracle.svm.hosted.substitute.AutomaticUnsafeTransformationSupport"), diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SVMHost.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SVMHost.java index 1cf344e0c27a..72b35539aaf0 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SVMHost.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SVMHost.java @@ -1222,7 +1222,7 @@ private boolean isSupportedMethod(BigBang bb, ResolvedJavaMethod method) { * they are replaced by the invocation plugin with a constant. If reachable in an extension * image, the plugin will replace it again. */ - if (GuestAnnotationAccess.isAnnotationPresent(method, Fold.class) || GuestAnnotationAccess.isAnnotationPresent(method, GuestFold.class)) { + if (GuestAnnotationAccess.isAnnotationPresent(method, Fold.class) && GuestAnnotationAccess.isAnnotationPresent(method, GuestFold.class)) { return false; } From 50ddf6a34bf3cd952e50361a4998484343d0733f Mon Sep 17 00:00:00 2001 From: Vojin Jovanovic Date: Wed, 29 Jul 2026 11:36:08 +0200 Subject: [PATCH 52/63] GR-69858: Fix layered provider verification cache --- .../hosted/jca/SecurityServicesFeature.java | 42 +++++-------------- 1 file changed, 10 insertions(+), 32 deletions(-) diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jca/SecurityServicesFeature.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jca/SecurityServicesFeature.java index ae06c6431d84..d134129a495d 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jca/SecurityServicesFeature.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jca/SecurityServicesFeature.java @@ -320,7 +320,7 @@ public static class Options { private Field classCacheField; private Field constructorCacheField; - private ConcurrentHashMap cachedVerificationCache; + private final ConcurrentHashMap cachedVerificationCache = emptyVerificationCache(); private ProviderList cachedProviders; private Class jceSecurityClass; @@ -560,32 +560,14 @@ public Object transform(Object receiver, Object originalValue) { } }); - access.registerFieldValueTransformer(verificationResultsField, new FieldValueTransformerWithAvailability() { - // JVMCI migration blocked by GR-72131: Refactor security service code for project - // Terminus. - /* - * We must wait until all providers have been registered before filtering the list. - */ - @Override - public boolean isAvailable() { - return BuildPhaseProvider.isHostedUniverseBuilt(); - } - - @Override - public Object transform(Object receiver, Object originalValue) { - if (cachedVerificationCache != null) { - if (SubstrateUtil.assertionsEnabled()) { - var emptyCache = emptyVerificationCache(); - assert cachedVerificationCache.equals(emptyCache) : Assertions.errorMessage(cachedVerificationCache, emptyCache); - } - } - /* - * This object is manually rescanned during analysis to ensure its entire type - * structure is part of the analysis universe. - */ - return cachedVerificationCache; - } - }); + /* + * Verification outcomes are preserved separately by provider class, so this JDK cache + * is always empty. Keep one stable, immediately available object: application-layer + * constant relinking reads static-final fields before the hosted universe is built. + * The object is manually rescanned during analysis to ensure its entire type structure + * is part of the analysis universe. + */ + access.registerFieldValueTransformer(verificationResultsField, (_, _) -> cachedVerificationCache); } } @@ -1254,11 +1236,7 @@ public void duringAnalysis(DuringAnalysisAccess a) { private void maybeScanVerificationResultsField(DuringAnalysisAccessImpl access) { if (access.getMetaAccess().lookupJavaField(verificationResultsField).isRead()) { - var emptyCache = emptyVerificationCache(); - if (cachedVerificationCache == null || !cachedVerificationCache.equals(emptyCache)) { - cachedVerificationCache = emptyCache; - access.rescanObject(cachedVerificationCache, scanReason); - } + access.rescanObject(cachedVerificationCache, scanReason); } } From 92f3dea232fc7f4cf08b085630d88b80532653e5 Mon Sep 17 00:00:00 2001 From: Vojin Jovanovic Date: Wed, 29 Jul 2026 17:23:43 +0200 Subject: [PATCH 53/63] GR-69858: Fix security service native test options --- ...urityServiceRuntimeInitializationTest.java | 40 ++++++++++++++----- .../test/services/SecurityServiceTest.java | 6 ++- 2 files changed, 35 insertions(+), 11 deletions(-) diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/services/SecurityServiceRuntimeInitializationTest.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/services/SecurityServiceRuntimeInitializationTest.java index 7fa628b884ef..47d19134069d 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/services/SecurityServiceRuntimeInitializationTest.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/services/SecurityServiceRuntimeInitializationTest.java @@ -45,7 +45,10 @@ @NativeImageBuildArgs({ "--future-defaults=run-time-initialize-security-providers", - "-H:AdditionalSecurityServiceTypes=JCACompliantNoOpService" + "--exact-reachability-metadata=com.oracle.svm.test.services", + "-H:+UnlockExperimentalVMOptions", + "-H:AdditionalSecurityServiceTypes=com.oracle.svm.test.services.SecurityServiceTest$JCACompliantNoOpService", + "-H:-UnlockExperimentalVMOptions" }) public class SecurityServiceRuntimeInitializationTest { private static final String OMITTED_PROVIDER_ALGORITHM = "SHA256withECDSA"; @@ -87,11 +90,9 @@ public void testUnknownSecurityServices() throws Exception { @Test public void testServiceLoaderProviderWithoutMetadataUsesReflectionLookupFailure() { Assert.assertTrue(ImageInfo.inImageRuntimeCode()); - Assert.assertThrows(ClassNotFoundException.class, () -> Class.forName(SERVICE_LOADED_PROVIDER_CLASS_NAME)); - ServiceConfigurationError error = Assert.assertThrows(ServiceConfigurationError.class, () -> ServiceLoader.load(Provider.class).stream() - .anyMatch(provider -> provider.type().getName().equals(SERVICE_LOADED_PROVIDER_CLASS_NAME))); + ServiceConfigurationError error = Assert.assertThrows(ServiceConfigurationError.class, + () -> findServiceLoaderProvider(SERVICE_LOADED_PROVIDER_CLASS_NAME)); Assert.assertTrue(error.getMessage().contains(SERVICE_LOADED_PROVIDER_CLASS_NAME)); - Assert.assertTrue(error.getCause() instanceof ClassNotFoundException); Assert.assertThrows(NoSuchAlgorithmException.class, () -> SecurityServiceTest.JCACompliantNoOpService.getInstance(SERVICE_LOADED_PROVIDER_ALGORITHM)); } @@ -99,14 +100,33 @@ public void testServiceLoaderProviderWithoutMetadataUsesReflectionLookupFailure( /** §FS-security-providers.7.2: Tests preserved metadata-registered service providers. */ @Test public void testServiceLoaderProviderWithMetadataIsPreserved() { - Provider provider = ServiceLoader.load(Provider.class).stream() - .filter(candidate -> candidate.type().getName().equals(REFLECTION_METADATA_PROVIDER_CLASS_NAME)) - .findFirst() - .orElseThrow(() -> new AssertionError("Metadata-registered security provider should be visible through ServiceLoader.")) - .get(); + Provider provider = findServiceLoaderProvider(REFLECTION_METADATA_PROVIDER_CLASS_NAME).get(); Assert.assertEquals(REFLECTION_METADATA_PROVIDER_NAME, provider.getName()); } + private static ServiceLoader.Provider findServiceLoaderProvider(String providerClassName) { + Iterator> providers = ServiceLoader.load(Provider.class).stream().iterator(); + while (true) { + try { + if (!providers.hasNext()) { + throw new AssertionError("Security provider should be visible through ServiceLoader: " + providerClassName); + } + ServiceLoader.Provider provider = providers.next(); + if (provider.type().getName().equals(providerClassName)) { + return provider; + } + } catch (ServiceConfigurationError error) { + if (error.getMessage().contains(providerClassName)) { + throw error; + } + /* + * Some JDK modules contribute providers whose implementation module is not in + * the image. They are unrelated to the provider selected by this test. + */ + } + } + } + /** §FS-security-providers.7.3: Tests compatibility-mode provider inclusion. */ @Test public void testReachableBuiltInProviderIsIncluded() { diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/services/SecurityServiceTest.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/services/SecurityServiceTest.java index f9a4dc8d5ef4..7b11b2db0fec 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/services/SecurityServiceTest.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/services/SecurityServiceTest.java @@ -62,7 +62,11 @@ /** * Tests the {@code SecurityServicesFeature}. */ -@NativeImageBuildArgs("-H:AdditionalSecurityServiceTypes=JCACompliantNoOpService") +@NativeImageBuildArgs({ + "-H:+UnlockExperimentalVMOptions", + "-H:AdditionalSecurityServiceTypes=com.oracle.svm.test.services.SecurityServiceTest$JCACompliantNoOpService", + "-H:-UnlockExperimentalVMOptions" +}) public class SecurityServiceTest { private static final String REFLECTION_METADATA_PROVIDER_NAME = "reflection-metadata-provider"; private static final String REFLECTION_METADATA_PROVIDER_ALGORITHM = "reflection-metadata-algo"; From 7457c8713d2e8b63aa45ad1a23a83ce5dcf4bde0 Mon Sep 17 00:00:00 2001 From: Vojin Jovanovic Date: Wed, 29 Jul 2026 18:48:07 +0200 Subject: [PATCH 54/63] GR-69858: Preserve application provider verification --- .../oracle/svm/hosted/jca/SecurityServicesFeature.java | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jca/SecurityServicesFeature.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jca/SecurityServicesFeature.java index d134129a495d..f06041a784db 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jca/SecurityServicesFeature.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jca/SecurityServicesFeature.java @@ -974,7 +974,14 @@ private static void registerSpiClass(Method getSpiClassMethod, String serviceTyp } private Object getProviderVerificationResult(Provider provider) { - // §FS-security-providers.5.3: Preserve the build-time outcome by provider class. + /* + * §FS-security-providers.5.3: Class registration establishes successful verification for + * an application-supplied provider that is not in the build-time configured provider list. + */ + if (!buildTimeProvidersByClassName.containsKey(provider.getClass().getName())) { + return Boolean.TRUE; + } + // Preserve the build-time outcome of configured providers by provider class. try { Method getVerificationResult = ReflectionUtil.lookupMethod(jceSecurityClass, "getVerificationResult", Provider.class); /* From 444165dc56039c3e8aa324bc466ef0cbcb50539b Mon Sep 17 00:00:00 2001 From: Vojin Jovanovic Date: Thu, 30 Jul 2026 12:46:05 +0200 Subject: [PATCH 55/63] GR-69858: Conditionally register security provider construction --- .../hosted/jca/SecurityServicesFeature.java | 33 +++++++++++++++++-- 1 file changed, 31 insertions(+), 2 deletions(-) diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jca/SecurityServicesFeature.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jca/SecurityServicesFeature.java index f06041a784db..b8e9a7f70a5c 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jca/SecurityServicesFeature.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jca/SecurityServicesFeature.java @@ -82,8 +82,10 @@ import org.graalvm.collections.EconomicSet; import org.graalvm.nativeimage.ImageSingletons; +import org.graalvm.nativeimage.dynamicaccess.AccessCondition; import org.graalvm.nativeimage.hosted.RuntimeReflection; import org.graalvm.nativeimage.impl.RuntimeClassInitializationSupport; +import org.graalvm.nativeimage.impl.RuntimeReflectionSupport; import com.oracle.graal.pointsto.constraints.UnsupportedPlatformException; import com.oracle.graal.pointsto.meta.AnalysisMethod; @@ -96,6 +98,7 @@ import com.oracle.svm.core.jdk.JNIRegistrationUtil; import com.oracle.svm.core.jdk.NativeLibrarySupport; import com.oracle.svm.core.jdk.PlatformNativeLibrarySupport; +import com.oracle.svm.core.jdk.SecurityProviderRuntimeAccess; import com.oracle.svm.core.jdk.SecurityProviderRuntimeState; import com.oracle.svm.core.jdk.SecuritySubstitutions; import com.oracle.svm.hosted.FeatureImpl.BeforeAnalysisAccessImpl; @@ -104,6 +107,7 @@ import com.oracle.svm.hosted.ImageClassLoader; import com.oracle.svm.hosted.analysis.Inflation; import com.oracle.svm.hosted.c.NativeLibraries; +import com.oracle.svm.hosted.classinitialization.ClassInitializationSupport; import com.oracle.svm.hosted.substitute.DeletedElementException; import com.oracle.svm.hosted.substitute.AnnotationSubstitutionProcessor; import com.oracle.svm.shared.BuildPhaseProvider; @@ -333,6 +337,7 @@ public static class Options { private SecurityProviderCatalogRegistrar catalogRegistrar; private ReflectionRegistrationView reflectionRegistrationView; private boolean preserveAll; + private boolean registerProviderTypeReachedTracking; @Override public void afterRegistration(AfterRegistrationAccess a) { @@ -361,6 +366,11 @@ public void registerService(DuringAnalysisAccess access, Service service) { @Override public void registerSelectedConstructionPath(DuringAnalysisAccess access, Class providerClass) { if (mode.explicitRegistration()) { + /* §FS-security-providers.7.1: + * The run-time provider-list loader invokes this path from a different module. + * Open non-exported JDK provider packages only to that loader. + */ + ModuleSupport.accessModuleByClass(ModuleSupport.Access.OPEN, SecurityProviderRuntimeAccess.class, providerClass); SecurityServicesFeature.registerSelectedConstructionPath(providerClass); } else { /* §FS-security-providers.7.3: @@ -497,17 +507,31 @@ public void beforeAnalysis(BeforeAnalysisAccess a) { */ access.ensureInitialized("sun.security.util.AnchorCertificates"); + registerProviderTypeReachedTracking = mode.explicitRegistration(); initializeServiceRegistrationData(); preserveAll = access.getImageClassLoader().classLoaderSupport.isPreserveAll(); reflectionRegistrationView = ReflectionRegistrationView.singleton(); access.registerSubtypeReachabilityHandler((_, providerClass) -> addCandidateProviderClass(providerClass), Provider.class); registerServiceProviderCandidates(access); + if (mode.explicitRegistration()) { + /* §FS-security-providers.2.1 and §FS-security-providers.7.3: + * Provider selection happens during analysis. Candidate discovery above also loads + * descriptor-only provider classes. Pre-register only type-reached tracking here so + * selected construction paths can remain conditional without retaining unselected + * providers. + */ + for (Class providerClass : loader.findSubclasses(Provider.class, false)) { + ClassInitializationSupport.singleton().addForTypeReachedTracking(providerClass); + } + } LegacySecurityProviderCompatibility.registerAdditionalProviders(access, providerClass -> { if (shouldRegisterProviderClassForReflection(access, providerClass)) { + addCandidateProviderClass(providerClass); providerPlanner.requestCompleteProvider(providerClass, SecurityProviderRegistrationPlanner.Source.LEGACY_ADDITIONAL_PROVIDER); registerProviderClassForReflection(providerClass); } }); + registerProviderTypeReachedTracking = false; if (Options.EnableSecurityServicesFeature.getValue()) { registerServiceReachabilityHandlers(access); } @@ -798,6 +822,9 @@ private void registerServiceProviderCandidates(BeforeAnalysisAccess access) { } private void addCandidateProviderClass(Class providerClass) { + if (registerProviderTypeReachedTracking) { + ClassInitializationSupport.singleton().addForTypeReachedTracking(providerClass); + } providerPlanner.addCandidate(providerClass); } @@ -1139,13 +1166,15 @@ private static void registerProviderClassForReflection(Class providerClass) { } private static void registerSelectedConstructionPath(Class providerClass) { + AccessCondition condition = AccessCondition.typeReached(providerClass); + RuntimeReflectionSupport reflection = ImageSingletons.lookup(RuntimeReflectionSupport.class); Constructor constructor = findDeclaredNullaryConstructor(providerClass); if (constructor != null) { - RuntimeReflection.register(constructor); + reflection.register(condition, false, constructor); } else { Method providerMethod = findProviderMethod(providerClass); if (providerMethod != null) { - RuntimeReflection.register(providerMethod); + reflection.register(condition, false, providerMethod); } } } From 495ab4181bea2b2bd3eb07389993ff63ccad69fa Mon Sep 17 00:00:00 2001 From: Vojin Jovanovic Date: Thu, 30 Jul 2026 17:24:18 +0200 Subject: [PATCH 56/63] GR-69858: Scope provider construction registration to selection --- .../hosted/jca/SecurityServicesFeature.java | 28 +++---------------- 1 file changed, 4 insertions(+), 24 deletions(-) diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jca/SecurityServicesFeature.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jca/SecurityServicesFeature.java index b8e9a7f70a5c..f7b86f6fa4aa 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jca/SecurityServicesFeature.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jca/SecurityServicesFeature.java @@ -85,7 +85,6 @@ import org.graalvm.nativeimage.dynamicaccess.AccessCondition; import org.graalvm.nativeimage.hosted.RuntimeReflection; import org.graalvm.nativeimage.impl.RuntimeClassInitializationSupport; -import org.graalvm.nativeimage.impl.RuntimeReflectionSupport; import com.oracle.graal.pointsto.constraints.UnsupportedPlatformException; import com.oracle.graal.pointsto.meta.AnalysisMethod; @@ -105,9 +104,9 @@ import com.oracle.svm.hosted.FeatureImpl.DuringAnalysisAccessImpl; import com.oracle.svm.hosted.FeatureImpl.DuringSetupAccessImpl; import com.oracle.svm.hosted.ImageClassLoader; +import com.oracle.svm.hosted.InternalReflectiveAccess; import com.oracle.svm.hosted.analysis.Inflation; import com.oracle.svm.hosted.c.NativeLibraries; -import com.oracle.svm.hosted.classinitialization.ClassInitializationSupport; import com.oracle.svm.hosted.substitute.DeletedElementException; import com.oracle.svm.hosted.substitute.AnnotationSubstitutionProcessor; import com.oracle.svm.shared.BuildPhaseProvider; @@ -337,7 +336,6 @@ public static class Options { private SecurityProviderCatalogRegistrar catalogRegistrar; private ReflectionRegistrationView reflectionRegistrationView; private boolean preserveAll; - private boolean registerProviderTypeReachedTracking; @Override public void afterRegistration(AfterRegistrationAccess a) { @@ -369,7 +367,7 @@ public void registerSelectedConstructionPath(DuringAnalysisAccess access, Class< /* §FS-security-providers.7.1: * The run-time provider-list loader invokes this path from a different module. * Open non-exported JDK provider packages only to that loader. - */ + */ ModuleSupport.accessModuleByClass(ModuleSupport.Access.OPEN, SecurityProviderRuntimeAccess.class, providerClass); SecurityServicesFeature.registerSelectedConstructionPath(providerClass); } else { @@ -507,23 +505,11 @@ public void beforeAnalysis(BeforeAnalysisAccess a) { */ access.ensureInitialized("sun.security.util.AnchorCertificates"); - registerProviderTypeReachedTracking = mode.explicitRegistration(); initializeServiceRegistrationData(); preserveAll = access.getImageClassLoader().classLoaderSupport.isPreserveAll(); reflectionRegistrationView = ReflectionRegistrationView.singleton(); access.registerSubtypeReachabilityHandler((_, providerClass) -> addCandidateProviderClass(providerClass), Provider.class); registerServiceProviderCandidates(access); - if (mode.explicitRegistration()) { - /* §FS-security-providers.2.1 and §FS-security-providers.7.3: - * Provider selection happens during analysis. Candidate discovery above also loads - * descriptor-only provider classes. Pre-register only type-reached tracking here so - * selected construction paths can remain conditional without retaining unselected - * providers. - */ - for (Class providerClass : loader.findSubclasses(Provider.class, false)) { - ClassInitializationSupport.singleton().addForTypeReachedTracking(providerClass); - } - } LegacySecurityProviderCompatibility.registerAdditionalProviders(access, providerClass -> { if (shouldRegisterProviderClassForReflection(access, providerClass)) { addCandidateProviderClass(providerClass); @@ -531,7 +517,6 @@ public void beforeAnalysis(BeforeAnalysisAccess a) { registerProviderClassForReflection(providerClass); } }); - registerProviderTypeReachedTracking = false; if (Options.EnableSecurityServicesFeature.getValue()) { registerServiceReachabilityHandlers(access); } @@ -822,9 +807,6 @@ private void registerServiceProviderCandidates(BeforeAnalysisAccess access) { } private void addCandidateProviderClass(Class providerClass) { - if (registerProviderTypeReachedTracking) { - ClassInitializationSupport.singleton().addForTypeReachedTracking(providerClass); - } providerPlanner.addCandidate(providerClass); } @@ -1166,15 +1148,13 @@ private static void registerProviderClassForReflection(Class providerClass) { } private static void registerSelectedConstructionPath(Class providerClass) { - AccessCondition condition = AccessCondition.typeReached(providerClass); - RuntimeReflectionSupport reflection = ImageSingletons.lookup(RuntimeReflectionSupport.class); Constructor constructor = findDeclaredNullaryConstructor(providerClass); if (constructor != null) { - reflection.register(condition, false, constructor); + InternalReflectiveAccess.singleton().register(AccessCondition.unconditional(), constructor); } else { Method providerMethod = findProviderMethod(providerClass); if (providerMethod != null) { - reflection.register(condition, false, providerMethod); + InternalReflectiveAccess.singleton().register(AccessCondition.unconditional(), providerMethod); } } } From 6b2d18e5caf46eb673ec000344a8b2d54188de7a Mon Sep 17 00:00:00 2001 From: Vojin Jovanovic Date: Sat, 1 Aug 2026 00:30:17 +0200 Subject: [PATCH 57/63] GR-69858: Support JCE callers and duplicate provider names --- .../docs/functional-spec/security-providers.md | 3 +++ .../svm/core/jdk/SecurityProviderRuntimeState.java | 13 ++++++++++--- .../oracle/svm/core/jdk/SecuritySubstitutions.java | 10 ++++++++++ 3 files changed, 23 insertions(+), 3 deletions(-) diff --git a/substratevm/docs/functional-spec/security-providers.md b/substratevm/docs/functional-spec/security-providers.md index 95da628dae1b..7358e8f75ff3 100644 --- a/substratevm/docs/functional-spec/security-providers.md +++ b/substratevm/docs/functional-spec/security-providers.md @@ -386,6 +386,9 @@ At run time, Native Image loads that resolved provider directly through its regi nullary construction path. It does not scan or instantiate unrelated provider descriptors while resolving the configured name. +Provider names do not globally identify provider classes. If multiple registered provider classes +report the same name, Native Image retains their class-based registration but does not treat that +name as an unambiguous configured-provider-to-class mapping. An unregistered provider is not added to the list, and its services remain unavailable. Filtering unregistered providers preserves the ordering and lookup results specified in sections 1.3, 3.2, and 4. diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SecurityProviderRuntimeState.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SecurityProviderRuntimeState.java index ac8fc38044fc..e5594a042f39 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SecurityProviderRuntimeState.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SecurityProviderRuntimeState.java @@ -52,6 +52,7 @@ public record ProviderInfo(AcquisitionKind acquisitionKind, Exception verificati private final EconomicMap providerInfos = ImageHeapMap.createNonLayeredMap(); private final EconomicMap configuredProviderClassNames = ImageHeapMap.createNonLayeredMap(); + private final EconomicMap ambiguousConfiguredProviderNames = ImageHeapMap.createNonLayeredMap(); private Properties savedInitialSecurityProperties; private Constructor sunECConstructor; @@ -88,9 +89,12 @@ private synchronized void registerProvider(String providerClassName, Acquisition @Platforms(Platform.HOSTED_ONLY.class) public synchronized void registerConfiguredProviderName(String providerName, String providerClassName) { String previousClassName = configuredProviderClassNames.get(providerName); - assert previousClassName == null || previousClassName.equals(providerClassName) : providerName + - " maps to both " + previousClassName + " and " + providerClassName; - configuredProviderClassNames.put(providerName, providerClassName); + if (previousClassName == null) { + configuredProviderClassNames.put(providerName, providerClassName); + } else if (!previousClassName.equals(providerClassName)) { + /* §FS-security-providers.7.1: Provider names are not globally unique class keys. */ + ambiguousConfiguredProviderNames.put(providerName, true); + } } private static ProviderInfo merge(ProviderInfo oldInfo, ProviderInfo newInfo) { @@ -125,6 +129,9 @@ public static boolean isJdkConstructible(String providerClassName) { public static String getConfiguredProviderClassName(String providerName) { String result = null; for (SecurityProviderRuntimeState state : singletons()) { + if (Boolean.TRUE.equals(state.ambiguousConfiguredProviderNames.get(providerName))) { + return null; + } String providerClassName = state.configuredProviderClassNames.get(providerName); if (providerClassName != null) { if (result != null && !result.equals(providerClassName)) { diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SecuritySubstitutions.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SecuritySubstitutions.java index d3fc156bb576..ca39e0046bf0 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SecuritySubstitutions.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SecuritySubstitutions.java @@ -84,6 +84,16 @@ final class Target_javax_crypto_JceSecurityManager { Target_javax_crypto_CryptoPermission getCryptoPermission(String var1) { return SubstrateUtil.cast(Target_javax_crypto_CryptoAllPermission.INSTANCE, Target_javax_crypto_CryptoPermission.class); } + + /** + * Native Image cannot perform the JAR verification used by the JDK to establish caller trust. + * All callers embedded in an image are trusted; provider verification remains enforced + * separately by {@link JceProviderVerificationSupport}. + */ + @Substitute + boolean isCallerTrusted(Class callerClass, Provider provider) { + return true; + } } @TargetClass(className = "javax.crypto.CryptoPermission") From e1748fa234c5c937fd003581f94e132994f90579 Mon Sep 17 00:00:00 2001 From: Vojin Jovanovic Date: Mon, 3 Aug 2026 22:25:54 +0200 Subject: [PATCH 58/63] GR-69858: Trace cached security provider services --- .../functional-spec/security-providers.md | 2 + .../svm/agent/BreakpointInterceptor.java | 62 +++++++++++++++++-- .../agent/NativeImageAgentJNIHandleSet.java | 17 ++++- 3 files changed, 76 insertions(+), 5 deletions(-) diff --git a/substratevm/docs/functional-spec/security-providers.md b/substratevm/docs/functional-spec/security-providers.md index 7358e8f75ff3..af206356587e 100644 --- a/substratevm/docs/functional-spec/security-providers.md +++ b/substratevm/docs/functional-spec/security-providers.md @@ -349,6 +349,8 @@ traced factory calls. This includes a service implementation named only by `Provider.Service.getClassName()`: the caller-filtered trace must retain the construction access performed inside `Provider.Service.newInstance` and attribute it to the application operation that selected the service. +Tracing must retain that construction access when `Provider.Service` reuses its cached +implementation class and therefore performs no subsequent reflective class lookup. The trace may locate `Provider.Service.newInstance` through contiguous helper frames declared by `Provider.Service`, but it must not cross a frame declared by another class. diff --git a/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/BreakpointInterceptor.java b/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/BreakpointInterceptor.java index ecc3111c1a5e..72d92d85b41b 100644 --- a/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/BreakpointInterceptor.java +++ b/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/BreakpointInterceptor.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2021, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -763,14 +763,68 @@ private static boolean getSecurityServiceForProvider(JNIEnvironment jni, JNIObje /** §FS-security-providers.6.1: Trace the provider selected for service instantiation. */ private static boolean newSecurityServiceInstance(JNIEnvironment jni, JNIObjectHandle thread, @SuppressWarnings("unused") Breakpoint bp, InterceptedState state) { JNIObjectHandle service = getReceiver(thread); + JNIObjectHandle callerClass = findExternalSecurityCaller(jni, state, 1); + JNIObjectHandle effectiveCallerClass = callerClass.notEqual(nullHandle()) ? callerClass : state.getDirectCallerClass(); + traceCachedSecurityServiceImplementation(jni, thread, service, effectiveCallerClass, state); JNIObjectHandle provider = Support.callObjectMethod(jni, service, agent.handles().javaSecurityProviderServiceGetProvider); boolean validResult = !clearException(jni) && provider.notEqual(nullHandle()); - JNIObjectHandle callerClass = findExternalSecurityCaller(jni, state, 1); - traceSecurityProvider(jni, provider, validResult, - callerClass.notEqual(nullHandle()) ? callerClass : state.getDirectCallerClass(), state); + traceSecurityProvider(jni, provider, validResult, effectiveCallerClass, state); return true; } + /** §FS-security-providers.6.1: Retain a service implementation cached by Provider.Service. */ + private static void traceCachedSecurityServiceImplementation(JNIEnvironment jni, JNIObjectHandle thread, JNIObjectHandle service, JNIObjectHandle callerClass, + InterceptedState state) { + NativeImageAgentJNIHandleSet handles = agent.handles(); + if (handles.javaSecurityProviderServiceClassCache.isNull() || + handles.javaSecurityProviderServiceEngineDescription.isNull() || + handles.javaSecurityProviderEngineDescriptionConstructorParameterClass.isNull()) { + return; + } + + JNIObjectHandle cachedClass = jniFunctions().getGetObjectField().invoke(jni, service, handles.javaSecurityProviderServiceClassCache); + if (clearException(jni) || cachedClass.equal(nullHandle())) { + return; + } + if (!jniFunctions().getIsInstanceOf().invoke(jni, cachedClass, handles.javaLangClass)) { + if (!jniFunctions().getIsInstanceOf().invoke(jni, cachedClass, handles.javaLangRefReference)) { + return; + } + cachedClass = Support.callObjectMethod(jni, cachedClass, handles.javaLangRefReferenceGet); + if (clearException(jni) || cachedClass.equal(nullHandle()) || + !jniFunctions().getIsInstanceOf().invoke(jni, cachedClass, handles.javaLangClass)) { + return; + } + } + + JNIObjectHandle parameterClass; + JNIObjectHandle engineDescription = jniFunctions().getGetObjectField().invoke(jni, service, handles.javaSecurityProviderServiceEngineDescription); + if (clearException(jni)) { + return; + } + if (engineDescription.notEqual(nullHandle())) { + parameterClass = jniFunctions().getGetObjectField().invoke(jni, engineDescription, handles.javaSecurityProviderEngineDescriptionConstructorParameterClass); + if (clearException(jni)) { + return; + } + } else { + JNIObjectHandle constructorParameter = getObjectArgument(thread, 0); + parameterClass = constructorParameter.equal(nullHandle()) ? nullHandle() : jniFunctions().getGetObjectClass().invoke(jni, constructorParameter); + } + + String[] parameterTypes; + if (parameterClass.equal(nullHandle())) { + parameterTypes = new String[0]; + } else { + String parameterType = getClassNameOrNull(jni, parameterClass); + if (parameterType == null) { + return; + } + parameterTypes = new String[]{parameterType}; + } + traceReflectBreakpoint(jni, cachedClass, cachedClass, callerClass, "invokeConstructor", true, state.getFullStackTraceOrNull(), (Object) parameterTypes); + } + /** * §FS-security-providers.6.1: Trace a provider that was cached before a Security API lookup. */ diff --git a/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/NativeImageAgentJNIHandleSet.java b/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/NativeImageAgentJNIHandleSet.java index 94c30cef1bc0..fafd68ce8e0b 100644 --- a/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/NativeImageAgentJNIHandleSet.java +++ b/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/NativeImageAgentJNIHandleSet.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -57,9 +57,15 @@ public class NativeImageAgentJNIHandleSet extends JNIHandleSet { final JNIMethodId javaLangObjectGetClass; final JNIMethodId javaLangObjectToString; + final JNIObjectHandle javaLangRefReference; + final JNIMethodId javaLangRefReferenceGet; + final JNIObjectHandle javaSecurityProviderService; final JNIMethodId javaSecurityProviderServiceGetProvider; final JNIMethodId javaSecurityProviderServiceNewInstance; + final JNIFieldId javaSecurityProviderServiceClassCache; + final JNIFieldId javaSecurityProviderServiceEngineDescription; + final JNIFieldId javaSecurityProviderEngineDescriptionConstructorParameterClass; final JNIMethodId javaSecurityProviderGetName; final JNIMethodId javaSecurityGetProvider; final JNIMethodId javaSecurityGetProviders; @@ -177,9 +183,18 @@ public class NativeImageAgentJNIHandleSet extends JNIHandleSet { javaLangObjectGetClass = getMethodId(env, javaLangObject, "getClass", "()Ljava/lang/Class;", false); javaLangObjectToString = getMethodId(env, javaLangObject, "toString", "()Ljava/lang/String;", false); + javaLangRefReference = newClassGlobalRef(env, "java/lang/ref/Reference"); + javaLangRefReferenceGet = getMethodId(env, javaLangRefReference, "get", "()Ljava/lang/Object;", false); + javaSecurityProviderService = newClassGlobalRef(env, "java/security/Provider$Service"); javaSecurityProviderServiceGetProvider = getMethodId(env, javaSecurityProviderService, "getProvider", "()Ljava/security/Provider;", false); javaSecurityProviderServiceNewInstance = getMethodId(env, javaSecurityProviderService, "newInstance", "(Ljava/lang/Object;)Ljava/lang/Object;", false); + javaSecurityProviderServiceClassCache = getFieldIdOptional(env, javaSecurityProviderService, "classCache", "Ljava/lang/Object;", false); + javaSecurityProviderServiceEngineDescription = getFieldIdOptional(env, javaSecurityProviderService, "engineDescription", "Ljava/security/Provider$EngineDescription;", false); + JNIObjectHandle javaSecurityProviderEngineDescription = findClassOptional(env, "java/security/Provider$EngineDescription"); + javaSecurityProviderEngineDescriptionConstructorParameterClass = javaSecurityProviderEngineDescription.equal(nullHandle()) + ? WordFactory.nullPointer() + : getFieldIdOptional(env, javaSecurityProviderEngineDescription, "constructorParameterClass", "Ljava/lang/Class;", false); JNIObjectHandle javaSecurityProvider = findClass(env, "java/security/Provider"); javaSecurityProviderGetName = getMethodId(env, javaSecurityProvider, "getName", "()Ljava/lang/String;", false); From 2676d9dfee0d9b818053d650d5cc5c575ac89eec Mon Sep 17 00:00:00 2001 From: Vojin Jovanovic Date: Tue, 4 Aug 2026 12:14:34 +0200 Subject: [PATCH 59/63] Fix Eclipse formatting --- .../src/com/oracle/svm/hosted/reflect/ReflectionDataBuilder.java | 1 + 1 file changed, 1 insertion(+) diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/ReflectionDataBuilder.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/ReflectionDataBuilder.java index 5ccc0200eb7f..d86e0a724703 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/ReflectionDataBuilder.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/ReflectionDataBuilder.java @@ -1673,6 +1673,7 @@ private static Set g .collect(Collectors.toUnmodifiableSet()); } } + public static class TestBackdoor { public static void registerField(ReflectionDataBuilder reflectionDataBuilder, ConfigurationMemberAccessibility accessibility, Field field) { reflectionDataBuilder.registerField(unconditional(), accessibility, false, GuestAccess.get().lookupField(field)); From e070c88d9710f6e46b3e0e6b91e2fb6618733bb1 Mon Sep 17 00:00:00 2001 From: Vojin Jovanovic Date: Wed, 5 Aug 2026 15:10:14 +0200 Subject: [PATCH 60/63] GR-69858: Trace selected JCE service implementations --- substratevm/mx.substratevm/mx_substratevm.py | 2 + .../svm/agent/BreakpointInterceptor.java | 72 +++++++++++++++++++ .../agent/NativeImageAgentJNIHandleSet.java | 2 + .../config/SecurityProviderAgentTest.java | 41 +++++++++++ .../SecurityProviderAgentVerifierTest.java | 15 ++++ 5 files changed, 132 insertions(+) diff --git a/substratevm/mx.substratevm/mx_substratevm.py b/substratevm/mx.substratevm/mx_substratevm.py index fef5975f5e35..75fa9f4e39e1 100644 --- a/substratevm/mx.substratevm/mx_substratevm.py +++ b/substratevm/mx.substratevm/mx_substratevm.py @@ -947,6 +947,8 @@ def run_agent_security_provider_config_test(agent_path): 'verifyMutationRecordedOnlySuppliedProvider'), ('service-construction', 'providerServiceHelpersRetainConstructorMetadata', 'verifyProviderServiceConstructorWasRecorded'), + ('service-selection', 'jceServiceSelectionRetainsConstructorMetadata', + 'verifySelectedJceServiceConstructorWasRecorded'), ] for name, generator_method, verifier_method in cases: config_dir = join(svmbuild_dir(), 'security-provider-agent-' + name + '-test-config') diff --git a/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/BreakpointInterceptor.java b/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/BreakpointInterceptor.java index 72d92d85b41b..f8549832b588 100644 --- a/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/BreakpointInterceptor.java +++ b/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/BreakpointInterceptor.java @@ -825,6 +825,76 @@ private static void traceCachedSecurityServiceImplementation(JNIEnvironment jni, traceReflectBreakpoint(jni, cachedClass, cachedClass, callerClass, "invokeConstructor", true, state.getFullStackTraceOrNull(), (Object) parameterTypes); } + /** + * Retains a JCE service selected before provider verification. Native Image establishes a + * successful JCE verification outcome for a registered application-supplied provider, while + * HotSpot can reject the same unsigned provider before {@link java.security.Provider.Service} + * reaches {@code newInstance}. Record the constructor that the native executable will use + * without loading or caching the implementation class in the traced JVM. + */ + private static boolean getSecurityServiceProviderForJceSelection(JNIEnvironment jni, JNIObjectHandle thread, @SuppressWarnings("unused") Breakpoint bp, InterceptedState state) { + JNIObjectHandle callerClass = state.getDirectCallerClass(); + String callerClassName = getClassNameOrNull(jni, callerClass); + if (callerClassName == null || !callerClassName.startsWith("javax.crypto.")) { + return true; + } + + JNIObjectHandle applicationCallerClass = findExternalSecurityCaller(jni, state, 1); + if (applicationCallerClass.equal(nullHandle())) { + return true; + } + + traceSelectedJceServiceImplementation(jni, getReceiver(thread), applicationCallerClass, state); + return true; + } + + private static void traceSelectedJceServiceImplementation(JNIEnvironment jni, JNIObjectHandle service, JNIObjectHandle callerClass, InterceptedState state) { + if (tracer == null) { + return; + } + NativeImageAgentJNIHandleSet handles = agent.handles(); + if (handles.javaSecurityProviderServiceEngineDescription.isNull() || + handles.javaSecurityProviderEngineDescriptionConstructorParameterClass.isNull()) { + return; + } + + JNIObjectHandle engineDescription = jniFunctions().getGetObjectField().invoke(jni, service, handles.javaSecurityProviderServiceEngineDescription); + if (clearException(jni) || engineDescription.equal(nullHandle())) { + /* The constructor contract of an unknown engine cannot be inferred safely. */ + return; + } + + JNIObjectHandle parameterClass = jniFunctions().getGetObjectField().invoke(jni, engineDescription, handles.javaSecurityProviderEngineDescriptionConstructorParameterClass); + if (clearException(jni)) { + return; + } + + String[] parameterTypes; + if (parameterClass.equal(nullHandle())) { + parameterTypes = new String[0]; + } else { + String parameterType = getClassNameOrNull(jni, parameterClass); + if (parameterType == null) { + return; + } + parameterTypes = new String[]{parameterType}; + } + + JNIObjectHandle classNameHandle = Support.callObjectMethod(jni, service, handles.javaSecurityProviderServiceGetClassName); + if (clearException(jni)) { + return; + } + String className = fromJniString(jni, classNameHandle); + if (className == null || !ClassNameSupport.isValidReflectionName(className)) { + return; + } + + NamedConfigurationTypeDescriptor implementationType = NamedConfigurationTypeDescriptor.fromReflectionName(className); + tracer.traceCall("reflect", "invokeConstructor", implementationType, implementationType, + getClassNameOr(jni, callerClass, null, Tracer.UNKNOWN_VALUE), true, state.getFullStackTraceOrNull(), (Object) parameterTypes); + clearException(jni); + } + /** * §FS-security-providers.6.1: Trace a provider that was cached before a Security API lookup. */ @@ -2109,6 +2179,8 @@ private interface BreakpointHandler { BreakpointInterceptor::getSecurityServiceForProvider), brk("java/security/Provider$Service", "newInstance", "(Ljava/lang/Object;)Ljava/lang/Object;", BreakpointInterceptor::newSecurityServiceInstance), + brk("java/security/Provider$Service", "getProvider", "()Ljava/security/Provider;", + BreakpointInterceptor::getSecurityServiceProviderForJceSelection), optionalBrk("sun/security/jca/ProviderConfig", "getProvider", "()Ljava/security/Provider;", BreakpointInterceptor::getCachedSecurityProvider), diff --git a/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/NativeImageAgentJNIHandleSet.java b/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/NativeImageAgentJNIHandleSet.java index fafd68ce8e0b..b9179da8e36a 100644 --- a/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/NativeImageAgentJNIHandleSet.java +++ b/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/NativeImageAgentJNIHandleSet.java @@ -61,6 +61,7 @@ public class NativeImageAgentJNIHandleSet extends JNIHandleSet { final JNIMethodId javaLangRefReferenceGet; final JNIObjectHandle javaSecurityProviderService; + final JNIMethodId javaSecurityProviderServiceGetClassName; final JNIMethodId javaSecurityProviderServiceGetProvider; final JNIMethodId javaSecurityProviderServiceNewInstance; final JNIFieldId javaSecurityProviderServiceClassCache; @@ -187,6 +188,7 @@ public class NativeImageAgentJNIHandleSet extends JNIHandleSet { javaLangRefReferenceGet = getMethodId(env, javaLangRefReference, "get", "()Ljava/lang/Object;", false); javaSecurityProviderService = newClassGlobalRef(env, "java/security/Provider$Service"); + javaSecurityProviderServiceGetClassName = getMethodId(env, javaSecurityProviderService, "getClassName", "()Ljava/lang/String;", false); javaSecurityProviderServiceGetProvider = getMethodId(env, javaSecurityProviderService, "getProvider", "()Ljava/security/Provider;", false); javaSecurityProviderServiceNewInstance = getMethodId(env, javaSecurityProviderService, "newInstance", "(Ljava/lang/Object;)Ljava/lang/Object;", false); javaSecurityProviderServiceClassCache = getFieldIdOptional(env, javaSecurityProviderService, "classCache", "Ljava/lang/Object;", false); diff --git a/substratevm/src/com.oracle.svm.configure.test/src/com/oracle/svm/configure/test/config/SecurityProviderAgentTest.java b/substratevm/src/com.oracle.svm.configure.test/src/com/oracle/svm/configure/test/config/SecurityProviderAgentTest.java index 5bc21aa8dacc..9e0d0a7bfefb 100644 --- a/substratevm/src/com.oracle.svm.configure.test/src/com/oracle/svm/configure/test/config/SecurityProviderAgentTest.java +++ b/substratevm/src/com.oracle.svm.configure.test/src/com/oracle/svm/configure/test/config/SecurityProviderAgentTest.java @@ -49,6 +49,7 @@ public class SecurityProviderAgentTest { private static final String GENERATOR_ENABLED_PROPERTY = SecurityProviderAgentTest.class.getName() + ".generator.enabled"; private static final String KEM_ALGORITHM = "AgentKEM"; + private static final String DELAYED_KEM_ALGORITHM = "AgentDelayedKEM"; /** Tests §FS-security-providers.6.1. */ @Test @@ -93,6 +94,22 @@ public void providerServiceHelpersRetainConstructorMetadata() throws Exception { } } + /** Tests §FS-security-providers.6.1 for selection before service instantiation. */ + @Test + public void jceServiceSelectionRetainsConstructorMetadata() throws Exception { + assumeTrue("Test must be explicitly enabled because it is designed to run under the agent", + Boolean.getBoolean(GENERATOR_ENABLED_PROPERTY)); + + Provider provider = new ProgrammaticallyAddedDelayedKEMProvider(); + int position = Security.addProvider(provider); + try { + Assert.assertTrue("The test provider must be added", position > 0); + Assert.assertNotNull(KEM.getInstance(DELAYED_KEM_ALGORITHM)); + } finally { + Security.removeProvider(provider.getName()); + } + } + static final class ReflectiveProbe { } @@ -115,6 +132,16 @@ static final class ProgrammaticallyAddedKEMProvider extends Provider { } } + static final class ProgrammaticallyAddedDelayedKEMProvider extends Provider { + private static final long serialVersionUID = 1L; + + @SuppressWarnings("deprecation") + ProgrammaticallyAddedDelayedKEMProvider() { + super("AgentDelayedKEMProvider", 1.0, "Provider used to verify pre-instantiation service tracing"); + put("KEM." + DELAYED_KEM_ALGORITHM, DelayedTestKEM.class.getName()); + } + } + public static final class TestKEM implements KEMSpi { @Override public EncapsulatorSpi engineNewEncapsulator(PublicKey publicKey, AlgorithmParameterSpec spec, SecureRandom secureRandom) @@ -128,4 +155,18 @@ public DecapsulatorSpi engineNewDecapsulator(PrivateKey privateKey, AlgorithmPar throw new UnsupportedOperationException(); } } + + public static final class DelayedTestKEM implements KEMSpi { + @Override + public EncapsulatorSpi engineNewEncapsulator(PublicKey publicKey, AlgorithmParameterSpec spec, SecureRandom secureRandom) + throws InvalidAlgorithmParameterException, InvalidKeyException { + throw new UnsupportedOperationException(); + } + + @Override + public DecapsulatorSpi engineNewDecapsulator(PrivateKey privateKey, AlgorithmParameterSpec spec) + throws InvalidAlgorithmParameterException, InvalidKeyException { + throw new UnsupportedOperationException(); + } + } } diff --git a/substratevm/src/com.oracle.svm.configure.test/src/com/oracle/svm/configure/test/config/SecurityProviderAgentVerifierTest.java b/substratevm/src/com.oracle.svm.configure.test/src/com/oracle/svm/configure/test/config/SecurityProviderAgentVerifierTest.java index 6f0db467690f..2f2b6997c61f 100644 --- a/substratevm/src/com.oracle.svm.configure.test/src/com/oracle/svm/configure/test/config/SecurityProviderAgentVerifierTest.java +++ b/substratevm/src/com.oracle.svm.configure.test/src/com/oracle/svm/configure/test/config/SecurityProviderAgentVerifierTest.java @@ -103,6 +103,21 @@ public void verifyProviderServiceConstructorWasRecorded() throws Exception { Assert.assertEquals("ACCESSED", constructorInfo.getAccessibility().toString()); } + @Test + public void verifySelectedJceServiceConstructorWasRecorded() throws Exception { + assumeTrue("Test must be explicitly enabled because it verifies a previous agent run", + Boolean.getBoolean(VERIFIER_ENABLED_PROPERTY)); + + TypeConfiguration reflectionConfiguration = loadActualConfig().getReflectionConfiguration(); + ConfigurationType kemType = reflectionConfiguration.get(UnresolvedAccessCondition.unconditional(), + NamedConfigurationTypeDescriptor.fromReflectionName(SecurityProviderAgentTest.DelayedTestKEM.class.getName())); + Assert.assertNotNull("Missing reflection metadata for the selected KEM service implementation", kemType); + ConfigurationMemberInfo constructorInfo = ConfigurationType.TestBackdoor.getMethodInfoIfPresent( + kemType, new ConfigurationMethod("", "()V")); + Assert.assertNotNull("Missing selected KEM service implementation constructor metadata", constructorInfo); + Assert.assertEquals("ACCESSED", constructorInfo.getAccessibility().toString()); + } + private static void assertRecorded(TypeConfiguration reflectionConfiguration, String className) { Assert.assertNotNull("Missing reflection metadata for " + className, reflectionConfiguration.get(UnresolvedAccessCondition.unconditional(), From 48d7cdef82ef6dba3fa840aefe54c338c184d9e1 Mon Sep 17 00:00:00 2001 From: Vojin Jovanovic Date: Thu, 6 Aug 2026 18:34:30 +0200 Subject: [PATCH 61/63] GR-69858: Trace native provider list mutation --- .../SecurityProviderTracingSubstitutions.java | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SecurityProviderTracingSubstitutions.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SecurityProviderTracingSubstitutions.java index 29661509b8d9..8dd306eb75ce 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SecurityProviderTracingSubstitutions.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SecurityProviderTracingSubstitutions.java @@ -28,6 +28,7 @@ import com.oracle.svm.core.annotate.Substitute; import com.oracle.svm.core.annotate.TargetClass; +import com.oracle.svm.shared.util.BasedOnJDKFile; @TargetClass(value = java.security.Security.class, onlyWith = SecurityProvidersInitializedAtBuildTime.class) final class Target_java_security_Security_ProviderLookup { @@ -40,6 +41,25 @@ public static Provider getProvider(String name) { } +/** Keeps provider mutation tracing active in both provider-list initialization modes. */ +@TargetClass(java.security.Security.class) +final class Target_java_security_Security_ProviderMutation { + /** §FS-security-providers.6.1: Mutation traces only the supplied provider. */ + @Substitute + @BasedOnJDKFile("https://github.com/graalvm/labs-openjdk/blob/jvmci-25.2-b20/src/java.base/share/classes/java/security/Security.java#L469-L478") + public static synchronized int insertProviderAt(Provider provider, int position) { + SecurityProviderRuntimeAccess.traceLookup(provider); + + sun.security.jca.ProviderList providers = sun.security.jca.Providers.getFullProviderList(); + sun.security.jca.ProviderList updatedProviders = sun.security.jca.ProviderList.insertAt(providers, provider, position - 1); + if (providers == updatedProviders) { + return -1; + } + sun.security.jca.Providers.setProviderList(updatedProviders); + return updatedProviders.getIndex(provider.getName()) + 1; + } +} + /** Keeps provider enumeration tracing active in both provider-list initialization modes. */ @TargetClass(java.security.Security.class) final class Target_java_security_Security_ProviderEnumeration { From 19aac29835cf1bb7405c3a4be28193e010e4cf02 Mon Sep 17 00:00:00 2001 From: Vojin Jovanovic Date: Fri, 7 Aug 2026 13:09:02 +0200 Subject: [PATCH 62/63] GR-69858: Retain native provider construction tracing --- .../jdk/SecurityProviderRuntimeAccess.java | 21 ++++++++++++++++--- .../SecurityProviderTracingSubstitutions.java | 6 +++--- .../SecuritySubstitutionRuntimeInit.java | 4 ++-- 3 files changed, 23 insertions(+), 8 deletions(-) diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SecurityProviderRuntimeAccess.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SecurityProviderRuntimeAccess.java index f38a55451f5c..9f96e27d09f8 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SecurityProviderRuntimeAccess.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SecurityProviderRuntimeAccess.java @@ -27,9 +27,11 @@ import java.security.Provider; import java.util.function.Supplier; +import com.oracle.svm.configure.config.ConfigurationMemberInfo; import com.oracle.svm.core.FutureDefaultsOptions; import com.oracle.svm.core.metadata.MetadataTracer; import com.oracle.svm.shared.NeverInline; +import com.oracle.svm.shared.security.SecurityProviderCatalog; public final class SecurityProviderRuntimeAccess { private static final ThreadLocal LOAD_UNREGISTERED_CONFIGURED_PROVIDER = new ThreadLocal<>(); @@ -99,11 +101,24 @@ public static Provider traceLookup(Provider provider) { return provider; } - /** §FS-security-providers.6.1: Enumeration traces every provider returned by the JDK. */ - public static Provider[] traceLookups(Provider[] providers) { + /** §FS-security-providers.6.1: JDK-managed provider lookups retain construction. */ + public static Provider traceJdkProviderLookup(Provider provider) { + if (provider != null && MetadataTracer.enabled()) { + Class providerClass = provider.getClass(); + MetadataTracer tracer = MetadataTracer.singleton(); + tracer.traceReflectionType(providerClass); + if (SecurityProviderCatalog.isDirectlyConstructible(providerClass.getName())) { + tracer.traceMethodAccess(providerClass, "", "()", ConfigurationMemberInfo.ConfigurationMemberDeclaration.DECLARED); + } + } + return provider; + } + + /** §FS-security-providers.6.1: Enumeration traces every JDK-managed provider returned. */ + public static Provider[] traceJdkProviderLookups(Provider[] providers) { if (providers != null && MetadataTracer.enabled()) { for (Provider provider : providers) { - traceLookup(provider); + traceJdkProviderLookup(provider); } } return providers; diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SecurityProviderTracingSubstitutions.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SecurityProviderTracingSubstitutions.java index 8dd306eb75ce..0addc13e62b6 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SecurityProviderTracingSubstitutions.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SecurityProviderTracingSubstitutions.java @@ -33,10 +33,10 @@ @TargetClass(value = java.security.Security.class, onlyWith = SecurityProvidersInitializedAtBuildTime.class) final class Target_java_security_Security_ProviderLookup { - /** §FS-security-providers.6: Successful name-based lookup traces provider type access. */ + /** §FS-security-providers.6: Successful name-based lookup traces provider construction. */ @Substitute public static Provider getProvider(String name) { - return SecurityProviderRuntimeAccess.traceLookup(sun.security.jca.Providers.getProviderList().getProvider(name)); + return SecurityProviderRuntimeAccess.traceJdkProviderLookup(sun.security.jca.Providers.getProviderList().getProvider(name)); } } @@ -65,7 +65,7 @@ public static synchronized int insertProviderAt(Provider provider, int position) final class Target_java_security_Security_ProviderEnumeration { @Substitute public static Provider[] getProviders() { - return SecurityProviderRuntimeAccess.traceLookups(sun.security.jca.Providers.getFullProviderList().toArray()); + return SecurityProviderRuntimeAccess.traceJdkProviderLookups(sun.security.jca.Providers.getFullProviderList().toArray()); } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/runtimeinit/SecuritySubstitutionRuntimeInit.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/runtimeinit/SecuritySubstitutionRuntimeInit.java index d613adaad158..51522b9077f7 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/runtimeinit/SecuritySubstitutionRuntimeInit.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/runtimeinit/SecuritySubstitutionRuntimeInit.java @@ -204,7 +204,7 @@ final class Target_sun_security_jca_ProviderList { public Provider getProvider(String name) { int index = getIndex(name); if (index >= 0) { - return SecurityProviderRuntimeAccess.traceLookup(getProvider(index)); + return SecurityProviderRuntimeAccess.traceJdkProviderLookup(getProvider(index)); } for (Target_sun_security_jca_ProviderConfig config : configs) { String configuredProviderName = config.provName; @@ -213,7 +213,7 @@ public Provider getProvider(String name) { boolean matches = configuredProviderName.equals(name) || (providerName != null && providerName.equals(name)) || (providerFQName != null && providerFQName.equals(name)); if (matches) { Provider provider = SecurityProviderRuntimeAccess.loadUnregisteredConfiguredProvider(config::getProvider); - return SecurityProviderRuntimeAccess.traceLookup(provider); + return SecurityProviderRuntimeAccess.traceJdkProviderLookup(provider); } } return null; From 5aa28315351f810ad7f999beac2a3d800f831e67 Mon Sep 17 00:00:00 2001 From: Vojin Jovanovic Date: Fri, 7 Aug 2026 16:21:20 +0200 Subject: [PATCH 63/63] GR-69858: Trace selected provider services --- .../jdk/SecurityProviderRuntimeAccess.java | 12 +++++++++++ .../SecurityProviderTracingSubstitutions.java | 21 +++++++++++++++++++ 2 files changed, 33 insertions(+) diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SecurityProviderRuntimeAccess.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SecurityProviderRuntimeAccess.java index 9f96e27d09f8..1dfecd3e0b66 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SecurityProviderRuntimeAccess.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SecurityProviderRuntimeAccess.java @@ -101,6 +101,18 @@ public static Provider traceLookup(Provider provider) { return provider; } + /** §FS-security-providers.6.1: Successful service selection traces its provider and SPI. */ + public static void traceServiceSelection(Provider.Service service, Class serviceClass) { + if (MetadataTracer.enabled()) { + Provider provider = service.getProvider(); + MetadataTracer tracer = MetadataTracer.singleton(); + tracer.traceReflectionType(provider.getClass()); + if (serviceClass != null) { + tracer.traceReflectionType(serviceClass); + } + } + } + /** §FS-security-providers.6.1: JDK-managed provider lookups retain construction. */ public static Provider traceJdkProviderLookup(Provider provider) { if (provider != null && MetadataTracer.enabled()) { diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SecurityProviderTracingSubstitutions.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SecurityProviderTracingSubstitutions.java index 0addc13e62b6..d8e832008b5a 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SecurityProviderTracingSubstitutions.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SecurityProviderTracingSubstitutions.java @@ -24,6 +24,7 @@ */ package com.oracle.svm.core.jdk; +import java.security.NoSuchAlgorithmException; import java.security.Provider; import com.oracle.svm.core.annotate.Substitute; @@ -69,5 +70,25 @@ public static Provider[] getProviders() { } } +@TargetClass(className = "sun.security.jca.GetInstance") +final class Target_sun_security_jca_GetInstance_Tracing { + /** + * §FS-security-providers.6.1: The JDK may cache the SPI class before native metadata tracing + * starts. Trace it and the selected provider after successful service construction so the + * collected metadata remains replayable for application-supplied providers. + */ + @Substitute + @BasedOnJDKFile("https://github.com/graalvm/labs-openjdk/blob/jvmci-25.2-b20/src/java.base/share/classes/sun/security/jca/GetInstance.java#L243-L253") + public static void checkSuperClass(Provider.Service service, Class subClass, Class superClass) throws NoSuchAlgorithmException { + if (superClass != null && !superClass.isAssignableFrom(subClass)) { + // Checkstyle: allow inconsistent exceptions and errors (JDK-compatible message) + throw new NoSuchAlgorithmException("class configured for " + service.getType() + ": " + + service.getClassName() + " not a " + service.getType()); + // Checkstyle: disallow inconsistent exceptions and errors + } + SecurityProviderRuntimeAccess.traceServiceSelection(service, superClass); + } +} + public final class SecurityProviderTracingSubstitutions { }