diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/game/HMCLGameRepository.java b/HMCL/src/main/java/org/jackhuang/hmcl/game/HMCLGameRepository.java index 94b0e6829..f298f9a80 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/game/HMCLGameRepository.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/game/HMCLGameRepository.java @@ -328,7 +328,7 @@ public class HMCLGameRepository extends DefaultGameRepository { vs.setUsesGlobal(true); } - public LaunchOptions getLaunchOptions(String version, JavaVersion javaVersion, File gameDir, boolean makeLaunchScript) { + public LaunchOptions getLaunchOptions(String version, JavaVersion javaVersion, File gameDir, List javaAgents, boolean makeLaunchScript) { VersionSetting vs = getVersionSetting(version); LaunchOptions.Builder builder = new LaunchOptions.Builder() @@ -359,7 +359,8 @@ public class HMCLGameRepository extends DefaultGameRepository { .setProcessPriority(vs.getProcessPriority()) .setUseNativeGLFW(vs.isUseNativeGLFW()) .setUseNativeOpenAL(vs.isUseNativeOpenAL()) - .setDaemon(!makeLaunchScript && vs.getLauncherVisibility().isDaemon()); + .setDaemon(!makeLaunchScript && vs.getLauncherVisibility().isDaemon()) + .setJavaAgents(javaAgents); if (config().hasProxy()) { builder.setProxy(ProxyManager.getProxy()); if (config().hasProxyAuth()) { diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/game/LauncherHelper.java b/HMCL/src/main/java/org/jackhuang/hmcl/game/LauncherHelper.java index d3fb71abc..e731412ab 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/game/LauncherHelper.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/game/LauncherHelper.java @@ -20,6 +20,7 @@ package org.jackhuang.hmcl.game; import javafx.application.Platform; import javafx.stage.Stage; import org.jackhuang.hmcl.Launcher; +import org.jackhuang.hmcl.Metadata; import org.jackhuang.hmcl.auth.*; import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorDownloadException; import org.jackhuang.hmcl.download.DefaultDependencyManager; @@ -55,9 +56,13 @@ import org.jackhuang.hmcl.util.versioning.VersionNumber; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; +import java.io.InputStream; import java.net.SocketTimeoutException; import java.net.URL; import java.nio.file.AccessDeniedException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardCopyOption; import java.util.*; import java.util.concurrent.*; import java.util.concurrent.atomic.AtomicReference; @@ -124,9 +129,12 @@ public final class LauncherHelper { Optional gameVersion = repository.getGameVersion(version); boolean integrityCheck = repository.unmarkVersionLaunchedAbnormally(selectedVersion); CountDownLatch launchingLatch = new CountDownLatch(1); + List javaAgents = new ArrayList<>(0); AtomicReference javaVersionRef = new AtomicReference<>(); + getLog4jPatch(version).ifPresent(javaAgents::add); + TaskExecutor executor = checkGameState(profile, setting, version) .thenComposeAsync(javaVersion -> { javaVersionRef.set(Objects.requireNonNull(javaVersion)); @@ -172,7 +180,7 @@ public final class LauncherHelper { } }).withStage("launch.state.logging_in")) .thenComposeAsync(authInfo -> Task.supplyAsync(() -> { - LaunchOptions launchOptions = repository.getLaunchOptions(selectedVersion, javaVersionRef.get(), profile.getGameDir(), scriptFile != null); + LaunchOptions launchOptions = repository.getLaunchOptions(selectedVersion, javaVersionRef.get(), profile.getGameDir(), javaAgents, scriptFile != null); return new HMCLGameLauncher( repository, version, @@ -598,6 +606,47 @@ public final class LauncherHelper { return future; } + private static Optional getLog4jPatch(Version version) { + Optional log4jVersion = 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()))) + .map(Library::getVersion) + .findFirst(); + + if (log4jVersion.isPresent()) { + final String agentFileName = "log4j-patch-agent-1.0.jar"; + + Path agentFile = Metadata.HMCL_DIRECTORY.resolve(agentFileName).toAbsolutePath(); + String agentFilePath = agentFile.toString(); + if (agentFilePath.indexOf('=') >= 0) { + LOG.warning("Invalid character '=' in the HMCL directory path, unable to attach log4j-patch"); + return Optional.empty(); + } + + if (Files.notExists(agentFile)) { + try (InputStream input = DefaultLauncher.class.getResourceAsStream("/assets/game/" + agentFileName)) { + LOG.info("Extract log4j patch to " + agentFilePath); + Files.createDirectories(agentFile.getParent()); + Files.copy(input, agentFile, StandardCopyOption.REPLACE_EXISTING); + } catch (IOException e) { + LOG.log(Level.WARNING, "Failed to extract log4j patch"); + try { + Files.deleteIfExists(agentFile); + } catch (IOException ex) { + LOG.log(Level.WARNING, "Failed to delete incomplete log4j patch", ex); + } + return Optional.empty(); + } + } + + boolean isBeta = log4jVersion.get().startsWith("2.0-beta"); + return Optional.of(agentFilePath + "=" + isBeta); + } else { + LOG.info("No log4j with security vulnerabilities found"); + return Optional.empty(); + } + } + private void checkExit() { switch (launcherVisibility) { case HIDE_AND_REOPEN: diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/launch/DefaultLauncher.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/launch/DefaultLauncher.java index 1646854ba..47c1e92bc 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/launch/DefaultLauncher.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/launch/DefaultLauncher.java @@ -42,7 +42,6 @@ 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; @@ -126,8 +125,6 @@ public class DefaultLauncher extends Launcher { } } - Optional log4j = findLog4j(); - // JVM Args if (!options.isNoGeneratedJVMArgs()) { appendJvmArgs(res); @@ -192,13 +189,6 @@ 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 @@ -207,7 +197,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) && log4j.isPresent()) { + if (!"-Dlog4j2.formatMsgNoLookups=false".equals(formatMsgNoLookups) && isUsingLog4j()) { res.addDefault("-Dlog4j.configurationFile=", getLog4jConfigurationFile().getAbsolutePath()); } @@ -369,17 +359,8 @@ public class DefaultLauncher extends Launcher { } } - private static final Library LOG4J_PATCH_AGENT = new Library(new Artifact("org.glavo", "log4j-patch-agent", "1.0")); - - private Optional 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; + private boolean isUsingLog4j() { + return VersionNumber.VERSION_COMPARATOR.compare(repository.getGameVersion(version).orElse("1.7"), "1.7") >= 0; } public File getLog4jConfigurationFile() { @@ -400,17 +381,6 @@ 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); - } - } - } - protected Map getConfigurations() { return mapOf( // defined by Minecraft official launcher @@ -470,12 +440,8 @@ public class DefaultLauncher extends Launcher { decompressNatives(nativeFolder); } - Optional log4j = findLog4j(); - if (log4j.isPresent()) { + if (isUsingLog4j()) extractLog4jConfigurationFile(); - if (isLog4jUnsafe(log4j.get())) - extractLog4jAgent(); - } File runDirectory = repository.getRunDirectory(version.getId()); @@ -546,12 +512,8 @@ public class DefaultLauncher extends Launcher { decompressNatives(nativeFolder); } - Optional log4j = findLog4j(); - if (log4j.isPresent()) { + if (isUsingLog4j()) extractLog4jConfigurationFile(); - if (isLog4jUnsafe(log4j.get())) - extractLog4jAgent(); - } String scriptExtension = FileUtils.getExtension(scriptFile); boolean usePowerShell = "ps1".equals(scriptExtension);