obabichevjb opened a new issue, #16491:
URL: https://github.com/apache/dubbo/issues/16491

   ### Pre-check
   
   - [x] I am sure that all the content I provide is in English.
   
   
   ### Search before asking
   
   - [x] I had searched in the 
[issues](https://github.com/apache/dubbo/issues?q=is%3Aissue) and found no 
similar issues.
   
   
   ### Apache Dubbo Component
   
   Java SDK (apache/dubbo)
   
   ### Dubbo Version
   
   Dubbo Java 3.3.6, OpenJDK 21 (Amazon Corretto 21.0.5), macOS 15.6 
(OS-independent)
   Kotlin 2.4.20 / Java 17+ sealed types, Gradle 9.5, default hessian2 
serialization, STRICT mode
   
   ### Steps to reproduce this issue
   
   `SerializeSecurityConfigurator.registerInterface` auto-allow-lists the 
classes it can reach from an
   exported interface's signatures. `checkClass` walks **up** into supertypes 
and **into** fields and
   generic arguments, but never **down** into subtypes. A sealed parameter or 
return type therefore
   gets the parent trusted and none of the subtypes that actually travel on the 
wire.
   
   This is the default configuration — `autoTrustSerializeClass` defaults to 
`true` and the check
   status to `STRICT` — and **no `security/serialize.allowlist` file is 
supplied below**.
   
   Provider and consumer run in one JVM over a real Netty `dubbo://` transport 
with a direct URL and
   `RegistryConfig("N/A")`, so no registry or other infrastructure is needed; 
because the transport is
   real rather than `injvm://`, values are genuinely serialized.
   
   `build.gradle.kts`:
   
   ```kotlin
   plugins {
       kotlin("jvm") version "2.4.20"
       application
   }
   repositories { mavenCentral() }
   dependencies {
       implementation("org.apache.dubbo:dubbo:3.3.6")
       implementation("io.netty:netty-all:4.1.118.Final")
       implementation("org.javassist:javassist:3.30.2-GA")
       implementation("org.slf4j:slf4j-simple:2.0.13")
   }
   kotlin { jvmToolchain(21) }
   application { mainClass = "repro.MainKt" }
   ```
   
   `src/main/java/repro/JReply.java` and `JFound.java` — a Java 17 sealed 
hierarchy, to show this is
   not Kotlin-specific:
   
   ```java
   package repro;
   
   import java.io.Serializable;
   
   public sealed interface JReply extends Serializable permits JFound {}
   ```
   
   ```java
   package repro;
   
   public final class JFound implements JReply {
       private String text;
   
       public JFound() {}
   
       public JFound(String text) {
           this.text = text;
       }
   
       public String getText() {
           return text;
       }
   
       public void setText(String text) {
           this.text = text;
       }
   
       @Override
       public String toString() {
           return "JFound(" + text + ")";
       }
   }
   ```
   
   `src/main/kotlin/Main.kt`:
   
   ```kotlin
   package repro
   
   import java.io.Serializable
   import org.apache.dubbo.config.*
   import org.apache.dubbo.config.bootstrap.DubboBootstrap
   
   sealed interface Reply : Serializable
   
   data class Found(val text: String) : Reply
   
   data object NotFound : Reply
   
   interface ReplyService {
       fun lookup(key: String): Reply       // returns Found or NotFound
       fun javaLookup(key: String): JReply  // Java 17 sealed, same shape
   }
   
   class ReplyServiceImpl : ReplyService {
       override fun lookup(key: String): Reply = if (key == "x") Found("hit") 
else NotFound
       override fun javaLookup(key: String): JReply = JFound("hit")
   }
   
   private fun attempt(label: String, call: () -> Any) {
       try {
           println("$label -> OK: ${call()}")
       } catch (t: Throwable) {
           val root = generateSequence(t) { it.cause }.last()
           println("$label -> ${root::class.java.name}: 
${root.message?.lines()?.first()}")
       }
   }
   
   fun main() {
       DubboBootstrap.getInstance()
           .application(ApplicationConfig("sealed-repro"))
           .registry(RegistryConfig("N/A"))
           .protocol(ProtocolConfig("dubbo", 20883))
           .service(ServiceConfig<ReplyService>().apply {
               setInterface(ReplyService::class.java)
               setRef(ReplyServiceImpl())
           })
           .start()
   
       val svc = ReferenceConfig<ReplyService>().apply {
           setInterface(ReplyService::class.java)
           url = "dubbo://127.0.0.1:20883"
       }.get()
   
       println("Dubbo ${org.apache.dubbo.common.Version.getVersion()}, no 
security/serialize.allowlist supplied")
       attempt("kotlin data class  (subtype of sealed Reply) ") { 
svc.lookup("x") }
       attempt("kotlin data object (subtype of sealed Reply) ") { 
svc.lookup("y") }
       attempt("java 17 sealed     (subtype of sealed JReply)") { 
svc.javaLookup("x") }
   
       DubboBootstrap.getInstance().destroy()
   }
   ```
   
   `gradle run` prints (long lines wrapped, otherwise verbatim):
   
   ```
   Dubbo 3.3.6, no security/serialize.allowlist supplied
   kotlin data class  (subtype of sealed Reply)  -> 
org.apache.dubbo.remoting.RemotingException:
     Failed to send response: ... result=AppResponse [value=Found(text=hit), 
exception=null]],
     cause: java.io.IOException: 
org.apache.dubbo.common.serialize.SerializationException:
     java.lang.IllegalArgumentException: [Serialization Security] Serialized 
class repro.Found is
     not in allow list. Current mode is `STRICT`, will disallow to deserialize 
it by default.
     Please add it into security/serialize.allowlist or follow FAQ to configure 
it.
   kotlin data object (subtype of sealed Reply)  -> ... Serialized class 
repro.NotFound is not in allow list ...
   java 17 sealed     (subtype of sealed JReply) -> ... Serialized class 
repro.JFound is not in allow list ...
   ```
   
   `Reply`, `JReply` and `ReplyService` are all in the allow list. `Found`, 
`NotFound` and `JFound` are
   not. Note that the check fires while the **provider serializes the 
response**, so the consumer sees
   a `RemotingException` wrapping the `IllegalArgumentException` rather than a 
deserialization failure.
   
   A larger demo exists that sends one payload down each route by which 
auto-trust can reach a class,
   with Java controls, so the pattern is isolated rather than inferred:
   
   ```
   route to payload           | declared as    | gate hit
   ---------------------------+----------------+--------------
   direct param/return        | DirectPlain    | SERIALIZABLE   (not 
Serializable; a Java POJO behaves the same)
   direct param/return        | DirectSerial.  | --             (trusted)
   field of param/return      | Envelope       | --             (trusted)
   generic type argument      | List<Listed>   | --             (trusted)
   subtype of sealed          | Reply          | ALLOWLIST      <-- this report 
(Kotlin data class)
   subtype of sealed          | Reply          | ALLOWLIST      <-- this report 
(Kotlin data object)
   subtype of sealed          | JSealedReply   | ALLOWLIST      <-- this report 
(Java 17 sealed)
   subtype of interface       | JReply (Java)  | ALLOWLIST      (non-sealed: 
cannot be enumerated)
   ```
   
   Only the subtype route fails. Direct, field and generic routes all pass, and 
nothing about Kotlin or
   `data class` varies between those rows.
   
   
   ### What you expected to happen
   
   The subtypes a sealed type permits should be auto-trusted along with the 
parent.
   
   A sealed type enumerates its subtypes in its own source, and the JVM refuses 
to load any subtype
   absent from that list. Trusting them therefore grants exactly the set the 
author declared and
   nothing an attacker can extend — a more precise grant than the 
package-prefix trust Dubbo already
   applies when `trustSerializeClassLevel` is set. A non-sealed hierarchy is 
open, cannot be
   enumerated, and must keep behaving as it does today.
   
   What happens instead is that the developer has to list every subtype by 
hand, repeat that for every
   sealed hierarchy, and remember to update it whenever a subtype is added — 
for a set the compiler
   already knows in full.
   
   ### Anything else
   
   _No response_
   
   ### Do you have a (mini) reproduction demo?
   
   - [ ] Yes, I have a minimal reproduction demo to help resolve this issue 
more effectively!
   
   ### Are you willing to submit a pull request to fix on your own?
   
   - [x] Yes I am willing to submit a pull request on my own!
   
   ### Code of Conduct
   
   - [x] I agree to follow this project's [Code of 
Conduct](https://www.apache.org/foundation/policies/conduct)
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to