From c5c939df634dae7d5c641f2cdb881af04875ff90 Mon Sep 17 00:00:00 2001 From: embeddedt <42941056+embeddedt@users.noreply.github.com> Date: Thu, 27 Apr 2023 15:00:19 -0400 Subject: [PATCH 1/2] Clear manifest digests --- .../modernfix/util/ClassInfoManager.java | 36 ++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/embeddedt/modernfix/util/ClassInfoManager.java b/src/main/java/org/embeddedt/modernfix/util/ClassInfoManager.java index 34a6d53f..47dfae89 100644 --- a/src/main/java/org/embeddedt/modernfix/util/ClassInfoManager.java +++ b/src/main/java/org/embeddedt/modernfix/util/ClassInfoManager.java @@ -1,11 +1,17 @@ package org.embeddedt.modernfix.util; +import com.google.common.collect.ImmutableMap; +import net.minecraftforge.fml.ModList; +import net.minecraftforge.forgespi.language.IModFileInfo; +import org.embeddedt.modernfix.ModernFix; import org.embeddedt.modernfix.core.ModernFixMixinPlugin; import org.spongepowered.asm.mixin.transformer.ClassInfo; import java.lang.reflect.Field; -import java.util.Iterator; +import java.util.HashMap; import java.util.Map; +import java.util.jar.Attributes; +import java.util.jar.Manifest; public class ClassInfoManager { private static Map classInfoCache = null; @@ -27,5 +33,33 @@ public class ClassInfoManager { } catch(RuntimeException e) { e.printStackTrace(); } + + // Clear manifest entries + int numManifestsCleared = 0; + for(IModFileInfo mod : ModList.get().getModFiles()) { + Manifest manifest = mod.getFile().getSecureJar().getManifest(); + if(manifest.getEntries() instanceof HashMap entryMap) { + for (Map.Entry entry : entryMap.entrySet()) { + Attributes attributes = entry.getValue(); + if (attributes.size() == 1 && attributes.getValue("SHA-256-Digest") != null) { + try { + entry.setValue(EmptyAttributes.INSTANCE); + numManifestsCleared++; + } catch (RuntimeException ignored) { + } + } + } + } + } + if(numManifestsCleared > 0) + ModernFix.LOGGER.info("Cleared {} manifest attributes", numManifestsCleared); + } + + static class EmptyAttributes extends Attributes { + public static final EmptyAttributes INSTANCE = new EmptyAttributes(); + EmptyAttributes() { + super(1); + this.map = ImmutableMap.of(); + } } } From b8450b6acdcfea89a1cc91882db06b28d223ed17 Mon Sep 17 00:00:00 2001 From: embeddedt <42941056+embeddedt@users.noreply.github.com> Date: Thu, 27 Apr 2023 15:24:23 -0400 Subject: [PATCH 2/2] Clear SecureJar structs --- .../modernfix/util/ClassInfoManager.java | 139 +++++++++++++++++- 1 file changed, 137 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/embeddedt/modernfix/util/ClassInfoManager.java b/src/main/java/org/embeddedt/modernfix/util/ClassInfoManager.java index 47dfae89..2b7640a2 100644 --- a/src/main/java/org/embeddedt/modernfix/util/ClassInfoManager.java +++ b/src/main/java/org/embeddedt/modernfix/util/ClassInfoManager.java @@ -1,15 +1,20 @@ package org.embeddedt.modernfix.util; import com.google.common.collect.ImmutableMap; +import cpw.mods.jarhandling.SecureJar; +import cpw.mods.jarhandling.impl.Jar; import net.minecraftforge.fml.ModList; import net.minecraftforge.forgespi.language.IModFileInfo; import org.embeddedt.modernfix.ModernFix; import org.embeddedt.modernfix.core.ModernFixMixinPlugin; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import org.spongepowered.asm.mixin.transformer.ClassInfo; +import sun.misc.Unsafe; import java.lang.reflect.Field; -import java.util.HashMap; -import java.util.Map; +import java.security.CodeSigner; +import java.util.*; import java.util.jar.Attributes; import java.util.jar.Manifest; @@ -53,6 +58,136 @@ public class ClassInfoManager { } if(numManifestsCleared > 0) ModernFix.LOGGER.info("Cleared {} manifest attributes", numManifestsCleared); + + try { + clearSecureJarStructs(); + } catch(Throwable e) { + ModernFix.LOGGER.error("Couldn't clear Jar structs", e); + } + + } + + private static void clearSecureJarStructs() throws Throwable { + // Clear Jar signing data + Unsafe unsafe; + Field f = Unsafe.class.getDeclaredField("theUnsafe"); + f.setAccessible(true); + unsafe = (Unsafe)f.get(null); + + Field statusDataField, pendingSignersField, verifiedSignersField; + statusDataField = Jar.class.getDeclaredField("statusData"); + pendingSignersField = Jar.class.getDeclaredField("pendingSigners"); + verifiedSignersField = Jar.class.getDeclaredField("verifiedSigners"); + + long statusDataOffset = unsafe.objectFieldOffset(statusDataField); + long pendingSignersOffset = unsafe.objectFieldOffset(pendingSignersField); + long verifiedSignersOffset = unsafe.objectFieldOffset(verifiedSignersField); + + for(IModFileInfo mod : ModList.get().getModFiles()) { + SecureJar secureJar = mod.getFile().getSecureJar(); + if(secureJar instanceof Jar) { + unsafe.putObject(secureJar, statusDataOffset, LyingStatusDataMap.INSTANCE); + unsafe.putObject(secureJar, pendingSignersOffset, EmptyCodeSignerTable.INSTANCE); + unsafe.putObject(secureJar, verifiedSignersOffset, EmptyCodeSignerTable.INSTANCE); + } + } + } + + static class EmptyCodeSignerTable extends Hashtable { + public static final EmptyCodeSignerTable INSTANCE = new EmptyCodeSignerTable(); + private static final CodeSigner[] VAL = new CodeSigner[0]; + + @Override + public synchronized CodeSigner[] put(String key, CodeSigner[] value) { + return null; + } + + @Override + public synchronized boolean isEmpty() { + return true; + } + + @Override + public synchronized boolean containsKey(Object key) { + return false; + } + + @Override + public synchronized CodeSigner[] get(Object key) { + return VAL; + } + } + + /** + * This map is used to replace the statusData map. + * + * The lying in containsKey is intentionally done to force certain code paths to run in Jar. + * Otherwise the security information might be recomputed many times. + */ + static class LyingStatusDataMap implements Map { + public static final LyingStatusDataMap INSTANCE = new LyingStatusDataMap(); + @Override + public int size() { + return 0; + } + + @Override + public boolean isEmpty() { + return false; + } + + @Override + public boolean containsKey(Object o) { + return true; + } + + @Override + public boolean containsValue(Object o) { + return false; + } + + @Override + public Object get(Object o) { + return null; + } + + @Nullable + @Override + public Object put(String s, Object o) { + return null; + } + + @Override + public Object remove(Object o) { + return null; + } + + @Override + public void putAll(@NotNull Map map) { + + } + + @Override + public void clear() { + } + + @NotNull + @Override + public Set keySet() { + return Collections.emptySet(); + } + + @NotNull + @Override + public Collection values() { + return Collections.emptyList(); + } + + @NotNull + @Override + public Set> entrySet() { + return Collections.emptySet(); + } } static class EmptyAttributes extends Attributes {