Codebase list apache-log4j1.2 / 4a12a82
Fix CVE-2021-4104 and CVE-2022-23305 Markus Koschany 2 years ago
3 changed file(s) with 799 addition(s) and 1 deletion(s). Raw diff Collapse all Expand all
0 From: Markus Koschany <apo@debian.org>
1 Date: Mon, 31 Jan 2022 11:18:33 +0100
2 Subject: CVE-2021-4104
3
4 Origin: https://github.com/qos-ch/reload4j/commit/fb7b1ff1c8beb8544933248d00a46e9e30547e87
5 Origin: https://github.com/qos-ch/reload4j/commit/e65c98bbba48cb877e057992847114f1f0923da6
6 ---
7 .../java/org/apache/log4j/net/JMSAppender.java | 11 ++---
8 src/main/java/org/apache/log4j/net/JNDIUtil.java | 54 +++++++++++++++++++++
9 .../java/org/apache/log4j/net/JNDIUtilTest.java | 55 ++++++++++++++++++++++
10 3 files changed, 114 insertions(+), 6 deletions(-)
11 create mode 100755 src/main/java/org/apache/log4j/net/JNDIUtil.java
12 create mode 100755 src/test/java/org/apache/log4j/net/JNDIUtilTest.java
13
14 diff --git a/src/main/java/org/apache/log4j/net/JMSAppender.java b/src/main/java/org/apache/log4j/net/JMSAppender.java
15 index 3482702..c390aef 100644
16 --- a/src/main/java/org/apache/log4j/net/JMSAppender.java
17 +++ b/src/main/java/org/apache/log4j/net/JMSAppender.java
18 @@ -32,7 +32,6 @@ import javax.jms.TopicPublisher;
19 import javax.jms.TopicSession;
20 import javax.naming.Context;
21 import javax.naming.InitialContext;
22 -import javax.naming.NameNotFoundException;
23 import javax.naming.NamingException;
24 import java.util.Properties;
25
26 @@ -241,12 +240,12 @@ public class JMSAppender extends AppenderSkeleton {
27 }
28
29 protected Object lookup(Context ctx, String name) throws NamingException {
30 - try {
31 - return ctx.lookup(name);
32 - } catch(NameNotFoundException e) {
33 - LogLog.error("Could not find name ["+name+"].");
34 - throw e;
35 + Object result = JNDIUtil.lookupObject(ctx, name);
36 + if (result == null) {
37 + String msg = "Could not find name [" + name + "].";
38 + throw new NamingException(msg);
39 }
40 + return result;
41 }
42
43 protected boolean checkEntryConditions() {
44 diff --git a/src/main/java/org/apache/log4j/net/JNDIUtil.java b/src/main/java/org/apache/log4j/net/JNDIUtil.java
45 new file mode 100755
46 index 0000000..3a66a05
47 --- /dev/null
48 +++ b/src/main/java/org/apache/log4j/net/JNDIUtil.java
49 @@ -0,0 +1,54 @@
50 +/*
51 + * Licensed to the Apache Software Foundation (ASF) under one or more
52 + * contributor license agreements. See the NOTICE file distributed with
53 + * this work for additional information regarding copyright ownership.
54 + * The ASF licenses this file to You under the Apache License, Version 2.0
55 + * (the "License"); you may not use this file except in compliance with
56 + * the License. You may obtain a copy of the License at
57 + *
58 + * http://www.apache.org/licenses/LICENSE-2.0
59 + *
60 + * Unless required by applicable law or agreed to in writing, software
61 + * distributed under the License is distributed on an "AS IS" BASIS,
62 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
63 + * See the License for the specific language governing permissions and
64 + * limitations under the License.
65 + */
66 +package org.apache.log4j.net;
67 +
68 +import javax.naming.Context;
69 +import javax.naming.NamingException;
70 +
71 +public class JNDIUtil {
72 +
73 + // See https://jakarta.ee/specifications/platform/8/platform-spec-8.html#a616
74 + // there are the java:comp, java:module, java:app, java:global namespaces
75 + public static final String JNDI_JAVA_NAMESPACE = "java:";
76 +
77 + static final String RESTRICTION_MSG = "JNDI name must start with " + JNDI_JAVA_NAMESPACE + " but was ";
78 +
79 + public static Object lookupObject(Context ctx, String name) throws NamingException {
80 + if (ctx == null) {
81 + return null;
82 + }
83 +
84 + if (isNullOrEmpty(name)) {
85 + return null;
86 + }
87 +
88 + jndiNameSecurityCheck(name);
89 +
90 + Object lookup = ctx.lookup(name);
91 + return lookup;
92 + }
93 +
94 + private static boolean isNullOrEmpty(String str) {
95 + return ((str == null) || str.trim().length() == 0);
96 + }
97 +
98 + public static void jndiNameSecurityCheck(String name) throws NamingException {
99 + if (!name.startsWith(JNDI_JAVA_NAMESPACE)) {
100 + throw new NamingException(RESTRICTION_MSG + name);
101 + }
102 + }
103 +}
104 diff --git a/src/test/java/org/apache/log4j/net/JNDIUtilTest.java b/src/test/java/org/apache/log4j/net/JNDIUtilTest.java
105 new file mode 100755
106 index 0000000..2439bc7
107 --- /dev/null
108 +++ b/src/test/java/org/apache/log4j/net/JNDIUtilTest.java
109 @@ -0,0 +1,55 @@
110 +/*
111 + * Licensed to the Apache Software Foundation (ASF) under one or more
112 + * contributor license agreements. See the NOTICE file distributed with
113 + * this work for additional information regarding copyright ownership.
114 + * The ASF licenses this file to You under the Apache License, Version 2.0
115 + * (the "License"); you may not use this file except in compliance with
116 + * the License. You may obtain a copy of the License at
117 + *
118 + * http://www.apache.org/licenses/LICENSE-2.0
119 + *
120 + * Unless required by applicable law or agreed to in writing, software
121 + * distributed under the License is distributed on an "AS IS" BASIS,
122 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
123 + * See the License for the specific language governing permissions and
124 + * limitations under the License.
125 + */
126 +package org.apache.log4j.net;
127 +
128 +import static org.junit.Assert.fail;
129 +
130 +import javax.naming.Context;
131 +import javax.naming.InitialContext;
132 +import javax.naming.NamingException;
133 +
134 +import org.junit.Test;
135 +
136 +
137 +/**
138 + * Test copied form the logback project with permission.
139 + *
140 + * @author Ceki Gulcu
141 + *
142 + */
143 +public class JNDIUtilTest {
144 +
145 + @Test
146 + public void ensureJavaNameSpace() throws NamingException {
147 +
148 + try {
149 + Context ctxt = new InitialContext();
150 + JNDIUtil.lookupObject(ctxt, "ldap:...");
151 + } catch (NamingException e) {
152 + String excaptionMsg = e.getMessage();
153 + if (excaptionMsg.startsWith(JNDIUtil.RESTRICTION_MSG))
154 + return;
155 + else {
156 + fail("unexpected exception " + e);
157 + }
158 + }
159 +
160 + fail("Should aNot yet implemented");
161 + }
162 +
163 +
164 +}
165 \ No newline at end of file
0 From: Markus Koschany <apo@debian.org>
1 Date: Mon, 31 Jan 2022 10:55:04 +0100
2 Subject: CVE-2022-23305
3
4 Origin: https://github.com/qos-ch/reload4j/pull/26/commits/33d1697bb13b8cf869c450a64f8550b1593f8035
5 ---
6 pom.xml | 6 +
7 .../java/org/apache/log4j/jdbc/JDBCAppender.java | 97 ++++++++--
8 .../org/apache/log4j/jdbc/JdbcPatternParser.java | 101 ++++++++++
9 src/test/input/jdbc_h2_bufferSize1.properties | 25 +++
10 src/test/input/jdbc_h2_bufferSize2.properties | 25 +++
11 .../org/apache/log4j/jdbc/JdbcAppenderTest.java | 208 +++++++++++++++++++++
12 .../apache/log4j/jdbc/JdbcPatternParserTest.java | 50 +++++
13 7 files changed, 495 insertions(+), 17 deletions(-)
14 create mode 100644 src/main/java/org/apache/log4j/jdbc/JdbcPatternParser.java
15 create mode 100644 src/test/input/jdbc_h2_bufferSize1.properties
16 create mode 100644 src/test/input/jdbc_h2_bufferSize2.properties
17 create mode 100644 src/test/java/org/apache/log4j/jdbc/JdbcAppenderTest.java
18 create mode 100644 src/test/java/org/apache/log4j/jdbc/JdbcPatternParserTest.java
19
20 diff --git a/pom.xml b/pom.xml
21 index 93881cd..3ec2ccc 100644
22 --- a/pom.xml
23 +++ b/pom.xml
24 @@ -577,6 +577,12 @@ target platform and specify -Dntdll_target=msbuild on the mvn command line.
25 <version>1.0</version>
26 <optional>true</optional>
27 </dependency>
28 + <dependency>
29 + <groupId>com.h2database</groupId>
30 + <artifactId>h2</artifactId>
31 + <version>2.1.210</version>
32 + <scope>test</scope>
33 + </dependency>
34 </dependencies>
35 <distributionManagement>
36 <repository>
37 diff --git a/src/main/java/org/apache/log4j/jdbc/JDBCAppender.java b/src/main/java/org/apache/log4j/jdbc/JDBCAppender.java
38 index ad35f65..2f979ae 100644
39 --- a/src/main/java/org/apache/log4j/jdbc/JDBCAppender.java
40 +++ b/src/main/java/org/apache/log4j/jdbc/JDBCAppender.java
41 @@ -18,6 +18,7 @@ package org.apache.log4j.jdbc;
42
43 import java.sql.Connection;
44 import java.sql.DriverManager;
45 +import java.sql.PreparedStatement;
46 import java.sql.SQLException;
47 import java.sql.Statement;
48 import java.util.ArrayList;
49 @@ -78,6 +79,12 @@ import org.apache.log4j.spi.LoggingEvent;
50 public class JDBCAppender extends org.apache.log4j.AppenderSkeleton
51 implements org.apache.log4j.Appender {
52
53 + private final Boolean secureSqlReplacement =
54 + Boolean.parseBoolean(System.getProperty("org.apache.log4j.jdbc.JDBCAppender.secure_jdbc_replacement", "true"));
55 +
56 + private static final IllegalArgumentException ILLEGAL_PATTERN_FOR_SECURE_EXEC =
57 + new IllegalArgumentException("Only org.apache.log4j.PatternLayout is supported for now due to CVE-2022-23305");
58 +
59 /**
60 * URL of the DB for default connection handling
61 */
62 @@ -113,6 +120,8 @@ public class JDBCAppender extends org.apache.log4j.AppenderSkeleton
63 */
64 protected String sqlStatement = "";
65
66 + private JdbcPatternParser preparedStatementParser = new JdbcPatternParser();
67 +
68 /**
69 * size of LoggingEvent buffer before writting to the database.
70 * Default is 1.
71 @@ -245,11 +254,11 @@ public class JDBCAppender extends org.apache.log4j.AppenderSkeleton
72 */
73 protected Connection getConnection() throws SQLException {
74 if (!DriverManager.getDrivers().hasMoreElements())
75 - setDriver("sun.jdbc.odbc.JdbcOdbcDriver");
76 + setDriver("sun.jdbc.odbc.JdbcOdbcDriver");
77
78 if (connection == null) {
79 connection = DriverManager.getConnection(databaseURL, databaseUser,
80 - databasePassword);
81 + databasePassword);
82 }
83
84 return connection;
85 @@ -280,29 +289,83 @@ public class JDBCAppender extends org.apache.log4j.AppenderSkeleton
86 * If a statement fails the LoggingEvent stays in the buffer!
87 */
88 public void flushBuffer() {
89 + if (buffer.isEmpty()) {
90 + return;
91 + }
92 //Do the actual logging
93 removes.ensureCapacity(buffer.size());
94 - for (Iterator i = buffer.iterator(); i.hasNext();) {
95 - LoggingEvent logEvent = (LoggingEvent)i.next();
96 - try {
97 - String sql = getLogStatement(logEvent);
98 - execute(sql);
99 - }
100 - catch (SQLException e) {
101 - errorHandler.error("Failed to excute sql", e,
102 - ErrorCode.FLUSH_FAILURE);
103 - } finally {
104 - removes.add(logEvent);
105 - }
106 + if (secureSqlReplacement) {
107 + flushBufferSecure();
108 + } else {
109 + flushBufferInsecure();
110 }
111 -
112 // remove from the buffer any events that were reported
113 buffer.removeAll(removes);
114 -
115 +
116 // clear the buffer of reported events
117 removes.clear();
118 }
119
120 + private void flushBufferInsecure() {
121 + for (Iterator i = buffer.iterator(); i.hasNext(); ) {
122 + LoggingEvent logEvent = (LoggingEvent) i.next();
123 + try {
124 + String sql = getLogStatement(logEvent);
125 + execute(sql);
126 + } catch (SQLException e) {
127 + errorHandler.error("Failed to excute sql", e, ErrorCode.FLUSH_FAILURE);
128 + } finally {
129 + removes.add(logEvent);
130 + }
131 + }
132 + }
133 +
134 + private void flushBufferSecure() {
135 + // Prepare events that we will store to the DB
136 + removes.addAll(buffer);
137 +
138 + if (layout.getClass() != PatternLayout.class) {
139 + errorHandler.error("Failed to convert pattern " + layout + " to SQL", ILLEGAL_PATTERN_FOR_SECURE_EXEC, ErrorCode.MISSING_LAYOUT);
140 + return;
141 + }
142 + PatternLayout patternLayout = (PatternLayout) layout;
143 + preparedStatementParser.setPattern(patternLayout.getConversionPattern());
144 + Connection con = null;
145 + boolean useBatch = removes.size() > 1;
146 + try {
147 + con = getConnection();
148 + PreparedStatement ps = con.prepareStatement(preparedStatementParser.getParameterizedSql());
149 + try {
150 + for (Iterator i = removes.iterator(); i.hasNext(); ) {
151 + LoggingEvent logEvent = (LoggingEvent) i.next();
152 + try {
153 + preparedStatementParser.setParameters(ps, logEvent);
154 + if (useBatch) {
155 + ps.addBatch();
156 + }
157 + } catch (SQLException e) {
158 + errorHandler.error("Failed to append parameters", e, ErrorCode.FLUSH_FAILURE);
159 + }
160 + }
161 + if (useBatch) {
162 + ps.executeBatch();
163 + } else {
164 + ps.execute();
165 + }
166 + } finally {
167 + try {
168 + ps.close();
169 + } catch (SQLException ignored) {
170 + }
171 + }
172 + } catch (SQLException e) {
173 + errorHandler.error("Failed to append messages sql", e, ErrorCode.FLUSH_FAILURE);
174 + } finally {
175 + if (con != null) {
176 + closeConnection(con);
177 + }
178 + }
179 + }
180
181 /** closes the appender before disposal */
182 public void finalize() {
183 @@ -391,7 +454,7 @@ public class JDBCAppender extends org.apache.log4j.AppenderSkeleton
184 Class.forName(driverClass);
185 } catch (Exception e) {
186 errorHandler.error("Failed to load driver", e,
187 - ErrorCode.GENERIC_FAILURE);
188 + ErrorCode.GENERIC_FAILURE);
189 }
190 }
191 }
192 diff --git a/src/main/java/org/apache/log4j/jdbc/JdbcPatternParser.java b/src/main/java/org/apache/log4j/jdbc/JdbcPatternParser.java
193 new file mode 100644
194 index 0000000..691ed56
195 --- /dev/null
196 +++ b/src/main/java/org/apache/log4j/jdbc/JdbcPatternParser.java
197 @@ -0,0 +1,101 @@
198 +/*
199 + * Licensed to the Apache Software Foundation (ASF) under one or more
200 + * contributor license agreements. See the NOTICE file distributed with
201 + * this work for additional information regarding copyright ownership.
202 + * The ASF licenses this file to You under the Apache License, Version 2.0
203 + * (the "License"); you may not use this file except in compliance with
204 + * the License. You may obtain a copy of the License at
205 + *
206 + * http://www.apache.org/licenses/LICENSE-2.0
207 + *
208 + * Unless required by applicable law or agreed to in writing, software
209 + * distributed under the License is distributed on an "AS IS" BASIS,
210 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
211 + * See the License for the specific language governing permissions and
212 + * limitations under the License.
213 + */
214 +package org.apache.log4j.jdbc;
215 +
216 +import org.apache.log4j.helpers.PatternConverter;
217 +import org.apache.log4j.helpers.PatternParser;
218 +import org.apache.log4j.spi.LoggingEvent;
219 +
220 +import java.sql.PreparedStatement;
221 +import java.sql.SQLException;
222 +import java.util.ArrayList;
223 +import java.util.List;
224 +import java.util.regex.Matcher;
225 +import java.util.regex.Pattern;
226 +
227 +class JdbcPatternParser {
228 + private final static Pattern STRING_LITERAL_PATTERN = Pattern.compile("'((?>[^']|'')+)'");
229 +
230 + private String lastPattern;
231 + private String parameterizedSql;
232 + private final List<String> argPatterns = new ArrayList<String>();
233 + private final List<PatternConverter> args = new ArrayList<PatternConverter>();
234 + private StringBuffer buffer = new StringBuffer();
235 +
236 + public String getParameterizedSql() {
237 + return parameterizedSql;
238 + }
239 +
240 + @Override
241 + public String toString() {
242 + return "JdbcPatternParser{sql=" + parameterizedSql + ",args=" + argPatterns + "}";
243 + }
244 +
245 + /**
246 + * Converts '....' literals into bind variables in JDBC.
247 + */
248 + void setPattern(String pattern) {
249 + if (pattern == null) {
250 + throw new IllegalArgumentException("Null pattern");
251 + }
252 + if (pattern.equals(lastPattern)) {
253 + return;
254 + }
255 + Matcher m = STRING_LITERAL_PATTERN.matcher(pattern);
256 + StringBuffer sb = new StringBuffer();
257 + args.clear();
258 + argPatterns.clear();
259 + while (m.find()) {
260 + String literal = m.group(1);
261 + if (literal.indexOf('%') == -1) {
262 + // Just literal, append it as is
263 + // It can't contain user-provided parts like %m, etc.
264 + m.appendReplacement(sb, "'$1'");
265 + continue;
266 + }
267 +
268 + // Replace with bind
269 + m.appendReplacement(sb, "?");
270 + // We will use prepared statements, so we don't need to escape quotes.
271 + // And we assume the users had 'That''s a string with quotes' in their configs.
272 + literal = literal.replaceAll("''", "'");
273 + argPatterns.add(literal);
274 + args.add(new PatternParser(literal).parse());
275 + }
276 + m.appendTail(sb);
277 + parameterizedSql = sb.toString();
278 + lastPattern = pattern;
279 + }
280 +
281 + public void setParameters(PreparedStatement ps, LoggingEvent logEvent) throws SQLException {
282 + for (int i = 0; i < args.size(); i++) {
283 + buffer.setLength(0);
284 + PatternConverter c = args.get(i);
285 + while (c != null) {
286 + c.format(buffer, logEvent);
287 + c = c.next;
288 + }
289 + ps.setString(i + 1, buffer.toString());
290 + }
291 + // This clears "toString cache"
292 + buffer.setLength(0);
293 + if (buffer.capacity() > 100000) {
294 + // Avoid leaking too much memory if we discover long parameter
295 + buffer = new StringBuffer();
296 + }
297 + }
298 +}
299 diff --git a/src/test/input/jdbc_h2_bufferSize1.properties b/src/test/input/jdbc_h2_bufferSize1.properties
300 new file mode 100644
301 index 0000000..77e6083
302 --- /dev/null
303 +++ b/src/test/input/jdbc_h2_bufferSize1.properties
304 @@ -0,0 +1,25 @@
305 +# Licensed to the Apache Software Foundation (ASF) under one or more
306 +# contributor license agreements. See the NOTICE file distributed with
307 +# this work for additional information regarding copyright ownership.
308 +# The ASF licenses this file to You under the Apache License, Version 2.0
309 +# (the "License"); you may not use this file except in compliance with
310 +# the License. You may obtain a copy of the License at
311 +#
312 +# http://www.apache.org/licenses/LICENSE-2.0
313 +#
314 +# Unless required by applicable law or agreed to in writing, software
315 +# distributed under the License is distributed on an "AS IS" BASIS,
316 +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
317 +# See the License for the specific language governing permissions and
318 +# limitations under the License.
319 +
320 +log4j.threshold=ALL
321 +log4j.rootLogger=ALL,A
322 +log4j.appender.A=org.apache.log4j.jdbc.JDBCAppender
323 +log4j.appender.A.URL=jdbc:h2:mem:test_db
324 +log4j.appender.A.driver=org.h2.Driver
325 +log4j.appender.A.bufferSize=1
326 +log4j.appender.A.user=
327 +log4j.appender.A.password=
328 +log4j.appender.A.layout=org.apache.log4j.PatternLayout
329 +log4j.appender.A.sql=insert into logs(level,location,message,message2) values('%p','%c','%m', ' %c %p %m')
330 diff --git a/src/test/input/jdbc_h2_bufferSize2.properties b/src/test/input/jdbc_h2_bufferSize2.properties
331 new file mode 100644
332 index 0000000..fba02a3
333 --- /dev/null
334 +++ b/src/test/input/jdbc_h2_bufferSize2.properties
335 @@ -0,0 +1,25 @@
336 +# Licensed to the Apache Software Foundation (ASF) under one or more
337 +# contributor license agreements. See the NOTICE file distributed with
338 +# this work for additional information regarding copyright ownership.
339 +# The ASF licenses this file to You under the Apache License, Version 2.0
340 +# (the "License"); you may not use this file except in compliance with
341 +# the License. You may obtain a copy of the License at
342 +#
343 +# http://www.apache.org/licenses/LICENSE-2.0
344 +#
345 +# Unless required by applicable law or agreed to in writing, software
346 +# distributed under the License is distributed on an "AS IS" BASIS,
347 +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
348 +# See the License for the specific language governing permissions and
349 +# limitations under the License.
350 +
351 +log4j.threshold=ALL
352 +log4j.rootLogger=ALL,A
353 +log4j.appender.A=org.apache.log4j.jdbc.JDBCAppender
354 +log4j.appender.A.URL=jdbc:h2:mem:test_db
355 +log4j.appender.A.driver=org.h2.Driver
356 +log4j.appender.A.bufferSize=2
357 +log4j.appender.A.user=
358 +log4j.appender.A.password=
359 +log4j.appender.A.layout=org.apache.log4j.PatternLayout
360 +log4j.appender.A.sql=insert into logs(level,location,message,message2) values('%p','%c','%m', ' %c %p %m')
361 diff --git a/src/test/java/org/apache/log4j/jdbc/JdbcAppenderTest.java b/src/test/java/org/apache/log4j/jdbc/JdbcAppenderTest.java
362 new file mode 100644
363 index 0000000..f851063
364 --- /dev/null
365 +++ b/src/test/java/org/apache/log4j/jdbc/JdbcAppenderTest.java
366 @@ -0,0 +1,208 @@
367 +/*
368 + * Licensed to the Apache Software Foundation (ASF) under one or more
369 + * contributor license agreements. See the NOTICE file distributed with
370 + * this work for additional information regarding copyright ownership.
371 + * The ASF licenses this file to You under the Apache License, Version 2.0
372 + * (the "License"); you may not use this file except in compliance with
373 + * the License. You may obtain a copy of the License at
374 + *
375 + * http://www.apache.org/licenses/LICENSE-2.0
376 + *
377 + * Unless required by applicable law or agreed to in writing, software
378 + * distributed under the License is distributed on an "AS IS" BASIS,
379 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
380 + * See the License for the specific language governing permissions and
381 + * limitations under the License.
382 + */
383 +package org.apache.log4j.jdbc;
384 +
385 +import org.apache.log4j.Appender;
386 +import org.apache.log4j.LogManager;
387 +import org.apache.log4j.Logger;
388 +import org.apache.log4j.PropertyConfigurator;
389 +import org.apache.log4j.TestContants;
390 +import org.apache.log4j.VectorErrorHandler;
391 +import org.apache.log4j.xml.XLevel;
392 +import org.junit.After;
393 +import org.junit.Assert;
394 +import org.junit.Before;
395 +import org.junit.Test;
396 +
397 +import java.sql.Connection;
398 +import java.sql.DriverManager;
399 +import java.sql.PreparedStatement;
400 +import java.sql.ResultSet;
401 +import java.sql.SQLException;
402 +import java.sql.Statement;
403 +import java.util.ArrayList;
404 +import java.util.Collections;
405 +import java.util.List;
406 +
407 +public class JdbcAppenderTest {
408 + // Closing the last connection to "in-memory h2" removes the database
409 + // So we keep the connection during the test
410 + // The logger opens its own connection
411 + Connection con;
412 +
413 + @Before
414 + public void setup() throws SQLException {
415 + con = DriverManager.getConnection("jdbc:h2:mem:test_db");
416 + Statement st = con.createStatement();
417 + try {
418 + st.execute("create table logs(level varchar(100),location varchar(100),message varchar(100),message2 varchar(100))");
419 + } finally {
420 + st.close();
421 + }
422 + }
423 +
424 + @After
425 + public void cleanup() throws SQLException {
426 + LogManager.shutdown();
427 + con.close();
428 + }
429 +
430 + @Test
431 + public void verifyJdbcInsecure() throws SQLException {
432 + String secureJdbcReplacement = "org.apache.log4j.jdbc.JDBCAppender.secure_jdbc_replacement";
433 + System.setProperty(secureJdbcReplacement, "false");
434 + try {
435 + PropertyConfigurator.configure(TestContants.TEST_INPUT_PREFIX + "jdbc_h2_bufferSize1.properties");
436 + Appender jdbcAppender = LogManager.getRootLogger().getAppender("A");
437 + // Keep errors
438 + VectorErrorHandler errorHandler = new VectorErrorHandler();
439 + jdbcAppender.setErrorHandler(errorHandler);
440 +
441 + Logger logger = Logger.getLogger(JdbcAppenderTest.class);
442 +
443 + String oldThreadName = Thread.currentThread().getName();
444 + try {
445 + Thread.currentThread().setName("main");
446 + logger.debug("message with '' quote");
447 + Assert.assertEquals(
448 + "batchSize=1, so messages should be added immediately",
449 + "DEBUG; org.apache.log4j.jdbc.JdbcAppenderTest; message with ' quote; org.apache.log4j.jdbc.JdbcAppenderTest DEBUG message with ' quote\n",
450 + joinSorted(getMessages()));
451 +
452 + // It should fail
453 + logger.fatal("message with ' quote");
454 +
455 + Assert.assertEquals(
456 + "Inserting a message with ' should cause failure in insecure mode, so only one message should be in the DB",
457 + "DEBUG; org.apache.log4j.jdbc.JdbcAppenderTest; message with ' quote; org.apache.log4j.jdbc.JdbcAppenderTest DEBUG message with ' quote\n",
458 + joinSorted(getMessages()));
459 +
460 + StringBuilder exceptions = new StringBuilder();
461 + StringBuilder errorCodes = new StringBuilder();
462 + for (int i = 0; i < errorHandler.size(); i++) {
463 + Exception ex = errorHandler.getException(i);
464 + exceptions.append(ex.toString());
465 + errorCodes.append(
466 + ex instanceof SQLException
467 + ? ("SQLException.getErrorCode() = " + ((SQLException) ex).getErrorCode())
468 + : ("SQL Exception expected, got " + ex.getClass()));
469 + exceptions.append('\n');
470 + errorCodes.append('\n');
471 + }
472 + Assert.assertEquals(
473 + "Logging a message with ' should trigger SQLException 'Syntax error in SQL statement...' when using insecure JDBCAppender mode, actual exceptions are\n" + exceptions,
474 + "SQLException.getErrorCode() = 42001\n",
475 + errorCodes.toString()
476 + );
477 + } finally {
478 + Thread.currentThread().setName(oldThreadName);
479 + }
480 + } finally {
481 + System.getProperties().remove(secureJdbcReplacement);
482 + }
483 + }
484 +
485 + @Test
486 + public void verifyJdbcBufferSize1() throws SQLException {
487 + PropertyConfigurator.configure(TestContants.TEST_INPUT_PREFIX + "jdbc_h2_bufferSize1.properties");
488 +
489 + Logger logger = Logger.getLogger(JdbcAppenderTest.class);
490 +
491 + String oldThreadName = Thread.currentThread().getName();
492 + try {
493 + Thread.currentThread().setName("main");
494 + logger.debug("message with ' quote");
495 + Assert.assertEquals(
496 + "batchSize=1, so messages should be added immediately",
497 + "DEBUG; org.apache.log4j.jdbc.JdbcAppenderTest; message with ' quote; org.apache.log4j.jdbc.JdbcAppenderTest DEBUG message with ' quote\n",
498 + joinSorted(getMessages()));
499 +
500 + logger.fatal("message with \" quote");
501 +
502 + Assert.assertEquals(
503 + "batchSize=1, so two messages should be in DB after two logging calls",
504 + "DEBUG; org.apache.log4j.jdbc.JdbcAppenderTest; message with ' quote; org.apache.log4j.jdbc.JdbcAppenderTest DEBUG message with ' quote\n" +
505 + "FATAL; org.apache.log4j.jdbc.JdbcAppenderTest; message with \" quote; org.apache.log4j.jdbc.JdbcAppenderTest FATAL message with \" quote\n",
506 + joinSorted(getMessages()));
507 + } finally {
508 + Thread.currentThread().setName(oldThreadName);
509 + }
510 + }
511 +
512 + @Test
513 + public void verifyJdbcBufferSize2() throws SQLException {
514 + PropertyConfigurator.configure(TestContants.TEST_INPUT_PREFIX + "jdbc_h2_bufferSize2.properties");
515 +
516 + Logger logger = Logger.getLogger(JdbcAppenderTest.class);
517 +
518 + String oldThreadName = Thread.currentThread().getName();
519 + try {
520 + Thread.currentThread().setName("main");
521 + logger.log(XLevel.TRACE, "xtrace message");
522 + logger.debug("message with ' quote");
523 + logger.info("message with \" quote");
524 + logger.warn("?");
525 + // bufferSize=2, so this message won't yet be stored to the db
526 + logger.error("m4");
527 +
528 + Assert.assertEquals(
529 + "DEBUG; org.apache.log4j.jdbc.JdbcAppenderTest; message with ' quote; org.apache.log4j.jdbc.JdbcAppenderTest DEBUG message with ' quote\n" +
530 + "INFO; org.apache.log4j.jdbc.JdbcAppenderTest; message with \" quote; org.apache.log4j.jdbc.JdbcAppenderTest INFO message with \" quote\n" +
531 + "TRACE; org.apache.log4j.jdbc.JdbcAppenderTest; xtrace message; org.apache.log4j.jdbc.JdbcAppenderTest TRACE xtrace message\n" +
532 + "WARN; org.apache.log4j.jdbc.JdbcAppenderTest; ?; org.apache.log4j.jdbc.JdbcAppenderTest WARN ?\n",
533 + joinSorted(getMessages()));
534 +
535 + logger.fatal("m5");
536 +
537 + Assert.assertEquals(
538 + "Logging m5 message should trigger buffer flush for both m4 and m5",
539 + "DEBUG; org.apache.log4j.jdbc.JdbcAppenderTest; message with ' quote; org.apache.log4j.jdbc.JdbcAppenderTest DEBUG message with ' quote\n" +
540 + "ERROR; org.apache.log4j.jdbc.JdbcAppenderTest; m4; org.apache.log4j.jdbc.JdbcAppenderTest ERROR m4\n" +
541 + "FATAL; org.apache.log4j.jdbc.JdbcAppenderTest; m5; org.apache.log4j.jdbc.JdbcAppenderTest FATAL m5\n" +
542 + "INFO; org.apache.log4j.jdbc.JdbcAppenderTest; message with \" quote; org.apache.log4j.jdbc.JdbcAppenderTest INFO message with \" quote\n" +
543 + "TRACE; org.apache.log4j.jdbc.JdbcAppenderTest; xtrace message; org.apache.log4j.jdbc.JdbcAppenderTest TRACE xtrace message\n" +
544 + "WARN; org.apache.log4j.jdbc.JdbcAppenderTest; ?; org.apache.log4j.jdbc.JdbcAppenderTest WARN ?\n",
545 + joinSorted(getMessages()));
546 + } finally {
547 + Thread.currentThread().setName(oldThreadName);
548 + }
549 + }
550 +
551 + private static String joinSorted(List<String> list) {
552 + Collections.sort(list);
553 + StringBuilder sb = new StringBuilder();
554 + for (String s : list) {
555 + sb.append(s).append('\n');
556 + }
557 + return sb.toString();
558 + }
559 +
560 + private List<String> getMessages() throws SQLException {
561 + List<String> res = new ArrayList<String>();
562 + PreparedStatement ps = con.prepareStatement("select level,location,message,message2 from logs");
563 + ResultSet rs = ps.executeQuery();
564 + try {
565 + while (rs.next()) {
566 + res.add(rs.getString(1) + "; " + rs.getString(2)
567 + + "; " + rs.getString(3) + "; " + rs.getString(4));
568 + }
569 + } finally {
570 + rs.close();
571 + }
572 + return res;
573 + }
574 +}
575 diff --git a/src/test/java/org/apache/log4j/jdbc/JdbcPatternParserTest.java b/src/test/java/org/apache/log4j/jdbc/JdbcPatternParserTest.java
576 new file mode 100644
577 index 0000000..aafbd80
578 --- /dev/null
579 +++ b/src/test/java/org/apache/log4j/jdbc/JdbcPatternParserTest.java
580 @@ -0,0 +1,50 @@
581 +/*
582 + * Licensed to the Apache Software Foundation (ASF) under one or more
583 + * contributor license agreements. See the NOTICE file distributed with
584 + * this work for additional information regarding copyright ownership.
585 + * The ASF licenses this file to You under the Apache License, Version 2.0
586 + * (the "License"); you may not use this file except in compliance with
587 + * the License. You may obtain a copy of the License at
588 + *
589 + * http://www.apache.org/licenses/LICENSE-2.0
590 + *
591 + * Unless required by applicable law or agreed to in writing, software
592 + * distributed under the License is distributed on an "AS IS" BASIS,
593 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
594 + * See the License for the specific language governing permissions and
595 + * limitations under the License.
596 + */
597 +package org.apache.log4j.jdbc;
598 +
599 +import org.junit.Assert;
600 +import org.junit.Test;
601 +
602 +public class JdbcPatternParserTest {
603 + JdbcPatternParser parser = new JdbcPatternParser();
604 +
605 + @Test
606 + public void testParameterizedSql() {
607 + assertParameterizedSql(
608 + "JdbcPatternParser{sql=INSERT INTO A1 (TITLE3) VALUES ( ? ),args=[ %d - %c %-5p %c %x - %m%n ]}",
609 + "INSERT INTO A1 (TITLE3) VALUES ( ' %d - %c %-5p %c %x - %m%n ' )"
610 + );
611 + assertParameterizedSql(
612 + "JdbcPatternParser{sql=INSERT INTO A1 (TITLE3) VALUES ( ?, ?, ?, ?, ?, ? ),args=[%d, %c, %-5p, '%c, %x, - %m%n ]}",
613 + "INSERT INTO A1 (TITLE3) VALUES ( '%d', '%c', '%-5p', ' ''%c', '%x', ' - %m%n ' )"
614 + );
615 +
616 + assertParameterizedSql(
617 + "JdbcPatternParser{sql=INSERT INTO A1 (TITLE3) VALUES ( ' just string literal', 'another literal with quotes '' asdf', ?),args=[message: %m]}",
618 + "INSERT INTO A1 (TITLE3) VALUES ( ' just string literal', 'another literal with quotes '' asdf', 'message: %m')"
619 + );
620 + }
621 +
622 + private void assertParameterizedSql(String expected, String input) {
623 + parser.setPattern(input);
624 + Assert.assertEquals(
625 + "parser.setPattern(...).toString() for " + input,
626 + expected,
627 + parser.toString()
628 + );
629 + }
630 +}
00 build_fix.patch
1
21 remove-activation-framework-dependency.patch
32 add-missing-classes.patch
43 CVE-2019-17571.patch
54 disable-examples.patch
5 CVE-2022-23305.patch
6 CVE-2021-4104.patch