[Date Prev][Date Next] [Thread Prev][Thread Next] [Date Index] [Thread Index]

Bug#992599: buster-pu: package commons-io/2.6-2



Package: release.debian.org
Severity: normal
Tags: buster
User: release.debian.org@packages.debian.org
Usertags: pu
X-Debbugs-Cc: apo@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 <apo@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 <apo@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

Reply to: