diff --git a/CVE-2024-21742-pre.patch b/CVE-2024-21742-pre.patch new file mode 100644 index 0000000000000000000000000000000000000000..c0b3d1bbd9a537c08b84f28fb0498b7c90c9a82a --- /dev/null +++ b/CVE-2024-21742-pre.patch @@ -0,0 +1,95 @@ +From 9d0a79dd2382440a2c2c1ceee660eb996643f972 Mon Sep 17 00:00:00 2001 +From: benwa +Date: Fri, 9 Mar 2018 14:29:07 +0700 +Subject: [PATCH] MIME4J-268 DefaultMessageWriter should expose a convenient + *asBytes* method + +--- + .../mime4j/message/DefaultMessageWriter.java | 8 +++ + .../message/DefaultMessageWriterTest.java | 50 +++++++++++++++++++ + 2 files changed, 58 insertions(+) + create mode 100644 dom/src/test/java/org/apache/james/mime4j/message/DefaultMessageWriterTest.java + +diff --git a/dom/src/main/java/org/apache/james/mime4j/message/DefaultMessageWriter.java b/dom/src/main/java/org/apache/james/mime4j/message/DefaultMessageWriter.java +index 34890a72..4ce7f7b9 100644 +--- a/dom/src/main/java/org/apache/james/mime4j/message/DefaultMessageWriter.java ++++ b/dom/src/main/java/org/apache/james/mime4j/message/DefaultMessageWriter.java +@@ -19,6 +19,7 @@ + + package org.apache.james.mime4j.message; + ++import java.io.ByteArrayOutputStream; + import java.io.IOException; + import java.io.OutputStream; + +@@ -48,6 +49,13 @@ public class DefaultMessageWriter implements MessageWriter { + private static final byte[] CRLF = { '\r', '\n' }; + private static final byte[] DASHES = { '-', '-' }; + ++ public static byte[] asBytes(Message message) throws IOException { ++ ByteArrayOutputStream buffer = new ByteArrayOutputStream(); ++ DefaultMessageWriter writer = new DefaultMessageWriter(); ++ writer.writeMessage(message, buffer); ++ return buffer.toByteArray(); ++ } ++ + /** + * Protected constructor prevents direct instantiation. + */ +diff --git a/dom/src/test/java/org/apache/james/mime4j/message/DefaultMessageWriterTest.java b/dom/src/test/java/org/apache/james/mime4j/message/DefaultMessageWriterTest.java +new file mode 100644 +index 00000000..dece2b50 +--- /dev/null ++++ b/dom/src/test/java/org/apache/james/mime4j/message/DefaultMessageWriterTest.java +@@ -0,0 +1,50 @@ ++/**************************************************************** ++ * 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.james.mime4j.message; ++ ++import static org.assertj.core.api.Assertions.assertThat; ++ ++import org.apache.james.mime4j.Charsets; ++import org.apache.james.mime4j.dom.Message; ++import org.junit.Test; ++ ++public class DefaultMessageWriterTest { ++ ++ @Test ++ public void asBytesShouldSerializeTheMessage() throws Exception { ++ byte[] bytes = DefaultMessageWriter.asBytes( ++ Message.Builder.of() ++ .setBody("this is the body", Charsets.UTF_8) ++ .setFrom("sender@localhost") ++ .setTo("receiver@localhost") ++ .setSubject("Cool subject") ++ .build()); ++ ++ assertThat(new String(bytes, Charsets.UTF_8.name())) ++ .isEqualTo("MIME-Version: 1.0\r\n" + ++ "Content-Type: text/plain; charset=UTF-8\r\n" + ++ "From: sender@localhost\r\n" + ++ "To: receiver@localhost\r\n" + ++ "Subject: Cool subject\r\n" + ++ "\r\n" + ++ "this is the body"); ++ } ++ ++} +\ No newline at end of file diff --git a/CVE-2024-21742.patch b/CVE-2024-21742.patch new file mode 100644 index 0000000000000000000000000000000000000000..92cce6ed14c28314e3f334515c02b7e3cca05ab6 --- /dev/null +++ b/CVE-2024-21742.patch @@ -0,0 +1,159 @@ +From 9dec5df2a588fed8027839815daefa79ee66efd1 Mon Sep 17 00:00:00 2001 +From: Benoit TELLIER +Date: Fri, 5 Jan 2024 08:12:54 +0100 +Subject: [PATCH] [FIX] Prevent header injection with MIME4J DOM (#91) + +--- + core/pom.xml | 5 +++ + .../apache/james/mime4j/stream/RawField.java | 23 +++++++++++ + .../james/mime4j/stream/RawFieldTest.java | 40 +++++++++++++++++-- + .../message/DefaultMessageWriterTest.java | 11 +++++ + 4 files changed, 76 insertions(+), 3 deletions(-) + +diff --git a/core/pom.xml b/core/pom.xml +index 942e3e3..412ad67 100644 +--- a/core/pom.xml ++++ b/core/pom.xml +@@ -42,6 +42,11 @@ + commons-io + test + ++ ++ org.assertj ++ assertj-core ++ test ++ + + + +diff --git a/core/src/main/java/org/apache/james/mime4j/stream/RawField.java b/core/src/main/java/org/apache/james/mime4j/stream/RawField.java +index 8bcaa77..03a00dd 100644 +--- a/core/src/main/java/org/apache/james/mime4j/stream/RawField.java ++++ b/core/src/main/java/org/apache/james/mime4j/stream/RawField.java +@@ -55,6 +55,29 @@ public final class RawField implements Field { + + public RawField(String name, String body) { + this(null, -1, name, body); ++ ++ int pos = 0; ++ ++ while (true) { ++ pos = body.indexOf('\r', pos); ++ if (pos < 0) { ++ break; ++ } ++ if (pos < body.length() + 2) { ++ if (body.charAt(pos + 1) != '\n') { ++ throw new IllegalArgumentException("Injection of un-encoded line breaks inside header field could be assimilated to header injection"); ++ } ++ if (pos != body.length() - 2 && !isSpace(body, pos + 2)) { ++ throw new IllegalArgumentException("Injection of un-encoded line breaks inside header field could be assimilated to header injection"); ++ } ++ } ++ pos ++; ++ } ++ } ++ ++ private static boolean isSpace(String body, int pos) { ++ return body.charAt(pos) == ' ' ++ || body.charAt(pos) == '\t'; + } + + public ByteSequence getRaw() { +diff --git a/core/src/test/java/org/apache/james/mime4j/stream/RawFieldTest.java b/core/src/test/java/org/apache/james/mime4j/stream/RawFieldTest.java +index 5a1cc7d..90d8513 100644 +--- a/core/src/test/java/org/apache/james/mime4j/stream/RawFieldTest.java ++++ b/core/src/test/java/org/apache/james/mime4j/stream/RawFieldTest.java +@@ -19,6 +19,9 @@ + + package org.apache.james.mime4j.stream; + ++import static org.assertj.core.api.AssertionsForClassTypes.assertThatCode; ++import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy; ++ + import junit.framework.Assert; + import org.apache.james.mime4j.util.ByteSequence; + import org.apache.james.mime4j.util.ContentUtil; +@@ -45,11 +48,11 @@ public class RawFieldTest { + Assert.assertEquals("stuff", field1.getBody()); + Assert.assertEquals("raw: stuff", field1.toString()); + +- RawField field2 = new RawField("raw", null); ++ RawField field2 = new RawField("raw", "any"); + Assert.assertNull(field2.getRaw()); + Assert.assertEquals("raw", field2.getName()); +- Assert.assertEquals(null, field2.getBody()); +- Assert.assertEquals("raw: ", field2.toString()); ++ Assert.assertEquals("any", field2.getBody()); ++ Assert.assertEquals("raw: any", field2.toString()); + } + + @Test +@@ -63,4 +66,35 @@ public class RawFieldTest { + Assert.assertEquals(s, field.toString()); + } + ++ @Test ++ public void shouldRejectAmbiguousLineEnding() { ++ assertThatThrownBy(() -> new RawField("Name", "Value\r\ncheating")).isInstanceOf(IllegalArgumentException.class); ++ } ++ ++ @Test ++ public void shouldAcceptCRLFTerminatedHeader() { ++ assertThatCode(() -> new RawField("Name", "Value\r\n")).doesNotThrowAnyException(); ++ } ++ ++ @Test ++ public void shouldAcceptTabFolding() { ++ assertThatCode(() -> new RawField("Name", "Value\r\n\thello")).doesNotThrowAnyException(); ++ } ++ ++ @Test ++ public void shouldAcceptSpaceFolding() { ++ assertThatCode(() -> new RawField("Name", "Value\r\n hello")).doesNotThrowAnyException(); ++ } ++ ++ @Test ++ public void shouldAcceptOnlyDelimiter() { ++ assertThatCode(() -> new RawField("Name", "\r\n")).doesNotThrowAnyException(); ++ } ++ ++ ++ @Test ++ public void shouldAcceptNoDelimiter() { ++ assertThatCode(() -> new RawField("Name", "Value")).doesNotThrowAnyException(); ++ } ++ + } +diff --git a/dom/src/test/java/org/apache/james/mime4j/message/DefaultMessageWriterTest.java b/dom/src/test/java/org/apache/james/mime4j/message/DefaultMessageWriterTest.java +index dece2b5..19eafe2 100644 +--- a/dom/src/test/java/org/apache/james/mime4j/message/DefaultMessageWriterTest.java ++++ b/dom/src/test/java/org/apache/james/mime4j/message/DefaultMessageWriterTest.java +@@ -20,6 +20,7 @@ + package org.apache.james.mime4j.message; + + import static org.assertj.core.api.Assertions.assertThat; ++import static org.assertj.core.api.Assertions.assertThatThrownBy; + + import org.apache.james.mime4j.Charsets; + import org.apache.james.mime4j.dom.Message; +@@ -46,5 +47,15 @@ public class DefaultMessageWriterTest { + "\r\n" + + "this is the body"); + } ++ ++ @Test ++ public void shouldThrowOnHeaderInjectionAttempt() throws Exception { ++ Message.Builder builder = Message.Builder.of() ++ .setBody("this is the body", Charsets.UTF_8) ++ .setFrom("sender@localhost"); ++ ++ assertThatThrownBy(() -> builder.setContentTransferEncoding("victim@attacker.com\r\nReply-To: attacker@evil.com")) ++ .isInstanceOf(IllegalArgumentException.class); ++ } + + } +\ No newline at end of file +-- +2.33.0 + diff --git a/apache-mime4j.spec b/apache-mime4j.spec index e3b06db3ed0858d968361ee0881650c9eaccc99e..5adf12a5118062ebee96df1d899b629db9a5826c 100644 --- a/apache-mime4j.spec +++ b/apache-mime4j.spec @@ -1,10 +1,14 @@ Name: apache-mime4j Version: 0.8.1 -Release: 1 +Release: 2 Summary: Apache JAMES Mime4j License: ASL 2.0 URL: http://james.apache.org/mime4j Source0: http://archive.apache.org/dist/james/mime4j/0.8.1/james-mime4j-sources-0.8.1.zip +# https://github.com/apache/james-mime4j/commit/9d0a79dd2382440a2c2c1ceee660eb996643f972 +Patch0: CVE-2024-21742-pre.patch +# https://github.com/apache/james-mime4j/commit/9dec5df2a588fed8027839815daefa79ee66efd1 +Patch1: CVE-2024-21742.patch BuildRequires: maven-local mvn(com.google.guava:guava:18.0) mvn(commons-io:commons-io) BuildRequires: mvn(commons-logging:commons-logging) mvn(junit:junit) BuildRequires: mvn(org.apache:apache:pom:) mvn(org.apache.felix:maven-bundle-plugin) @@ -20,7 +24,7 @@ Summary: Javadoc for %{name} API documentation for %{name}. %prep -%setup -q -n james-mime4j +%autosetup -n james-mime4j -p1 %pom_remove_plugin :apache-rat-plugin %pom_remove_plugin :maven-jar-plugin %pom_disable_module assemble @@ -29,7 +33,7 @@ for p in core dom storage; do done %build -%mvn_build +%mvn_build -f -- -Dsource=8 %install %mvn_install @@ -42,5 +46,8 @@ done %license LICENSE NOTICE %changelog +* Thu Feb 29 2024 yaoxin - 0.8.1-2 +- Fix CVE-2024-21742 + * Thu Aug 13 2020 chengzihan - 0.8.1-1 - Package init