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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy