Skip to content

Commit bb09515

Browse files
committed
Signed-off-by: Ceki Gulcu <ceki@qos.ch>
1 parent 4573294 commit bb09515

File tree

8 files changed

+275
-19
lines changed

8 files changed

+275
-19
lines changed

logback-classic/src/main/java/ch/qos/logback/classic/spi/LoggingEventVO.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
package ch.qos.logback.classic.spi;
1515

1616
import java.io.IOException;
17+
import java.io.InvalidObjectException;
1718
import java.io.ObjectInputStream;
1819
import java.io.ObjectOutputStream;
1920
import java.io.Serializable;
@@ -38,6 +39,7 @@ public class LoggingEventVO implements ILoggingEvent, Serializable {
3839

3940
private static final int NULL_ARGUMENT_ARRAY = -1;
4041
private static final String NULL_ARGUMENT_ARRAY_ELEMENT = "NULL_ARGUMENT_ARRAY_ELEMENT";
42+
private static final int ARGUMENT_ARRAY_DESERIALIZATION_LIMIT = 128;
4143

4244
private String threadName;
4345
private String loggerName;
@@ -181,6 +183,11 @@ private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundE
181183
level = Level.toLevel(levelInt);
182184

183185
int argArrayLen = in.readInt();
186+
// Prevent DOS attacks via large or negative arrays
187+
if (argArrayLen < NULL_ARGUMENT_ARRAY || argArrayLen > ARGUMENT_ARRAY_DESERIALIZATION_LIMIT) {
188+
throw new InvalidObjectException("Argument array length is invalid: " + argArrayLen);
189+
}
190+
184191
if (argArrayLen != NULL_ARGUMENT_ARRAY) {
185192
argumentArray = new String[argArrayLen];
186193
for (int i = 0; i < argArrayLen; i++) {
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<!--
3+
~ Logback: the reliable, generic, fast and flexible logging framework.
4+
~ Copyright (C) 1999-2023, QOS.ch. All rights reserved.
5+
~
6+
~ This program and the accompanying materials are dual-licensed under
7+
~ either the terms of the Eclipse Public License v1.0 as published by
8+
~ the Eclipse Foundation
9+
~
10+
~ or (per the licensee's choosing)
11+
~
12+
~ under the terms of the GNU Lesser General Public License version 2.1
13+
~ as published by the Free Software Foundation.
14+
-->
15+
16+
<configuration debug="true">
17+
<appender name="GENERAL" class="ch.qos.logback.core.rolling.RollingFileAppender">
18+
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
19+
<fileNamePattern>${logback_1754_targetDirectory}/test-%d{yyyy-MM-dd}.log</fileNamePattern>
20+
<maxHistory>120</maxHistory>
21+
</rollingPolicy>
22+
<encoder>
23+
<pattern>%date{HH:mm:ss.SSS} [%level] %logger{0} [%thread] [%class{3}:%line] : %msg%n</pattern>
24+
</encoder>
25+
<prudent>true</prudent>
26+
</appender>
27+
<root level="debug">
28+
<appender-ref ref="GENERAL" />
29+
</root>
30+
</configuration>
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
/*
2+
* Logback: the reliable, generic, fast and flexible logging framework.
3+
* Copyright (C) 1999-2023, QOS.ch. All rights reserved.
4+
*
5+
* This program and the accompanying materials are dual-licensed under
6+
* either the terms of the Eclipse Public License v1.0 as published by
7+
* the Eclipse Foundation
8+
*
9+
* or (per the licensee's choosing)
10+
*
11+
* under the terms of the GNU Lesser General Public License version 2.1
12+
* as published by the Free Software Foundation.
13+
*/
14+
15+
package ch.qos.logback.classic.issue.logback_1754;
16+
17+
import ch.qos.logback.classic.ClassicConstants;
18+
import ch.qos.logback.classic.ClassicTestConstants;
19+
import ch.qos.logback.core.testUtil.RandomUtil;
20+
import org.slf4j.Logger;
21+
import org.slf4j.LoggerFactory;
22+
23+
import java.util.ArrayList;
24+
import java.util.List;
25+
import java.util.concurrent.CountDownLatch;
26+
27+
import static ch.qos.logback.classic.util.ContextInitializer.CONFIG_FILE_PROPERTY;
28+
29+
public class LogbackTest {
30+
31+
private static final int THREADS = 16;
32+
33+
private void runTest() {
34+
35+
int diff = RandomUtil.getPositiveInt();
36+
//System.setProperty("logback.statusListenerClass", "sysout");
37+
System.setProperty(CONFIG_FILE_PROPERTY, ClassicTestConstants.INPUT_PREFIX+"issue/logback-1754.xml");
38+
System.setProperty("logback_1754_targetDirectory", ClassicTestConstants.OUTPUT_DIR_PREFIX+"safeWrite_"+diff);
39+
40+
CountDownLatch latch = new CountDownLatch(THREADS);
41+
List<Thread> threads = new ArrayList<Thread>(THREADS);
42+
for (int i = 0; i < THREADS; i++) {
43+
LoggerThread thread = new LoggerThread(latch, "message from thread " + i);
44+
thread.start();
45+
threads.add(thread);
46+
}
47+
for (Thread thread : threads) {
48+
try {
49+
thread.join();
50+
} catch (InterruptedException e) {
51+
Thread.currentThread().interrupt();
52+
throw new RuntimeException(e);
53+
}
54+
}
55+
}
56+
57+
public static void main(String... args) {
58+
new LogbackTest().runTest();
59+
}
60+
61+
private static final class LoggerThread extends Thread {
62+
private static final Logger LOG = LoggerFactory.getLogger(LoggerThread.class);
63+
private final CountDownLatch latch;
64+
private final String message;
65+
66+
LoggerThread(CountDownLatch latch, String message) {
67+
setDaemon(false);
68+
this.latch = latch;
69+
this.message = message;
70+
}
71+
72+
@Override
73+
public void run() {
74+
latch.countDown();
75+
LOG.info(message);
76+
}
77+
}
78+
}

logback-core/src/main/java/ch/qos/logback/core/net/HardenedObjectInputStream.java

Lines changed: 53 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,27 @@
1+
/**
2+
* Logback: the reliable, generic, fast and flexible logging framework.
3+
* Copyright (C) 1999-2023, QOS.ch. All rights reserved.
4+
*
5+
* This program and the accompanying materials are dual-licensed under
6+
* either the terms of the Eclipse Public License v1.0 as published by
7+
* the Eclipse Foundation
8+
*
9+
* or (per the licensee's choosing)
10+
*
11+
* under the terms of the GNU Lesser General Public License version 2.1
12+
* as published by the Free Software Foundation.
13+
*/
114
package ch.qos.logback.core.net;
215

16+
import ch.qos.logback.core.util.EnvUtil;
17+
318
import java.io.IOException;
419
import java.io.InputStream;
520
import java.io.InvalidClassException;
621
import java.io.ObjectInputStream;
722
import java.io.ObjectStreamClass;
23+
import java.lang.reflect.InvocationTargetException;
24+
import java.lang.reflect.Method;
825
import java.util.ArrayList;
926
import java.util.List;
1027

@@ -22,10 +39,12 @@ public class HardenedObjectInputStream extends ObjectInputStream {
2239

2340
final List<String> whitelistedClassNames;
2441
final static String[] JAVA_PACKAGES = new String[] { "java.lang", "java.util" };
42+
final private static int DEPTH_LIMIT = 16;
43+
final private static int ARRAY_LIMIT = 10000;
2544

2645
public HardenedObjectInputStream(InputStream in, String[] whilelist) throws IOException {
2746
super(in);
28-
47+
initObjectFilter();
2948
this.whitelistedClassNames = new ArrayList<String>();
3049
if (whilelist != null) {
3150
for (int i = 0; i < whilelist.length; i++) {
@@ -36,11 +55,43 @@ public HardenedObjectInputStream(InputStream in, String[] whilelist) throws IOEx
3655

3756
public HardenedObjectInputStream(InputStream in, List<String> whitelist) throws IOException {
3857
super(in);
39-
58+
initObjectFilter();
4059
this.whitelistedClassNames = new ArrayList<String>();
4160
this.whitelistedClassNames.addAll(whitelist);
4261
}
4362

63+
private void initObjectFilter() {
64+
65+
// invoke the following code by reflection
66+
// this.setObjectInputFilter(ObjectInputFilter.Config.createFilter(
67+
// "maxarray=" + ARRAY_LIMIT + ";maxdepth=" + DEPTH_LIMIT + ";"
68+
// ));
69+
if(EnvUtil.isJDK9OrHigher()) {
70+
try {
71+
ClassLoader classLoader = this.getClass().getClassLoader();
72+
73+
Class oifClass = classLoader.loadClass("java.io.ObjectInputFilter");
74+
Class oifConfigClass = classLoader.loadClass("java.io.ObjectInputFilter$Config");
75+
Method setObjectInputFilterMethod = this.getClass().getMethod("setObjectInputFilter", oifClass);
76+
77+
Method createFilterMethod = oifConfigClass.getMethod("createFilter", String.class);
78+
Object filter = createFilterMethod.invoke(null, "maxarray=" + ARRAY_LIMIT + ";maxdepth=" + DEPTH_LIMIT + ";");
79+
setObjectInputFilterMethod.invoke(this, filter);
80+
} catch (ClassNotFoundException e) {
81+
// this code should be unreachable
82+
throw new RuntimeException("Failed to initialize object filter", e);
83+
} catch (InvocationTargetException e) {
84+
// this code should be unreachable
85+
throw new RuntimeException("Failed to initialize object filter", e);
86+
} catch (NoSuchMethodException e) {
87+
// this code should be unreachable
88+
throw new RuntimeException("Failed to initialize object filter", e);
89+
} catch (IllegalAccessException e) {
90+
// this code should be unreachable
91+
throw new RuntimeException("Failed to initialize object filter", e);
92+
}
93+
}
94+
}
4495
@Override
4596
protected Class<?> resolveClass(ObjectStreamClass anObjectStreamClass) throws IOException, ClassNotFoundException {
4697

logback-core/src/main/java/ch/qos/logback/core/util/EnvUtil.java

Lines changed: 24 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/**
22
* Logback: the reliable, generic, fast and flexible logging framework.
3-
* Copyright (C) 1999-2015, QOS.ch. All rights reserved.
3+
* Copyright (C) 1999-2023, QOS.ch. All rights reserved.
44
*
55
* This program and the accompanying materials are dual-licensed under
66
* either the terms of the Eclipse Public License v1.0 as published by
@@ -22,22 +22,27 @@
2222
public class EnvUtil {
2323

2424
static private boolean isJDK_N_OrHigher(int n) {
25-
List<String> versionList = new ArrayList<String>();
26-
// this code should work at least until JDK 10 (assuming n parameter is
27-
// always 6 or more)
28-
for (int i = 0; i < 5; i++) {
29-
versionList.add("1." + (n + i));
30-
}
31-
32-
String javaVersion = System.getProperty("java.version");
33-
if (javaVersion == null) {
25+
String javaVersionStr = System.getProperty("java.version", "");
26+
if (javaVersionStr.isEmpty())
3427
return false;
28+
29+
int version = getJDKVersion(javaVersionStr);
30+
return version > 0 && n <= version;
31+
}
32+
33+
static public int getJDKVersion(String javaVersionStr) {
34+
int version = 0;
35+
36+
for (char ch : javaVersionStr.toCharArray()) {
37+
if (Character.isDigit(ch)) {
38+
version = (version * 10) + (ch - 48);
39+
} else if (version == 1) {
40+
version = 0;
41+
} else {
42+
break;
43+
}
3544
}
36-
for (String v : versionList) {
37-
if (javaVersion.startsWith(v))
38-
return true;
39-
}
40-
return false;
45+
return version;
4146
}
4247

4348
static public boolean isJDK5() {
@@ -52,6 +57,10 @@ static public boolean isJDK7OrHigher() {
5257
return isJDK_N_OrHigher(7);
5358
}
5459

60+
static public boolean isJDK9OrHigher() {
61+
return isJDK_N_OrHigher(9);
62+
}
63+
5564
static public boolean isJaninoAvailable() {
5665
ClassLoader classLoader = EnvUtil.class.getClassLoader();
5766
try {

logback-core/src/test/java/ch/qos/logback/core/net/HardenedObjectInputStreamTest.java

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,17 @@
11
package ch.qos.logback.core.net;
22

33
import static org.junit.Assert.assertEquals;
4+
import static org.junit.Assert.fail;
45

56
import java.io.ByteArrayInputStream;
67
import java.io.ByteArrayOutputStream;
78
import java.io.IOException;
9+
import java.io.InvalidClassException;
810
import java.io.ObjectOutputStream;
11+
import java.util.HashSet;
12+
import java.util.Set;
913

14+
import ch.qos.logback.core.util.EnvUtil;
1015
import org.junit.After;
1116
import org.junit.Before;
1217
import org.junit.Test;
@@ -54,5 +59,47 @@ private void writeObject(ObjectOutputStream oos, Object o) throws IOException {
5459
oos.flush();
5560
oos.close();
5661
}
57-
62+
63+
@Test
64+
public void denialOfService() throws ClassNotFoundException, IOException {
65+
66+
if(!EnvUtil.isJDK9OrHigher()) {
67+
return;
68+
}
69+
70+
ByteArrayInputStream bis = new ByteArrayInputStream(payload());
71+
inputStream = new HardenedObjectInputStream(bis, whitelist);
72+
try {
73+
inputStream.readObject();
74+
fail("InvalidClassException expected");
75+
} catch(InvalidClassException e) {
76+
}
77+
finally {
78+
inputStream.close();
79+
}
80+
}
81+
82+
private byte[] payload() throws IOException {
83+
Set root = buildEvilHashset();
84+
writeObject(oos, root);
85+
return bos.toByteArray();
86+
}
87+
88+
private Set buildEvilHashset() {
89+
Set root = new HashSet();
90+
Set s1 = root;
91+
Set s2 = new HashSet();
92+
for (int i = 0; i < 100; i++) {
93+
Set t1 = new HashSet();
94+
Set t2 = new HashSet();
95+
t1.add("foo"); // make it not equal to t2
96+
s1.add(t1);
97+
s1.add(t2);
98+
s2.add(t1);
99+
s2.add(t2);
100+
s1 = t1;
101+
s2 = t2;
102+
}
103+
return root;
104+
}
58105
}

logback-core/src/test/java/ch/qos/logback/core/rolling/ScaffoldingForRollingTests.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,10 @@
2424

2525
import java.io.File;
2626
import java.io.IOException;
27-
import java.sql.Date;
2827
import java.text.SimpleDateFormat;
2928
import java.util.ArrayList;
3029
import java.util.Calendar;
30+
import java.util.Date;
3131
import java.util.Enumeration;
3232
import java.util.List;
3333
import java.util.concurrent.Future;

0 commit comments

Comments
 (0)