Skip to content

Commit 5b1b03a

Browse files
authored
Version 1.5 (#76)
* feat: cache remote config locally and allow changing the config directory (#73) * fix(fabric-agent): allow SiB classes to be loaded from the parent classloader (#74) * Fix other ois implementations (#75) * feat: add support for patching custom OIS implementations * fix: move custom OIS implementations to patch to a separate config value Otherwise the class patching in older SiB versions would break with a newer remote config * fix(modlauncher): add custom ois classes to transformer targets * chore: bump version to 1.5
1 parent 2d0e3ee commit 5b1b03a

8 files changed

Lines changed: 141 additions & 18 deletions

File tree

agent/src/main/java/io/dogboy/serializationisbad/agent/SIBTransformer.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ public byte[] transform(ClassLoader loader, String className, Class<?> classBein
1212
try {
1313
if (className == null) return classfileBuffer;
1414
if ("net/minecraft/launchwrapper/ITweaker".equals(className)) SerializationIsBadAgent.insertLaunchWrapperExclusion();
15+
if ("net/fabricmc/loader/ModContainer".equals(className)) SerializationIsBadAgent.insertFabricValidParentUrl(loader);
1516

1617
String classNameDots = className.replace('/', '.');
1718

agent/src/main/java/io/dogboy/serializationisbad/agent/SerializationIsBadAgent.java

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@
66
import java.lang.instrument.Instrumentation;
77
import java.lang.reflect.Field;
88
import java.lang.reflect.Method;
9+
import java.nio.file.Path;
10+
import java.util.HashSet;
11+
import java.util.Set;
912

1013
public class SerializationIsBadAgent {
1114

@@ -35,4 +38,39 @@ static void insertLaunchWrapperExclusion() {
3538
}
3639
}
3740

41+
/**
42+
* Another hacky workaround for newer Fabric versions that enforce
43+
* classpath isolation. This adds the path to the SiB jar to the
44+
* list of jar paths that are allowed to be loaded by the parent
45+
* classloader
46+
*
47+
* @param fabricClassLoader The classloader that was used to load the Fabric classes
48+
*/
49+
static void insertFabricValidParentUrl(ClassLoader fabricClassLoader) {
50+
try {
51+
Path sibPath = new File(SerializationIsBadAgent.class.getProtectionDomain().getCodeSource().getLocation().toURI()).toPath();
52+
53+
// basically accessing the following:
54+
// ((KnotClassDelegate) ((Knot) FabricLauncherBase.getLauncher()).classLoader).validParentCodeSources
55+
56+
Class<?> fabricLauncherBaseClass = Class.forName("net.fabricmc.loader.impl.launch.FabricLauncherBase", true, fabricClassLoader);
57+
Method getLauncherMethod = fabricLauncherBaseClass.getDeclaredMethod("getLauncher");
58+
Object fabricLauncher = getLauncherMethod.invoke(null);
59+
Field classLoaderField = fabricLauncher.getClass().getDeclaredField("classLoader");
60+
classLoaderField.setAccessible(true);
61+
Object classLoader = classLoaderField.get(fabricLauncher);
62+
Field validParentCodeSourcesField = classLoader.getClass().getDeclaredField("validParentCodeSources");
63+
validParentCodeSourcesField.setAccessible(true);
64+
@SuppressWarnings("unchecked")
65+
Set<Path> validParentCodeSources = (Set<Path>) validParentCodeSourcesField.get(classLoader);
66+
67+
Set<Path> newValidParentCodeSources = new HashSet<>(validParentCodeSources);
68+
newValidParentCodeSources.add(sibPath);
69+
70+
validParentCodeSourcesField.set(classLoader, newValidParentCodeSources);
71+
} catch (Throwable e) {
72+
SerializationIsBad.logger.error("Failed to insert Fabric valid parent URL", e);
73+
}
74+
}
75+
3876
}

core/src/main/java/io/dogboy/serializationisbad/core/ClassFilteringObjectInputStream.java

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ public ClassFilteringObjectInputStream(InputStream in, PatchModule patchModule)
2424
this(in, patchModule, null);
2525
}
2626

27-
private boolean isClassAllowed(String className) {
27+
private static boolean isClassAllowed(String className, PatchModule patchModule) {
2828
// strip all array dimensions, just get the base type
2929
while (className.startsWith("[")) {
3030
className = className.substring(1);
@@ -35,12 +35,12 @@ private boolean isClassAllowed(String className) {
3535
}
3636

3737
if (SerializationIsBad.getInstance().getConfig().getClassAllowlist().contains(className)
38-
|| this.patchModule.getClassAllowlist().contains(className)) {
38+
|| patchModule.getClassAllowlist().contains(className)) {
3939
return true;
4040
}
4141

4242
Set<String> allowedPackages = new HashSet<>(SerializationIsBad.getInstance().getConfig().getPackageAllowlist());
43-
allowedPackages.addAll(this.patchModule.getPackageAllowlist());
43+
allowedPackages.addAll(patchModule.getPackageAllowlist());
4444

4545
for (String allowedPackage : allowedPackages) {
4646
if (className.startsWith(allowedPackage + ".")) {
@@ -53,13 +53,7 @@ private boolean isClassAllowed(String className) {
5353

5454
@Override
5555
protected Class<?> resolveClass(ObjectStreamClass desc) throws IOException, ClassNotFoundException {
56-
SerializationIsBad.logger.debug("Resolving class " + desc.getName());
57-
58-
if (!this.isClassAllowed(desc.getName())) {
59-
SerializationIsBad.logger.warn("Tried to resolve class " + desc.getName() + ", which is not allowed to be deserialized");
60-
if (SerializationIsBad.getInstance().getConfig().isExecuteBlocking())
61-
throw new ClassNotFoundException("Class " + desc.getName() + " is not allowed to be deserialized");
62-
}
56+
ClassFilteringObjectInputStream.resolveClassPrecheck(desc, this.patchModule);
6357

6458
if (this.parentClassLoader == null) {
6559
return super.resolveClass(desc);
@@ -78,6 +72,16 @@ protected Class<?> resolveClass(ObjectStreamClass desc) throws IOException, Clas
7872
}
7973
}
8074

75+
public static void resolveClassPrecheck(ObjectStreamClass desc, PatchModule patchModule) throws ClassNotFoundException {
76+
SerializationIsBad.logger.debug("Resolving class " + desc.getName());
77+
78+
if (!ClassFilteringObjectInputStream.isClassAllowed(desc.getName(), patchModule)) {
79+
SerializationIsBad.logger.warn("Tried to resolve class " + desc.getName() + ", which is not allowed to be deserialized");
80+
if (SerializationIsBad.getInstance().getConfig().isExecuteBlocking())
81+
throw new ClassNotFoundException("Class " + desc.getName() + " is not allowed to be deserialized");
82+
}
83+
}
84+
8185
private static final HashMap<String, Class<?>> primClasses = new HashMap<>(8, 1.0F);
8286
static {
8387
ClassFilteringObjectInputStream.primClasses.put("boolean", boolean.class);

core/src/main/java/io/dogboy/serializationisbad/core/Patches.java

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,8 @@ public class Patches {
2020

2121
public static PatchModule getPatchModuleForClass(String className) {
2222
for (PatchModule patchModule : SerializationIsBad.getInstance().getConfig().getPatchModules()) {
23-
if (patchModule.getClassesToPatch().contains(className)) {
23+
if (patchModule.getClassesToPatch().contains(className)
24+
|| patchModule.getCustomOISClasses().contains(className)) {
2425
return patchModule;
2526
}
2627
}
@@ -43,6 +44,33 @@ private static byte[] writeClassNode(ClassNode classNode) {
4344

4445
private static void applyPatches(String className, ClassNode classNode, boolean passClassLoader) {
4546
SerializationIsBad.logger.info("Applying patches to " + className);
47+
PatchModule patchModule = Patches.getPatchModuleForClass(className);
48+
if (patchModule == null) {
49+
SerializationIsBad.logger.info(" No patches to apply");
50+
return;
51+
}
52+
53+
if (patchModule.getCustomOISClasses().contains(className) && "java/io/ObjectInputStream".equals(classNode.superName)) {
54+
for (MethodNode methodNode : classNode.methods) {
55+
if (!"resolveClass".equals(methodNode.name)) continue;
56+
57+
InsnList additionalInstructions = new InsnList();
58+
additionalInstructions.add(new VarInsnNode(Opcodes.ALOAD, 1)); // Class Descriptor
59+
additionalInstructions.add(new LdcInsnNode(className));
60+
additionalInstructions.add(new MethodInsnNode(Opcodes.INVOKESTATIC, "io/dogboy/serializationisbad/core/Patches",
61+
"getPatchModuleForClass", "(Ljava/lang/String;)Lio/dogboy/serializationisbad/core/config/PatchModule;", false));
62+
additionalInstructions.add(new MethodInsnNode(Opcodes.INVOKESTATIC, "io/dogboy/serializationisbad/core/ClassFilteringObjectInputStream",
63+
"resolveClassPrecheck", "(Ljava/io/ObjectStreamClass;Lio/dogboy/serializationisbad/core/config/PatchModule;)V", false));
64+
65+
methodNode.instructions.insertBefore(methodNode.instructions.getFirst(), additionalInstructions);
66+
67+
SerializationIsBad.logger.info(" Injecting resolveClass precheck in method " + methodNode.name);
68+
69+
break;
70+
}
71+
72+
return;
73+
}
4674

4775
for (MethodNode methodNode : classNode.methods) {
4876
InsnList instructions = methodNode.instructions;

core/src/main/java/io/dogboy/serializationisbad/core/SerializationIsBad.java

Lines changed: 43 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,12 @@
99

1010
import javax.net.ssl.HttpsURLConnection;
1111
import javax.net.ssl.SSLContext;
12+
import java.io.ByteArrayOutputStream;
1213
import java.io.File;
1314
import java.io.FileInputStream;
1415
import java.io.FileOutputStream;
1516
import java.io.IOException;
17+
import java.io.InputStream;
1618
import java.io.InputStreamReader;
1719
import java.net.URL;
1820
import java.nio.charset.StandardCharsets;
@@ -72,8 +74,17 @@ public SIBConfig getConfig() {
7274
return this.config;
7375
}
7476

77+
private static File getConfigDir(File minecraftDir) {
78+
String configDirOverride = System.getProperty("serializationisbad.configdir");
79+
if (configDirOverride != null) {
80+
return new File(configDirOverride);
81+
}
82+
83+
return new File(minecraftDir, "config");
84+
}
85+
7586
private static SIBConfig readConfig(File minecraftDir) {
76-
File configFile = new File(new File(minecraftDir, "config"), "serializationisbad.json");
87+
File configFile = new File(SerializationIsBad.getConfigDir(minecraftDir), "serializationisbad.json");
7788
Gson gson = new GsonBuilder().setPrettyPrinting().create();
7889

7990
SIBConfig localConfig = new SIBConfig();
@@ -98,7 +109,7 @@ private static SIBConfig readConfig(File minecraftDir) {
98109
return localConfig;
99110
}
100111

101-
SIBConfig remoteConfig = SerializationIsBad.readRemoteConfig(localConfig.getRemoteConfigUrl());
112+
SIBConfig remoteConfig = SerializationIsBad.readRemoteConfig(minecraftDir, localConfig.getRemoteConfigUrl());
102113
if (remoteConfig != null) {
103114
SerializationIsBad.logger.info("Using remote config file");
104115
return remoteConfig;
@@ -108,8 +119,10 @@ private static SIBConfig readConfig(File minecraftDir) {
108119
return localConfig;
109120
}
110121

111-
private static SIBConfig readRemoteConfig(String url) {
122+
private static SIBConfig readRemoteConfig(File minecraftDir, String url) {
112123
Gson gson = new Gson();
124+
File cacheFile = new File(SerializationIsBad.getConfigDir(minecraftDir), "serializationisbad-remotecache.json");
125+
113126
try {
114127
HttpsURLConnection connection = (HttpsURLConnection) new URL(url).openConnection();
115128
SSLContext sslContext = SSLContext.getInstance("TLSv1.2");
@@ -120,16 +133,41 @@ private static SIBConfig readRemoteConfig(String url) {
120133

121134
if (connection.getResponseCode() != 200) throw new IOException("Invalid response code: " + connection.getResponseCode());
122135

123-
try (InputStreamReader inputStreamReader = new InputStreamReader(connection.getInputStream(), StandardCharsets.UTF_8)) {
124-
return gson.fromJson(inputStreamReader, SIBConfig.class);
136+
byte[] configBytes = SerializationIsBad.readInputStream(connection.getInputStream());
137+
SIBConfig remoteConfig = gson.fromJson(new String(configBytes, StandardCharsets.UTF_8), SIBConfig.class);
138+
139+
try (FileOutputStream fileOutputStream = new FileOutputStream(cacheFile)) {
140+
fileOutputStream.write(configBytes);
125141
}
142+
143+
return remoteConfig;
126144
} catch (Exception e) {
127145
SerializationIsBad.logger.error("Failed to load remote config file", e);
128146
}
129147

148+
if (cacheFile.isFile()) {
149+
try (FileInputStream fileInputStream = new FileInputStream(cacheFile)) {
150+
return gson.fromJson(new InputStreamReader(fileInputStream, StandardCharsets.UTF_8), SIBConfig.class);
151+
} catch (Exception e) {
152+
SerializationIsBad.logger.error("Failed to load cached remote config file", e);
153+
}
154+
}
155+
130156
return null;
131157
}
132158

159+
private static byte[] readInputStream(InputStream inputStream) throws IOException {
160+
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
161+
byte[] buffer = new byte[4096];
162+
int read;
163+
164+
while ((read = inputStream.read(buffer)) != -1) {
165+
byteArrayOutputStream.write(buffer, 0, read);
166+
}
167+
168+
return byteArrayOutputStream.toByteArray();
169+
}
170+
133171
private static String getImplementationType() {
134172
for (StackTraceElement stackTraceElement : Thread.currentThread().getStackTrace()) {
135173
if (stackTraceElement.getClassName().startsWith("io.dogboy.serializationisbad.")

core/src/main/java/io/dogboy/serializationisbad/core/config/PatchModule.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,13 @@
55

66
public class PatchModule {
77
private Set<String> classesToPatch;
8+
private Set<String> customOISClasses;
89
private Set<String> classAllowlist;
910
private Set<String> packageAllowlist;
1011

1112
public PatchModule() {
1213
this.classesToPatch = new HashSet<>();
14+
this.customOISClasses = new HashSet<>();
1315
this.classAllowlist = new HashSet<>();
1416
this.packageAllowlist = new HashSet<>();
1517
}
@@ -22,6 +24,15 @@ public void setClassesToPatch(Set<String> classesToPatch) {
2224
this.classesToPatch = classesToPatch;
2325
}
2426

27+
28+
public Set<String> getCustomOISClasses() {
29+
return this.customOISClasses;
30+
}
31+
32+
public void setCustomOISClasses(Set<String> customOISClasses) {
33+
this.customOISClasses = customOISClasses;
34+
}
35+
2536
public Set<String> getClassAllowlist() {
2637
return this.classAllowlist;
2738
}
@@ -37,4 +48,5 @@ public Set<String> getPackageAllowlist() {
3748
public void setPackageAllowlist(Set<String> packageAllowlist) {
3849
this.packageAllowlist = packageAllowlist;
3950
}
51+
4052
}

gradle.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
group=io.dogboy.serializationisbad
22
name=serializationisbad
3-
version=1.4
3+
version=1.5

modlauncher/src/main/java/io/dogboy/serializationisbad/modlauncher/SIBTransformer.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
import java.util.Set;
1313
import java.util.stream.Collectors;
14+
import java.util.stream.Stream;
1415

1516
public class SIBTransformer implements ITransformer<ClassNode> {
1617
private final PatchModule patchModule;
@@ -36,7 +37,8 @@ public TransformerVoteResult castVote(ITransformerVotingContext context) {
3637

3738
@Override
3839
public Set<Target> targets() {
39-
return this.patchModule.getClassesToPatch().stream()
40+
return Stream.concat(this.patchModule.getClassesToPatch().stream(),
41+
this.patchModule.getCustomOISClasses().stream())
4042
.map(Target::targetClass)
4143
.collect(Collectors.toSet());
4244
}

0 commit comments

Comments
 (0)