Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

AOT mode with signed JARs fails #29019

Closed
mhalbritter opened this issue Aug 26, 2022 · 13 comments
Closed

AOT mode with signed JARs fails #29019

mhalbritter opened this issue Aug 26, 2022 · 13 comments
Assignees
Labels
status: declined A suggestion or change that we don't feel we should currently apply theme: aot An issue related to Ahead-of-time processing type: enhancement A general enhancement

Comments

@mhalbritter
Copy link
Contributor

mhalbritter commented Aug 26, 2022

Original issue on Spring Native side: spring-attic/spring-native#1699

Hey,

Spring AOT mode currently fails if you're using an auto-configuration from an external JAR which has been signed with jarsigner tool.

I've created a reproducer here: https://github.com/mhalbritter/spring-aot-jarsigner-reproducer

The problem is that dependency.jar contains an auto-configuration named DependencyAutoConfiguration in the dependency package. The dependency.jar has been signed with jarsigner and contains a META-INF/SIGN-KEY.SF file. The AOT mode generates code (dependency.DependencyAutoConfiguration__BeanDefinitions) which uses the same package as in dependency.jar, which is getting included in the main boot JAR. But this JAR doesn't have the same signature on it. This will lead to this exception thrown by the JVM when using gradle bootRun:

java.lang.SecurityException: class "dependency.DependencyAutoConfiguration__BeanDefinitions"'s signer information does not match signer information of other classes in the same package
        at java.base/java.lang.ClassLoader.checkCerts(ClassLoader.java:1158) ~[na:na]
        at java.base/java.lang.ClassLoader.preDefineClass(ClassLoader.java:902) ~[na:na]
        at java.base/java.lang.ClassLoader.defineClass(ClassLoader.java:1010) ~[na:na]
        at java.base/java.security.SecureClassLoader.defineClass(SecureClassLoader.java:150) ~[na:na]
        at java.base/jdk.internal.loader.BuiltinClassLoader.defineClass(BuiltinClassLoader.java:862) ~[na:na]
        at java.base/jdk.internal.loader.BuiltinClassLoader.findClassOnClassPathOrNull(BuiltinClassLoader.java:760) ~[na:na]
        at java.base/jdk.internal.loader.BuiltinClassLoader.loadClassOrNull(BuiltinClassLoader.java:681) ~[na:na]
        at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:639) ~[na:na]
        at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:188) ~[na:na]
        at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:520) ~[na:na]
        at com.example.signerdemo.SignerDemoApplication__BeanFactoryRegistrations.registerBeanDefinitions(SignerDemoApplication__BeanFactoryRegistrations.java:48) ~[aot/:na]
        at com.example.signerdemo.SignerDemoApplication__ApplicationContextInitializer.initialize(SignerDemoApplication__ApplicationContextInitializer.java:19) ~[aot/:na]
        at com.example.signerdemo.SignerDemoApplication__ApplicationContextInitializer.initialize(SignerDemoApplication__ApplicationContextInitializer.java:13) ~[aot/:na]
        at org.springframework.context.aot.ApplicationContextAotInitializer.initialize(ApplicationContextAotInitializer.java:53) ~[spring-context-6.0.0-SNAPSHOT.jar:6.0.0-SNAPSHOT]
        at org.springframework.boot.SpringApplication.lambda$addAotGeneratedInitializerIfNecessary$2(SpringApplication.java:419) ~[spring-boot-3.0.0-SNAPSHOT.jar:3.0.0-SNAPSHOT]
        at org.springframework.boot.SpringApplication.applyInitializers(SpringApplication.java:604) ~[spring-boot-3.0.0-SNAPSHOT.jar:3.0.0-SNAPSHOT]
        at org.springframework.boot.SpringApplication.prepareContext(SpringApplication.java:380) ~[spring-boot-3.0.0-SNAPSHOT.jar:3.0.0-SNAPSHOT]
        at org.springframework.boot.SpringApplication.run(SpringApplication.java:311) ~[spring-boot-3.0.0-SNAPSHOT.jar:3.0.0-SNAPSHOT]
        at org.springframework.boot.SpringApplication.run(SpringApplication.java:1303) ~[spring-boot-3.0.0-SNAPSHOT.jar:3.0.0-SNAPSHOT]
        at org.springframework.boot.SpringApplication.run(SpringApplication.java:1292) ~[spring-boot-3.0.0-SNAPSHOT.jar:3.0.0-SNAPSHOT]
        at com.example.signerdemo.SignerDemoApplication.main(SignerDemoApplication.java:17) ~[main/:na]
@mhalbritter
Copy link
Contributor Author

Interestingly, the generated JAR from boot can be run, both in normal and in AOT mode.

But when i try to build the native image with gradle nativeCompile, the building of the image fails with:

[1/7] Initializing...                                                                                    (0,0s @ 0,27GB)
Fatal error: java.lang.SecurityException: class "dependency.SomeBean"'s signer information does not match signer information of other classes in the same package
        at java.base/java.lang.ClassLoader.checkCerts(ClassLoader.java:1158)
        at java.base/java.lang.ClassLoader.preDefineClass(ClassLoader.java:902)
        at java.base/java.lang.ClassLoader.defineClass(ClassLoader.java:1010)
        at java.base/java.security.SecureClassLoader.defineClass(SecureClassLoader.java:150)
        at java.base/java.net.URLClassLoader.defineClass(URLClassLoader.java:524)
        at java.base/java.net.URLClassLoader$1.run(URLClassLoader.java:427)
        at java.base/java.net.URLClassLoader$1.run(URLClassLoader.java:421)
        at java.base/java.security.AccessController.doPrivileged(AccessController.java:712)
        at java.base/java.net.URLClassLoader.findClass(URLClassLoader.java:420)
        at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:587)
        at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:520)
        at java.base/java.lang.Class.getDeclaredMethods0(Native Method)
        at java.base/java.lang.Class.privateGetDeclaredMethods(Class.java:3402)
        at java.base/java.lang.Class.getDeclaredMethod(Class.java:2673)
        at org.graalvm.nativeimage.builder/com.oracle.svm.hosted.NativeImageGeneratorRunner.buildImage(NativeImageGeneratorRunner.java:359)
        at org.graalvm.nativeimage.builder/com.oracle.svm.hosted.NativeImageGeneratorRunner.build(NativeImageGeneratorRunner.java:585)
        at org.graalvm.nativeimage.builder/com.oracle.svm.hosted.NativeImageGeneratorRunner.main(NativeImageGeneratorRunner.java:128)
    Error: Image build request failed with exit status 1

> Task :app:nativeCompile FAILED

@sdeleuze
Copy link
Contributor

Maybe a GraalVM bug to raise on their bugtracker?

@mhalbritter
Copy link
Contributor Author

I don't think this is a bug, as we're doing something (generating code in the same package as in a signed JAR) which is not allowed by the JVM. I'm quite surprised that the resulting JAR from gradle build runs. I would have expected that to fail in the same way.

@stliu
Copy link

stliu commented Aug 29, 2022

Interestingly, the generated JAR from boot can be run, both in normal and in AOT mode.
yeah, seems it only throws SecurityException with gradle bootRun but not from java -jar app-0.0.1-SNAPSHOT.jar

what's the differences for these two?

@sdeleuze
Copy link
Contributor

sdeleuze commented Sep 19, 2022

FYI this issue is blocking Azure support deployed JAR to be signed.

what's the differences for these two?

Not sure since that's more a Boot question. Maybe java -jar app-0.0.1-SNAPSHOT.jar just checks the app JAR (which is unsigned) while gradle bootRun is running in exploded mode and try to load dependency-0.0.1-SNAPSHOT.jar which is signed.

@snicoll
Copy link
Member

snicoll commented Sep 22, 2022

@jhoeller and I brainstormed this morning and we believe that offering an option where AOT does not create a split package should be added. We're even considering this to be the default, with an opt-in optimization to the current behavior.

We like that AOT creates a structure that matches the structure of the original configuration. With Spring Boot in particular, it is very easy to see which auto-configurations were processed. We think we should keep this, by adding this infrastructure under the application's package name. Rather than generating code in org.springframework.boot.web.servlet.SomeAutoConfiguration it could be com.example.myapp.aot.org.springframework.boot.web.servlet.SomeAutoConfiguration or even com.example.myapp.aot.boot.web.servlet.SomeAutoConfiguration where com.example.myapp is the package of the application.

Looking at the API, we've already quite a good abstraction with ClassNameGenerator that we can extend. A first step would be to be able to manage package spaces that do not exist. I've started to work on this.

@sdeleuze sdeleuze removed their assignment Sep 23, 2022
@sdeleuze
Copy link
Contributor

Looks good, but please let's have data points on the RSS footprint impact and a team discussion before deciding if we switch the default or not.

@snicoll
Copy link
Member

snicoll commented Sep 23, 2022

Some WIP is here https://github.com/snicoll/spring-framework/tree/gh-29019 - I can see two problems so far:

  • Creating a class for a Feature does not work as it's using the class of the component. We probably need to change ClassNameGenerator to handle both strategies somewho.
  • The generated code does not compile as AccessVisibility is very basic.

@snicoll
Copy link
Member

snicoll commented Sep 23, 2022

Unfortunately, the default code fragments assume that if an privileged access is required, the generated code is in the package where the privileged member is located. We need to improve that before considering what I've started as an option.

@snicoll
Copy link
Member

snicoll commented Sep 26, 2022

Also blocked by #28875

@sdeleuze
Copy link
Contributor

sdeleuze commented Sep 28, 2022

I had a deeper look on alternative solutions, and was able to find a workaround on both JVM and native by passing a -Djava.security.properties=custom.security parameter to java or native-image with custom.security content being jdk.jar.disabledAlgorithms=MD2, MD5, RSA, DSA.

Few remarks:

  • The default configuration provided on most JDK is jdk.jar.disabledAlgorithms=MD2, MD5, RSA keySize < 1024, DSA keySize < 1024.
  • Another solution could be to explode signed JARs since the verification does not happen on directories, but using -Djava.security.properties looks less involved.
  • It is still possible to verify the JAR signature with jarsigner -verify foo.jar because we don't modify the JVM default security configuration and the JAR signature itself is valid, the SecurityException appears only when loading classes from split packages.

@jhoeller
Copy link
Contributor

jhoeller commented Oct 5, 2022

After a lot of consideration, we have realized that this is a problem that is not practical to solve at the core framework level. Our generated configuration needs to have access to package-local elements in common scenarios, not least of it all in order to avoid unnecessary reflection. For that reason, we decided to preserve our package-local generation approach.

If a separate jar with a split package arrangement or different jar signatures turns out to be an issue (also e.g. in the module system), the application build may combine them into a single jar that contains both the original classes and the generated configuration. Alternatively, the application build may also simply remove the jar signature before proceeding.

@jhoeller jhoeller closed this as completed Oct 5, 2022
@snicoll snicoll closed this as not planned Won't fix, can't repro, duplicate, stale Oct 5, 2022
@snicoll snicoll removed this from the 6.0.0-RC1 milestone Oct 5, 2022
@snicoll snicoll added the status: declined A suggestion or change that we don't feel we should currently apply label Oct 5, 2022
@sdeleuze
Copy link
Contributor

FYI the currrent workaround proposed to get both Native Build Tools and Buildpacks support is:

<build>
   <plugins>
      <plugin>
         <groupId>org.graalvm.buildtools</groupId>
         <artifactId>native-maven-plugin</artifactId>
         <configuration>
            <buildArgs>
               <arg>-Djava.security.properties=src/main/resources/custom.security</arg>
            </buildArgs>
         </configuration>
      </plugin>
      <plugin>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-maven-plugin</artifactId>
         <configuration>
            <image>
               <env>
                  <BP_NATIVE_IMAGE_BUILD_ARGUMENTS>-Djava.security.properties=/workspace/BOOT-INF/classes/custom.security</BP_NATIVE_IMAGE_BUILD_ARGUMENTS>
               </env>
            </image>
         </configuration>
      </plugin>
   </plugins>
</build>

With an src/main/resources/custom.security file with the following content:

jdk.jar.disabledAlgorithms=MD2, MD5, RSA, DSA

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
status: declined A suggestion or change that we don't feel we should currently apply theme: aot An issue related to Ahead-of-time processing type: enhancement A general enhancement
Projects
None yet
Development

No branches or pull requests

6 participants