Package: release.debian.org Severity: normal Tags: buster User: release.debian....@packages.debian.org Usertags: pu X-Debbugs-Cc: a...@debian.org
[ Reason ] Fixing CVE-2021-29425 in Buster which has been marked no-dsa by the security team. [ Impact ] Buster would still be vulnerable to CVE-2021-29425. [ Tests ] I have manually tested the code and it works. [ Checklist ] [x] *all* changes are documented in the d/changelog [x] I reviewed all changes and I approve them [x] attach debdiff against the package in (old)stable [x] the issue is verified as fixed in unstable Regards, Markus
diff -Nru commons-io-2.6/debian/changelog commons-io-2.6/debian/changelog --- commons-io-2.6/debian/changelog 2018-02-05 14:41:54.000000000 +0100 +++ commons-io-2.6/debian/changelog 2021-08-20 22:25:28.000000000 +0200 @@ -1,3 +1,15 @@ +commons-io (2.6-2+deb10u1) buster; urgency=medium + + * Team upload. + * Fix CVE-2021-29425: + When invoking the method FileNameUtils.normalize with an improper input + string, like "//../foo", or "\\..\foo", the result would be the same + value, thus possibly providing access to files in the parent directory, + but not further above (thus "limited" path traversal), if the calling code + would use the result to construct a path value. + + -- Markus Koschany <a...@debian.org> Fri, 20 Aug 2021 22:25:28 +0200 + commons-io (2.6-2) unstable; urgency=medium * Team upload. diff -Nru commons-io-2.6/debian/patches/CVE-2021-29425.patch commons-io-2.6/debian/patches/CVE-2021-29425.patch --- commons-io-2.6/debian/patches/CVE-2021-29425.patch 1970-01-01 01:00:00.000000000 +0100 +++ commons-io-2.6/debian/patches/CVE-2021-29425.patch 2021-08-20 22:25:28.000000000 +0200 @@ -0,0 +1,245 @@ +From: Markus Koschany <a...@debian.org> +Date: Mon, 2 Aug 2021 12:30:01 +0200 +Subject: CVE-2021-29425 + +Origin: https://github.com/apache/commons-io/pull/52 +--- + .../java/org/apache/commons/io/FilenameUtils.java | 156 ++++++++++++++++++++- + .../apache/commons/io/FilenameUtilsTestCase.java | 36 +++++ + 2 files changed, 191 insertions(+), 1 deletion(-) + +diff --git a/src/main/java/org/apache/commons/io/FilenameUtils.java b/src/main/java/org/apache/commons/io/FilenameUtils.java +index 9cddebb..f3bd5a6 100644 +--- a/src/main/java/org/apache/commons/io/FilenameUtils.java ++++ b/src/main/java/org/apache/commons/io/FilenameUtils.java +@@ -19,8 +19,12 @@ package org.apache.commons.io; + import java.io.File; + import java.io.IOException; + import java.util.ArrayList; ++import java.util.Arrays; + import java.util.Collection; ++import java.util.List; + import java.util.Stack; ++import java.util.regex.Matcher; ++import java.util.regex.Pattern; + + /** + * General filename and filepath manipulation utilities. +@@ -679,7 +683,9 @@ public class FilenameUtils { + } + posUnix = posUnix == NOT_FOUND ? posWin : posUnix; + posWin = posWin == NOT_FOUND ? posUnix : posWin; +- return Math.min(posUnix, posWin) + 1; ++ int pos = Math.min(posUnix, posWin) + 1; ++ String hostnamePart = filename.substring(2, pos - 1); ++ return isValidHostName(hostnamePart) ? pos : NOT_FOUND; + } else { + return isSeparator(ch0) ? 1 : 0; + } +@@ -1450,4 +1456,152 @@ public class FilenameUtils { + return list.toArray( new String[ list.size() ] ); + } + ++ /** ++ * Checks whether a given string is a valid host name according to ++ * RFC 3986. ++ * ++ * <p>Accepted are IP addresses (v4 and v6) as well as what the ++ * RFC calls a "reg-name". Percent encoded names don't seem to be ++ * valid names in UNC paths.</p> ++ * ++ * @see "https://tools.ietf.org/html/rfc3986#section-3.2.2" ++ * @param name the hostname to validate ++ * @return true if the given name is a valid host name ++ */ ++ private static boolean isValidHostName(String name) { ++ return isIPv6Address(name) || isRFC3986HostName(name); ++ } ++ ++ private static final Pattern IPV4_PATTERN = ++ Pattern.compile("^(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})$"); ++ private static final int IPV4_MAX_OCTET_VALUE = 255; ++ ++ /** ++ * Checks whether a given string represents a valid IPv4 address. ++ * ++ * @param name the name to validate ++ * @return true if the given name is a valid IPv4 address ++ */ ++ // mostly copied from org.apache.commons.validator.routines.InetAddressValidator#isValidInet4Address ++ private static boolean isIPv4Address(String name) { ++ Matcher m = IPV4_PATTERN.matcher(name); ++ if (!m.matches() || m.groupCount() != 4) { ++ return false; ++ } ++ ++ // verify that address subgroups are legal ++ for (int i = 1; i <= 4; i++) { ++ String ipSegment = m.group(i); ++ int iIpSegment = Integer.parseInt(ipSegment); ++ if (iIpSegment > IPV4_MAX_OCTET_VALUE) { ++ return false; ++ } ++ ++ if (ipSegment.length() > 1 && ipSegment.startsWith("0")) { ++ return false; ++ } ++ ++ } ++ ++ return true; ++ } ++ ++ private static final int IPV6_MAX_HEX_GROUPS = 8; ++ private static final int IPV6_MAX_HEX_DIGITS_PER_GROUP = 4; ++ private static final int MAX_UNSIGNED_SHORT = 0xffff; ++ private static final int BASE_16 = 16; ++ ++ // copied from org.apache.commons.validator.routines.InetAddressValidator#isValidInet6Address ++ /** ++ * Checks whether a given string represents a valid IPv6 address. ++ * ++ * @param inet6Address the name to validate ++ * @return true if the given name is a valid IPv6 address ++ */ ++ private static boolean isIPv6Address(String inet6Address) { ++ boolean containsCompressedZeroes = inet6Address.contains("::"); ++ if (containsCompressedZeroes && (inet6Address.indexOf("::") != inet6Address.lastIndexOf("::"))) { ++ return false; ++ } ++ if ((inet6Address.startsWith(":") && !inet6Address.startsWith("::")) ++ || (inet6Address.endsWith(":") && !inet6Address.endsWith("::"))) { ++ return false; ++ } ++ String[] octets = inet6Address.split(":"); ++ if (containsCompressedZeroes) { ++ List<String> octetList = new ArrayList<String>(Arrays.asList(octets)); ++ if (inet6Address.endsWith("::")) { ++ // String.split() drops ending empty segments ++ octetList.add(""); ++ } else if (inet6Address.startsWith("::") && !octetList.isEmpty()) { ++ octetList.remove(0); ++ } ++ octets = octetList.toArray(new String[octetList.size()]); ++ } ++ if (octets.length > IPV6_MAX_HEX_GROUPS) { ++ return false; ++ } ++ int validOctets = 0; ++ int emptyOctets = 0; // consecutive empty chunks ++ for (int index = 0; index < octets.length; index++) { ++ String octet = octets[index]; ++ if (octet.length() == 0) { ++ emptyOctets++; ++ if (emptyOctets > 1) { ++ return false; ++ } ++ } else { ++ emptyOctets = 0; ++ // Is last chunk an IPv4 address? ++ if (index == octets.length - 1 && octet.contains(".")) { ++ if (!isIPv4Address(octet)) { ++ return false; ++ } ++ validOctets += 2; ++ continue; ++ } ++ if (octet.length() > IPV6_MAX_HEX_DIGITS_PER_GROUP) { ++ return false; ++ } ++ int octetInt = 0; ++ try { ++ octetInt = Integer.parseInt(octet, BASE_16); ++ } catch (NumberFormatException e) { ++ return false; ++ } ++ if (octetInt < 0 || octetInt > MAX_UNSIGNED_SHORT) { ++ return false; ++ } ++ } ++ validOctets++; ++ } ++ if (validOctets > IPV6_MAX_HEX_GROUPS || (validOctets < IPV6_MAX_HEX_GROUPS && !containsCompressedZeroes)) { ++ return false; ++ } ++ return true; ++ } ++ ++ private static final Pattern REG_NAME_PART_PATTERN = Pattern.compile("^[a-zA-Z0-9][a-zA-Z0-9-]*$"); ++ ++ /** ++ * Checks whether a given string is a valid host name according to ++ * RFC 3986 - not accepting IP addresses. ++ * ++ * @see "https://tools.ietf.org/html/rfc3986#section-3.2.2" ++ * @param name the hostname to validate ++ * @return true if the given name is a valid host name ++ */ ++ private static boolean isRFC3986HostName(String name) { ++ String[] parts = name.split("\\.", -1); ++ for (int i = 0; i < parts.length; i++) { ++ if (parts[i].length() == 0) { ++ // trailing dot is legal, otherwise we've hit a .. sequence ++ return i == parts.length - 1; ++ } ++ if (!REG_NAME_PART_PATTERN.matcher(parts[i]).matches()) { ++ return false; ++ } ++ } ++ return true; ++ } + } +diff --git a/src/test/java/org/apache/commons/io/FilenameUtilsTestCase.java b/src/test/java/org/apache/commons/io/FilenameUtilsTestCase.java +index 234c25e..a8ede91 100644 +--- a/src/test/java/org/apache/commons/io/FilenameUtilsTestCase.java ++++ b/src/test/java/org/apache/commons/io/FilenameUtilsTestCase.java +@@ -244,6 +244,33 @@ public class FilenameUtilsTestCase { + assertEquals(null, FilenameUtils.normalize("//server/../a")); + assertEquals(null, FilenameUtils.normalize("//server/..")); + assertEquals(SEP + SEP + "server" + SEP + "", FilenameUtils.normalize("//server/")); ++ ++ assertEquals(SEP + SEP + "127.0.0.1" + SEP + "a" + SEP + "b" + SEP + "c.txt", FilenameUtils.normalize("\\\\127.0.0.1\\a\\b\\c.txt")); ++ assertEquals(SEP + SEP + "::1" + SEP + "a" + SEP + "b" + SEP + "c.txt", FilenameUtils.normalize("\\\\::1\\a\\b\\c.txt")); ++ assertEquals(SEP + SEP + "1::" + SEP + "a" + SEP + "b" + SEP + "c.txt", FilenameUtils.normalize("\\\\1::\\a\\b\\c.txt")); ++ assertEquals(SEP + SEP + "server.example.org" + SEP + "a" + SEP + "b" + SEP + "c.txt", FilenameUtils.normalize("\\\\server.example.org\\a\\b\\c.txt")); ++ assertEquals(SEP + SEP + "server.sub.example.org" + SEP + "a" + SEP + "b" + SEP + "c.txt", FilenameUtils.normalize("\\\\server.sub.example.org\\a\\b\\c.txt")); ++ assertEquals(SEP + SEP + "server." + SEP + "a" + SEP + "b" + SEP + "c.txt", FilenameUtils.normalize("\\\\server.\\a\\b\\c.txt")); ++ assertEquals(SEP + SEP + "1::127.0.0.1" + SEP + "a" + SEP + "b" + SEP + "c.txt", ++ FilenameUtils.normalize("\\\\1::127.0.0.1\\a\\b\\c.txt")); ++ ++ // not valid IPv4 addresses but technically a valid "reg-name"s according to RFC1034 ++ assertEquals(SEP + SEP + "127.0.0.256" + SEP + "a" + SEP + "b" + SEP + "c.txt", ++ FilenameUtils.normalize("\\\\127.0.0.256\\a\\b\\c.txt")); ++ assertEquals(SEP + SEP + "127.0.0.01" + SEP + "a" + SEP + "b" + SEP + "c.txt", ++ FilenameUtils.normalize("\\\\127.0.0.01\\a\\b\\c.txt")); ++ ++ assertEquals(null, FilenameUtils.normalize("\\\\-server\\a\\b\\c.txt")); ++ assertEquals(null, FilenameUtils.normalize("\\\\.\\a\\b\\c.txt")); ++ assertEquals(null, FilenameUtils.normalize("\\\\..\\a\\b\\c.txt")); ++ assertEquals(null, FilenameUtils.normalize("\\\\127.0..1\\a\\b\\c.txt")); ++ assertEquals(null, FilenameUtils.normalize("\\\\::1::2\\a\\b\\c.txt")); ++ assertEquals(null, FilenameUtils.normalize("\\\\:1\\a\\b\\c.txt")); ++ assertEquals(null, FilenameUtils.normalize("\\\\1:\\a\\b\\c.txt")); ++ assertEquals(null, FilenameUtils.normalize("\\\\1:2:3:4:5:6:7:8:9\\a\\b\\c.txt")); ++ assertEquals(null, FilenameUtils.normalize("\\\\g:2:3:4:5:6:7:8\\a\\b\\c.txt")); ++ assertEquals(null, FilenameUtils.normalize("\\\\1ffff:2:3:4:5:6:7:8\\a\\b\\c.txt")); ++ assertEquals(null, FilenameUtils.normalize("\\\\1:2\\a\\b\\c.txt")); + } + + @Test +@@ -560,6 +587,15 @@ public class FilenameUtilsTestCase { + assertEquals(1, FilenameUtils.getPrefixLength("/:foo")); + assertEquals(1, FilenameUtils.getPrefixLength("/:/")); + assertEquals(1, FilenameUtils.getPrefixLength("/:::::::.txt")); ++ ++ assertEquals(12, FilenameUtils.getPrefixLength("\\\\127.0.0.1\\a\\b\\c.txt")); ++ assertEquals(6, FilenameUtils.getPrefixLength("\\\\::1\\a\\b\\c.txt")); ++ assertEquals(21, FilenameUtils.getPrefixLength("\\\\server.example.org\\a\\b\\c.txt")); ++ assertEquals(10, FilenameUtils.getPrefixLength("\\\\server.\\a\\b\\c.txt")); ++ ++ assertEquals(-1, FilenameUtils.getPrefixLength("\\\\-server\\a\\b\\c.txt")); ++ assertEquals(-1, FilenameUtils.getPrefixLength("\\\\.\\a\\b\\c.txt")); ++ assertEquals(-1, FilenameUtils.getPrefixLength("\\\\..\\a\\b\\c.txt")); + } + + @Test diff -Nru commons-io-2.6/debian/patches/series commons-io-2.6/debian/patches/series --- commons-io-2.6/debian/patches/series 1970-01-01 01:00:00.000000000 +0100 +++ commons-io-2.6/debian/patches/series 2021-08-20 22:25:28.000000000 +0200 @@ -0,0 +1 @@ +CVE-2021-29425.patch