mirror of
https://github.com/HMCL-dev/HMCL.git
synced 2025-09-08 11:25:46 -04:00
Fix #1241: Use javaagent patch log4j
This commit is contained in:
parent
4b73af9250
commit
cc28f1d5b5
@ -76,43 +76,17 @@ public class MaintainTask extends Task<Version> {
|
||||
version = maintainOptiFineLibrary(repository, unique(version), false);
|
||||
}
|
||||
|
||||
Library log4jPatch = null;
|
||||
|
||||
List<Library> libraries = version.getLibraries();
|
||||
for (Library library : libraries) {
|
||||
if (library.is("org.apache.logging.log4j", "log4j-core")) {
|
||||
if (library.getVersion().startsWith("2.0-beta")) {
|
||||
if ("2.0-beta9".equals(library.getVersion())) {
|
||||
log4jPatch = new Library(new Artifact("org.glavo", "log4j-patch-beta9", "1.0"));
|
||||
} else {
|
||||
Logging.LOG.warning("Log4j " + library.getVersion() + " cannot be patched");
|
||||
}
|
||||
} else if (VersionNumber.VERSION_COMPARATOR.compare(library.getVersion(), "2.16") < 0) {
|
||||
log4jPatch = new Library(new Artifact("org.glavo", "log4j-patch", "1.0"));
|
||||
}
|
||||
break;
|
||||
if (libraries.size() > 0) {
|
||||
Library library = libraries.get(0);
|
||||
if ("org.glavo".equals(library.getGroupId())
|
||||
&& ("log4j-patch".equals(library.getArtifactId()) || "log4j-patch-beta9".equals(library.getArtifactId()))
|
||||
&& "1.0".equals(library.getVersion())
|
||||
&& library.getDownload() == null) {
|
||||
version = version.setLibraries(libraries.subList(1, libraries.size()));
|
||||
}
|
||||
}
|
||||
|
||||
if (log4jPatch != null) {
|
||||
ArrayList<Library> patchedLibraries = new ArrayList<>(libraries.size() + 1);
|
||||
patchedLibraries.add(log4jPatch);
|
||||
patchedLibraries.addAll(libraries);
|
||||
version = version.setLibraries(patchedLibraries);
|
||||
|
||||
Path log4jPatchPath = repository.getLibraryFile(version, log4jPatch).toPath();
|
||||
String patchName = log4jPatch.getArtifactId() + "-" + log4jPatch.getVersion();
|
||||
if (Files.notExists(log4jPatchPath)) {
|
||||
try (InputStream input = MaintainTask.class.getResourceAsStream("/assets/game/" + patchName + ".jar")) {
|
||||
Files.createDirectories(log4jPatchPath.getParent());
|
||||
Files.copy(input, log4jPatchPath, StandardCopyOption.REPLACE_EXISTING);
|
||||
} catch (IOException e) {
|
||||
Logging.LOG.log(Level.WARNING, "Unable to unpack " + patchName, e);
|
||||
}
|
||||
}
|
||||
Logging.LOG.info("Apply patch " + patchName + " to log4j");
|
||||
}
|
||||
|
||||
return version;
|
||||
}
|
||||
|
||||
|
@ -19,15 +19,10 @@ package org.jackhuang.hmcl.launch;
|
||||
|
||||
import org.jackhuang.hmcl.auth.AuthInfo;
|
||||
import org.jackhuang.hmcl.download.LibraryAnalyzer;
|
||||
import org.jackhuang.hmcl.game.Argument;
|
||||
import org.jackhuang.hmcl.game.Arguments;
|
||||
import org.jackhuang.hmcl.game.GameRepository;
|
||||
import org.jackhuang.hmcl.game.LaunchOptions;
|
||||
import org.jackhuang.hmcl.game.Library;
|
||||
import org.jackhuang.hmcl.game.NativesDirectoryType;
|
||||
import org.jackhuang.hmcl.game.Version;
|
||||
import org.jackhuang.hmcl.game.*;
|
||||
import org.jackhuang.hmcl.util.Lang;
|
||||
import org.jackhuang.hmcl.util.Log4jLevel;
|
||||
import org.jackhuang.hmcl.util.Logging;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
import org.jackhuang.hmcl.util.gson.UUIDTypeAdapter;
|
||||
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||
@ -48,6 +43,7 @@ import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.nio.file.StandardCopyOption;
|
||||
import java.util.*;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.logging.Level;
|
||||
@ -131,6 +127,8 @@ public class DefaultLauncher extends Launcher {
|
||||
}
|
||||
}
|
||||
|
||||
Optional<Library> log4j = findLog4j();
|
||||
|
||||
// JVM Args
|
||||
if (!options.isNoGeneratedJVMArgs()) {
|
||||
appendJvmArgs(res);
|
||||
@ -195,6 +193,13 @@ public class DefaultLauncher extends Launcher {
|
||||
|
||||
res.addDefault("-Dfml.ignoreInvalidMinecraftCertificates=", "true");
|
||||
res.addDefault("-Dfml.ignorePatchDiscrepancies=", "true");
|
||||
|
||||
if (log4j.isPresent()) {
|
||||
String enableJndi = res.addDefault("-Dlog4j2.enableJndi=", "false");
|
||||
if (!"-Dlog4j2.enableJndi=true".equals(enableJndi)) {
|
||||
res.add("-javaagent:" + repository.getLibraryFile(version, LOG4J_PATCH_AGENT).getAbsolutePath() + "=" + log4j.get().getVersion().startsWith("2.0-beta"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Fix RCE vulnerability of log4j2
|
||||
@ -203,7 +208,7 @@ public class DefaultLauncher extends Launcher {
|
||||
res.addDefault("-Dcom.sun.jndi.cosnaming.object.trustURLCodebase=", "false");
|
||||
|
||||
String formatMsgNoLookups = res.addDefault("-Dlog4j2.formatMsgNoLookups=", "true");
|
||||
if (!"-Dlog4j2.formatMsgNoLookups=false".equals(formatMsgNoLookups) && isUsingLog4j()) {
|
||||
if (!"-Dlog4j2.formatMsgNoLookups=false".equals(formatMsgNoLookups) && log4j.isPresent()) {
|
||||
res.addDefault("-Dlog4j.configurationFile=", getLog4jConfigurationFile().getAbsolutePath());
|
||||
}
|
||||
|
||||
@ -360,8 +365,17 @@ public class DefaultLauncher extends Launcher {
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isUsingLog4j() {
|
||||
return VersionNumber.VERSION_COMPARATOR.compare(repository.getGameVersion(version).orElse("Unknown"), "1.7") >= 0;
|
||||
private static final Library LOG4J_PATCH_AGENT = new Library(new Artifact("org.glavo", "log4j-patch-agent", "1.0"));
|
||||
|
||||
private Optional<Library> findLog4j() {
|
||||
return version.getLibraries().stream()
|
||||
.filter(it -> it.is("org.apache.logging.log4j", "log4j-core")
|
||||
&& (VersionNumber.VERSION_COMPARATOR.compare(it.getVersion(), "2.17") < 0 || "2.0-beta9".equals(it.getVersion())))
|
||||
.findFirst();
|
||||
}
|
||||
|
||||
private boolean isLog4jUnsafe(Library log4j) {
|
||||
return VersionNumber.VERSION_COMPARATOR.compare(log4j.getVersion(), "2.17") < 0;
|
||||
}
|
||||
|
||||
public File getLog4jConfigurationFile() {
|
||||
@ -371,7 +385,7 @@ public class DefaultLauncher extends Launcher {
|
||||
public void extractLog4jConfigurationFile() throws IOException {
|
||||
File targetFile = getLog4jConfigurationFile();
|
||||
InputStream source;
|
||||
if (VersionNumber.VERSION_COMPARATOR.compare(repository.getGameVersion(version).orElse("Unknown"), "1.12") < 0) {
|
||||
if (VersionNumber.VERSION_COMPARATOR.compare(repository.getGameVersion(version).orElse("0.0"), "1.12") < 0) {
|
||||
source = DefaultLauncher.class.getResourceAsStream("/assets/game/log4j2-1.7.xml");
|
||||
} else {
|
||||
source = DefaultLauncher.class.getResourceAsStream("/assets/game/log4j2-1.12.xml");
|
||||
@ -383,6 +397,19 @@ public class DefaultLauncher extends Launcher {
|
||||
}
|
||||
}
|
||||
|
||||
public void extractLog4jAgent() throws IOException {
|
||||
Path log4jPatchPath = repository.getLibraryFile(version, LOG4J_PATCH_AGENT).toPath();
|
||||
String patchName = LOG4J_PATCH_AGENT.getArtifactId() + "-" + LOG4J_PATCH_AGENT.getVersion();
|
||||
if (Files.notExists(log4jPatchPath)) {
|
||||
try (InputStream input = DefaultLauncher.class.getResourceAsStream("/assets/game/" + patchName + ".jar")) {
|
||||
Files.createDirectories(log4jPatchPath.getParent());
|
||||
Files.copy(input, log4jPatchPath, StandardCopyOption.REPLACE_EXISTING);
|
||||
} catch (IOException e) {
|
||||
Logging.LOG.log(Level.WARNING, "Unable to unpack " + patchName, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected Map<String, String> getConfigurations() {
|
||||
return mapOf(
|
||||
// defined by Minecraft official launcher
|
||||
@ -450,8 +477,11 @@ public class DefaultLauncher extends Launcher {
|
||||
decompressNatives(nativeFolder);
|
||||
}
|
||||
|
||||
if (isUsingLog4j()) {
|
||||
Optional<Library> log4j = findLog4j();
|
||||
if (log4j.isPresent()) {
|
||||
extractLog4jConfigurationFile();
|
||||
if (isLog4jUnsafe(log4j.get()))
|
||||
extractLog4jAgent();
|
||||
}
|
||||
|
||||
File runDirectory = repository.getRunDirectory(version.getId());
|
||||
@ -528,8 +558,11 @@ public class DefaultLauncher extends Launcher {
|
||||
decompressNatives(nativeFolder);
|
||||
}
|
||||
|
||||
if (isUsingLog4j()) {
|
||||
Optional<Library> log4j = findLog4j();
|
||||
if (log4j.isPresent()) {
|
||||
extractLog4jConfigurationFile();
|
||||
if (isLog4jUnsafe(log4j.get()))
|
||||
extractLog4jAgent();
|
||||
}
|
||||
|
||||
String scriptExtension = FileUtils.getExtension(scriptFile);
|
||||
|
@ -1,15 +1,21 @@
|
||||
version '1.0'
|
||||
|
||||
sourceSets.create("agent") {
|
||||
java {
|
||||
srcDir 'src/main/agent'
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compileOnly group: 'org.apache.logging.log4j', name: 'log4j-core', version: '2.0-beta9'
|
||||
}
|
||||
|
||||
tasks.compileJava {
|
||||
tasks.withType(JavaCompile) {
|
||||
sourceCompatibility = 8
|
||||
targetCompatibility = 8
|
||||
|
||||
doLast {
|
||||
FileTree tree = fileTree('build/classes/java/main')
|
||||
FileTree tree = fileTree('build/classes/java')
|
||||
tree.include '**/*.class'
|
||||
tree.each {
|
||||
RandomAccessFile rf = new RandomAccessFile(it, "rw")
|
||||
@ -20,27 +26,27 @@ tasks.compileJava {
|
||||
}
|
||||
}
|
||||
|
||||
task patchJar(type: Jar) {
|
||||
task agentJar(type: Jar) {
|
||||
dependsOn(tasks.compileJava)
|
||||
|
||||
baseName = 'log4j-patch'
|
||||
baseName = 'log4j-patch-agent'
|
||||
|
||||
from(sourceSets.main.output) {
|
||||
include('**/JndiLookup.class')
|
||||
manifest {
|
||||
attributes 'Premain-Class': 'org.glavo.log4j.patch.agent.Log4jAgent'
|
||||
}
|
||||
}
|
||||
|
||||
task patchBeta9Jar(type: Jar) {
|
||||
dependsOn(tasks.compileJava)
|
||||
|
||||
baseName = 'log4j-patch-beta9'
|
||||
|
||||
from(sourceSets.agent.output)
|
||||
from(sourceSets.main.output) {
|
||||
include '**/Interpolator.class'
|
||||
includeEmptyDirs = false
|
||||
|
||||
eachFile {
|
||||
it.path = "org/glavo/log4j/patch/agent/${it.name}.bin"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tasks.jar {
|
||||
enabled = false
|
||||
dependsOn patchJar, patchBeta9Jar
|
||||
dependsOn agentJar
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,71 @@
|
||||
package org.glavo.log4j.patch.agent;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.lang.instrument.ClassFileTransformer;
|
||||
import java.lang.instrument.IllegalClassFormatException;
|
||||
import java.lang.instrument.Instrumentation;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.ProtectionDomain;
|
||||
import java.util.Arrays;
|
||||
|
||||
public final class Log4jAgent {
|
||||
static final String JNDI_LOOKUP_CLASS_NAME = "org/apache/logging/log4j/core/lookup/JndiLookup";
|
||||
static final String INTERPOLATOR_CLASS_NAME = "org/apache/logging/log4j/core/lookup/Interpolator";
|
||||
|
||||
static final byte[] INTERPOLATOR_CLASS_SHA = {53, 103, 16, 123, 51, 29, 65, -70, -32, 71, -11, 7, 114, -15, 72, 127, 40, -38, 35, 18};
|
||||
|
||||
static boolean isBeta = false;
|
||||
|
||||
private static byte[] loadResource(String name) {
|
||||
try {
|
||||
try (InputStream input = Log4jAgent.class.getResourceAsStream(name)) {
|
||||
if (input == null) {
|
||||
throw new AssertionError(name + " not found");
|
||||
}
|
||||
int available = input.available();
|
||||
if (available <= 0) {
|
||||
throw new AssertionError();
|
||||
}
|
||||
byte[] res = new byte[available];
|
||||
if (input.read(res) != available) {
|
||||
throw new AssertionError();
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
throw new InternalError(ex);
|
||||
}
|
||||
}
|
||||
|
||||
public static void premain(String agentArgs, Instrumentation inst) throws Exception {
|
||||
if ("true".equals(agentArgs)) {
|
||||
isBeta = true;
|
||||
}
|
||||
inst.addTransformer(new Transformer());
|
||||
}
|
||||
|
||||
private static final class Transformer implements ClassFileTransformer {
|
||||
|
||||
@Override
|
||||
public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
|
||||
if (!isBeta && JNDI_LOOKUP_CLASS_NAME.equals(className)) {
|
||||
return loadResource("JndiLookup.class.bin");
|
||||
}
|
||||
if (isBeta && INTERPOLATOR_CLASS_NAME.equals(className)) {
|
||||
try {
|
||||
MessageDigest sha1 = MessageDigest.getInstance("SHA-1");
|
||||
sha1.update(classfileBuffer);
|
||||
if (Arrays.equals(INTERPOLATOR_CLASS_SHA, sha1.digest())) {
|
||||
return loadResource("Interpolator.class.bin");
|
||||
}
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new InternalError(e);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user