001/**
002 * Copyright (C) 2006-2025 Talend Inc. - www.talend.com
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016package org.talend.sdk.component.runtime.serialization;
017
018import static java.util.Optional.ofNullable;
019import static java.util.stream.Collectors.toList;
020import static java.util.stream.Collectors.toSet;
021import static lombok.AccessLevel.PRIVATE;
022
023import java.io.IOException;
024import java.io.InputStream;
025import java.io.ObjectInputStream;
026import java.io.ObjectStreamClass;
027import java.lang.reflect.Proxy;
028import java.util.Collection;
029import java.util.function.Predicate;
030import java.util.stream.Stream;
031
032import lombok.NoArgsConstructor;
033import lombok.extern.slf4j.Slf4j;
034
035public class EnhancedObjectInputStream extends ObjectInputStream {
036
037    private final ClassLoader loader;
038
039    private final Predicate<String> classesWhitelist;
040
041    protected EnhancedObjectInputStream(final InputStream in, final ClassLoader loader, final Predicate<String> filter)
042            throws IOException {
043        super(in);
044        this.loader = loader;
045        this.classesWhitelist = filter;
046    }
047
048    public EnhancedObjectInputStream(final InputStream in, final ClassLoader loader) throws IOException {
049        this(in, loader, Defaults.SECURITY_FILTER_WHITELIST);
050    }
051
052    @Override
053    protected Class<?> resolveClass(final ObjectStreamClass desc) throws ClassNotFoundException {
054        final String name = desc.getName();
055        if (name.equals("boolean")) {
056            return boolean.class;
057        }
058        if (name.equals("byte")) {
059            return byte.class;
060        }
061        if (name.equals("char")) {
062            return char.class;
063        }
064        if (name.equals("short")) {
065            return short.class;
066        }
067        if (name.equals("int")) {
068            return int.class;
069        }
070        if (name.equals("long")) {
071            return long.class;
072        }
073        if (name.equals("float")) {
074            return float.class;
075        }
076        if (name.equals("double")) {
077            return double.class;
078        }
079
080        doSecurityCheck(name);
081
082        try {
083            return Class.forName(name, false, loader);
084        } catch (final ClassNotFoundException e) {
085            // try again from beam classloader for complex classloader graphs,
086            // really a fallback mode
087            return Class.forName(name, false, getClass().getClassLoader());
088        }
089    }
090
091    @Override
092    protected Class<?> resolveProxyClass(final String[] interfaces) throws ClassNotFoundException {
093        final Class[] interfaceTypes = new Class[interfaces.length];
094        for (int i = 0; i < interfaces.length; i++) {
095            doSecurityCheck(interfaces[i]);
096            interfaceTypes[i] = Class.forName(interfaces[i], false, loader);
097        }
098
099        try {
100            return Proxy.getProxyClass(loader, interfaceTypes);
101        } catch (final IllegalArgumentException e) {
102            throw new ClassNotFoundException(null, e);
103        }
104    }
105
106    private void doSecurityCheck(final String name) {
107        if (!classesWhitelist.test(processForWhiteListing(name))) {
108            throw new SecurityException("'" + name + "' not supported, add it in "
109                    + "-Dtalend.component.runtme.serialization.java" + ".inputstream.whitelist if needed");
110        }
111    }
112
113    private String processForWhiteListing(final String name) {
114        if (name.startsWith("[L") && name.endsWith(";")) {
115            return name.substring(2, name.length() - 1);
116        }
117        return name;
118    }
119
120    @Slf4j
121    @NoArgsConstructor(access = PRIVATE)
122    static class Defaults {
123
124        // IMPORTANT: this MUST come from the JVM and not from a SPI which could enable to break that!
125        static final Predicate<String> SECURITY_FILTER_WHITELIST =
126                ofNullable(System.getProperty("talend.component.runtime.serialization.java.inputstream.whitelist"))
127                        .map(s -> Stream
128                                .of(s.split(","))
129                                .map(String::trim)
130                                .filter(it -> !it.isEmpty())
131                                .collect(toList()))
132                        .map(l -> (Predicate<String>) name -> l.stream().anyMatch(name::startsWith))
133                        .orElseGet(() -> {
134                            final Collection<String> blacklist = Stream
135                                    .of("org.codehaus.groovy.runtime.", "org.apache.commons.collections.functors.",
136                                            "org.apache.commons.collections4.functors.", "org.apache.xalan",
137                                            "java.lang.Process", "java.util.logging.", "java.rmi.server.",
138                                            "com.sun.org.apache.xalan.internal.xsltc.trax.", "com.sun.rowset.",
139                                            "org.springframework.beans.factory.config.",
140                                            "org.apache.tomcat.dbcp.dbcp2.", "com.sun.org.apache.bcel.internal.util.",
141                                            "org.hibernate.jmx.", "org.apache.ibatis.", "jodd.db.connection.",
142                                            "oracle.jdbc.", "org.slf4j.ext.", "flex.messaging.util.concurrent.",
143                                            "com.sun.deploy.security.ruleset.", "org.apache.axis2.jaxws.spi.handler.",
144                                            "org.apache.axis2.transport.jms.", "org.jboss.util.propertyeditor.",
145                                            "org.apache.openjpa.ee.")
146                                    .collect(toSet());
147                            log
148                                    .warn("talend.component.runtime.serialization.java.inputstream.whitelist "
149                                            + "system property not set, "
150                                            + "will use default blacklist but this is not considered as a secure setup. "
151                                            + "Blacklisted packages: {}", blacklist);
152                            return name -> blacklist.stream().noneMatch(name::startsWith);
153                        });
154    }
155}