]> git.ipfire.org Git - thirdparty/libarchive.git/commitdiff
Merge pull request #2927 from stoeckmann/pathmatch_recursion
authorTim Kientzle <kientzle@acm.org>
Sat, 9 May 2026 20:48:20 +0000 (13:48 -0700)
committerMartin Matuska <martin@matuska.de>
Tue, 23 Jun 2026 08:27:36 +0000 (10:27 +0200)
archive_match: Prevent call stack overflow

(cherry picked from commit 5aa477220e0c45a7b30c9d36d27c8bc27fbb72cb)

cpio/cpio.c
libarchive/archive_match.c
libarchive/archive_pathmatch.c
libarchive/archive_read_disk_entry_from_file.c
libarchive/archive_read_disk_posix.c
libarchive/archive_read_disk_windows.c
tar/read.c
tar/write.c

index 6e6c2c3356c02ea33c3510846892b43188b29646..ebdace394ceaa3e7969a3f00ddd0fdf4016e8692 100644 (file)
@@ -1002,7 +1002,10 @@ mode_in(struct cpio *cpio)
                        lafe_errc(1, archive_errno(a),
                            "%s", archive_error_string(a));
                }
-               if (archive_match_path_excluded(cpio->matching, entry))
+               r = archive_match_path_excluded(cpio->matching, entry);
+               if (r < 0)
+                       lafe_errc(1, 0, "%s", archive_error_string(cpio->matching));
+               if (r)
                        continue;
                if (cpio->option_rename)
                        cpio_rename(entry);
@@ -1117,7 +1120,10 @@ mode_list(struct cpio *cpio)
                        lafe_errc(1, archive_errno(a),
                            "%s", archive_error_string(a));
                }
-               if (archive_match_path_excluded(cpio->matching, entry))
+               r = archive_match_path_excluded(cpio->matching, entry);
+               if (r < 0)
+                       lafe_errc(1, 0, "%s", archive_error_string(cpio->matching));
+               if (r)
                        continue;
                if (cpio->verbose)
                        list_item_verbose(cpio, entry);
index 51a0e3fd57789e63cd3cd570bc73838555d8fa99..c40a043ad2a3a1fb83937cdb1089203ddcfcec20 100644 (file)
@@ -216,6 +216,14 @@ error_nomem(struct archive_match *a)
        return (ARCHIVE_FATAL);
 }
 
+static int
+error_pattern(struct archive_match *a)
+{
+       archive_set_error(&(a->archive), EINVAL, "Failed to apply pattern");
+       a->archive.state = ARCHIVE_STATE_FATAL;
+       return (ARCHIVE_FATAL);
+}
+
 /*
  * Create an ARCHIVE_MATCH object.
  */
@@ -269,7 +277,7 @@ archive_match_free(struct archive *_a)
  *
  * Returns 1 if archive entry is excluded.
  * Returns 0 if archive entry is not excluded.
- * Returns <0 if something error happened.
+ * Returns <0 if some error happened.
  */
 int
 archive_match_excluded(struct archive *_a, struct archive_entry *entry)
@@ -293,6 +301,8 @@ archive_match_excluded(struct archive *_a, struct archive_entry *entry)
 #else
                r = path_excluded(a, 1, archive_entry_pathname(entry));
 #endif
+               if (r < 0)
+                       return (error_pattern(a));
                if (r != 0)
                        return (r);
        }
@@ -449,13 +459,14 @@ archive_match_include_pattern_from_file_w(struct archive *_a,
  *
  * Returns 1 if archive entry is excluded.
  * Returns 0 if archive entry is not excluded.
- * Returns <0 if something error happened.
+ * Returns <0 if some error happened.
  */
 int
 archive_match_path_excluded(struct archive *_a,
     struct archive_entry *entry)
 {
        struct archive_match *a;
+       int r;
 
        archive_check_magic(_a, ARCHIVE_MATCH_MAGIC,
            ARCHIVE_STATE_NEW, "archive_match_path_excluded");
@@ -471,10 +482,13 @@ archive_match_path_excluded(struct archive *_a,
        if ((a->setflag & PATTERN_IS_SET) == 0)
                return (0);
 #if defined(_WIN32) && !defined(__CYGWIN__)
-       return (path_excluded(a, 0, archive_entry_pathname_w(entry)));
+       r = path_excluded(a, 0, archive_entry_pathname_w(entry));
 #else
-       return (path_excluded(a, 1, archive_entry_pathname(entry)));
+       r = path_excluded(a, 1, archive_entry_pathname(entry));
 #endif
+       if (r < 0)
+               return (error_pattern(a));
+       return (r);
 }
 
 /*
@@ -1007,7 +1021,7 @@ archive_match_exclude_entry(struct archive *_a, int flag,
  *
  * Returns 1 if archive entry is excluded.
  * Returns 0 if archive entry is not excluded.
- * Returns <0 if something error happened.
+ * Returns <0 if some error happened.
  */
 int
 archive_match_time_excluded(struct archive *_a,
@@ -1653,7 +1667,7 @@ archive_match_include_gname_w(struct archive *_a, const wchar_t *gname)
  *
  * Returns 1 if archive entry is excluded.
  * Returns 0 if archive entry is not excluded.
- * Returns <0 if something error happened.
+ * Returns <0 if some error happened.
  */
 int
 archive_match_owner_excluded(struct archive *_a,
index 9ea13f7c6de7b754588670854f88646f6e930b05..f65f43745527f70697539ae791dcd963ece5e79c 100644 (file)
@@ -35,6 +35,8 @@
 
 #include "archive_pathmatch.h"
 
+#define MAX_RECURSION  100
+
 /*
  * Check whether a character 'c' is matched by a list specification [...]:
  *    * Leading '!' or '^' negates the class.
@@ -167,9 +169,13 @@ pm_slashskip_w(const wchar_t *s) {
 }
 
 static int
-pm(const char *p, const char *s, int flags)
+pm(const char *p, const char *s, int flags, int depth)
 {
        const char *end;
+       int r;
+
+       if (depth > MAX_RECURSION)
+               return (-1);
 
        /*
         * Ignore leading './', './/', '././', etc.
@@ -202,8 +208,9 @@ pm(const char *p, const char *s, int flags)
                        if (*p == '\0')
                                return (1);
                        while (*s) {
-                               if (pm(p, s, flags))
-                                       return (1);
+                               r = pm(p, s, flags, depth + 1);
+                               if (r)
+                                       return (r);
                                ++s;
                        }
                        return (0);
@@ -272,9 +279,13 @@ pm(const char *p, const char *s, int flags)
 }
 
 static int
-pm_w(const wchar_t *p, const wchar_t *s, int flags)
+pm_w(const wchar_t *p, const wchar_t *s, int flags, int depth)
 {
        const wchar_t *end;
+       int r;
+
+       if (depth > MAX_RECURSION)
+               return (-1);
 
        /*
         * Ignore leading './', './/', '././', etc.
@@ -307,8 +318,9 @@ pm_w(const wchar_t *p, const wchar_t *s, int flags)
                        if (*p == L'\0')
                                return (1);
                        while (*s) {
-                               if (pm_w(p, s, flags))
-                                       return (1);
+                               r = pm_w(p, s, flags, depth + 1);
+                               if (r)
+                                       return (r);
                                ++s;
                        }
                        return (0);
@@ -401,22 +413,25 @@ __archive_pathmatch(const char *p, const char *s, int flags)
                        ++p;
                while (*s == '/')
                        ++s;
-               return (pm(p, s, flags));
+               return (pm(p, s, flags, 0));
        }
 
        /* If start is unanchored, try to match start of each path element. */
        if (flags & PATHMATCH_NO_ANCHOR_START) {
                for ( ; s != NULL; s = strchr(s, '/')) {
+                       int r;
+
                        if (*s == '/')
                                s++;
-                       if (pm(p, s, flags))
-                               return (1);
+                       r = pm(p, s, flags, 0);
+                       if (r)
+                               return (r);
                }
                return (0);
        }
 
        /* Default: Match from beginning. */
-       return (pm(p, s, flags));
+       return (pm(p, s, flags, 0));
 }
 
 int
@@ -443,20 +458,23 @@ __archive_pathmatch_w(const wchar_t *p, const wchar_t *s, int flags)
                        ++p;
                while (*s == L'/')
                        ++s;
-               return (pm_w(p, s, flags));
+               return (pm_w(p, s, flags, 0));
        }
 
        /* If start is unanchored, try to match start of each path element. */
        if (flags & PATHMATCH_NO_ANCHOR_START) {
                for ( ; s != NULL; s = wcschr(s, L'/')) {
+                       int r;
+
                        if (*s == L'/')
                                s++;
-                       if (pm_w(p, s, flags))
-                               return (1);
+                       r = pm_w(p, s, flags, 0);
+                       if (r)
+                               return (r);
                }
                return (0);
        }
 
        /* Default: Match from beginning. */
-       return (pm_w(p, s, flags));
+       return (pm_w(p, s, flags, 0));
 }
index 6e6bae6a4e0676ea430f7b28347ab0a5b692e21c..b5f1dd96327e9b492cc5130df5f678de95c1075a 100644 (file)
@@ -894,7 +894,7 @@ setup_sparse_fiemap(struct archive_read_disk *a,
 
                r = ioctl(*fd, FS_IOC_FIEMAP, fm);
                if (r < 0) {
-                       /* When something error happens, it is better we
+                       /* When some error happens, it is better we
                         * should return ARCHIVE_OK because an earlier
                         * version(<2.6.28) cannot perform FS_IOC_FIEMAP. */
                        goto exit_setup_sparse_fiemap;
index a7176bdd939dd6de710828ba5213e8f5efdfd814..a12c2113404f286f58183208aea7d10bd67f7a82 100644 (file)
@@ -920,7 +920,7 @@ next_entry(struct archive_read_disk *a, struct tree *t,
        if (a->matching) {
                r = archive_match_path_excluded(a->matching, entry);
                if (r < 0) {
-                       archive_set_error(&(a->archive), errno,
+                       archive_set_error(&(a->archive), archive_errno(a->matching),
                            "%s", archive_error_string(a->matching));
                        return (r);
                }
@@ -1034,7 +1034,7 @@ next_entry(struct archive_read_disk *a, struct tree *t,
        if (a->matching) {
                r = archive_match_time_excluded(a->matching, entry);
                if (r < 0) {
-                       archive_set_error(&(a->archive), errno,
+                       archive_set_error(&(a->archive), archive_errno(a->matching),
                            "%s", archive_error_string(a->matching));
                        return (r);
                }
@@ -1060,7 +1060,7 @@ next_entry(struct archive_read_disk *a, struct tree *t,
        if (a->matching) {
                r = archive_match_owner_excluded(a->matching, entry);
                if (r < 0) {
-                       archive_set_error(&(a->archive), errno,
+                       archive_set_error(&(a->archive), archive_errno(a->matching),
                            "%s", archive_error_string(a->matching));
                        return (r);
                }
index 0e890497a443d962435318edf1a4f6eab27ccda2..ac01179628fdf67f049c78c0829d8a0fdc0549fe 100644 (file)
@@ -946,7 +946,7 @@ next_entry(struct archive_read_disk *a, struct tree *t,
        if (a->matching) {
                r = archive_match_path_excluded(a->matching, entry);
                if (r < 0) {
-                       archive_set_error(&(a->archive), errno,
+                       archive_set_error(&(a->archive), archive_errno(a->matching),
                            "%s", archive_error_string(a->matching));
                        return (r);
                }
@@ -1018,7 +1018,7 @@ next_entry(struct archive_read_disk *a, struct tree *t,
        if (a->matching) {
                r = archive_match_time_excluded(a->matching, entry);
                if (r < 0) {
-                       archive_set_error(&(a->archive), errno,
+                       archive_set_error(&(a->archive), archive_errno(a->matching),
                            "%s", archive_error_string(a->matching));
                        return (r);
                }
@@ -1044,7 +1044,7 @@ next_entry(struct archive_read_disk *a, struct tree *t,
        if (a->matching) {
                r = archive_match_owner_excluded(a->matching, entry);
                if (r < 0) {
-                       archive_set_error(&(a->archive), errno,
+                       archive_set_error(&(a->archive), archive_errno(a->matching),
                            "%s", archive_error_string(a->matching));
                        return (r);
                }
index 4acbf6b2a9dc1c29b6a5977f0b022e9b5658e6f0..57ae5200cf0d69d13da73093afb543918aa669d7 100644 (file)
@@ -234,9 +234,14 @@ read_archive(struct bsdtar *bsdtar, char mode, struct archive *writer)
        for (;;) {
                /* Support --fast-read option */
                const char *p;
-               if ((bsdtar->flags & OPTFLAG_FAST_READ) &&
-                   archive_match_path_unmatched_inclusions(bsdtar->matching) == 0)
-                       break;
+
+               if (bsdtar->flags & OPTFLAG_FAST_READ) {
+                   r = archive_match_path_unmatched_inclusions(bsdtar->matching);
+                       if (r < 0)
+                               lafe_errc(1, 0, "%s", archive_error_string(a));
+                       if (!r)
+                               break;
+               }
 
                r = archive_read_next_header(a, &entry);
                progress_data.entry = entry;
@@ -282,7 +287,11 @@ read_archive(struct bsdtar *bsdtar, char mode, struct archive *writer)
                 * rewrite, there would be no way to exclude foo1/bar
                 * while allowing foo2/bar.)
                 */
-               if (archive_match_excluded(bsdtar->matching, entry))
+               r = archive_match_excluded(bsdtar->matching, entry);
+               if (r == ARCHIVE_FATAL)
+                       lafe_errc(1, 0, "%s",
+                                   archive_error_string(bsdtar->matching));
+               if (r)
                        continue; /* Excluded by a pattern test. */
 
                if (mode == 't') {
@@ -395,7 +404,7 @@ unmatched_inclusions_warn(struct archive *matching, const char *msg)
            matching, &p)) == ARCHIVE_OK)
                lafe_warnc(0, "%s: %s", p, msg);
        if (r == ARCHIVE_FATAL)
-               lafe_errc(1, errno, "Out of memory");
+               lafe_errc(1, 0, "%s", archive_error_string(matching));
 
        return (archive_match_path_unmatched_inclusions(matching));
 }
index abff3831bcdaaa1bf35c8b9e3bf9a6ba126f4fad..302df020ad3a70d0531e6cb402605a29bf952427 100644 (file)
@@ -699,7 +699,18 @@ append_archive(struct bsdtar *bsdtar, struct archive *a, struct archive *ina)
        int e;
 
        while (ARCHIVE_OK == (e = archive_read_next_header(ina, &in_entry))) {
-               if (archive_match_excluded(bsdtar->matching, in_entry))
+               e = archive_match_excluded(bsdtar->matching, in_entry);
+               if (e < 0) {
+                       if (!bsdtar->verbose)
+                               lafe_errc(1, 0, "%s",
+                                   archive_error_string(bsdtar->matching));
+                       else {
+                               fprintf(stderr, ": %s",
+                                   archive_error_string(bsdtar->matching));
+                               exit(1);
+                       }
+               }
+               if (e)
                        continue;
                if(edit_pathname(bsdtar, in_entry))
                        continue;