Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
import static org.apache.dubbo.common.constants.CommonConstants.SERIALIZE_ALLOW_LIST_FILE_PATH;
import static org.apache.dubbo.common.constants.CommonConstants.SERIALIZE_BLOCKED_LIST_FILE_PATH;
import static org.apache.dubbo.common.constants.LoggerCodeConstants.COMMON_IO_EXCEPTION;
import static org.apache.dubbo.common.constants.LoggerCodeConstants.COMMON_REFLECTIVE_OPERATION_FAILED;

public class SerializeSecurityConfigurator implements ScopeClassLoaderListener<ModuleModel> {
private static final ErrorTypeAwareLogger LOGGER =
Expand Down Expand Up @@ -229,25 +230,34 @@ public synchronized void registerInterface(Class<?> clazz) {
checkClass(parameterType);
}

Type[] genericParameterTypes = method.getGenericParameterTypes();
for (Type genericParameterType : genericParameterTypes) {
checkType(genericParameterType);
try {
for (Type genericParameterType : method.getGenericParameterTypes()) {
checkType(genericParameterType);
}
} catch (LinkageError | TypeNotPresentException e) {
logScanFailure(clazz.getName() + "#" + method.getName() + " parameter types", e);
}

Class<?> returnType = method.getReturnType();
checkClass(returnType);

Type genericReturnType = method.getGenericReturnType();
checkType(genericReturnType);
try {
checkType(method.getGenericReturnType());
} catch (LinkageError | TypeNotPresentException e) {
logScanFailure(clazz.getName() + "#" + method.getName() + " return type", e);
}

Class<?>[] exceptionTypes = method.getExceptionTypes();
for (Class<?> exceptionType : exceptionTypes) {
checkClass(exceptionType);
}

Type[] genericExceptionTypes = method.getGenericExceptionTypes();
for (Type genericExceptionType : genericExceptionTypes) {
checkType(genericExceptionType);
try {
for (Type genericExceptionType : method.getGenericExceptionTypes()) {
checkType(genericExceptionType);
}
} catch (LinkageError | TypeNotPresentException e) {
logScanFailure(clazz.getName() + "#" + method.getName() + " exception types", e);
}
}
}
Expand All @@ -266,28 +276,32 @@ private void checkType(Type type) {
return;
}

if (type instanceof ParameterizedType) {
ParameterizedType parameterizedType = (ParameterizedType) type;
checkClass((Class<?>) parameterizedType.getRawType());
for (Type actualTypeArgument : parameterizedType.getActualTypeArguments()) {
checkType(actualTypeArgument);
}
} else if (type instanceof GenericArrayType) {
GenericArrayType genericArrayType = (GenericArrayType) type;
checkType(genericArrayType.getGenericComponentType());
} else if (type instanceof TypeVariable) {
TypeVariable typeVariable = (TypeVariable) type;
for (Type bound : typeVariable.getBounds()) {
checkType(bound);
}
} else if (type instanceof WildcardType) {
WildcardType wildcardType = (WildcardType) type;
for (Type bound : wildcardType.getUpperBounds()) {
checkType(bound);
}
for (Type bound : wildcardType.getLowerBounds()) {
checkType(bound);
try {
if (type instanceof ParameterizedType) {
ParameterizedType parameterizedType = (ParameterizedType) type;
checkClass((Class<?>) parameterizedType.getRawType());
for (Type actualTypeArgument : parameterizedType.getActualTypeArguments()) {
checkType(actualTypeArgument);
}
} else if (type instanceof GenericArrayType) {
GenericArrayType genericArrayType = (GenericArrayType) type;
checkType(genericArrayType.getGenericComponentType());
} else if (type instanceof TypeVariable) {
TypeVariable typeVariable = (TypeVariable) type;
for (Type bound : typeVariable.getBounds()) {
checkType(bound);
}
} else if (type instanceof WildcardType) {
WildcardType wildcardType = (WildcardType) type;
for (Type bound : wildcardType.getUpperBounds()) {
checkType(bound);
}
for (Type bound : wildcardType.getLowerBounds()) {
checkType(bound);
}
}
} catch (LinkageError | TypeNotPresentException e) {
logScanFailure("generic type bounds or arguments", e);
}
}

Expand Down Expand Up @@ -319,21 +333,34 @@ private boolean checkClass(Class<?> clazz) {
checkClass(interfaceClass);
}

for (Type genericInterface : clazz.getGenericInterfaces()) {
checkType(genericInterface);
try {
for (Type genericInterface : clazz.getGenericInterfaces()) {
checkType(genericInterface);
}
} catch (LinkageError | TypeNotPresentException e) {
logScanFailure(className + " generic interfaces", e);
}

Class<?> superclass = clazz.getSuperclass();
if (superclass != null) {
checkClass(superclass);
}

Type genericSuperclass = clazz.getGenericSuperclass();
if (genericSuperclass != null) {
checkType(genericSuperclass);
try {
checkType(clazz.getGenericSuperclass());
} catch (LinkageError | TypeNotPresentException e) {
logScanFailure(className + " generic superclass", e);
}

Field[] fields = clazz.getDeclaredFields();
Field[] fields;
try {
fields = clazz.getDeclaredFields();
} catch (LinkageError | TypeNotPresentException e) {
// Reflection resolves all raw field types before returning any Field objects.
// Skip this class's fields, but let callers continue scanning other reachable types.
logScanFailure(className + " fields", e);
return true;
}

for (Field field : fields) {
if (Modifier.isTransient(field.getModifiers())) {
Expand All @@ -342,12 +369,27 @@ private boolean checkClass(Class<?> clazz) {

Class<?> fieldClass = field.getType();
checkClass(fieldClass);
checkType(field.getGenericType());
try {
checkType(field.getGenericType());
} catch (LinkageError | TypeNotPresentException e) {
logScanFailure(className + "#" + field.getName(), e);
}
}

return true;
}

private void logScanFailure(String source, Throwable cause) {
LOGGER.warn(
COMMON_REFLECTIVE_OPERATION_FAILED,
"",
"",
"Failed to inspect " + source
+ " for the serialization allow list; continuing with other types. "
+ "Check that the application's dependencies are available.",
cause);
}

private void addToAllow(Class<?> clazz) {
if (classHolder != null) {
classHolder.storeClass(clazz);
Expand Down
100 changes: 100 additions & 0 deletions dubbo-common/src/test/java/com/serialization/MissingTypeFixtures.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.serialization;

import java.io.Serializable;
import java.util.List;

/**
* Models loaded without OptionalDependency to exercise incomplete application classpaths.
* @date 2026-11-05
*/
public class MissingTypeFixtures {
public static class OptionalDependency {}

public static class Healthy implements Serializable {
public Nested value;
}

public static class Nested implements Serializable {}

public static class Unrelated implements Serializable {}

public static class MissingField {
public OptionalDependency optional;
}

public static class MissingTransientField {
public transient OptionalDependency optional;
}

public static class MissingGenericField {
public List<OptionalDependency> optional;
public Healthy healthy;
}

public interface Contract<T> {}

public static class MissingGenericInterface implements Contract<OptionalDependency> {
public Healthy healthy;
}

public static class Base<T> {}

public static class MissingGenericSuperclass extends Base<OptionalDependency> {
public Healthy healthy;
}

public static class MissingBound<T extends OptionalDependency> {
public List<T> optional;
public Healthy healthy;
}

public interface FieldService {
Healthy read(MissingField value);
}

public interface TransientFieldService {
Healthy read(MissingTransientField value);
}

public interface GenericParameterService {
Healthy read(List<OptionalDependency> value);
}

public interface GenericFieldService {
void read(MissingGenericField value);
}

public interface GenericInterfaceService {
void read(MissingGenericInterface value);
}

public interface GenericSuperclassService {
void read(MissingGenericSuperclass value);
}

public interface BoundService {
void read(MissingBound<?> value);
}

public interface GenericMethodService {
List<OptionalDependency> optional();

Healthy read();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.dubbo.common.utils;

import org.apache.dubbo.config.ApplicationConfig;
import org.apache.dubbo.rpc.model.ApplicationModel;
import org.apache.dubbo.rpc.model.FrameworkModel;

import java.io.IOException;
import java.io.InputStream;

import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;

import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;

/**
* Verifies that missing optional types do not prevent discovery of independent DTOs.
* @date 2026-11-05
*/
class SerializeSecurityConfiguratorMissingTypeTest {
private static final String FIXTURES = "com.serialization.MissingTypeFixtures$";

@ParameterizedTest
@ValueSource(
strings = {
"FieldService",
"TransientFieldService",
"GenericParameterService",
"GenericFieldService",
"GenericInterfaceService",
"GenericSuperclassService",
"BoundService",
"GenericMethodService"
})
void continuesScanningAfterMissingType(String serviceName) throws Exception {
ClassLoader loader = new MissingDependencyClassLoader();
assertThrows(ClassNotFoundException.class, () -> loader.loadClass(FIXTURES + "OptionalDependency"));
Class<?> service = loader.loadClass(FIXTURES + serviceName);
Class<?> healthy = loader.loadClass(FIXTURES + "Healthy");
Class<?> nested = loader.loadClass(FIXTURES + "Nested");

FrameworkModel framework = new FrameworkModel();
try {
ApplicationModel application = framework.newApplication();
ApplicationConfig config = new ApplicationConfig("missing-type-test");
config.setSerializeCheckStatus(SerializeCheckStatus.STRICT.name());
config.setTrustSerializeClassLevel(Integer.MAX_VALUE);
application.getApplicationConfigManager().setApplication(config);
SerializeSecurityConfigurator configurator = new SerializeSecurityConfigurator(application.newModule());
DefaultSerializeClassChecker checker =
framework.getBeanFactory().getBean(DefaultSerializeClassChecker.class);

assertThrows(IllegalArgumentException.class, () -> checker.loadClass(loader, healthy.getName()));
assertDoesNotThrow(() -> configurator.registerInterface(service));
assertEquals(healthy, checker.loadClass(loader, healthy.getName()));
assertEquals(nested, checker.loadClass(loader, nested.getName()));
assertThrows(IllegalArgumentException.class, () -> checker.loadClass(loader, FIXTURES + "Unrelated"));
assertDoesNotThrow(() -> configurator.registerInterface(service));
} finally {
framework.destroy();
}
}

private static class MissingDependencyClassLoader extends ClassLoader {
MissingDependencyClassLoader() {
super(SerializeSecurityConfiguratorMissingTypeTest.class.getClassLoader());
}

@Override
protected synchronized Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
if (name.equals(FIXTURES + "OptionalDependency")) {
throw new ClassNotFoundException(name);
}
if (!name.startsWith(FIXTURES)) {
return super.loadClass(name, resolve);
}
Class<?> loaded = findLoadedClass(name);
if (loaded == null) {
try (InputStream stream = getParent().getResourceAsStream(name.replace('.', '/') + ".class")) {
if (stream == null) {
throw new ClassNotFoundException(name);
}
byte[] bytes = IOUtils.toByteArray(stream);
loaded = defineClass(name, bytes, 0, bytes.length);
} catch (IOException e) {
throw new ClassNotFoundException(name, e);
}
}
if (resolve) {
resolveClass(loaded);
}
return loaded;
}
}
}
Loading