[External] : Re: Applying Software Supply Chain concepts to AOTCache/CDS
Aman Sharma
amansha at kth.se
Tue Feb 24 17:34:17 UTC 2026
Hi Dan,
Thanks for your response! I read through this post, and indeed linear training sounds exactly like what I was proposing.
> have their uses train on top of the framework trained cache
Won't this have the same problem as library specific caches? In both case there will be a lot of JDK classes that would need to be identical across maintainer and user of the framework.
I started building a prototype<https://github.com/openjdk/jdk/compare/jdk-27+7...algomaster99:jdk:merge-at-runtime> on these ideas and how I am trying to model it is as follows:
1. Add and AOTMode=record option that would set the turn on the appropriate options<https://github.com/openjdk/jdk/compare/jdk-27+7...algomaster99:jdk:merge-at-runtime#diff-06d3a6d0bcf035b0b530313dc67b1ef7ef69869bcdee596a28f9efd97c86ace8R510-R514>. Here are some tests<https://github.com/openjdk/jdk/compare/jdk-27+7...algomaster99:jdk:merge-at-runtime#diff-43829529221dd55353adf98bc64f2d2530a720fd233b0b41fc1cfdb0920c9fd0> also which also validate how the behaviour of options should look.
2. Now, before exiting the VM that is executing the main program. I start my merging logic.
3. Currently, my merging logic currently only identifies the classes that are not loaded from AOTCache (shared_classpath_index != -1).
4. Next, I am thinking to add these classes in the current ro and rw region but I haven't figured out how I should do it.
Example run:
I have two programs. I train and assemble an AOTCache with the first one and then do a production run with the second.
public class HelloWorldInAOTCache {
public static void main(String[] args) {
System.out.println("Hello, World!");
}
}
public class HelloWorldNotInAOTCache {
public static void main(String[] args) {
System.out.println("Hello, World!");
HelloUniverse.main(args);
}
}
class HelloUniverse {
public static void main(String[] args) {
System.out.println("Hello, Universe!");
}
}
build/linux-x86_64-server-fastdebug/images/jdk/bin/java -Xlog:aot+merge=debug,class+load=info -XX:AOTMode=merge -XX:AOTCache=hello.aot HelloWorldNotInAOTCache.java (added a new merge tag for easy logging)
This outputs
```
[0,818s][info][aot,merge ] Total loaded classes: 2636, already in cache: 2615, new classes to merge: 21
...
[0,818s][debug][aot,merge ] new class: java.lang.invoke.LambdaForm$MH/0x0000000061003800 (loader: bootstrap) (isHidden: 1)
[0,818s][debug][aot,merge ] new class: HelloUniverse (loader: com.sun.tools.javac.launcher.MemoryClassLoader) (isHidden: 0)
[0,818s][debug][aot,merge ] new class: HelloWorldNotInAOTCache (loader: com.sun.tools.javac.launcher.MemoryClassLoader) (isHidden: 0)
```
Now I want these two non-hidden classes somehow merged into the cache. I am currently prioritizing only merging classes in loaded and linked state similar to the foundations in JEP 483<https://openjdk.org/jeps/483>.
I tried using existing API like "VM_PopulateDumpSharedSpace" which I thought would take care of building and writing the static archive, but I landed into some exceptions which I still need to process.
The eventual goal (or Deploy{AOTCache2}) would be something like this as I imagine:
build/linux-x86_64-server-fastdebug/images/jdk/bin/java -Xlog:class+load=info -XX:AOTCache=hello-merged.aot HelloWorldNotInAOTCache.java | grep "Hello"
```
[0,670s][info][class,load] HelloUniverse source: shared objects file
Hello, World!
[0,670s][info][class,load] HelloWorldNotInAOTCache source: shared objects file
Hello, Universe!
```
Could you please give some feedback on this approach?
I also want to ask you about the debugging workflow. For each small change, I have to run `make images CONF=linux-x86_64-server-fastdebug` which takes at least two minutes to build. Is there something I can do to make it a bit faster? I cannot run make default and test my features because of https://bugs.openjdk.org/browse/JDK-8301715.
Regards,
Aman Sharma
PhD Student
KTH Royal Institute of Technology
School of Electrical Engineering and Computer Science (EECS)
Department of Theoretical Computer Science (TCS)
<http://www.kth.se><https://www.kth.se/profile/amansha><https://www.kth.se/profile/amansha>
<https://www.kth.se/profile/amansha>https://algomaster99.github.io/
________________________________
From: Dan Heidinga <dan.heidinga at oracle.com>
Sent: Friday, February 20, 2026 7:36:07 PM
To: Aman Sharma; leyden-dev at openjdk.org
Cc: Martin Monperrus; Benoit Baudry; Roberto Castaneda Lozano
Subject: Re: Applying Software Supply Chain concepts to AOTCache/CDS
Please ignore the "Confidential - Oracle Restricted \Including External Recipients” text on the previous email - email client accidentally adding incorrect labels.
—Dan
From: leyden-dev <leyden-dev-retn at openjdk.org> on behalf of Dan Heidinga <dan.heidinga at oracle.com>
Date: Friday, February 20, 2026 at 10:14 AM
To: Aman Sharma <amansha at kth.se>, leyden-dev at openjdk.org <leyden-dev at openjdk.org>
Cc: Martin Monperrus <monperrus at kth.se>, Benoit Baudry <benoit.baudry at umontreal.ca>, Roberto Castaneda Lozano <roberto.castaneda.lozano at oracle.com>
Subject: Re: Applying Software Supply Chain concepts to AOTCache/CDS
Confidential - Oracle Restricted \Including External Recipients
Hi Aman,
I’d suggest giving this a read as a starting point: https://openjdk.org/projects/leyden/notes/05-training-runs
Leyden’s model is that the AOTCache is application-specific. The more accurate - that is, the more representative of the production run - the training run is, the greater the benefit in production runs. To achieve this, Leyden ensures that classes used in the production run are the same as the classes used in the training run as this allows us to side-step all kinds of versioning and compatibility issues. Library-specific caches don’t make sense in this model as the other classes - like JDK class - which are used by a given library aren’t present (“owned”) by the jar and yet still need to be identical across uses.
The idea of combining caches does make sense when done carefully. The primary use case for this is frameworks - like Helidon, Micronaut, Spring, Quarkus, etc - that want to train their framework and have their uses train on top of the framework trained cache. The document linked above calls that “linear training”, though I think we often use the term “iterative training” in our discussions.
The design builds on the previous levels being “the same” - the classes are the same for AOT Class loading and linking. AOT method profiling requires the classes to be “the same” so they profiles correctly apply. AOT code generation needs the classes and profiles to be the same to generate the right code. Merging from different sources violates that “sameness” requirement.
—Dan
Confidential - Oracle Restricted \Including External Recipients
From: leyden-dev <leyden-dev-retn at openjdk.org> on behalf of Aman Sharma <amansha at kth.se>
Date: Tuesday, February 17, 2026 at 3:50 PM
To: leyden-dev at openjdk.org <leyden-dev at openjdk.org>
Cc: Martin Monperrus <monperrus at kth.se>, Benoit Baudry <benoit.baudry at umontreal.ca>, Roberto Castaneda Lozano <roberto.castaneda.lozano at oracle.com>
Subject: Applying Software Supply Chain concepts to AOTCache/CDS
Hi all,
I want to attach the thread below since there is some context for what I will say next, and I believe it should be okay since the issue has been addressed in https://github.com/openjdk/jdk/pull/29728.
I want to explore the idea of sharing AOTCache as software supply chain artifact. Currently, Java packages are published on Maven Central and consumers can run them as applications or reuse them as libraries. I am proposing that AOTCache is shared along with the Java package by the developer/publisher so that downstream consumers can use it to achieve optimal performance. They could either 1) use the AOTCache as-is for the corresponding application or the downstream developer could 2) merge the AOTCache from Maven Central with the AOTCache from their application. In this way the distribution of AOTCache as a supply chain artifact can take place.
[cid:5f9ec946-9815-45e5-affa-da2032f00e67]
In this figure, each rectangle is a Java package, and each cylinder is an AOTCache. As you can see, the AOTCache of App (AOTApp) is generated by merging all its dependencies' caches.
The thread below can also be summarised in a figure.
[cid:1b52b633-5ec5-49b5-b5aa-ed4767298ee2]
Basically, the red AOT11 contains a poisoned class that shares the fully qualified name with a class name in say D2. In this case, the App was loading the class from the AOTCache (AOTApp) instead the benign class in D2.
I think sharing AOTCache by developers is a good idea because:
1. Developers would probably have a better idea of how well to exercise their application and hence their downstream consumer does not have to worry about creating an "ideal" workload.
2. Merging AOTCache in this way does not require creating complex workloads because your library code is already available in the AOTCache. You only need to add your own classes from the application.
Now, 1) "use the AOTCache as-is for the corresponding application" could be realized by developers pushing their AOTCache on Maven Central along with their jars. However, achieving 2 is non-trivial because merge option is not available in the JVM.
How have I tried to achieve merging:
1. I first tried to create a parser for *.aot files. The idea was to deserialize this memory mapped file into objects declared by src/hotspot/share/oops<https://github.com/chains-project/aotp/blob/main/src/main/java/io/github/chains_project/aotp/oops/klass/SPECIFICATION.md>. And then do the merging using Java APIs I created. However, there were too many pointers that needed to be adjusted and I did not understand how I would parse or generate the bitmap region.
2. I took a step back and tried to modify *.classlist files generated using CDS training run. Here I tried creating separate classlists for different submodules of PDFBox<https://github.com/apache/pdfbox> and then combining them manually. Then I created the CDS archive (.jsa file) using this "combined" classlist. During the production run, I could observe that some classes from different classlist were loaded from shared object files. (I had to disable some checks for time and size of jar in JDK for this work). However, I am more inclined to have the AOTCache or CDS archive as an artifact which is distributed.
3. Now I am trying to patch JDK code with an option "-XX:+AOTMerge" which would work with "-XX:AOTCache"and this would return "merged.aot". To me, AOTCache is a dump of the memory so I think this is feasible. Given the size of JDK code and how sophisticated HotSpot is, I can be completely wrong about this. I spun up a blueprint for this feature<https://github.com/openjdk/jdk/commit/9d303022edfa6b67a895b7e826382bdbf246c230> and now I am trying to use APIs like "AOTMetaspace::dump_static_archive(thread);" which, of course, end in SEGFaults. Can anyone please give me advice on how to go about implementing this feature? I can use some advice on how exactly AOTCache is dumped.
Caveats I have considered.
1. I know AOTCaches and CDS archive are specific to a single JVM version, classpath, etc
2. The merged AOTCache may be too bloated.
Yet I am motivated to explore more here.
Please share your thoughts on this, and I would also love some technical advice. Thank you in advance!
Regards,
Aman Sharma
PhD Student
KTH Royal Institute of Technology
School of Electrical Engineering and Computer Science (EECS)
Department of Theoretical Computer Science (TCS)
https://algomaster99.github.io/
________________________________
From: Aman Sharma
Sent: Friday, January 30, 2026 11:53 AM
To: leyden-dev at openjdk.org
Cc: Martin Monperrus; roberto.castaneda.lozano at oracle.com
Subject: Integrity violation in AOTCache
Hi all,
I have been playing around with AOTCache and I tried a small with it experiment whose idea was to shadow a class using AOTCache. By class shadowing, I mean loading a different class than intended but they both share the same fully qualified name. We also explored this concept in the paper: Maven-Hijack: Software Supply Chain Attack Exploiting Packaging Order<https://arxiv.org/abs/2407.18760v4>, and now I am trying to extend it to AOTCache.
The steps in the experiment are based on POC<https://github.com/chains-project/maven-hijack-poc> from the same paper and are written briefly below. The exact commands are documented here<https://github.com/chains-project/maven-hijack-poc/blob/main/java/maven/abstract-project/AOTCache.md>.
1. Build the application with one of the dependencies having malicious class. The malicious class has the same name as one of the other classes, say `org.postrgresql.Driver` but has malicious contents<https://github.com/chains-project/maven-hijack-poc/blob/0310de24103a55d1f51f70ef625933a40a7a55b3/java/maven/abstract-project/install-me-first/D11/src/main/java/org/postgresql/Driver.java#L8-L23>.
2. Create an AOTCache using these dependencies in jar. This creates a "polluted AOTCache".
3. Now using the polluted cache, run the application that is packaged with genuine dependencies. Apparently, the JVM initializes the malicious class from AOTCache instead of loading it from classpath. In other words, `java -XX:AOTCache=maven.aot -jar target/victim-1.0.jar` and `java -jar target/victim-1.0.jar` give different outputs.
I see this as a weakness if the poisoned AOTCache is distributed as an artifact for consumers to be used because maybe it is not expected from consumers to perform a training run themselves. I believe there should be some sort of integrity checks before a class is initialized from AOTCache. I noticed there are already some<https://github.com/openjdk/jdk/blob/e3b5b261af6acbe7ab074f301c70283b06c17d39/src/hotspot/share/code/aotCodeCache.cpp#L435> (please share if there are more, and I have missed them), but none of them relate to what I am mentioning. I am happy to listen to some thoughts on this.
Regards,
Aman Sharma
PhD Student
KTH Royal Institute of Technology
School of Electrical Engineering and Computer Science (EECS)
Department of Theoretical Computer Science (TCS)
https://algomaster99.github.io/
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://mail.openjdk.org/pipermail/leyden-dev/attachments/20260224/f4acd319/attachment.htm>
-------------- next part --------------
A non-text attachment was scrubbed...
Name: Untitled Diagram.drawio(2).png
Type: image/png
Size: 22280 bytes
Desc: Untitled Diagram.drawio(2).png
URL: <https://mail.openjdk.org/pipermail/leyden-dev/attachments/20260224/f4acd319/UntitledDiagram.drawio2.png>
-------------- next part --------------
A non-text attachment was scrubbed...
Name: Untitled Diagram.drawio(3).png
Type: image/png
Size: 24921 bytes
Desc: Untitled Diagram.drawio(3).png
URL: <https://mail.openjdk.org/pipermail/leyden-dev/attachments/20260224/f4acd319/UntitledDiagram.drawio3.png>
More information about the leyden-dev
mailing list