From d0f45e3e820f3601b487777a52a9353ee5a62165 Mon Sep 17 00:00:00 2001 From: Zomabies Date: Thu, 6 Nov 2025 03:01:29 +0800 Subject: [PATCH] Fix signers not propagated to class --- .../impl/launch/knot/KnotClassDelegate.java | 85 +++++++++++++------ .../loader/impl/util/SystemProperties.java | 2 + 2 files changed, 62 insertions(+), 25 deletions(-) diff --git a/src/main/java/net/fabricmc/loader/impl/launch/knot/KnotClassDelegate.java b/src/main/java/net/fabricmc/loader/impl/launch/knot/KnotClassDelegate.java index 2446ae622..486deeb87 100644 --- a/src/main/java/net/fabricmc/loader/impl/launch/knot/KnotClassDelegate.java +++ b/src/main/java/net/fabricmc/loader/impl/launch/knot/KnotClassDelegate.java @@ -32,8 +32,8 @@ import java.nio.file.Path; import java.nio.file.Paths; import java.nio.file.StandardCopyOption; +import java.security.CodeSigner; import java.security.CodeSource; -import java.security.cert.Certificate; import java.util.Collection; import java.util.Collections; import java.util.HashMap; @@ -41,6 +41,9 @@ import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; +import java.util.jar.Attributes; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; import java.util.jar.Manifest; import java.util.zip.CRC32; import java.util.zip.ZipEntry; @@ -69,16 +72,19 @@ final class KnotClassDelegate impleme private static final boolean LOG_CLASS_LOAD_ERRORS = LOG_CLASS_LOAD || SystemProperties.isSet(SystemProperties.DEBUG_LOG_CLASS_LOAD_ERRORS); private static final boolean LOG_TRANSFORM_ERRORS = SystemProperties.isSet(SystemProperties.DEBUG_LOG_TRANSFORM_ERRORS); private static final boolean DISABLE_ISOLATION = SystemProperties.isSet(SystemProperties.DEBUG_DISABLE_CLASS_PATH_ISOLATION); + private static final boolean DISABLE_JAR_SIGNERS = SystemProperties.isSet(SystemProperties.DEBUG_DISABLE_JAR_SIGNERS); static final class Metadata { - static final Metadata EMPTY = new Metadata(null, null); + static final Metadata EMPTY = new Metadata(null, null, false); final Manifest manifest; final CodeSource codeSource; + final boolean isManifestSigned; - Metadata(Manifest manifest, CodeSource codeSource) { + Metadata(Manifest manifest, CodeSource codeSource, boolean isManifestSigned) { this.manifest = manifest; this.codeSource = codeSource; + this.isManifestSigned = isManifestSigned; } } @@ -189,7 +195,7 @@ public void setValidParentClassPath(Collection paths) { @Override public Manifest getManifest(Path codeSource) { - return getMetadata(LoaderUtil.normalizeExistingPath(codeSource)).manifest; + return getMetadata(LoaderUtil.normalizeExistingPath(codeSource), null).manifest; } @Override @@ -373,14 +379,14 @@ private Metadata getMetadata(String name) { URL url = classLoader.getResource(fileName); if (url == null || !hasRegularCodeSource(url)) return Metadata.EMPTY; - return getMetadata(getCodeSource(url, fileName)); + return getMetadata(getCodeSource(url, fileName), fileName); } - private Metadata getMetadata(Path codeSource) { - return metadataCache.computeIfAbsent(codeSource, (Path path) -> { + private Metadata getMetadata(Path codeSource, String fileName) { + Metadata metadata = metadataCache.computeIfAbsent(codeSource, (Path path) -> { Manifest manifest = null; - CodeSource cs = null; - Certificate[] certificates = null; + CodeSource cs; + boolean isManifestSigned = false; try { if (Files.isDirectory(path)) { @@ -389,8 +395,13 @@ private Metadata getMetadata(Path codeSource) { URLConnection connection = new URL("jar:" + path.toUri().toString() + "!/").openConnection(); if (connection instanceof JarURLConnection) { - manifest = ((JarURLConnection) connection).getManifest(); - certificates = ((JarURLConnection) connection).getCertificates(); + JarFile jarFile = ((JarURLConnection) connection).getJarFile(); + manifest = jarFile.getManifest(); + + if (!DISABLE_JAR_SIGNERS && manifest != null) { + JarEntry jarEntry = jarFile.getJarEntry(JarFile.MANIFEST_NAME); + isManifestSigned = jarEntry.getCodeSigners() != null; // signed jar + } } if (manifest == null) { @@ -398,13 +409,6 @@ private Metadata getMetadata(Path codeSource) { manifest = ManifestUtil.readManifestFromBasePath(jarFs.get().getRootDirectories().iterator().next()); } } - - // TODO - /* JarEntry codeEntry = codeSourceJar.getJarEntry(filename); - - if (codeEntry != null) { - cs = new CodeSource(codeSourceURL, codeEntry.getCodeSigners()); - } */ } } catch (IOException | FileSystemNotFoundException e) { if (FabricLauncherBase.getLauncher().isDevelopment()) { @@ -412,16 +416,47 @@ private Metadata getMetadata(Path codeSource) { } } - if (cs == null) { - try { - cs = new CodeSource(UrlUtil.asUrl(path), certificates); - } catch (MalformedURLException e) { - throw new RuntimeException(e); - } + try { + cs = new CodeSource(UrlUtil.asUrl(path), (CodeSigner[]) null); + } catch (MalformedURLException e) { + throw new RuntimeException(e); } - return new Metadata(manifest, cs); + return new Metadata(manifest, cs, isManifestSigned); }); + + if (DISABLE_JAR_SIGNERS || !metadata.isManifestSigned) { + return metadata; // fast path for directory/unsigned jar + } + + // obtain signers from filename + try { + boolean multiRelease = false; + Attributes attributes = metadata.manifest.getMainAttributes(); // manifest never null here + + if (attributes != null) { + // handles multi-release jars + // fix file not found exception when loaded class is inside multi-release dir + multiRelease = "true".equalsIgnoreCase(attributes.getValue("Multi-Release")); + } + + URLConnection connection = new URL("jar:" + codeSource.toUri().toString() + "!/" + (multiRelease ? "#runtime" : "")).openConnection(); + + if (connection instanceof JarURLConnection) { + JarFile jarFile = ((JarURLConnection) connection).getJarFile(); + + JarEntry jarEntry = jarFile.getJarEntry(fileName); + CodeSigner[] codeSigners = jarEntry.getCodeSigners(); + CodeSource signedCodeSource = new CodeSource(metadata.codeSource.getLocation(), codeSigners); + return new Metadata(metadata.manifest, signedCodeSource, true); + } + } catch (IOException e) { + if (FabricLauncherBase.getLauncher().isDevelopment()) { + Log.warn(LogCategory.KNOT, "Failed to read JAR", e); + } + } + + return metadata; } private byte[] getPostMixinClassByteArray(String name, boolean allowFromParent) { diff --git a/src/main/java/net/fabricmc/loader/impl/util/SystemProperties.java b/src/main/java/net/fabricmc/loader/impl/util/SystemProperties.java index dce2e1cb7..3e67745e7 100644 --- a/src/main/java/net/fabricmc/loader/impl/util/SystemProperties.java +++ b/src/main/java/net/fabricmc/loader/impl/util/SystemProperties.java @@ -70,6 +70,8 @@ public final class SystemProperties { public static final String DEBUG_LOG_TRANSFORM_ERRORS = "fabric.debug.logTransformErrors"; // disables system class path isolation, allowing bogus lib accesses (too early, transient jars) public static final String DEBUG_DISABLE_CLASS_PATH_ISOLATION = "fabric.debug.disableClassPathIsolation"; + // disables signers from being available in ProtectionDomain/Class#getSigners for signed JARs + public static final String DEBUG_DISABLE_JAR_SIGNERS = "fabric.debug.disableJarSigners"; // disables mod load order shuffling to be the same in-dev as in production public static final String DEBUG_DISABLE_MOD_SHUFFLE = "fabric.debug.disableModShuffle"; // workaround for bad load order dependencies