Skip to content

Commit

Permalink
[LOG4J2-1863] Add class filtering to AbstractSocketServer
Browse files Browse the repository at this point in the history
This allows a whitelist of class names to be specified to configure
which classes are allowed to be deserialized in both TcpSocketServer and
UdpSocketServer.
  • Loading branch information
jvz committed Apr 2, 2017
1 parent 5aff929 commit 5dcc192
Show file tree
Hide file tree
Showing 5 changed files with 140 additions and 6 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@
import java.net.InetAddress;
import java.net.URI;
import java.net.URL;
import java.util.Collections;
import java.util.List;
import java.util.Objects;

import com.beust.jcommander.Parameter;
Expand Down Expand Up @@ -70,6 +72,9 @@ protected static class CommandLineArguments extends BasicCommandLineArguments {
"-a" }, converter = InetAddressConverter.class, description = "Server socket local bind address.")
private InetAddress localBindAddress;

@Parameter(names = {"--classes", "-C"}, description = "Additional classes to allow deserialization")
private List<String> allowedClasses;

String getConfigLocation() {
return configLocation;
}
Expand Down Expand Up @@ -101,6 +106,14 @@ InetAddress getLocalBindAddress() {
void setLocalBindAddress(final InetAddress localBindAddress) {
this.localBindAddress = localBindAddress;
}

List<String> getAllowedClasses() {
return allowedClasses == null ? Collections.<String>emptyList() : allowedClasses;
}

void setAllowedClasses(final List<String> allowedClasses) {
this.allowedClasses = allowedClasses;
}
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,18 +19,37 @@
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.util.Collections;
import java.util.List;

import org.apache.logging.log4j.core.LogEvent;
import org.apache.logging.log4j.core.LogEventListener;
import org.apache.logging.log4j.core.util.FilteredObjectInputStream;

/**
* Reads and logs serialized {@link LogEvent} objects from an {@link ObjectInputStream}.
*/
public class ObjectInputStreamLogEventBridge extends AbstractLogEventBridge<ObjectInputStream> {

private final List<String> allowedClasses;

public ObjectInputStreamLogEventBridge() {
this(Collections.<String>emptyList());
}

/**
* Constructs an ObjectInputStreamLogEventBridge with additional allowed classes to deserialize.
*
* @param allowedClasses class names to also allow for deserialization
* @since 2.8.2
*/
public ObjectInputStreamLogEventBridge(final List<String> allowedClasses) {
this.allowedClasses = allowedClasses;
}

@Override
public void logEvents(final ObjectInputStream inputStream, final LogEventListener logEventListener)
throws IOException {
throws IOException {
try {
logEventListener.log((LogEvent) inputStream.readObject());
} catch (final ClassNotFoundException e) {
Expand All @@ -40,6 +59,6 @@ public void logEvents(final ObjectInputStream inputStream, final LogEventListene

@Override
public ObjectInputStream wrapStream(final InputStream inputStream) throws IOException {
return new ObjectInputStream(inputStream);
return new FilteredObjectInputStream(inputStream, allowedClasses);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
Expand Down Expand Up @@ -148,9 +150,26 @@ public static TcpSocketServer<ObjectInputStream> createSerializedSocketServer(fi
*/
public static TcpSocketServer<ObjectInputStream> createSerializedSocketServer(final int port, final int backlog,
final InetAddress localBindAddress) throws IOException {
return createSerializedSocketServer(port, backlog, localBindAddress, Collections.<String>emptyList());
}

/**
* Creates a socket server that reads serialized log events.
*
* @param port the port to listen
* @param localBindAddress The server socket's local bin address
* @param allowedClasses additional class names to allow for deserialization
* @return a new a socket server
* @throws IOException
* if an I/O error occurs when opening the socket.
* @since 2.8.2
*/
public static TcpSocketServer<ObjectInputStream> createSerializedSocketServer(
final int port, final int backlog, final InetAddress localBindAddress, final List<String> allowedClasses
) throws IOException {
LOGGER.entry(port);
final TcpSocketServer<ObjectInputStream> socketServer = new TcpSocketServer<>(port, backlog, localBindAddress,
new ObjectInputStreamLogEventBridge());
new ObjectInputStreamLogEventBridge(allowedClasses));
return LOGGER.exit(socketServer);
}

Expand Down Expand Up @@ -185,8 +204,8 @@ public static void main(final String[] args) throws Exception {
if (cla.getConfigLocation() != null) {
ConfigurationFactory.setConfigurationFactory(new ServerConfigurationFactory(cla.getConfigLocation()));
}
final TcpSocketServer<ObjectInputStream> socketServer = TcpSocketServer
.createSerializedSocketServer(cla.getPort(), cla.getBacklog(), cla.getLocalBindAddress());
final TcpSocketServer<ObjectInputStream> socketServer = TcpSocketServer.createSerializedSocketServer(
cla.getPort(), cla.getBacklog(), cla.getLocalBindAddress(), cla.getAllowedClasses());
final Thread serverThread = socketServer.startNewThread();
if (cla.isInteractive()) {
socketServer.awaitTermination(serverThread);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import java.io.OptionalDataException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.util.List;

import org.apache.logging.log4j.core.config.ConfigurationFactory;
import org.apache.logging.log4j.core.util.BasicCommandLineArguments;
Expand Down Expand Up @@ -63,6 +64,21 @@ public static UdpSocketServer<ObjectInputStream> createSerializedSocketServer(fi
return new UdpSocketServer<>(port, new ObjectInputStreamLogEventBridge());
}

/**
* Creates a socket server that reads serialized log events.
*
* @param port the port to listen
* @param allowedClasses additional classes to allow for deserialization
* @return a new a socket server
* @throws IOException if an I/O error occurs when opening the socket.
* @since 2.8.2
*/
public static UdpSocketServer<ObjectInputStream> createSerializedSocketServer(final int port,
final List<String> allowedClasses)
throws IOException {
return new UdpSocketServer<>(port, new ObjectInputStreamLogEventBridge(allowedClasses));
}

/**
* Creates a socket server that reads XML log events.
*
Expand Down Expand Up @@ -93,7 +109,7 @@ public static void main(final String[] args) throws Exception {
ConfigurationFactory.setConfigurationFactory(new ServerConfigurationFactory(cla.getConfigLocation()));
}
final UdpSocketServer<ObjectInputStream> socketServer = UdpSocketServer
.createSerializedSocketServer(cla.getPort());
.createSerializedSocketServer(cla.getPort(), cla.getAllowedClasses());
final Thread serverThread = socketServer.startNewThread();
if (cla.isInteractive()) {
socketServer.awaitTermination(serverThread);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
/*
* 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.logging.log4j.core.util;

import java.io.IOException;
import java.io.InputStream;
import java.io.InvalidObjectException;
import java.io.ObjectInputStream;
import java.io.ObjectStreamClass;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;

/**
* Extended ObjectInputStream that only allows certain classes to be deserialized.
*
* @since 2.8.2
*/
public class FilteredObjectInputStream extends ObjectInputStream {

private static final List<String> REQUIRED_JAVA_CLASSES = Arrays.asList(
// for StandardLevel
"java.lang.Enum",
// for location information
"java.lang.StackTraceElement",
// for Message delegate
"java.rmi.MarshalledObject",
"[B"
);

private final Collection<String> allowedClasses;

public FilteredObjectInputStream(final InputStream in, final Collection<String> allowedClasses) throws IOException {
super(in);
this.allowedClasses = allowedClasses;
}

@Override
protected Class<?> resolveClass(final ObjectStreamClass desc) throws IOException, ClassNotFoundException {
String name = desc.getName();
if (!(isAllowedByDefault(name) || allowedClasses.contains(name))) {
throw new InvalidObjectException("Class is not allowed for deserialization: " + name);
}
return super.resolveClass(desc);
}

private static boolean isAllowedByDefault(final String name) {
return name.startsWith("org.apache.logging.log4j.") ||
name.startsWith("[Lorg.apache.logging.log4j.") ||
REQUIRED_JAVA_CLASSES.contains(name);
}

}

0 comments on commit 5dcc192

Please sign in to comment.