From 6178dbc772dae82e85e6abe3a1b110ae0a2fd247 Mon Sep 17 00:00:00 2001 From: LexManos Date: Wed, 18 Oct 2023 10:46:23 -0700 Subject: [PATCH] Move project to Forge namespace, and rename to SecureModules Rewrite the ClassLoader to properly find resources from parent layers, support supplying a parent class loader and be easier to understand. Compatibility with cpw land SecureJarHandler is kept until next major breaking window. Targeting MC 1.21 if I don't forget. This project will need to be re-evaluated on if it even is necessary in the MC runtime at all. Yes, Jar signatures/code signers are great. However we exist in a world of coremods and runtime bytecode manipulation. There is no possible runtime security need for this. Things should be addressed in the MC universe using static analysis of the jar files themselves not runtime. Which can be done in a FAR simpler manor. --- .../workflows/aggregate-jmh-results.groovy | 72 +++ .github/workflows/collect_jmh_results.groovy | 44 -- .github/workflows/test_jvms.yml | 46 +- .gitignore | 11 +- build.gradle | 5 +- run_workflow_tests.sh | 4 +- settings.gradle | 6 +- sjh-jmh/src/main/java/module-info.java | 5 - sjh-test/src/test/java/module-info.java | 10 - .../test/TestClassLoader.java | 44 -- {sjh-jmh => sm-jmh}/build.gradle | 0 sm-jmh/src/main/java/module-info.java | 5 + .../benchmarks/JarModuleFinderBenchmark.java | 7 +- .../securemodules}/jmh/benchmarks/Main.java | 2 +- .../benchmarks/UnionFileSystemBenchmark.java | 2 +- {sjh-jmh => sm-jmh}/src/testjars/testjar1.jar | Bin {sjh-jmh => sm-jmh}/src/testjars/testjar2.jar | Bin {sjh-jmh => sm-jmh}/src/testjars/testjar3.jar | Bin .../src/testrawdir/ThisFileExists.txt | 0 .../src/testrawdir/ThisFileExists3.txt | 0 .../src/testrawdir2/ThisFileExists2.txt | 0 .../src/testrawdir2/ThisFileExists4.txt | 0 {sjh-test => sm-test}/build.gradle | 0 sm-test/src/test/java/module-info.java | 12 + .../securemodules/test/TestClassLoader.java | 31 + .../securemodules}/test/TestClassStuff.java | 2 +- .../test/TestDummyJarProvider.java | 11 +- .../securemodules}/test/TestMetadata.java | 2 +- .../test/TestSecureJarLoading.java | 10 +- .../securemodules}/test/TestUnionFS.java | 18 +- .../securemodules}/test/TestUnionPath.java | 2 +- .../src/test/resources/dir1.zip | Bin .../src/test/resources/dir1/masktest.txt | 0 .../src/test/resources/dir1/masktest2.txt | 0 .../resources/dir1/subdir1/masktestsd1.txt | 0 .../src/test/resources/dir2/masktest.txt | 0 .../src/test/resources/dir2/masktest3.txt | 0 .../src/test/resources/empty.zip | Bin .../src/test/resources/partial.zip | Bin .../src/test/resources/signed.zip | Bin .../src/test/resources/tampered.zip | Bin .../src/test/resources/unsigned.zip | Bin .../java/cpw/mods/cl/JarModuleFinder.java | 84 +-- .../java/cpw/mods/cl/ModularURLHandler.java | 100 ++-- .../java/cpw/mods/cl/ModuleClassLoader.java | 279 ++------- .../cpw/mods/cl/ProtectionDomainHelper.java | 60 +- .../cpw/mods/cl/UnionURLStreamHandler.java | 6 +- .../cpw/mods/jarhandling/JarMetadata.java | 40 +- .../cpw/mods/niofs/union/UnionFileSystem.java | 20 +- .../union/UnionURLStreamHandlerProvider.java | 47 ++ src/main/java/module-info.java | 23 +- .../LayerAwareURLStreamHandlerFactory.java | 56 ++ .../SecureModuleClassLoader.java | 543 ++++++++++++++++++ .../securemodules/SecureModuleFinder.java | 109 ++++ .../securemodules/SecureModuleReference.java | 34 ++ .../java.net.spi.URLStreamHandlerProvider | 1 + 56 files changed, 1135 insertions(+), 618 deletions(-) create mode 100644 .github/workflows/aggregate-jmh-results.groovy delete mode 100644 .github/workflows/collect_jmh_results.groovy delete mode 100644 sjh-jmh/src/main/java/module-info.java delete mode 100644 sjh-test/src/test/java/module-info.java delete mode 100644 sjh-test/src/test/java/net/minecraftforge/securejarhandler/test/TestClassLoader.java rename {sjh-jmh => sm-jmh}/build.gradle (100%) create mode 100644 sm-jmh/src/main/java/module-info.java rename {sjh-jmh/src/main/java/net/minecraftforge/securejarhandler => sm-jmh/src/main/java/net/minecraftforge/securemodules}/jmh/benchmarks/JarModuleFinderBenchmark.java (75%) rename {sjh-jmh/src/main/java/net/minecraftforge/securejarhandler => sm-jmh/src/main/java/net/minecraftforge/securemodules}/jmh/benchmarks/Main.java (80%) rename {sjh-jmh/src/main/java/net/minecraftforge/securejarhandler => sm-jmh/src/main/java/net/minecraftforge/securemodules}/jmh/benchmarks/UnionFileSystemBenchmark.java (99%) rename {sjh-jmh => sm-jmh}/src/testjars/testjar1.jar (100%) rename {sjh-jmh => sm-jmh}/src/testjars/testjar2.jar (100%) rename {sjh-jmh => sm-jmh}/src/testjars/testjar3.jar (100%) rename {sjh-jmh => sm-jmh}/src/testrawdir/ThisFileExists.txt (100%) rename {sjh-jmh => sm-jmh}/src/testrawdir/ThisFileExists3.txt (100%) rename {sjh-jmh => sm-jmh}/src/testrawdir2/ThisFileExists2.txt (100%) rename {sjh-jmh => sm-jmh}/src/testrawdir2/ThisFileExists4.txt (100%) rename {sjh-test => sm-test}/build.gradle (100%) create mode 100644 sm-test/src/test/java/module-info.java create mode 100644 sm-test/src/test/java/net/minecraftforge/securemodules/test/TestClassLoader.java rename {sjh-test/src/test/java/net/minecraftforge/securejarhandler => sm-test/src/test/java/net/minecraftforge/securemodules}/test/TestClassStuff.java (97%) rename {sjh-test/src/test/java/net/minecraftforge/securejarhandler => sm-test/src/test/java/net/minecraftforge/securemodules}/test/TestDummyJarProvider.java (92%) rename {sjh-test/src/test/java/net/minecraftforge/securejarhandler => sm-test/src/test/java/net/minecraftforge/securemodules}/test/TestMetadata.java (96%) rename {sjh-test/src/test/java/net/minecraftforge/securejarhandler => sm-test/src/test/java/net/minecraftforge/securemodules}/test/TestSecureJarLoading.java (95%) rename {sjh-test/src/test/java/net/minecraftforge/securejarhandler => sm-test/src/test/java/net/minecraftforge/securemodules}/test/TestUnionFS.java (93%) rename {sjh-test/src/test/java/net/minecraftforge/securejarhandler => sm-test/src/test/java/net/minecraftforge/securemodules}/test/TestUnionPath.java (99%) rename {sjh-test => sm-test}/src/test/resources/dir1.zip (100%) rename {sjh-test => sm-test}/src/test/resources/dir1/masktest.txt (100%) rename {sjh-test => sm-test}/src/test/resources/dir1/masktest2.txt (100%) rename {sjh-test => sm-test}/src/test/resources/dir1/subdir1/masktestsd1.txt (100%) rename {sjh-test => sm-test}/src/test/resources/dir2/masktest.txt (100%) rename {sjh-test => sm-test}/src/test/resources/dir2/masktest3.txt (100%) rename {sjh-test => sm-test}/src/test/resources/empty.zip (100%) rename {sjh-test => sm-test}/src/test/resources/partial.zip (100%) rename {sjh-test => sm-test}/src/test/resources/signed.zip (100%) rename {sjh-test => sm-test}/src/test/resources/tampered.zip (100%) rename {sjh-test => sm-test}/src/test/resources/unsigned.zip (100%) create mode 100644 src/main/java/cpw/mods/niofs/union/UnionURLStreamHandlerProvider.java create mode 100644 src/main/java/net/minecraftforge/securemodules/LayerAwareURLStreamHandlerFactory.java create mode 100644 src/main/java/net/minecraftforge/securemodules/SecureModuleClassLoader.java create mode 100644 src/main/java/net/minecraftforge/securemodules/SecureModuleFinder.java create mode 100644 src/main/java/net/minecraftforge/securemodules/SecureModuleReference.java create mode 100644 src/main/resources/META-INF/services/java.net.spi.URLStreamHandlerProvider diff --git a/.github/workflows/aggregate-jmh-results.groovy b/.github/workflows/aggregate-jmh-results.groovy new file mode 100644 index 0000000..ab3b904 --- /dev/null +++ b/.github/workflows/aggregate-jmh-results.groovy @@ -0,0 +1,72 @@ +import groovy.json.JsonSlurper +import net.steppschuh.markdowngenerator.table.Table + +import java.nio.file.Files +import java.nio.file.Path +import java.math.RoundingMode + +@GrabResolver(name='jitpack.io', root='https://jitpack.io/') +@GrabResolver(name = 'central', root='https://repo1.maven.org/maven2/') +@Grapes([ + @Grab('org.apache.groovy:groovy-json:4.0.13'), + @Grab('com.github.Steppschuh:Java-Markdown-Generator:1.3.2') +]) + +final versions = [] as SortedSet +final javas = [:] as TreeMap +final results = [:] as TreeMap + +final resultsPath = Path.of('build/test_artifacts') +for (def dir : Files.list(Path.of('build/test_artifacts'))) { + def dirName = dir.fileName.toString() + def file = dir.resolve('jmh_results.json') + if (!dirName.startsWith('jmh-') || !Files.exists(file)) + continue + (javaName,javaVersion) = dirName.substring('jmh-'.length()).split('-') + javas.computeIfAbsent(javaName, { [] }).add(javaVersion) + versions.add(javaVersion) + + def json = new JsonSlurper().parse(file.toFile()) + for (def bench : json) { + def byJava = results.computeIfAbsent(bench.benchmark, { [:] }) + def byVersion = byJava.computeIfAbsent(javaName, { [:] }) + + def result = bench.primaryMetric.score.setScale(3, RoundingMode.CEILING) + if (!bench.primaryMetric.scoreError.equals('NaN')) + result += ' ± ' + bench.primaryMetric.scoreError.setScale(3, RoundingMode.CEILING) + //result += bench.primaryMetric.scoreUnit + + byVersion.put(javaVersion, result) + } +} +def output = "" +results.forEach { bench, byJava -> + final table = new Table.Builder() + .withAlignments(Table.ALIGN_RIGHT, Table.ALIGN_RIGHT) + .addRow((['Vendor'] + versions).toArray()) + + javas.forEach { javaName, javaVersions -> + def row = [javaName] + if (!byJava.containsKey(javaName)) { + versions.forEach { javaVersion -> + row.add(javaVersions.contains(javaVersion) ? "MISSING" : "") + } + } else { + def byVersion = byJava.get(javaName) + versions.forEach { javaVersion -> + if (javaVersions.contains(javaVersion)) { + row.add(byVersion.containsKey(javaVersion) ? byVersion.get(javaVersion) : "MISSING") + } else { + row.add("") + } + } + } + table.addRow(row.toArray()) + } + + output += '### `' + bench + '` results\n' + + table.build() + '\n' + + '\n' +} + +new File('jmh_results.md').text = output \ No newline at end of file diff --git a/.github/workflows/collect_jmh_results.groovy b/.github/workflows/collect_jmh_results.groovy deleted file mode 100644 index 5c8980d..0000000 --- a/.github/workflows/collect_jmh_results.groovy +++ /dev/null @@ -1,44 +0,0 @@ -import groovy.json.JsonSlurper -import net.steppschuh.markdowngenerator.table.Table - -import java.nio.file.Files -import java.nio.file.Path -import java.math.RoundingMode - -@GrabResolver(name='jitpack.io', root='https://jitpack.io/') -@GrabResolver(name = 'central', root='https://repo1.maven.org/maven2/') -@Grapes([ - @Grab('org.apache.groovy:groovy-json:4.0.13'), - @Grab('com.github.Steppschuh:Java-Markdown-Generator:1.3.2') -]) - -final results = [:] as TreeMap - -final resultsPath = Path.of('build/jmh_results') -final files = Files.list(resultsPath).map { it.resolve('jmh_results.json') } -for (def file : files) { - if (!file.parent.fileName.toString().startsWith('jmh-') || !Files.exists(file)) - continue - def json = new JsonSlurper().parse(file.toFile()) - def name = file.parent.fileName.toString().substring('jmh-'.length()) - for (def bench : json) { - def result = bench.primaryMetric.score.setScale(3, RoundingMode.CEILING) - if (!bench.primaryMetric.scoreError.equals('NaN')) - result += ' ± ' + bench.primaryMetric.scoreError.setScale(3, RoundingMode.CEILING) - result += bench.primaryMetric.scoreUnit - results.computeIfAbsent(bench.benchmark, { [:] as TreeMap }).put(name, result) - } -} -def output = "" -results.forEach { bench, values -> - final table = new Table.Builder() - .withAlignments(Table.ALIGN_RIGHT, Table.ALIGN_RIGHT) - .addRow('JDK name & Version', 'Benchmark results') - values.forEach { jvm, result -> table.addRow(jvm, result) } - - output += '### `' + bench + '` results\n' + - table.build() + '\n' + - '\n' -} - -new File('jmh_results.md').text = output \ No newline at end of file diff --git a/.github/workflows/test_jvms.yml b/.github/workflows/test_jvms.yml index 16d8127..e75ef37 100644 --- a/.github/workflows/test_jvms.yml +++ b/.github/workflows/test_jvms.yml @@ -48,28 +48,29 @@ jobs: run: chmod +x ./gradlew - name: Run Jmh - run: ./gradlew --console=plain --continue :sjh-jmh:jmh -PjavaVendor=${{ matrix.jdk }} -PjavaVersion=${{ matrix.jvm_version }} + run: ./gradlew --console=plain --continue :sm-jmh:jmh -PjavaVendor=${{ matrix.jdk }} -PjavaVersion=${{ matrix.jvm_version }} - name: Upload JMH Results uses: actions/upload-artifact@v3 with: name: jmh-${{ matrix.jdk }}-${{ matrix.jvm_version }} path: build/jmh_results.json - - - name: Run Tests - run: ./gradlew --console=plain --continue :sjh-test:test -PjavaVendor=${{ matrix.jdk }} -PjavaVersion=${{ matrix.jvm_version }} - - - name: Upload Test Reports - uses: actions/upload-artifact@v3 - with: - name: test-reports-${{ matrix.jdk }}-${{ matrix.jvm_version }} - path: build/reports/ - - - name: Upload Test Results - uses: actions/upload-artifact@v3 - with: - name: test-results-${{ matrix.jdk }}-${{ matrix.jvm_version }} - path: build/test-results/ + + # It's faster to just run the tests locally. + #- name: Run Tests + # run: ./gradlew --console=plain --continue :sm-test:test -PjavaVendor=${{ matrix.jdk }} -PjavaVersion=${{ matrix.jvm_version }} + # + #- name: Upload Test Reports + # uses: actions/upload-artifact@v3 + # with: + # name: test-reports-${{ matrix.jdk }}-${{ matrix.jvm_version }} + # path: build/reports/ + # + #- name: Upload Test Results + # uses: actions/upload-artifact@v3 + # with: + # name: test-results-${{ matrix.jdk }}-${{ matrix.jvm_version }} + # path: build/test-results/ upload_results: name: Upload Jmh results @@ -92,16 +93,17 @@ jobs: path: build/test_artifacts - name: Collect JMH results - run: groovy .github/workflows/collect_jmh_results.groovy + run: groovy .github/workflows/aggregate-jmh-results.groovy - - name: Collect JUnit results - run: groovy .github/workflows/aggregate-junit-tests.groovy + #- name: Collect JUnit results + # run: groovy .github/workflows/aggregate-junit-tests.groovy - name: Upload Final Results uses: actions/upload-artifact@v3 with: name: aggregate-results - path: | - jmh_results.md - test_results.html + path: jmh_results.md + #path: | + # jmh_results.md + # test_results.html \ No newline at end of file diff --git a/.gitignore b/.gitignore index 7f2fbd7..5892530 100644 --- a/.gitignore +++ b/.gitignore @@ -17,18 +17,11 @@ /*/.gradle #misc -/out/ -/forge-1.16.5-36.1.16.jar -/tmp/ /repo/ -/Bookshelf-1.16.4-9.0.7-TAMPERED.jar -/Bookshelf-1.16.4-9.0.7-UNTAMPERED.jar -/inventorysorter-1.16.1-18.0.0.jar -/modlauncher-9.0.1.jar -/test.jar *.jfr *.factorypath -/sjh-jmh/.apt_generated/ +/sm-jmh/.apt_generated/ /jmh_results.md /test_results.html /artifacts/ +/test_artifacts.zip diff --git a/build.gradle b/build.gradle index b764839..11b72c6 100644 --- a/build.gradle +++ b/build.gradle @@ -6,6 +6,7 @@ plugins { id 'net.minecraftforge.gradleutils' version '2.+' } +group = 'net.minecraftforge' version = gradleutils.getTagOffsetVersion() logger.lifecycle('Version: ' + version) @@ -36,7 +37,7 @@ changelog { jar { manifest { attributes([ - 'Specification-Title': 'securejarhandler', + 'Specification-Title': 'securemodules', 'Specification-Vendor': 'forge', 'Specification-Version': gradleutils.gitInfo.tag, 'Implementation-Title': project.name, @@ -51,7 +52,7 @@ publishing { mavenJava(MavenPublication) { from components.java pom { - name = 'Secure Modular Jar handler' + name = 'Secure Modular handler' description = 'Making the Java modular system provide security information' } } diff --git a/run_workflow_tests.sh b/run_workflow_tests.sh index eb25261..4f6f4ca 100644 --- a/run_workflow_tests.sh +++ b/run_workflow_tests.sh @@ -13,4 +13,6 @@ cd artifacts/1/; zip -r ../../test_artifacts.zip *; cd - # Grab jmh results mv artifacts/1/aggregate-results/jmh_results.md . -mv artifacts/1/aggregate-results/test_results.html . \ No newline at end of file + +# Build JUnit Tests +./gradlew --continue test collectTests \ No newline at end of file diff --git a/settings.gradle b/settings.gradle index dc63737..d6cbeec 100644 --- a/settings.gradle +++ b/settings.gradle @@ -29,6 +29,6 @@ dependencyResolutionManagement { } } -rootProject.name = 'securejarhandler' -include 'sjh-jmh' -include 'sjh-test' \ No newline at end of file +rootProject.name = 'securemodules' +include 'sm-jmh' +include 'sm-test' \ No newline at end of file diff --git a/sjh-jmh/src/main/java/module-info.java b/sjh-jmh/src/main/java/module-info.java deleted file mode 100644 index fc9b566..0000000 --- a/sjh-jmh/src/main/java/module-info.java +++ /dev/null @@ -1,5 +0,0 @@ -module cpw.mods.securejarhandler.test.jmh { - requires cpw.mods.securejarhandler; - requires jmh.core; - requires jdk.unsupported; // Needed by jmh.core -} \ No newline at end of file diff --git a/sjh-test/src/test/java/module-info.java b/sjh-test/src/test/java/module-info.java deleted file mode 100644 index 439bb0d..0000000 --- a/sjh-test/src/test/java/module-info.java +++ /dev/null @@ -1,10 +0,0 @@ -module net.minecraftforge.securejarhandler.test { - requires cpw.mods.securejarhandler; - requires jdk.unsupported; - requires java.base; - requires org.junit.jupiter.api; - requires org.objectweb.asm; - requires org.objectweb.asm.tree; - requires net.minecraftforge.unsafe; - opens net.minecraftforge.securejarhandler.test to org.junit.platform.commons; -} \ No newline at end of file diff --git a/sjh-test/src/test/java/net/minecraftforge/securejarhandler/test/TestClassLoader.java b/sjh-test/src/test/java/net/minecraftforge/securejarhandler/test/TestClassLoader.java deleted file mode 100644 index eb220b2..0000000 --- a/sjh-test/src/test/java/net/minecraftforge/securejarhandler/test/TestClassLoader.java +++ /dev/null @@ -1,44 +0,0 @@ -package net.minecraftforge.securejarhandler.test; - -import cpw.mods.cl.JarModuleFinder; -import cpw.mods.cl.ModuleClassLoader; -import cpw.mods.jarhandling.SecureJar; -import org.junit.jupiter.api.Disabled; -import org.junit.jupiter.api.Test; - -import java.lang.module.ModuleFinder; -import java.nio.file.Files; -import java.nio.file.Paths; -import java.util.Arrays; -import java.util.List; -import java.util.ServiceLoader; -import java.util.function.Consumer; - -public class TestClassLoader { - public static void main(String[] args) { - new TestClassLoader().testCL(); - } - private static final String[] CL = "/home/cpw/minecraft/libraries/net/minecraftforge/fmlloader/36.1.24/fmlloader-36.1.24.jar:/home/cpw/minecraft/libraries/org/ow2/asm/asm/9.0/asm-9.0.jar:/home/cpw/minecraft/libraries/org/ow2/asm/asm-commons/9.0/asm-commons-9.0.jar:/home/cpw/minecraft/libraries/org/ow2/asm/asm-tree/9.0/asm-tree-9.0.jar:/home/cpw/minecraft/libraries/cpw/mods/modlauncher/9.0.1/modlauncher-9.0.1.jar:/home/cpw/minecraft/libraries/cpw/mods/grossjava9hacks/2.0.7/grossjava9hacks-2.0.7.jar:/home/cpw/minecraft/libraries/org/ow2/asm/asm-util/9.0/asm-util-9.0.jar:/home/cpw/minecraft/libraries/org/ow2/asm/asm-analysis/9.0/asm-analysis-9.0.jar:/home/cpw/minecraft/libraries/net/minecraftforge/accesstransformers/3.0.1/accesstransformers-3.0.1.jar:/home/cpw/minecraft/libraries/org/antlr/antlr4-runtime/4.9.1/antlr4-runtime-4.9.1.jar:/home/cpw/minecraft/libraries/net/minecraftforge/eventbus/4.0.0/eventbus-4.0.0.jar:/home/cpw/minecraft/libraries/net/minecraftforge/forgespi/3.2.0/forgespi-3.2.0.jar:/home/cpw/minecraft/libraries/net/minecraftforge/coremods/4.0.6/coremods-4.0.6.jar:/home/cpw/minecraft/libraries/net/minecraftforge/unsafe/0.2.0/unsafe-0.2.0.jar:/home/cpw/minecraft/libraries/com/electronwill/night-config/core/3.6.3/core-3.6.3.jar:/home/cpw/minecraft/libraries/com/electronwill/night-config/toml/3.6.3/toml-3.6.3.jar:/home/cpw/minecraft/libraries/org/jline/jline/3.12.1/jline-3.12.1.jar:/home/cpw/minecraft/libraries/org/apache/maven/maven-artifact/3.6.3/maven-artifact-3.6.3.jar:/home/cpw/minecraft/libraries/net/jodah/typetools/0.8.3/typetools-0.8.3.jar:/home/cpw/minecraft/libraries/net/minecrell/terminalconsoleappender/1.2.0/terminalconsoleappender-1.2.0.jar:/home/cpw/minecraft/libraries/net/sf/jopt-simple/jopt-simple/5.0.4/jopt-simple-5.0.4.jar:/home/cpw/minecraft/libraries/org/spongepowered/mixin/0.8.2/mixin-0.8.2.jar:/home/cpw/minecraft/libraries/net/minecraftforge/nashorn-core-compat/15.1.1.1/nashorn-core-compat-15.1.1.1.jar:/home/cpw/minecraft/libraries/com/mojang/blocklist/1.0.5/blocklist-1.0.5.jar:/home/cpw/minecraft/libraries/com/mojang/patchy/2.1.6/patchy-2.1.6.jar:/home/cpw/minecraft/libraries/com/github/oshi/oshi-core/5.3.4/oshi-core-5.3.4.jar:/home/cpw/minecraft/libraries/net/java/dev/jna/jna/5.6.0/jna-5.6.0.jar:/home/cpw/minecraft/libraries/net/java/dev/jna/jna-platform/5.6.0/jna-platform-5.6.0.jar:/home/cpw/minecraft/libraries/org/slf4j/slf4j-api/1.8.0-beta4/slf4j-api-1.8.0-beta4.jar:/home/cpw/minecraft/libraries/org/apache/logging/log4j/log4j-slf4j18-impl/2.14.1/log4j-slf4j18-impl-2.14.1.jar:/home/cpw/minecraft/libraries/com/ibm/icu/icu4j/66.1/icu4j-66.1.jar:/home/cpw/minecraft/libraries/com/mojang/javabridge/1.1.23/javabridge-1.1.23.jar:/home/cpw/minecraft/libraries/net/sf/jopt-simple/jopt-simple/5.0.3/jopt-simple-5.0.3.jar:/home/cpw/minecraft/libraries/io/netty/netty-all/4.1.25.Final/netty-all-4.1.25.Final.jar:/home/cpw/minecraft/libraries/com/google/guava/guava/21.0/guava-21.0.jar:/home/cpw/minecraft/libraries/org/apache/commons/commons-lang3/3.5/commons-lang3-3.5.jar:/home/cpw/minecraft/libraries/commons-io/commons-io/2.5/commons-io-2.5.jar:/home/cpw/minecraft/libraries/commons-codec/commons-codec/1.10/commons-codec-1.10.jar:/home/cpw/minecraft/libraries/net/java/jinput/jinput/2.0.5/jinput-2.0.5.jar:/home/cpw/minecraft/libraries/net/java/jutils/jutils/1.0.0/jutils-1.0.0.jar:/home/cpw/minecraft/libraries/com/mojang/brigadier/1.0.18/brigadier-1.0.18.jar:/home/cpw/minecraft/libraries/com/mojang/datafixerupper/4.0.26/datafixerupper-4.0.26.jar:/home/cpw/minecraft/libraries/com/google/code/gson/gson/2.8.0/gson-2.8.0.jar:/home/cpw/minecraft/libraries/com/mojang/authlib/2.2.30/authlib-2.2.30.jar:/home/cpw/minecraft/libraries/org/apache/commons/commons-compress/1.8.1/commons-compress-1.8.1.jar:/home/cpw/minecraft/libraries/org/apache/httpcomponents/httpclient/4.3.3/httpclient-4.3.3.jar:/home/cpw/minecraft/libraries/commons-logging/commons-logging/1.1.3/commons-logging-1.1.3.jar:/home/cpw/minecraft/libraries/org/apache/httpcomponents/httpcore/4.3.2/httpcore-4.3.2.jar:/home/cpw/minecraft/libraries/it/unimi/dsi/fastutil/8.2.1/fastutil-8.2.1.jar:/home/cpw/minecraft/libraries/org/apache/logging/log4j/log4j-api/2.14.1/log4j-api-2.14.1.jar:/home/cpw/minecraft/libraries/org/apache/logging/log4j/log4j-core/2.14.1/log4j-core-2.14.1.jar:/home/cpw/minecraft/libraries/org/lwjgl/lwjgl/3.2.2/lwjgl-3.2.2.jar:/home/cpw/minecraft/libraries/org/lwjgl/lwjgl-jemalloc/3.2.2/lwjgl-jemalloc-3.2.2.jar:/home/cpw/minecraft/libraries/org/lwjgl/lwjgl-openal/3.2.2/lwjgl-openal-3.2.2.jar:/home/cpw/minecraft/libraries/org/lwjgl/lwjgl-opengl/3.2.2/lwjgl-opengl-3.2.2.jar:/home/cpw/minecraft/libraries/org/lwjgl/lwjgl-glfw/3.2.2/lwjgl-glfw-3.2.2.jar:/home/cpw/minecraft/libraries/org/lwjgl/lwjgl-stb/3.2.2/lwjgl-stb-3.2.2.jar:/home/cpw/minecraft/libraries/org/lwjgl/lwjgl-tinyfd/3.2.2/lwjgl-tinyfd-3.2.2.jar:/home/cpw/minecraft/libraries/com/mojang/text2speech/1.11.3/text2speech-1.11.3.jar".split(":"); - - @SuppressWarnings("unchecked") - @Test - @Disabled - void testCL() { - var cl = Arrays.stream(CL) - .map(Paths::get) - .filter(Files::exists) - .map(SecureJar::from) - .toArray(SecureJar[]::new); - if (cl.length == 0) - return; - var jf = JarModuleFinder.of(cl); - var cf = ModuleLayer.boot().configuration(); - var newcf = cf.resolveAndBind(jf, ModuleFinder.ofSystem(), List.of("cpw.mods.modlauncher")); - var mycl = new ModuleClassLoader("test", newcf, List.of()); - var layer = ModuleLayer.defineModules(newcf, List.of(ModuleLayer.boot()), m->mycl); - Thread.currentThread().setContextClassLoader(mycl); - var sl = ServiceLoader.load(layer.layer(), Consumer.class); - var c = sl.stream().map(ServiceLoader.Provider::get).toList(); - c.get(0).accept(new String[0]); - } -} diff --git a/sjh-jmh/build.gradle b/sm-jmh/build.gradle similarity index 100% rename from sjh-jmh/build.gradle rename to sm-jmh/build.gradle diff --git a/sm-jmh/src/main/java/module-info.java b/sm-jmh/src/main/java/module-info.java new file mode 100644 index 0000000..efebb0a --- /dev/null +++ b/sm-jmh/src/main/java/module-info.java @@ -0,0 +1,5 @@ +module net.minecraftforge.securemodules.jmh { + requires cpw.mods.securejarhandler; // TODO: [SM][Deprecation] Remove CPW compatibility + requires jmh.core; + requires jdk.unsupported; // Needed by jmh.core +} \ No newline at end of file diff --git a/sjh-jmh/src/main/java/net/minecraftforge/securejarhandler/jmh/benchmarks/JarModuleFinderBenchmark.java b/sm-jmh/src/main/java/net/minecraftforge/securemodules/jmh/benchmarks/JarModuleFinderBenchmark.java similarity index 75% rename from sjh-jmh/src/main/java/net/minecraftforge/securejarhandler/jmh/benchmarks/JarModuleFinderBenchmark.java rename to sm-jmh/src/main/java/net/minecraftforge/securemodules/jmh/benchmarks/JarModuleFinderBenchmark.java index 44a95fb..333b1c4 100644 --- a/sjh-jmh/src/main/java/net/minecraftforge/securejarhandler/jmh/benchmarks/JarModuleFinderBenchmark.java +++ b/sm-jmh/src/main/java/net/minecraftforge/securemodules/jmh/benchmarks/JarModuleFinderBenchmark.java @@ -1,7 +1,8 @@ -package net.minecraftforge.securejarhandler.jmh.benchmarks; +package net.minecraftforge.securemodules.jmh.benchmarks; -import cpw.mods.cl.JarModuleFinder; import cpw.mods.jarhandling.SecureJar; +import net.minecraftforge.securemodules.SecureModuleFinder; + import org.openjdk.jmh.annotations.Benchmark; import org.openjdk.jmh.infra.Blackhole; @@ -16,7 +17,7 @@ public void benchJarModuleFinderOf(Blackhole blackhole) { var path3 = Paths.get("./src/testjars/testjar3.jar"); var secureJar1 = SecureJar.from(path1, path2); var secureJar2 = SecureJar.from(path3); - var jarModuleFinder = JarModuleFinder.of(secureJar1, secureJar2); + var jarModuleFinder = SecureModuleFinder.of(secureJar1, secureJar2); blackhole.consume(jarModuleFinder); } diff --git a/sjh-jmh/src/main/java/net/minecraftforge/securejarhandler/jmh/benchmarks/Main.java b/sm-jmh/src/main/java/net/minecraftforge/securemodules/jmh/benchmarks/Main.java similarity index 80% rename from sjh-jmh/src/main/java/net/minecraftforge/securejarhandler/jmh/benchmarks/Main.java rename to sm-jmh/src/main/java/net/minecraftforge/securemodules/jmh/benchmarks/Main.java index 3822553..7b76d6b 100644 --- a/sjh-jmh/src/main/java/net/minecraftforge/securejarhandler/jmh/benchmarks/Main.java +++ b/sm-jmh/src/main/java/net/minecraftforge/securemodules/jmh/benchmarks/Main.java @@ -1,4 +1,4 @@ -package net.minecraftforge.securejarhandler.jmh.benchmarks; +package net.minecraftforge.securemodules.jmh.benchmarks; // This is needed because eclipse doesn't allow for the main to be outside the module in module builds 0.o? public class Main { diff --git a/sjh-jmh/src/main/java/net/minecraftforge/securejarhandler/jmh/benchmarks/UnionFileSystemBenchmark.java b/sm-jmh/src/main/java/net/minecraftforge/securemodules/jmh/benchmarks/UnionFileSystemBenchmark.java similarity index 99% rename from sjh-jmh/src/main/java/net/minecraftforge/securejarhandler/jmh/benchmarks/UnionFileSystemBenchmark.java rename to sm-jmh/src/main/java/net/minecraftforge/securemodules/jmh/benchmarks/UnionFileSystemBenchmark.java index 563cdf9..81595a4 100644 --- a/sjh-jmh/src/main/java/net/minecraftforge/securejarhandler/jmh/benchmarks/UnionFileSystemBenchmark.java +++ b/sm-jmh/src/main/java/net/minecraftforge/securemodules/jmh/benchmarks/UnionFileSystemBenchmark.java @@ -1,4 +1,4 @@ -package net.minecraftforge.securejarhandler.jmh.benchmarks; +package net.minecraftforge.securemodules.jmh.benchmarks; import cpw.mods.niofs.union.UnionFileSystem; import cpw.mods.niofs.union.UnionFileSystemProvider; diff --git a/sjh-jmh/src/testjars/testjar1.jar b/sm-jmh/src/testjars/testjar1.jar similarity index 100% rename from sjh-jmh/src/testjars/testjar1.jar rename to sm-jmh/src/testjars/testjar1.jar diff --git a/sjh-jmh/src/testjars/testjar2.jar b/sm-jmh/src/testjars/testjar2.jar similarity index 100% rename from sjh-jmh/src/testjars/testjar2.jar rename to sm-jmh/src/testjars/testjar2.jar diff --git a/sjh-jmh/src/testjars/testjar3.jar b/sm-jmh/src/testjars/testjar3.jar similarity index 100% rename from sjh-jmh/src/testjars/testjar3.jar rename to sm-jmh/src/testjars/testjar3.jar diff --git a/sjh-jmh/src/testrawdir/ThisFileExists.txt b/sm-jmh/src/testrawdir/ThisFileExists.txt similarity index 100% rename from sjh-jmh/src/testrawdir/ThisFileExists.txt rename to sm-jmh/src/testrawdir/ThisFileExists.txt diff --git a/sjh-jmh/src/testrawdir/ThisFileExists3.txt b/sm-jmh/src/testrawdir/ThisFileExists3.txt similarity index 100% rename from sjh-jmh/src/testrawdir/ThisFileExists3.txt rename to sm-jmh/src/testrawdir/ThisFileExists3.txt diff --git a/sjh-jmh/src/testrawdir2/ThisFileExists2.txt b/sm-jmh/src/testrawdir2/ThisFileExists2.txt similarity index 100% rename from sjh-jmh/src/testrawdir2/ThisFileExists2.txt rename to sm-jmh/src/testrawdir2/ThisFileExists2.txt diff --git a/sjh-jmh/src/testrawdir2/ThisFileExists4.txt b/sm-jmh/src/testrawdir2/ThisFileExists4.txt similarity index 100% rename from sjh-jmh/src/testrawdir2/ThisFileExists4.txt rename to sm-jmh/src/testrawdir2/ThisFileExists4.txt diff --git a/sjh-test/build.gradle b/sm-test/build.gradle similarity index 100% rename from sjh-test/build.gradle rename to sm-test/build.gradle diff --git a/sm-test/src/test/java/module-info.java b/sm-test/src/test/java/module-info.java new file mode 100644 index 0000000..d20e7ce --- /dev/null +++ b/sm-test/src/test/java/module-info.java @@ -0,0 +1,12 @@ +module net.minecraftforge.securemodules.test { + requires cpw.mods.securejarhandler; // TODO: [SM][Deprecation] Remove CPW compatibility + + requires jdk.unsupported; + requires java.base; + requires org.junit.jupiter.api; + requires org.objectweb.asm; + requires org.objectweb.asm.tree; + requires net.minecraftforge.unsafe; + + opens net.minecraftforge.securemodules.test to org.junit.platform.commons; +} \ No newline at end of file diff --git a/sm-test/src/test/java/net/minecraftforge/securemodules/test/TestClassLoader.java b/sm-test/src/test/java/net/minecraftforge/securemodules/test/TestClassLoader.java new file mode 100644 index 0000000..5fe52d9 --- /dev/null +++ b/sm-test/src/test/java/net/minecraftforge/securemodules/test/TestClassLoader.java @@ -0,0 +1,31 @@ +package net.minecraftforge.securemodules.test; + +import net.minecraftforge.securemodules.SecureModuleClassLoader; +import net.minecraftforge.securemodules.SecureModuleFinder; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import java.lang.module.ModuleFinder; +import java.util.List; + +public class TestClassLoader { + @Test + void findsResourceFromNonBootLayer() throws Exception { + var cfg = ModuleLayer.boot().configuration().resolveAndBind(SecureModuleFinder.of(), ModuleFinder.ofSystem(), List.of()); + var cl = new SecureModuleClassLoader("test", cfg, List.of(ModuleLayer.boot()), null); + + var layer = ModuleLayer.boot().defineModules(cfg, m -> cl); // Should we actually find the classloader from the parent layers? + // It's used by the service loader to find module layers from classloaders + // and other internal JVM stuff. It doesn't actually look like anything breaks + assertNotNull(layer); + + var cls = Class.forName(cl.getClass().getName(), false, cl); + assertNotNull(cls); + + var className = cl.getClass().getName().replace('.', '/') + ".class"; + var resource = cl.getResource(className); + assertNotNull(resource); + } +} diff --git a/sjh-test/src/test/java/net/minecraftforge/securejarhandler/test/TestClassStuff.java b/sm-test/src/test/java/net/minecraftforge/securemodules/test/TestClassStuff.java similarity index 97% rename from sjh-test/src/test/java/net/minecraftforge/securejarhandler/test/TestClassStuff.java rename to sm-test/src/test/java/net/minecraftforge/securemodules/test/TestClassStuff.java index 314054c..a7d3609 100644 --- a/sjh-test/src/test/java/net/minecraftforge/securejarhandler/test/TestClassStuff.java +++ b/sm-test/src/test/java/net/minecraftforge/securemodules/test/TestClassStuff.java @@ -1,4 +1,4 @@ -package net.minecraftforge.securejarhandler.test; +package net.minecraftforge.securemodules.test; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; diff --git a/sjh-test/src/test/java/net/minecraftforge/securejarhandler/test/TestDummyJarProvider.java b/sm-test/src/test/java/net/minecraftforge/securemodules/test/TestDummyJarProvider.java similarity index 92% rename from sjh-test/src/test/java/net/minecraftforge/securejarhandler/test/TestDummyJarProvider.java rename to sm-test/src/test/java/net/minecraftforge/securemodules/test/TestDummyJarProvider.java index f356efc..b99faa4 100644 --- a/sjh-test/src/test/java/net/minecraftforge/securejarhandler/test/TestDummyJarProvider.java +++ b/sm-test/src/test/java/net/minecraftforge/securemodules/test/TestDummyJarProvider.java @@ -1,8 +1,9 @@ -package net.minecraftforge.securejarhandler.test; +package net.minecraftforge.securemodules.test; -import cpw.mods.cl.JarModuleFinder; -import cpw.mods.cl.ModuleClassLoader; import cpw.mods.jarhandling.SecureJar; +import net.minecraftforge.securemodules.SecureModuleClassLoader; +import net.minecraftforge.securemodules.SecureModuleFinder; + import org.junit.jupiter.api.Test; import org.objectweb.asm.ClassWriter; import org.objectweb.asm.Opcodes; @@ -27,9 +28,9 @@ public class TestDummyJarProvider { @Test public void testDummySecureJar() { - final var jmf = JarModuleFinder.of(new DummyJar()); + final var jmf = SecureModuleFinder.of(new DummyJar()); final var cf = Configuration.resolveAndBind(jmf, List.of(ModuleLayer.boot().configuration()), ModuleFinder.of(), List.of("testdummy")); - final var mcl = new ModuleClassLoader("TEST", cf, List.of(ModuleLayer.boot())); + final var mcl = new SecureModuleClassLoader("TEST", cf, List.of(ModuleLayer.boot()), null); final var ml = ModuleLayer.defineModules(cf, List.of(ModuleLayer.boot()), f->mcl); final var mod = ml.layer().findModule("testdummy").orElseThrow(); final var rmod = cf.findModule("testdummy").orElseThrow(); diff --git a/sjh-test/src/test/java/net/minecraftforge/securejarhandler/test/TestMetadata.java b/sm-test/src/test/java/net/minecraftforge/securemodules/test/TestMetadata.java similarity index 96% rename from sjh-test/src/test/java/net/minecraftforge/securejarhandler/test/TestMetadata.java rename to sm-test/src/test/java/net/minecraftforge/securemodules/test/TestMetadata.java index 33f8132..093d0fd 100644 --- a/sjh-test/src/test/java/net/minecraftforge/securejarhandler/test/TestMetadata.java +++ b/sm-test/src/test/java/net/minecraftforge/securemodules/test/TestMetadata.java @@ -1,4 +1,4 @@ -package net.minecraftforge.securejarhandler.test; +package net.minecraftforge.securemodules.test; import cpw.mods.jarhandling.JarMetadata; import org.junit.jupiter.api.Assertions; diff --git a/sjh-test/src/test/java/net/minecraftforge/securejarhandler/test/TestSecureJarLoading.java b/sm-test/src/test/java/net/minecraftforge/securemodules/test/TestSecureJarLoading.java similarity index 95% rename from sjh-test/src/test/java/net/minecraftforge/securejarhandler/test/TestSecureJarLoading.java rename to sm-test/src/test/java/net/minecraftforge/securemodules/test/TestSecureJarLoading.java index 4db940a..b6e88f9 100644 --- a/sjh-test/src/test/java/net/minecraftforge/securejarhandler/test/TestSecureJarLoading.java +++ b/sm-test/src/test/java/net/minecraftforge/securemodules/test/TestSecureJarLoading.java @@ -1,10 +1,9 @@ -package net.minecraftforge.securejarhandler.test; +package net.minecraftforge.securemodules.test; import cpw.mods.jarhandling.SecureJar; import cpw.mods.jarhandling.impl.Jar; import cpw.mods.jarhandling.impl.SecureJarVerifier; -import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import java.io.UncheckedIOException; @@ -17,13 +16,6 @@ import static org.junit.jupiter.api.Assertions.*; public class TestSecureJarLoading { - - @BeforeAll - static void setup() { - //System.setProperty("securejarhandler.debugVerifier", "true"); - System.setProperty("securejarhandler.useUnsafeAccessor", "true"); - } - @Test // All files are signed void testSecureJar() throws Exception { final var path = Paths.get("src", "test", "resources", "signed.zip"); diff --git a/sjh-test/src/test/java/net/minecraftforge/securejarhandler/test/TestUnionFS.java b/sm-test/src/test/java/net/minecraftforge/securemodules/test/TestUnionFS.java similarity index 93% rename from sjh-test/src/test/java/net/minecraftforge/securejarhandler/test/TestUnionFS.java rename to sm-test/src/test/java/net/minecraftforge/securemodules/test/TestUnionFS.java index 95ccfe5..2d07c58 100644 --- a/sjh-test/src/test/java/net/minecraftforge/securejarhandler/test/TestUnionFS.java +++ b/sm-test/src/test/java/net/minecraftforge/securemodules/test/TestUnionFS.java @@ -1,4 +1,4 @@ -package net.minecraftforge.securejarhandler.test; +package net.minecraftforge.securemodules.test; import org.junit.jupiter.api.Test; @@ -29,9 +29,9 @@ public class TestUnionFS { private static final UnionFileSystemProvider UFSP = (UnionFileSystemProvider) FileSystemProvider.installedProviders().stream().filter(fsp->fsp.getScheme().equals("union")).findFirst().orElseThrow(()->new IllegalStateException("Couldn't find UnionFileSystemProvider")); @SuppressWarnings("unchecked") - private void assertBasePaths(FileSystem fileSystem, List expected) throws Exception { + private void assertBasePaths(FileSystem fileSystem, List expected) throws Exception { if (!(fileSystem instanceof UnionFileSystem ufs)) - fail("FileSystem was not a UnionFileSystem"); + fail("FileSystem was not a UnionFileSystem"); var fld = UnionFileSystem.class.getDeclaredField("basepaths"); UnsafeHacks.setAccessible(fld); assertIterableEquals(expected, (List)fld.get(fileSystem)); @@ -62,9 +62,9 @@ void testUnionFileSystem() throws Exception { @Test void testUnionFileSystemJar() throws Throwable { - final var jar1 = Paths.get("..", "sjh-jmh","src", "testjars", "testjar1.jar").toAbsolutePath().normalize(); - final var jar2 = Paths.get("..", "sjh-jmh","src", "testjars", "testjar2.jar").toAbsolutePath().normalize(); - final var jar3 = Paths.get("..", "sjh-jmh","src", "testjars", "testjar3.jar").toAbsolutePath().normalize(); + final var jar1 = Paths.get("..", "sm-jmh","src", "testjars", "testjar1.jar").toAbsolutePath().normalize(); + final var jar2 = Paths.get("..", "sm-jmh","src", "testjars", "testjar2.jar").toAbsolutePath().normalize(); + final var jar3 = Paths.get("..", "sm-jmh","src", "testjars", "testjar3.jar").toAbsolutePath().normalize(); final var fileSystem = UFSP.newFileSystem(jar1, Map.of("additional", List.of(jar2, jar3))); assertBasePaths(fileSystem, List.of(jar3, jar2, jar1)); @@ -253,9 +253,9 @@ void testFileAttributes() { @Test public void testDirectoryVisitorJar() throws Exception { - final var jar1 = Paths.get("sjh-jmh","src", "testjars", "testjar1.jar").toAbsolutePath().normalize(); - final var jar2 = Paths.get("sjh-jmh","src", "testjars", "testjar2.jar").toAbsolutePath().normalize(); - final var jar3 = Paths.get("sjh-jmh","src", "testjars", "testjar3.jar").toAbsolutePath().normalize(); + final var jar1 = Paths.get("..", "sm-jmh","src", "testjars", "testjar1.jar").toAbsolutePath().normalize(); + final var jar2 = Paths.get("..", "sm-jmh","src", "testjars", "testjar2.jar").toAbsolutePath().normalize(); + final var jar3 = Paths.get("..", "sm-jmh","src", "testjars", "testjar3.jar").toAbsolutePath().normalize(); final var fileSystem = UFSP.newFileSystem(jar1, Map.of("additional", List.of(jar2, jar3))); var root = fileSystem.getPath("/"); diff --git a/sjh-test/src/test/java/net/minecraftforge/securejarhandler/test/TestUnionPath.java b/sm-test/src/test/java/net/minecraftforge/securemodules/test/TestUnionPath.java similarity index 99% rename from sjh-test/src/test/java/net/minecraftforge/securejarhandler/test/TestUnionPath.java rename to sm-test/src/test/java/net/minecraftforge/securemodules/test/TestUnionPath.java index 61403a2..16fd28e 100644 --- a/sjh-test/src/test/java/net/minecraftforge/securejarhandler/test/TestUnionPath.java +++ b/sm-test/src/test/java/net/minecraftforge/securemodules/test/TestUnionPath.java @@ -1,4 +1,4 @@ -package net.minecraftforge.securejarhandler.test; +package net.minecraftforge.securemodules.test; import org.junit.jupiter.api.Test; diff --git a/sjh-test/src/test/resources/dir1.zip b/sm-test/src/test/resources/dir1.zip similarity index 100% rename from sjh-test/src/test/resources/dir1.zip rename to sm-test/src/test/resources/dir1.zip diff --git a/sjh-test/src/test/resources/dir1/masktest.txt b/sm-test/src/test/resources/dir1/masktest.txt similarity index 100% rename from sjh-test/src/test/resources/dir1/masktest.txt rename to sm-test/src/test/resources/dir1/masktest.txt diff --git a/sjh-test/src/test/resources/dir1/masktest2.txt b/sm-test/src/test/resources/dir1/masktest2.txt similarity index 100% rename from sjh-test/src/test/resources/dir1/masktest2.txt rename to sm-test/src/test/resources/dir1/masktest2.txt diff --git a/sjh-test/src/test/resources/dir1/subdir1/masktestsd1.txt b/sm-test/src/test/resources/dir1/subdir1/masktestsd1.txt similarity index 100% rename from sjh-test/src/test/resources/dir1/subdir1/masktestsd1.txt rename to sm-test/src/test/resources/dir1/subdir1/masktestsd1.txt diff --git a/sjh-test/src/test/resources/dir2/masktest.txt b/sm-test/src/test/resources/dir2/masktest.txt similarity index 100% rename from sjh-test/src/test/resources/dir2/masktest.txt rename to sm-test/src/test/resources/dir2/masktest.txt diff --git a/sjh-test/src/test/resources/dir2/masktest3.txt b/sm-test/src/test/resources/dir2/masktest3.txt similarity index 100% rename from sjh-test/src/test/resources/dir2/masktest3.txt rename to sm-test/src/test/resources/dir2/masktest3.txt diff --git a/sjh-test/src/test/resources/empty.zip b/sm-test/src/test/resources/empty.zip similarity index 100% rename from sjh-test/src/test/resources/empty.zip rename to sm-test/src/test/resources/empty.zip diff --git a/sjh-test/src/test/resources/partial.zip b/sm-test/src/test/resources/partial.zip similarity index 100% rename from sjh-test/src/test/resources/partial.zip rename to sm-test/src/test/resources/partial.zip diff --git a/sjh-test/src/test/resources/signed.zip b/sm-test/src/test/resources/signed.zip similarity index 100% rename from sjh-test/src/test/resources/signed.zip rename to sm-test/src/test/resources/signed.zip diff --git a/sjh-test/src/test/resources/tampered.zip b/sm-test/src/test/resources/tampered.zip similarity index 100% rename from sjh-test/src/test/resources/tampered.zip rename to sm-test/src/test/resources/tampered.zip diff --git a/sjh-test/src/test/resources/unsigned.zip b/sm-test/src/test/resources/unsigned.zip similarity index 100% rename from sjh-test/src/test/resources/unsigned.zip rename to sm-test/src/test/resources/unsigned.zip diff --git a/src/main/java/cpw/mods/cl/JarModuleFinder.java b/src/main/java/cpw/mods/cl/JarModuleFinder.java index e9e39c8..68e8436 100644 --- a/src/main/java/cpw/mods/cl/JarModuleFinder.java +++ b/src/main/java/cpw/mods/cl/JarModuleFinder.java @@ -1,88 +1,16 @@ package cpw.mods.cl; import cpw.mods.jarhandling.SecureJar; -import java.io.IOException; -import java.io.InputStream; -import java.lang.module.ModuleFinder; -import java.lang.module.ModuleReader; -import java.lang.module.ModuleReference; -import java.net.URI; -import java.util.*; -import java.util.stream.Collectors; -import java.util.stream.Stream; +import net.minecraftforge.securemodules.SecureModuleFinder; -public class JarModuleFinder implements ModuleFinder { - private final Map moduleReferenceMap; - - JarModuleFinder(final SecureJar... jars) { - record ref(SecureJar.ModuleDataProvider jar, ModuleReference ref) {} - this.moduleReferenceMap = Arrays.stream(jars) - .map(jar->new ref(jar.moduleDataProvider(), new JarModuleReference(jar.moduleDataProvider()))) - .collect(Collectors.toMap(r->r.jar.name(), r->r.ref, (r1, r2)->r1)); - } - - @Override - public Optional find(final String name) { - return Optional.ofNullable(moduleReferenceMap.get(name)); - } - - @Override - public Set findAll() { - return new HashSet<>(moduleReferenceMap.values()); +/** Moved to {@link SecureModuleFinder} */ +@Deprecated(forRemoval = true) +public class JarModuleFinder extends SecureModuleFinder { + private JarModuleFinder(final SecureJar... jars) { + super(jars); } public static JarModuleFinder of(SecureJar... jars) { return new JarModuleFinder(jars); } - - static class JarModuleReference extends ModuleReference { - private final SecureJar.ModuleDataProvider jar; - - JarModuleReference(final SecureJar.ModuleDataProvider jar) { - super(jar.descriptor(), jar.uri()); - this.jar = jar; - } - - @Override - public ModuleReader open() throws IOException { - return new JarModuleReader(this.jar); - } - - public SecureJar.ModuleDataProvider jar() { - return this.jar; - } - } - - static class JarModuleReader implements ModuleReader { - private final SecureJar.ModuleDataProvider jar; - - public JarModuleReader(final SecureJar.ModuleDataProvider jar) { - this.jar = jar; - } - - @Override - public Optional find(final String name) throws IOException { - return jar.findFile(name); - } - - @Override - public Optional open(final String name) throws IOException { - return jar.open(name); - } - - @Override - public Stream list() throws IOException { - return null; - } - - @Override - public void close() throws IOException { - - } - - @Override - public String toString() { - return this.getClass().getName() + "[jar=" + jar + "]"; - } - } } diff --git a/src/main/java/cpw/mods/cl/ModularURLHandler.java b/src/main/java/cpw/mods/cl/ModularURLHandler.java index 08b9c21..bddb69d 100644 --- a/src/main/java/cpw/mods/cl/ModularURLHandler.java +++ b/src/main/java/cpw/mods/cl/ModularURLHandler.java @@ -6,70 +6,60 @@ import java.net.URL; import java.net.URLConnection; import java.net.URLStreamHandler; -import java.net.URLStreamHandlerFactory; -import java.util.Map; +import java.util.Optional; import java.util.ServiceLoader; import java.util.function.Function; -import java.util.stream.Collectors; -public class ModularURLHandler implements URLStreamHandlerFactory { - public static final ModularURLHandler INSTANCE = new ModularURLHandler(); - private Map handlers; +import net.minecraftforge.securemodules.LayerAwareURLStreamHandlerFactory; - public static void initFrom(ModuleLayer layer) { - if (layer == null) { - INSTANCE.handlers = null; - } else { - INSTANCE.handlers = ServiceLoader.load(layer, IURLProvider.class).stream() - .map(ServiceLoader.Provider::get) - .collect(Collectors.toMap(IURLProvider::protocol, Function.identity())); - } - } - @Override - public URLStreamHandler createURLStreamHandler(final String protocol) { - if (handlers == null) return null; - if (handlers.containsKey(protocol)) { - return new FunctionURLStreamHandler(handlers.get(protocol)); - } - return null; +/** + * This is our own fancy service provider solution to {@link java.net.spi.URLStreamHandlerProvider URLStreamHandlerProvider} + * only supporting the system class path for discovering services. For some dumb reasons cpw built a whole + * custom service for this instead of reusing the one provided by Java. + * + * I suspect that nobody actually fucking knows what this class is, and why it exists. + * It shouldn't be public API. There is no need for it. + */ +@Deprecated(forRemoval = true) +public class ModularURLHandler extends LayerAwareURLStreamHandlerFactory { + /** This is what consumers are expected to implement as a service */ + @Deprecated(forRemoval = true) + public interface IURLProvider { + String protocol(); + Function inputStreamFunction(); } - private static class FunctionURLStreamHandler extends URLStreamHandler { - private final IURLProvider iurlProvider; - - public FunctionURLStreamHandler(final IURLProvider iurlProvider) { - this.iurlProvider = iurlProvider; - } + public static final ModularURLHandler INSTANCE = new ModularURLHandler(); - @Override - protected URLConnection openConnection(final URL u) throws IOException { - return new FunctionURLConnection(u, this.iurlProvider); - } + public static void initFrom(ModuleLayer layer) { + new IllegalStateException("Who actually calls this? Report this stack trace to Forge").printStackTrace(); + INSTANCE.findProviders(layer); } - private static class FunctionURLConnection extends URLConnection { - private final IURLProvider provider; - - protected FunctionURLConnection(final URL url, final IURLProvider provider) { - super(url); - this.provider = provider; - } - - @Override - public void connect() throws IOException { - } - - @Override - public InputStream getInputStream() throws IOException { - try { - return provider.inputStreamFunction().apply(url); - } catch (UncheckedIOException e) { - throw e.getCause(); - } + @Override + public void findProviders(ModuleLayer layer) { + super.findProviders(layer); + if (layer != null) { + ServiceLoader.load(layer, IURLProvider.class).stream() + .map(ServiceLoader.Provider::get) + .forEach(provider -> { + handlers.put(provider.protocol(), Optional.of(new URLStreamHandler() { + @Override + protected URLConnection openConnection(URL u) throws IOException { + return new URLConnection(u) { + @Override public void connect() throws IOException {} + @Override + public InputStream getInputStream() throws IOException { + try { + return provider.inputStreamFunction().apply(url); + } catch (UncheckedIOException e) { + throw e.getCause(); + } + } + }; + } + })); + }); } } - public interface IURLProvider { - String protocol(); - Function inputStreamFunction(); - } } diff --git a/src/main/java/cpw/mods/cl/ModuleClassLoader.java b/src/main/java/cpw/mods/cl/ModuleClassLoader.java index 1497b1f..1a2b5f2 100644 --- a/src/main/java/cpw/mods/cl/ModuleClassLoader.java +++ b/src/main/java/cpw/mods/cl/ModuleClassLoader.java @@ -1,227 +1,66 @@ package cpw.mods.cl; -import cpw.mods.util.LambdaExceptionUtils; - import java.io.FileNotFoundException; import java.io.IOException; -import java.io.InputStream; -import java.io.UncheckedIOException; -import java.lang.module.*; -import java.net.MalformedURLException; -import java.net.URI; -import java.net.URL; -import java.util.*; +import java.lang.module.Configuration; +import java.lang.module.ModuleReader; +import java.lang.module.ModuleReference; +import java.util.List; import java.util.function.BiFunction; -import java.util.function.Supplier; -import java.util.stream.Collectors; -import java.util.stream.Stream; -public class ModuleClassLoader extends ClassLoader { +import net.minecraftforge.securemodules.SecureModuleClassLoader; + +@Deprecated(forRemoval = true) +public class ModuleClassLoader extends SecureModuleClassLoader { static { ClassLoader.registerAsParallelCapable(); - URL.setURLStreamHandlerFactory(ModularURLHandler.INSTANCE); - ModularURLHandler.initFrom(ModuleClassLoader.class.getModule().getLayer()); - } - - private final Configuration configuration; - private final Map resolvedRoots; - private final Map packageLookup; - private final Map parentLoaders; - private ClassLoader fallbackClassLoader = ClassLoader.getPlatformClassLoader(); - - public ModuleClassLoader(final String name, final Configuration configuration, final List parentLayers) { - super(name, null); - this.configuration = configuration; - this.resolvedRoots = configuration.modules().stream() - .map(ResolvedModule::reference) - .filter(JarModuleFinder.JarModuleReference.class::isInstance) - .collect(Collectors.toMap(r -> r.descriptor().name(), r -> (JarModuleFinder.JarModuleReference) r)); - - this.packageLookup = new HashMap<>(); - for (var mod : this.configuration.modules()) { - if (this.resolvedRoots.containsKey(mod.name())) { - mod.reference().descriptor().packages().forEach(pk->this.packageLookup.put(pk, mod)); - } - } - - this.parentLoaders = new HashMap<>(); - for (var rm : configuration.modules()) { - for (var other : rm.reads()) { - Supplier findClassLoader = ()->{ - // Loading a class requires its module to be part of resolvedRoots - // If it's not, we delegate loading to its module's classloader - if (!this.resolvedRoots.containsKey(other.name())) { - return parentLayers.stream() - .filter(l -> l.configuration() == other.configuration()) - .flatMap(layer->Optional.ofNullable(layer.findLoader(other.name())).stream()) - .findFirst() - .orElse(ClassLoader.getPlatformClassLoader()); - } else { - return ModuleClassLoader.this; - } - }; - var cl = findClassLoader.get(); - final var descriptor = other.reference().descriptor(); - if (descriptor.isAutomatic()) { - descriptor.packages().forEach(pn->this.parentLoaders.put(pn, cl)); - } else { - descriptor.exports().stream() - .filter(e -> !e.isQualified() || (e.isQualified() && other.configuration() == configuration && e.targets().contains(rm.name()))) - .map(ModuleDescriptor.Exports::source) - .forEach(pn->this.parentLoaders.put(pn, cl)); - } - } - } - } - - private URL readerToURL(final ModuleReader reader, final ModuleReference ref, final String name) { - try { - return ModuleClassLoader.toURL(reader.find(name)); - } catch (IOException e) { - return null; - } - } - - @SuppressWarnings("OptionalUsedAsFieldOrParameterType") - private static URL toURL(final Optional uri) { - if (uri.isPresent()) { - try { - return uri.get().toURL(); - } catch (MalformedURLException e) { - throw new IllegalArgumentException(e); - } - } - return null; - } - - @SuppressWarnings("OptionalUsedAsFieldOrParameterType") - private static Stream closeHandler(Optional supplier) { - final var is = supplier.orElse(null); - return Optional.ofNullable(is).stream().onClose(() -> Optional.ofNullable(is).ifPresent(LambdaExceptionUtils.rethrowConsumer(InputStream::close))); - } - protected byte[] getClassBytes(final ModuleReader reader, final ModuleReference ref, final String name) { - var cname = name.replace('.','/')+".class"; - - try (var istream = closeHandler(Optional.of(reader).flatMap(LambdaExceptionUtils.rethrowFunction(r->r.open(cname))))) { - return istream.map(LambdaExceptionUtils.rethrowFunction(InputStream::readAllBytes)) - .findFirst() - .orElseGet(()->new byte[0]); - } - } - - private Class readerToClass(final ModuleReader reader, final ModuleReference ref, final String name) { - var bytes = maybeTransformClassBytes(getClassBytes(reader, ref, name), name, null); - if (bytes.length == 0) return null; - var cname = name.replace('.','/')+".class"; - var modroot = this.resolvedRoots.get(ref.descriptor().name()); - ProtectionDomainHelper.tryDefinePackage(this, name, modroot.jar().getManifest(), t->modroot.jar().getManifest().getAttributes(t), this::definePackage); // Packages are dirctories, and can't be signed, so use raw attributes instead of signed. - var cs = ProtectionDomainHelper.createCodeSource(toURL(ref.location()), modroot.jar().verifyAndGetSigners(cname, bytes)); - return defineClass(name, bytes, 0, bytes.length, ProtectionDomainHelper.createProtectionDomain(cs, this)); - } - - protected byte[] maybeTransformClassBytes(final byte[] bytes, final String name, final String context) { - return bytes; - } - - @Override - protected Class loadClass(final String name, final boolean resolve) throws ClassNotFoundException { - synchronized (getClassLoadingLock(name)) { - var c = findLoadedClass(name); - if (c == null) { - var index = name.lastIndexOf('.'); - if (index >= 0) { - final var pname = name.substring(0, index); - if (this.packageLookup.containsKey(pname)) { - c = findClass(this.packageLookup.get(pname).name(), name); - } else { - c = this.parentLoaders.getOrDefault(pname, fallbackClassLoader).loadClass(name); - } - } - } - if (c == null) throw new ClassNotFoundException(name); - if (resolve) resolveClass(c); - return c; - } - } - - @Override - protected Class findClass(final String name) throws ClassNotFoundException { - final String mname = classNameToModuleName(name); - if (mname != null) { - return findClass(mname, name); - } else { - return super.findClass(name); - } - } - - protected String classNameToModuleName(final String name) { - final var pname = name.substring(0, name.lastIndexOf('.')); - return Optional.ofNullable(this.packageLookup.get(pname)).map(ResolvedModule::name).orElse(null); } - private Package definePackage(final String[] args) { - return definePackage(args[0], args[1], args[2], args[3], args[4], args[5], args[6], null); + /* ====================================================================== + * API Ment for modders to consume + * ====================================================================== + */ + public ModuleClassLoader(String name, Configuration config, @Deprecated List parentLayers) { + super(name, config, parentLayers, null); } @Override - public URL getResource(final String name) { - try { - var reslist = findResourceList(name); - if (!reslist.isEmpty()) { - return reslist.get(0); - } else { - return fallbackClassLoader.getResource(name); - } - } catch (IOException e) { - return null; - } + protected byte[] getClassBytes(ModuleReader reader, ModuleReference ref, String name) throws IOException { + return super.getClassBytes(reader, ref, name); } @Override - protected URL findResource(final String moduleName, final String name) throws IOException { - try { - return loadFromModule(moduleName, (reader, ref) -> this.readerToURL(reader, ref, name)); - } catch (UncheckedIOException ioe) { - throw ioe.getCause(); - } + protected byte[] maybeTransformClassBytes(byte[] bytes, String name, String context) { + return super.maybeTransformClassBytes(bytes, name, context); } @Override - public Enumeration getResources(final String name) throws IOException { - return Collections.enumeration(findResourceList(name)); - } - - private List findResourceList(final String name) throws IOException { - var idx = name.lastIndexOf('/'); - var pkgname = (idx == -1 || idx==name.length()-1) ? "" : name.substring(0,idx).replace('/','.'); - var module = packageLookup.get(pkgname); - if (module != null) { - var res = findResource(module.name(), name); - return res != null ? List.of(res): List.of(); - } else { - return resolvedRoots.values().stream() - .map(JarModuleFinder.JarModuleReference::jar) - .map(jar->jar.findFile(name)) - .map(ModuleClassLoader::toURL) - .filter(Objects::nonNull) - .toList(); - } - } - - @Override - protected Enumeration findResources(final String name) throws IOException { - return Collections.enumeration(findResourceList(name)); - } - - @Override - protected Class findClass(final String moduleName, final String name) { - try { - return loadFromModule(moduleName, (reader, ref) -> this.readerToClass(reader, ref, name)); - } catch (IOException e) { - return null; - } + protected byte[] getMaybeTransformedClassBytes(String name, String context) throws ClassNotFoundException { + return super.getMaybeTransformedClassBytes(name, context); + } + + /* ====================================================================== + * BACKWARD COMPATIBILITY CRAP + * ====================================================================== + */ + + /** + * modlauncher uses this to set the bootstrap classloader instead of using the System bootstrap. + * But defaults to using the System bootstrap until this is overwritten + * Honestly thats a stupid design and should just use the normal parental delegation like every + * other classloader implementation. + * + * TODO: [SM][Deprecation] Remove fallback classloader + */ + @Deprecated(forRemoval = true) + public void setFallbackClassLoader(final ClassLoader fallbackClassLoader) { + this.fallbackClassLoader = fallbackClassLoader; } + /** + * Who consumes this? You should tell me on discord, why you use it instead of other standard ClassLoader functions + */ + @Deprecated(forRemoval = true) protected T loadFromModule(final String moduleName, BiFunction lookup) throws IOException { var module = configuration.findModule(moduleName).orElseThrow(FileNotFoundException::new); var ref = module.reference(); @@ -230,33 +69,11 @@ protected T loadFromModule(final String moduleName, BiFunctionthis.getClassBytes(reader, ref, name)); - } else if (this.parentLoaders.containsKey(pname)) { - var cname = name.replace('.','/')+".class"; - try (var is = this.parentLoaders.get(pname).getResourceAsStream(cname)) { - if (is != null) - bytes = is.readAllBytes(); - } - } - } catch (IOException e) { - suppressed = e; - } - byte[] maybeTransformedBytes = maybeTransformClassBytes(bytes, name, context); - if (maybeTransformedBytes.length == 0) { - ClassNotFoundException e = new ClassNotFoundException(name); - if (suppressed != null) e.addSuppressed(suppressed); - throw e; - } - return maybeTransformedBytes; - } - - public void setFallbackClassLoader(final ClassLoader fallbackClassLoader) { - this.fallbackClassLoader = fallbackClassLoader; + /** + * I honestly have no idea why this would be protected instead of private. If you use it let me know. + */ + @Deprecated(forRemoval = true) + protected String classNameToModuleName(String name) { + return super.classNameToModuleName(name); } } diff --git a/src/main/java/cpw/mods/cl/ProtectionDomainHelper.java b/src/main/java/cpw/mods/cl/ProtectionDomainHelper.java index b9bb025..9bb7348 100644 --- a/src/main/java/cpw/mods/cl/ProtectionDomainHelper.java +++ b/src/main/java/cpw/mods/cl/ProtectionDomainHelper.java @@ -4,10 +4,13 @@ import java.security.*; import java.util.HashMap; import java.util.Map; -import java.util.function.Function; -import java.util.jar.Attributes; -import java.util.jar.Manifest; +/** + * This is just a class for caching code source and protection domains. + * Honestly, nobody should ever of been using this, and it's superfluous now + * that our ClassLoader extends {@link java.security.SecureClassLoader} + */ +@Deprecated(forRemoval = true) public class ProtectionDomainHelper { private static final Map csCache = new HashMap<>(); public static CodeSource createCodeSource(final URL url, final CodeSigner[] signers) { @@ -26,55 +29,4 @@ public static ProtectionDomain createProtectionDomain(CodeSource codeSource, Cla }); } } - - static Package tryDefinePackage(final ClassLoader classLoader, String name, Manifest man, Function trustedEntries, Function definePackage) throws IllegalArgumentException - { - final var pname = name.substring(0, name.lastIndexOf('.')); - if (classLoader.getDefinedPackage(pname) == null) { - synchronized (classLoader) { - if (classLoader.getDefinedPackage(pname) != null) return classLoader.getDefinedPackage(pname); - - String path = pname.replace('.', '/').concat("/"); - String specTitle = null, specVersion = null, specVendor = null; - String implTitle = null, implVersion = null, implVendor = null; - - if (man != null) { - Attributes attr = trustedEntries.apply(path); - if (attr != null) { - specTitle = attr.getValue(Attributes.Name.SPECIFICATION_TITLE); - specVersion = attr.getValue(Attributes.Name.SPECIFICATION_VERSION); - specVendor = attr.getValue(Attributes.Name.SPECIFICATION_VENDOR); - implTitle = attr.getValue(Attributes.Name.IMPLEMENTATION_TITLE); - implVersion = attr.getValue(Attributes.Name.IMPLEMENTATION_VERSION); - implVendor = attr.getValue(Attributes.Name.IMPLEMENTATION_VENDOR); - } - attr = man.getMainAttributes(); - if (attr != null) { - if (specTitle == null) { - specTitle = attr.getValue(Attributes.Name.SPECIFICATION_TITLE); - } - if (specVersion == null) { - specVersion = attr.getValue(Attributes.Name.SPECIFICATION_VERSION); - } - if (specVendor == null) { - specVendor = attr.getValue(Attributes.Name.SPECIFICATION_VENDOR); - } - if (implTitle == null) { - implTitle = attr.getValue(Attributes.Name.IMPLEMENTATION_TITLE); - } - if (implVersion == null) { - implVersion = attr.getValue(Attributes.Name.IMPLEMENTATION_VERSION); - } - if (implVendor == null) { - implVendor = attr.getValue(Attributes.Name.IMPLEMENTATION_VENDOR); - } - } - } - return definePackage.apply(new String[]{pname, specTitle, specVersion, specVendor, implTitle, implVersion, implVendor}); - } - } else { - return classLoader.getDefinedPackage(pname); - } - } - } diff --git a/src/main/java/cpw/mods/cl/UnionURLStreamHandler.java b/src/main/java/cpw/mods/cl/UnionURLStreamHandler.java index 4df95ce..3349485 100644 --- a/src/main/java/cpw/mods/cl/UnionURLStreamHandler.java +++ b/src/main/java/cpw/mods/cl/UnionURLStreamHandler.java @@ -8,6 +8,10 @@ import java.nio.file.Paths; import java.util.function.Function; +/** + * UnionFileSystem implements this as a base Java provider normally now. + */ +@Deprecated(forRemoval = true) public class UnionURLStreamHandler implements ModularURLHandler.IURLProvider { @Override public String protocol() { @@ -26,8 +30,6 @@ public Function inputStreamFunction() { } catch (URISyntaxException e) { throw new RuntimeException(e); } - - }; } } diff --git a/src/main/java/cpw/mods/jarhandling/JarMetadata.java b/src/main/java/cpw/mods/jarhandling/JarMetadata.java index f46f243..06130cc 100644 --- a/src/main/java/cpw/mods/jarhandling/JarMetadata.java +++ b/src/main/java/cpw/mods/jarhandling/JarMetadata.java @@ -13,15 +13,41 @@ public interface JarMetadata { String name(); String version(); ModuleDescriptor descriptor(); + + static JarMetadata from(final SecureJar jar, final Path... paths) { + return fromImpl(jar, paths); + } + + /** + * Attempts to find the module and version information based on filename/path. + * Supports files laid out in Maven style directories/filenames. + * As well as files that have 'version looking' endings. Currently defined as Everything after the last - being a digit or . + */ + static SimpleJarMetadata fromFileName(final Path path, final Set pkgs, final List providers) { + return fromFileNameImpl(path, pkgs, providers); + } + + /* ====================================================================== + * BACKWARD COMPATIBILITY CRAP + * ====================================================================== + */ // ALL from jdk.internal.module.ModulePath.java + @Deprecated(forRemoval = true) Pattern DASH_VERSION = Pattern.compile("-([.\\d]+)"); + @Deprecated(forRemoval = true) Pattern NON_ALPHANUM = Pattern.compile("[^A-Za-z0-9]"); + @Deprecated(forRemoval = true) Pattern REPEATING_DOTS = Pattern.compile("(\\.)(\\1)+"); + @Deprecated(forRemoval = true) Pattern LEADING_DOTS = Pattern.compile("^\\."); + @Deprecated(forRemoval = true) Pattern TRAILING_DOTS = Pattern.compile("\\.$"); // Extra sanitization + @Deprecated(forRemoval = true) Pattern MODULE_VERSION = Pattern.compile("(?<=^|-)([\\d][.\\d]*)"); + @Deprecated(forRemoval = true) Pattern NUMBERLIKE_PARTS = Pattern.compile("(?<=^|\\.)([0-9]+)"); // matches asdf.1.2b because both are invalid java identifiers + @Deprecated(forRemoval = true) List ILLEGAL_KEYWORDS = List.of( "abstract","continue","for","new","switch","assert", "default","goto","package","synchronized","boolean", @@ -31,9 +57,15 @@ public interface JarMetadata { "extends","int","short","try","char","final","interface", "static","void","class","finally","long","strictfp", "volatile","const","float","native","super","while"); + @Deprecated(forRemoval = true) Pattern KEYWORD_PARTS = Pattern.compile("(?<=^|\\.)(" + String.join("|", ILLEGAL_KEYWORDS) + ")(?=\\.|$)"); - static JarMetadata from(final SecureJar jar, final Path... path) { + + /* ====================================================================== + * INTERNAL IMPLEMENTATION CRAP + * ====================================================================== + */ + private static JarMetadata fromImpl(final SecureJar jar, final Path... path) { if (path.length==0) throw new IllegalArgumentException("Need at least one path"); final var pkgs = jar.getPackages(); var mi = jar.moduleDataProvider().findFile("module-info.class"); @@ -50,8 +82,8 @@ static JarMetadata from(final SecureJar jar, final Path... path) { } } } - static SimpleJarMetadata fromFileName(final Path path, final Set pkgs, final List providers) { + private static SimpleJarMetadata fromFileNameImpl(final Path path, final Set pkgs, final List providers) { // detect Maven-like paths Path versionMaybe = path.getParent(); if (versionMaybe != null) @@ -76,12 +108,12 @@ static SimpleJarMetadata fromFileName(final Path path, final Set pkgs, f } // fallback parsing - var fn = path.getFileName().toString(); + var fn = path.getFileName().toString(); var lastDot = fn.lastIndexOf('.'); if (lastDot > 0) { fn = fn.substring(0, lastDot); // strip extension if possible } - + var mat = DASH_VERSION.matcher(fn); if (mat.find()) { var potential = fn.substring(mat.start() + 1); diff --git a/src/main/java/cpw/mods/niofs/union/UnionFileSystem.java b/src/main/java/cpw/mods/niofs/union/UnionFileSystem.java index c1060a8..b85b1ba 100644 --- a/src/main/java/cpw/mods/niofs/union/UnionFileSystem.java +++ b/src/main/java/cpw/mods/niofs/union/UnionFileSystem.java @@ -1,6 +1,9 @@ package cpw.mods.niofs.union; -import java.io.*; +import java.io.ByteArrayInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodType; @@ -32,7 +35,6 @@ import java.util.function.Function; import java.util.stream.Collectors; import java.util.stream.IntStream; -import java.util.stream.Stream; import java.util.stream.StreamSupport; import net.minecraftforge.unsafe.UnsafeHacks; @@ -236,14 +238,6 @@ private Optional getFileAttributes(final Path path) { } } - private Optional findFirstPathAt(final UnionPath path) { - return this.basepaths.stream() - .map(p->toRealPath(p , path)) - .filter(p->p!=notExistingPath) - .filter(Files::exists) - .findFirst(); - } - private static boolean zipFsExists(UnionFileSystem ufs, Path path) { try { if (Optional.ofNullable(ufs.embeddedFileSystems.get(path.getFileSystem())).filter(efs->!efs.fsCh.isOpen()).isPresent()) throw new IllegalStateException("The zip file has closed!"); @@ -272,12 +266,6 @@ private Optional findFirstFiltered(final UnionPath path) { return Optional.empty(); } - private Stream streamPathList(final Function> function) { - return this.basepaths.stream() - .map(function) - .flatMap(Optional::stream); - } - @SuppressWarnings("unchecked") public A readAttributes(final UnionPath path, final Class type, final LinkOption... options) throws IOException { if (type == BasicFileAttributes.class) { diff --git a/src/main/java/cpw/mods/niofs/union/UnionURLStreamHandlerProvider.java b/src/main/java/cpw/mods/niofs/union/UnionURLStreamHandlerProvider.java new file mode 100644 index 0000000..5aeeb39 --- /dev/null +++ b/src/main/java/cpw/mods/niofs/union/UnionURLStreamHandlerProvider.java @@ -0,0 +1,47 @@ +package cpw.mods.niofs.union; + +import java.io.IOException; +import java.io.InputStream; +import java.io.UncheckedIOException; +import java.net.URISyntaxException; +import java.net.URL; +import java.net.URLConnection; +import java.net.URLStreamHandler; +import java.net.spi.URLStreamHandlerProvider; +import java.nio.file.Paths; + + +public class UnionURLStreamHandlerProvider extends URLStreamHandlerProvider { + @Override + public URLStreamHandler createURLStreamHandler(String protocol) { + if ("union".equals(protocol)) { + return new URLStreamHandler() { + @Override + protected URLConnection openConnection(URL url) throws IOException { + return new URLConnection(url) { + @Override public void connect() throws IOException {} + + @Override + public InputStream getInputStream() throws IOException { + try { + if (Paths.get(this.url.toURI()) instanceof UnionPath upath) + return upath.buildInputStream(); + throw new IllegalArgumentException("Invalid Path " + this.url.toURI()); + } catch (URISyntaxException e) { + return sneak(e); + } catch (UncheckedIOException e) { + throw e.getCause(); + } + } + }; + } + }; + } + return null; + } + + @SuppressWarnings("unchecked") + private static R sneak(Exception exception) throws E { + throw (E)exception; + } +} diff --git a/src/main/java/module-info.java b/src/main/java/module-info.java index 95481f3..fc1c3c1 100644 --- a/src/main/java/module-info.java +++ b/src/main/java/module-info.java @@ -1,18 +1,27 @@ -import cpw.mods.cl.ModularURLHandler; -import cpw.mods.cl.UnionURLStreamHandler; -import cpw.mods.niofs.union.UnionFileSystemProvider; - +@SuppressWarnings("removal") module cpw.mods.securejarhandler { + // Backwards compatibility crap, delete when moving to our own module. exports cpw.mods.jarhandling; exports cpw.mods.jarhandling.impl; // TODO - Bump version, and remove this export, you don't need our implementation exports cpw.mods.cl; exports cpw.mods.niofs.union; + + exports net.minecraftforge.securemodules; + requires org.objectweb.asm; requires org.objectweb.asm.tree; requires java.base; - requires net.minecraftforge.unsafe; + requires net.minecraftforge.unsafe; + + // TODO: Move UnionFS out into its own project + provides java.nio.file.spi.FileSystemProvider + with cpw.mods.niofs.union.UnionFileSystemProvider; + + uses java.net.spi.URLStreamHandlerProvider; + provides java.net.spi.URLStreamHandlerProvider with + cpw.mods.niofs.union.UnionURLStreamHandlerProvider; - provides java.nio.file.spi.FileSystemProvider with UnionFileSystemProvider; uses cpw.mods.cl.ModularURLHandler.IURLProvider; - provides ModularURLHandler.IURLProvider with UnionURLStreamHandler; + provides cpw.mods.cl.ModularURLHandler.IURLProvider with + cpw.mods.cl.UnionURLStreamHandler; } \ No newline at end of file diff --git a/src/main/java/net/minecraftforge/securemodules/LayerAwareURLStreamHandlerFactory.java b/src/main/java/net/minecraftforge/securemodules/LayerAwareURLStreamHandlerFactory.java new file mode 100644 index 0000000..1a7eea9 --- /dev/null +++ b/src/main/java/net/minecraftforge/securemodules/LayerAwareURLStreamHandlerFactory.java @@ -0,0 +1,56 @@ +package net.minecraftforge.securemodules; + +import java.net.URLStreamHandler; +import java.net.URLStreamHandlerFactory; +import java.net.spi.URLStreamHandlerProvider; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.ServiceLoader; + +/** + * This is our own solution to {@link java.net.spi.URLStreamHandlerProvider URLStreamHandlerProvider} + * only supporting the system class path for discovering services. + * + * This *should* be a package private class, but for now, it's public so {@link cpw.mods.cl.ModularURLHandler} can find it. + * I will make private once I break compatibility. + */ +public class LayerAwareURLStreamHandlerFactory implements URLStreamHandlerFactory { + protected Map> handlers; + private List services; + + /** TODO: [SM][Deprecation] Move to package private when I delete cpw.mods.cl.ModularURLHandler */ + @Deprecated + public void findProviders(ModuleLayer layer) { + if (layer == null) { + handlers = null; + services = null; + } else { + handlers = new HashMap<>(); + services = ServiceLoader.load(layer, URLStreamHandlerProvider.class).stream() + .map(ServiceLoader.Provider::get) + .toList(); + } + } + + @Override + public URLStreamHandler createURLStreamHandler(final String protocol) { + if (handlers == null) return null; + var cached = handlers.get(protocol); + if (cached != null) + return cached.orElse(null); + + for (var service : services) { + var handler = service.createURLStreamHandler(protocol); + if (handler != null) { + handlers.put(protocol, Optional.of(handler)); + return handler; + } + } + + handlers.put(protocol, Optional.empty()); + return null; + } + +} diff --git a/src/main/java/net/minecraftforge/securemodules/SecureModuleClassLoader.java b/src/main/java/net/minecraftforge/securemodules/SecureModuleClassLoader.java new file mode 100644 index 0000000..546641a --- /dev/null +++ b/src/main/java/net/minecraftforge/securemodules/SecureModuleClassLoader.java @@ -0,0 +1,543 @@ +package net.minecraftforge.securemodules; + +import java.io.IOException; +import java.lang.module.*; +import java.net.MalformedURLException; +import java.net.URI; +import java.net.URL; +import java.security.AllPermission; +import java.security.CodeSource; +import java.security.PermissionCollection; +import java.security.Permissions; +import java.security.SecureClassLoader; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.concurrent.ConcurrentHashMap; +import java.util.jar.Attributes; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +public class SecureModuleClassLoader extends SecureClassLoader { + private static void log(String message) { System.out.println(message); } // TODO: [SM] Introduce proper logging framework + + static { + ClassLoader.registerAsParallelCapable(); + setupModularURLHandler(); + } + + @SuppressWarnings("removal") + private static void setupModularURLHandler() { + var handler = cpw.mods.cl.ModularURLHandler.INSTANCE; + URL.setURLStreamHandlerFactory(handler); + handler.findProviders(SecureModuleClassLoader.class.getModule().getLayer()); + } + + // TODO: [SM][Deprecation] Make private once cpw.mods.cl.ModuleClassLoader is deleted + protected final Configuration configuration; + private final List parents; + private final Map ourModules = new HashMap<>(); + private final Map ourModulesSecure = new HashMap<>(); + private final Map packageToOurModules = new HashMap<>(); + private final Map packageToParentLoader = new HashMap<>(); + private final Map moduleReaders = new ConcurrentHashMap<>(); + private final List allParentLoaders; + + @Deprecated(forRemoval = true) + protected ClassLoader fallbackClassLoader = ClassLoader.getPlatformClassLoader(); + + public SecureModuleClassLoader(String name, Configuration config, List parentLayers) { + this(name, config, parentLayers, null); + } + + public SecureModuleClassLoader(String name, Configuration config, List parentLayers, ClassLoader parent) { + super(name, parent); + if (parent != null) // No need to be backwards compatible if they specify the parent + fallbackClassLoader = null; + + this.configuration = config; + this.parents = Stream.concat(parentLayers.stream(), List.of(ModuleLayer.boot()).stream()).distinct().toList(); // Old cpw code sends in duplicate layers Guard against + this.allParentLoaders = this.parents.stream() + .flatMap(p -> p.modules().stream()) + .map(Module::getClassLoader) + .filter(cl -> cl != null) + .distinct() + .collect(Collectors.toCollection(ArrayList::new)); + + // remove all that are covered by their parent loaders. + var overlaping = new ArrayList(); + for (var loader : this.allParentLoaders) { + do { + loader = loader.getParent(); + if (loader != null && this.allParentLoaders.contains(loader)) + overlaping.add(loader); + } while (loader != null); + } + this.allParentLoaders.removeAll(overlaping); + + + // So 'resolvedRoots' seems to be trying to be our special 'SecureJar' references, TODO: See if we actually need it. + for (var module : config.modules()) { + var ref = module.reference(); + this.ourModules.put(ref.descriptor().name(), ref); + + if (ref instanceof SecureModuleReference smr) { + this.ourModulesSecure.put(smr.descriptor().name(), smr); + for (var pkg : smr.descriptor().packages()) + this.packageToOurModules.put(pkg, module); + } else { + log("Invalid ModuleClassLoader module: " + module); + } + } + + /* + log("New ModuleClassLoader(" + name + ", @" + config.hashCode() + "[" + config + "])"); + for (var parent : parents) + log(" Parent @" + parent.hashCode() + "[" + parent.configuration() + "]"); + */ + + // Gather packages in other classloaders that our modules read + for (var module : config.modules()) { + for (var other : module.reads()) { + // If it reads the same layer as this, then we're good + if (other.configuration() == this.configuration) + continue; + + var layer = this.parents.stream() + .filter(p -> p.configuration() == other.configuration()) + .findFirst() + .orElse(null); + + + if (layer == null) + throw new IllegalStateException("Could not find parent layer for module `" + other.name() + "` read by `" + module.name() + "`"); + + + var cl = layer == null ? null : layer.findLoader(other.name()); + if (cl == null) + cl = ClassLoader.getPlatformClassLoader(); + + var descriptor = other.reference().descriptor(); + if (descriptor.isAutomatic()) { + for (var pkg : descriptor.packages()) + setLoader(pkg, cl); + } else { + for (var export : descriptor.exports()) { + if (!export.isQualified()) + setLoader(export.source(), cl); + else if (config == other.configuration() && !export.targets().contains(module.name())) + setLoader(export.source(), cl); + } + } + } + } + } + + protected byte[] getClassBytes(ModuleReader reader, ModuleReference ref, String name) throws IOException { + var read = reader.open(classToResource(name)); + if (!read.isPresent()) + return new byte[0]; + + try (var is = read.get()) { + return is.readAllBytes(); + } + } + + protected byte[] maybeTransformClassBytes(byte[] bytes, String name, String context) { + return bytes; + } + + protected byte[] getMaybeTransformedClassBytes(String name, String context) throws ClassNotFoundException { + Objects.requireNonNull(name); + byte[] bytes = new byte[0]; + Throwable suppressed = null; + try { + var pkg = classToPackage(name); + var module = this.packageToOurModules.get(pkg); + if (module != null) { + var ref = module.reference(); + try (var reader = ref.open()) { + return this.getClassBytes(reader, ref, name); + } + } else { + var parent = this.packageToParentLoader.get(pkg); + if (parent != null) { + try (var is = parent.getResourceAsStream(classToResource(name))) { + if (is != null) + bytes = is.readAllBytes(); + } + } + } + } catch (IOException e) { + suppressed = e; + } + + byte[] maybeTransformedBytes = maybeTransformClassBytes(bytes, name, context); + if (maybeTransformedBytes.length == 0) { + var e = new ClassNotFoundException(name); + if (suppressed != null) + e.addSuppressed(suppressed); + throw e; + } + return maybeTransformedBytes; + } + + /* ====================================================================== + * FIND FIRST RESOURCE + * ====================================================================== + */ + + @Override + public URL getResource(String name) { + Objects.requireNonNull(name); + + var url = super.getResource(name); + if (url != null) + return url; + + // The normal Modulear classloader { jdk.internal.loader.Loader.getResource(String) } delegates to the parent + // But for some reason we set the parent to null + // So manually look in the parent layers. + for (var parent : this.allParentLoaders) { + url = parent.getResource(name); + if (url != null) + return url; + } + + return null; + } + + @Override + public URL findResource(String name) { + Objects.requireNonNull(name); + var pkg = pathToPackage(name); + var module = this.packageToOurModules.get(pkg); + if (module != null) { + try { + var url = findResource(module.name(), name); + if (url != null && isOpenResource(name, url, module, pkg)) + return url; + } catch (IOException e) { + // We didn't find shit! + } + } else { + for (var moduleName : this.ourModules.keySet()) { + try { + var url = findResource(moduleName, name); + if (url != null) + return url; + } catch (IOException e) { + // Nope nothing in that module + } + } + } + + return null; + } + + @Override + protected URL findResource(String moduleName, String name) throws IOException { + Objects.requireNonNull(name); + var module = moduleName == null ? null : this.ourModules.get(moduleName); + if (module == null) + return null; // Not one of our modules + + var reader = getModuleReader(module); + var uri = reader.find(name); + if (uri.isPresent()) + return uri.get().toURL(); + + return null; + } + + /* ====================================================================== + * FIND ALL RESOURCES + * ====================================================================== + */ + + @Override + public Enumeration getResources(String name) throws IOException { + var results = new ArrayList>(); + results.add(findResources(name)); + + for (var parent : this.allParentLoaders) { + results.add(parent.getResources(name)); + } + + return new Enumeration<>() { + private Enumeration> itr = Collections.enumeration(results); + private Enumeration current = itr.hasMoreElements() ? itr.nextElement() : null; + + @Override + public boolean hasMoreElements() { + return current != null && current.hasMoreElements(); + } + + @Override + public URL nextElement() { + var ret = current.nextElement(); + if (!current.hasMoreElements()) + current = itr.hasMoreElements() ? itr.nextElement() : null; + return ret; + } + }; + } + + @Override + protected Enumeration findResources(String name) throws IOException { + var pkg = pathToPackage(name); + var module = packageToOurModules.get(pkg); + List ret; + + if (module != null) { + var url = findResource(module.name(), name); + if (url != null && isOpenResource(name, url, module, pkg)) + ret = List.of(url); + else + ret = List.of(); + } else { + ret = new ArrayList<>(); + for (var moduleName : this.ourModules.keySet()) { + try { + var url = findResource(moduleName, name); + if (url != null) + ret.add(url); + } catch (IOException e) { + // Nope nothing in that module + } + } + } + + return Collections.enumeration(ret); + } + + /* ====================================================================== + * FIND CLASSES + * ====================================================================== + */ + + @Override + protected Class findClass(String name) throws ClassNotFoundException { + var pkg = classToPackage(name); + var module = this.packageToOurModules.get(pkg); + if (module == null) + throw new ClassNotFoundException(name); + + var ref = module.reference(); + try (var reader = ref.open()) { + return readerToClass(reader, ref, name); + } catch (IOException e) { + throw new ClassNotFoundException(name, e); + } + } + + @Override + protected Class findClass(String moduleName, String name) { + var pkg = classToPackage(name); + var module = this.packageToOurModules.get(pkg); + if (module == null || !module.name().equals(moduleName)) + return null; + + var ref = module.reference(); + try (var reader = ref.open()) { + return readerToClass(reader, ref, name); + } catch (IOException e) { + return null; + } + } + + @Override + protected Class loadClass(String name, boolean resolve) throws ClassNotFoundException { + synchronized (getClassLoadingLock(name)) { + var c = findLoadedClass(name); + if (c == null) { + var pkg = classToPackage(name); + if (!pkg.isEmpty()) { + var module = this.packageToOurModules.get(pkg); + + if (module != null) + c = findClass(module.name(), name); + else { + var parent = this.packageToParentLoader.get(pkg); + if (parent == null) + parent = fallbackClassLoader; + c = parent.loadClass(name); + } + } + } + if (c == null) + throw new ClassNotFoundException(name); + + if (resolve) + resolveClass(c); + + return c; + } + } + + /* ====================================================================== + * INTERNAL IMPLEMENTATION CRAP + * ====================================================================== + */ + private Class readerToClass(ModuleReader reader, ModuleReference ref, String name) { + byte[] bytes; + try { + bytes = getClassBytes(reader, ref, name); + } catch (IOException e) { + return sneak(e); + } + + bytes = maybeTransformClassBytes(bytes, name, null); + if (bytes.length == 0) + return null; + + var data = this.ourModulesSecure.get(ref.descriptor().name()); + var url = ref.location().map(SecureModuleClassLoader::toURL).orElse(null); + tryDefinePackage(name, data, url); + + var signers = data == null ? null : data.getCodeSigners(classToResource(name), bytes); + var cs = new CodeSource(url, signers); + + return defineClass(name, bytes, 0, bytes.length, cs); + } + + @Override + protected PermissionCollection getPermissions(CodeSource codesource) { + var perms = new Permissions(); + // TODO: [SM] Gather permissions correctly + // typically what we would do is gather the permissions like URLClassLoader does, but for now, fuck it. + perms.add(new AllPermission()); + return perms; + } + + private String classToResource(String name) { + return name.replace('.', '/') + ".class"; + } + + private Package tryDefinePackage(String name, SecureModuleReference secure, URL base) throws IllegalArgumentException { + var pkg = classToPackage(name); + var ret = getDefinedPackage(pkg); + if (ret == null) { + synchronized (this) { + ret = getDefinedPackage(pkg); + if (ret == null) { + String path = pkg.replace('.', '/').concat("/"); + String specTitle = null, specVersion = null, specVendor = null; + String implTitle = null, implVersion = null, implVendor = null; + URL sealBase = null; + + if (secure != null) { + var main = secure.getMainAttributes(); + var trusted = secure.getTrustedAttributes(path); + specTitle = read(main, trusted, Attributes.Name.SPECIFICATION_TITLE); + specVersion = read(main, trusted, Attributes.Name.SPECIFICATION_VERSION); + specVendor = read(main, trusted, Attributes.Name.SPECIFICATION_VENDOR); + implTitle = read(main, trusted, Attributes.Name.IMPLEMENTATION_TITLE); + implVersion = read(main, trusted, Attributes.Name.IMPLEMENTATION_VERSION); + implVendor = read(main, trusted, Attributes.Name.IMPLEMENTATION_VENDOR); + if ("true".equals(read(main, trusted, Attributes.Name.SEALED))) + sealBase = null; //TODO: [SM] Implement and test package sealing + } + + ret = definePackage(pkg, specTitle, specVersion, specVendor, implTitle, implVersion, implVendor, sealBase); + } + } + } + return ret; + } + + private static URL toURL(URI uri) { + try { + return uri.toURL(); + } catch (MalformedURLException e) { + throw new IllegalArgumentException(e); + } + } + + private static String read(Attributes main, Attributes trusted, Attributes.Name name) { + if (trusted != null && trusted.containsKey(name)) return trusted.getValue(name); + return main == null ? null : main.getValue(name); + } + + @SuppressWarnings("unchecked") + private static R sneak(Exception exception) throws E { + throw (E)exception; + } + + private static String pathToPackage(String name) { + int idx = name.lastIndexOf('/'); + if (idx == -1 || idx == name.length() - 1) + return ""; + return name.substring(0, idx).replace('/', '.'); + } + + private static String classToPackage(String name) { + int idx = name.lastIndexOf('.'); + if (idx == -1 || idx == name.length() - 1) + return ""; + return name.substring(0, idx); + } + + private boolean setLoader(String pkg, ClassLoader loader) { + var existing = this.packageToParentLoader.putIfAbsent(pkg, loader); + if (existing != null && existing != loader) + throw new IllegalStateException("Package " + pkg + " cannot be imported from multiple loaders"); + return existing == null; + } + + + private static boolean isOpenResource(String name, URL url, ResolvedModule module, String pkg) { + if (name.endsWith(".class") || url.toString().endsWith("/")) + return true; + + var desc = module.reference().descriptor(); + if (desc.isOpen() || desc.isAutomatic()) + return true; + + for (var opens : desc.opens()) { + if (!opens.source().equals(pkg)) + continue; + + return !opens.isQualified(); + } + return false; + } + + private ModuleReader getModuleReader(ModuleReference reference) { + return this.moduleReaders.computeIfAbsent(reference, k -> { + try { + return reference.open(); + } catch (IOException e) { + return NOOP_READER; + } + }); + } + + private static ModuleReader NOOP_READER = new ModuleReader() { + @Override + public Optional find(String name) throws IOException { + return Optional.empty(); + } + + @Override + public Stream list() throws IOException { + return Stream.empty(); + } + + @Override + public void close() throws IOException { + } + }; + + // TODO: [SM][Deprecation] Make private once cpw.mods.cl.ModuleClassLoader is deleted + protected String classNameToModuleName(String name) { + var module = this.packageToOurModules.get(classToPackage(name)); + return module == null ? null : module.name(); + } + +} diff --git a/src/main/java/net/minecraftforge/securemodules/SecureModuleFinder.java b/src/main/java/net/minecraftforge/securemodules/SecureModuleFinder.java new file mode 100644 index 0000000..62850e0 --- /dev/null +++ b/src/main/java/net/minecraftforge/securemodules/SecureModuleFinder.java @@ -0,0 +1,109 @@ +package net.minecraftforge.securemodules; + +import cpw.mods.jarhandling.SecureJar; +import java.io.IOException; +import java.io.InputStream; +import java.lang.module.ModuleFinder; +import java.lang.module.ModuleReader; +import java.lang.module.ModuleReference; +import java.net.URI; +import java.security.CodeSigner; +import java.util.*; +import java.util.jar.Attributes; +import java.util.jar.Manifest; +import java.util.stream.Stream; + +/** + * + */ +public class SecureModuleFinder implements ModuleFinder { + private final Map references = new HashMap<>(); + + protected SecureModuleFinder(final SecureJar... jars) { + for (var jar : jars) { + var data = jar.moduleDataProvider(); + if (references.containsKey(data.name())) + System.out.println("Ignoring duplicate module on SecureModuleFinder: " + data.name() + ": " + jar); + else + references.put(data.name(), new Reference(data)); + } + } + + @Override + public Optional find(final String name) { + return Optional.ofNullable(references.get(name)); + } + + @Override + public Set findAll() { + return new HashSet<>(references.values()); + } + + public static SecureModuleFinder of(SecureJar... jars) { + return new SecureModuleFinder(jars); + } + + private static class Reference extends SecureModuleReference { + private final SecureJar.ModuleDataProvider jar; + private final Manifest manifest; + + Reference(final SecureJar.ModuleDataProvider jar) { + super(jar.descriptor(), jar.uri()); + this.jar = jar; + this.manifest = jar.getManifest(); + } + + @Override + public ModuleReader open() throws IOException { + return new Reader(this.jar); + } + + @Override + public Attributes getMainAttributes() { + return manifest == null ? null : manifest.getMainAttributes(); + } + + @Override + public Attributes getTrustedAttributes(String entry) { + // TODO: [SM] Add API to Secure jar to get Trusted Attributes + return manifest == null ? null : manifest.getAttributes(entry); + } + + @Override + public CodeSigner[] getCodeSigners(String entry, byte[] data) { + return this.jar.verifyAndGetSigners(entry, data); + } + } + + private static class Reader implements ModuleReader { + private final SecureJar.ModuleDataProvider jar; + + public Reader(final SecureJar.ModuleDataProvider jar) { + this.jar = jar; + } + + @Override + public Optional find(final String name) throws IOException { + return jar.findFile(name); + } + + @Override + public Optional open(final String name) throws IOException { + return jar.open(name); + } + + @Override + public Stream list() throws IOException { + return null; + } + + @Override + public void close() throws IOException { + } + + @Override + public String toString() { + return "JarModuleFinder.Reader[jar=" + jar + "]"; + } + } +} diff --git a/src/main/java/net/minecraftforge/securemodules/SecureModuleReference.java b/src/main/java/net/minecraftforge/securemodules/SecureModuleReference.java new file mode 100644 index 0000000..f3e5d04 --- /dev/null +++ b/src/main/java/net/minecraftforge/securemodules/SecureModuleReference.java @@ -0,0 +1,34 @@ +package net.minecraftforge.securemodules; + +import java.lang.module.ModuleDescriptor; +import java.lang.module.ModuleReference; +import java.net.URI; +import java.security.CodeSigner; +import java.util.jar.Attributes; + +/** + * A module reference that includes the extra information needed + * for ModuleClassLoader to properly create packages and signatures + */ +public abstract class SecureModuleReference extends ModuleReference { + protected SecureModuleReference(ModuleDescriptor descriptor, URI location) { + super(descriptor, location); + } + + /** + * Gets the main attributes of the module, this is typically just the jar's main attributes + */ + public abstract Attributes getMainAttributes(); + + /** + * Gets the attributes for a specific entry, typically a package. + * Should only return trusted values. Either from an unsigned jar, + * or one with verified manifest signatures. + */ + public abstract Attributes getTrustedAttributes(String entry); + + /* + * Returns the code signers that are verified to match the supplied entry and data. + */ + public abstract CodeSigner[] getCodeSigners(String entry, byte[] data); +} diff --git a/src/main/resources/META-INF/services/java.net.spi.URLStreamHandlerProvider b/src/main/resources/META-INF/services/java.net.spi.URLStreamHandlerProvider new file mode 100644 index 0000000..5006472 --- /dev/null +++ b/src/main/resources/META-INF/services/java.net.spi.URLStreamHandlerProvider @@ -0,0 +1 @@ +cpw.mods.niofs.union.UnionURLStreamHandlerProvider \ No newline at end of file