From: Tim Kientzle Date: Sat, 9 May 2026 20:48:20 +0000 (-0700) Subject: Merge pull request #2927 from stoeckmann/pathmatch_recursion X-Git-Tag: v3.8.8~128 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=68868d1602517bdbe57e8705fa0bdc36ddffcd69;p=thirdparty%2Flibarchive.git Merge pull request #2927 from stoeckmann/pathmatch_recursion archive_match: Prevent call stack overflow (cherry picked from commit 5aa477220e0c45a7b30c9d36d27c8bc27fbb72cb) --- diff --git a/cpio/cpio.c b/cpio/cpio.c index 6e6c2c335..ebdace394 100644 --- a/cpio/cpio.c +++ b/cpio/cpio.c @@ -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); diff --git a/libarchive/archive_match.c b/libarchive/archive_match.c index 51a0e3fd5..c40a043ad 100644 --- a/libarchive/archive_match.c +++ b/libarchive/archive_match.c @@ -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, diff --git a/libarchive/archive_pathmatch.c b/libarchive/archive_pathmatch.c index 9ea13f7c6..f65f43745 100644 --- a/libarchive/archive_pathmatch.c +++ b/libarchive/archive_pathmatch.c @@ -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)); } diff --git a/libarchive/archive_read_disk_entry_from_file.c b/libarchive/archive_read_disk_entry_from_file.c index 6e6bae6a4..b5f1dd963 100644 --- a/libarchive/archive_read_disk_entry_from_file.c +++ b/libarchive/archive_read_disk_entry_from_file.c @@ -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; diff --git a/libarchive/archive_read_disk_posix.c b/libarchive/archive_read_disk_posix.c index a7176bdd9..a12c21134 100644 --- a/libarchive/archive_read_disk_posix.c +++ b/libarchive/archive_read_disk_posix.c @@ -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); } diff --git a/libarchive/archive_read_disk_windows.c b/libarchive/archive_read_disk_windows.c index 0e890497a..ac0117962 100644 --- a/libarchive/archive_read_disk_windows.c +++ b/libarchive/archive_read_disk_windows.c @@ -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); } diff --git a/tar/read.c b/tar/read.c index 4acbf6b2a..57ae5200c 100644 --- a/tar/read.c +++ b/tar/read.c @@ -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)); } diff --git a/tar/write.c b/tar/write.c index abff3831b..302df020a 100644 --- a/tar/write.c +++ b/tar/write.c @@ -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;