blob: 50ddfdcfc3a8b3e283a143f4be4da9e0104b84b4 [file] [log] [blame]
/*
* 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.common.beanutil.JavaBeanSerializeUtil;
import org.apache.dubbo.common.config.ConfigurationUtils;
import org.apache.dubbo.common.constants.CommonConstants;
import org.apache.dubbo.common.logger.Logger;
import org.apache.dubbo.common.logger.LoggerFactory;
import java.io.IOException;
import java.util.Arrays;
import java.util.Locale;
import java.util.Set;
import java.util.concurrent.atomic.AtomicLong;
import static org.apache.dubbo.common.constants.CommonConstants.CLASS_DESERIALIZE_ALLOWED_LIST;
import static org.apache.dubbo.common.constants.CommonConstants.CLASS_DESERIALIZE_BLOCKED_LIST;
import static org.apache.dubbo.common.constants.CommonConstants.CLASS_DESERIALIZE_BLOCK_ALL;
import static org.apache.dubbo.common.constants.CommonConstants.SERIALIZE_BLOCKED_LIST_FILE_PATH;
public class SerializeClassChecker {
private static final Logger logger = LoggerFactory.getLogger(SerializeClassChecker.class);
private static volatile SerializeClassChecker INSTANCE = null;
private final boolean OPEN_CHECK_CLASS;
private final boolean BLOCK_ALL_CLASS_EXCEPT_ALLOW;
private final Set<String> CLASS_DESERIALIZE_ALLOWED_SET = new ConcurrentHashSet<>();
private final Set<String> CLASS_DESERIALIZE_BLOCKED_SET = new ConcurrentHashSet<>();
private final Object CACHE = new Object();
private final LFUCache<String, Object> CLASS_ALLOW_LFU_CACHE = new LFUCache<>();
private final LFUCache<String, Object> CLASS_BLOCK_LFU_CACHE = new LFUCache<>();
private final AtomicLong counter = new AtomicLong(0);
private SerializeClassChecker() {
String openCheckClass = ConfigurationUtils.getProperty(CommonConstants.CLASS_DESERIALIZE_OPEN_CHECK, "true");
OPEN_CHECK_CLASS = Boolean.parseBoolean(openCheckClass);
String blockAllClassExceptAllow = ConfigurationUtils.getProperty(CLASS_DESERIALIZE_BLOCK_ALL, "false");
BLOCK_ALL_CLASS_EXCEPT_ALLOW = Boolean.parseBoolean(blockAllClassExceptAllow);
String[] lines;
try {
ClassLoader classLoader = ClassUtils.getClassLoader(JavaBeanSerializeUtil.class);
if (classLoader != null) {
lines = IOUtils.readLines(classLoader.getResourceAsStream(SERIALIZE_BLOCKED_LIST_FILE_PATH));
} else {
lines = IOUtils.readLines(ClassLoader.getSystemResourceAsStream(SERIALIZE_BLOCKED_LIST_FILE_PATH));
}
for (String line : lines) {
line = line.trim();
if (StringUtils.isEmpty(line) || line.startsWith("#")) {
continue;
}
CLASS_DESERIALIZE_BLOCKED_SET.add(line);
}
} catch (IOException e) {
logger.error("Failed to load blocked class list! Will ignore default blocked list.", e);
}
String allowedClassList = ConfigurationUtils.getProperty(CLASS_DESERIALIZE_ALLOWED_LIST, "").trim().toLowerCase(Locale.ROOT);
String blockedClassList = ConfigurationUtils.getProperty(CLASS_DESERIALIZE_BLOCKED_LIST, "").trim().toLowerCase(Locale.ROOT);
if (StringUtils.isNotEmpty(allowedClassList)) {
String[] classStrings = allowedClassList.trim().split(",");
CLASS_DESERIALIZE_ALLOWED_SET.addAll(Arrays.asList(classStrings));
}
if (StringUtils.isNotEmpty(blockedClassList)) {
String[] classStrings = blockedClassList.trim().split(",");
CLASS_DESERIALIZE_BLOCKED_SET.addAll(Arrays.asList(classStrings));
}
}
public static SerializeClassChecker getInstance() {
if (INSTANCE == null) {
synchronized (SerializeClassChecker.class) {
if (INSTANCE == null) {
INSTANCE = new SerializeClassChecker();
}
}
}
return INSTANCE;
}
/**
* For ut only
*/
@Deprecated
protected static void clearInstance() {
INSTANCE = null;
}
/**
* Check if a class is in block list, using prefix match
*
* @throws IllegalArgumentException if class is blocked
* @param name class name ( all are convert to lower case )
*/
public void validateClass(String name) {
if(!OPEN_CHECK_CLASS){
return;
}
name = name.toLowerCase(Locale.ROOT);
if (CACHE == CLASS_ALLOW_LFU_CACHE.get(name)) {
return;
}
if (CACHE == CLASS_BLOCK_LFU_CACHE.get(name)) {
error(name);
}
for (String allowedPrefix : CLASS_DESERIALIZE_ALLOWED_SET) {
if (name.startsWith(allowedPrefix)) {
CLASS_ALLOW_LFU_CACHE.put(name, CACHE);
return;
}
}
for (String blockedPrefix : CLASS_DESERIALIZE_BLOCKED_SET) {
if (BLOCK_ALL_CLASS_EXCEPT_ALLOW || name.startsWith(blockedPrefix)) {
CLASS_BLOCK_LFU_CACHE.put(name, CACHE);
error(name);
}
}
CLASS_ALLOW_LFU_CACHE.put(name, CACHE);
}
private void error(String name) {
String notice = "Trigger the safety barrier! " +
"Catch not allowed serialize class. " +
"Class name: " + name + " . " +
"This means currently maybe being attacking by others." +
"If you are sure this is a mistake, " +
"please add this class name to `" + CLASS_DESERIALIZE_ALLOWED_LIST +
"` as a system environment property.";
if (counter.incrementAndGet() % 1000 == 0 || counter.get() < 100) {
logger.error(notice);
}
throw new IllegalArgumentException(notice);
}
}