From: Himanshu Jadon <[email protected]> Backport the upstream 3-commit fix chain for CVE-2026-5704.
The final fix is [1], which depends on the earlier cleanup in [2] and the behavioral change in [3]. Keep this patch order so the final fix applies cleanly and preserves the upstream logic. [1] https://cgit.git.savannah.gnu.org/cgit/tar.git/commit/?id=b8d8a61b25588caca4efaf9bdd2e3f1a49da77e3 [2] https://cgit.git.savannah.gnu.org/cgit/tar.git/commit/?id=112ead79312ea308e58414b74623f101b8c06f0b [3] https://cgit.git.savannah.gnu.org/cgit/tar.git/commit/?id=b009124ffde415515081db844d7a104e1d1c6c58 [4] https://security-tracker.debian.org/tracker/CVE-2026-5704 Signed-off-by: Himanshu Jadon <[email protected]> --- .../tar/tar/CVE-2026-5704-dependent_p1.patch | 484 +++++++++++++++ .../tar/tar/CVE-2026-5704-dependent_p2.patch | 169 ++++++ .../tar/tar/CVE-2026-5704.patch | 556 ++++++++++++++++++ meta/recipes-extended/tar/tar_1.35.bb | 3 + 4 files changed, 1212 insertions(+) create mode 100644 meta/recipes-extended/tar/tar/CVE-2026-5704-dependent_p1.patch create mode 100644 meta/recipes-extended/tar/tar/CVE-2026-5704-dependent_p2.patch create mode 100644 meta/recipes-extended/tar/tar/CVE-2026-5704.patch diff --git a/meta/recipes-extended/tar/tar/CVE-2026-5704-dependent_p1.patch b/meta/recipes-extended/tar/tar/CVE-2026-5704-dependent_p1.patch new file mode 100644 index 0000000000..b7180e1671 --- /dev/null +++ b/meta/recipes-extended/tar/tar/CVE-2026-5704-dependent_p1.patch @@ -0,0 +1,484 @@ +From cb6d041ba711620b52637ebd2f9cb0c445a91e4f Mon Sep 17 00:00:00 2001 +From: Paul Eggert <[email protected]> +Date: Fri, 1 Nov 2024 14:15:09 -0700 +Subject: [PATCH] Prefer other types to int in extract.c +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +* src/extract.c (fd_chmod, extract_chdir, open_output_file) +(extract_file, extract_link, extract_symlink, extract_node) +(extract_fifo, tar_extractor_t, pepare_to_extract): Prefer char to +int for typeflag, since it???s a char. All uses changed. +(fd_chmod): Use clearer code for errno. +(extract_dir, extract_file, create_placeholder_file, extract_link) +(extract_symlink, extract_node, extract_fifo, tar_extractor_t): +Return bool true for success, false for failure. All uses changed. +(open_output_file): Prefer bool for boolean. +(prepare_to_extract): Simplify by returning the extractor a null +pointer, rather than storing through a pointer to an extractor. + +CVE: CVE-2026-5704 +Upstream-Status: Backport [https://cgit.git.savannah.gnu.org/cgit/tar.git/commit/?id=112ead79312ea308e58414b74623f101b8c06f0b] + +Backport Changes: +- In src/extract.c, the extractor-selection and extraction-control-flow + hunk was adapted to the older tar-1.35 code layout around + prepare_to_extract() and extract_archive(). The backport preserves the + upstream logic but does not apply as an exact textual match in that + section. + +(cherry picked from commit 112ead79312ea308e58414b74623f101b8c06f0b) +Signed-off-by: Himanshu Jadon <[email protected]> +--- + src/extract.c | 141 ++++++++++++++++++++++++-------------------------- + 1 file changed, 69 insertions(+), 72 deletions(-) + +diff --git a/src/extract.c b/src/extract.c +index 314d8bc0..b384fed3 100644 +--- a/src/extract.c ++++ b/src/extract.c +@@ -254,9 +254,9 @@ fd_i_chmod (int fd, char const *file, mode_t mode, int atflag) + notation. + */ + static int +-fd_chmod(int fd, char const *file_name, int mode, int atflag, int typeflag) ++fd_chmod (int fd, char const *file_name, int mode, int atflag, char typeflag) + { +- int chmod_errno = fd_i_chmod (fd, file_name, mode, atflag) == 0 ? 0 : errno; ++ int chmod_errno = fd_i_chmod (fd, file_name, mode, atflag) < 0 ? errno : 0; + + /* On Solaris, chmod may fail if we don't have PRIV_ALL, because + setuid-root files would otherwise be a backdoor. See +@@ -265,7 +265,7 @@ fd_chmod(int fd, char const *file_name, int mode, int atflag, int typeflag) + if (chmod_errno == EPERM && (mode & S_ISUID) + && priv_set_restore_linkdir () == 0) + { +- chmod_errno = fd_i_chmod (fd, file_name, mode, atflag) == 0 ? 0 : errno; ++ chmod_errno = fd_i_chmod (fd, file_name, mode, atflag) < 0 ? errno : 0; + priv_set_remove_linkdir (); + } + +@@ -275,7 +275,7 @@ fd_chmod(int fd, char const *file_name, int mode, int atflag, int typeflag) + supported and if the file is not a symlink. This + introduces a race, alas. */ + if (atflag && typeflag != SYMTYPE && ! implemented (chmod_errno)) +- chmod_errno = fd_i_chmod (fd, file_name, mode, 0) == 0 ? 0 : errno; ++ chmod_errno = fd_i_chmod (fd, file_name, mode, 0) < 0 ? errno : 0; + + if (chmod_errno && (typeflag != SYMTYPE || implemented (chmod_errno))) + { +@@ -1036,8 +1036,8 @@ safe_dir_mode (struct stat const *st) + + /* Extractor functions for various member types */ + +-static int +-extract_dir (char *file_name, int typeflag) ++static bool ++extract_dir (char *file_name, char typeflag) + { + int status; + mode_t mode; +@@ -1089,7 +1089,7 @@ extract_dir (char *file_name, int typeflag) + + if (keep_directory_symlink_option + && is_directory_link (file_name, &st)) +- return 0; ++ return true; + + if ((st.st_mode != 0 && fstatat_flags == 0) + || deref_stat (file_name, &st) == 0) +@@ -1102,7 +1102,7 @@ extract_dir (char *file_name, int typeflag) + if (interdir_made) + { + repair_delayed_set_stat (file_name, &st); +- return 0; ++ return true; + } + else if (old_files_option == NO_OVERWRITE_DIR_OLD_FILES) + { +@@ -1154,7 +1154,7 @@ extract_dir (char *file_name, int typeflag) + if (errno != EEXIST) + { + mkdir_error (file_name); +- return 1; ++ return false; + } + break; + } +@@ -1167,13 +1167,13 @@ extract_dir (char *file_name, int typeflag) + delay_set_stat (file_name, ¤t_stat_info, + current_mode, current_mode_mask, + current_stat_info.stat.st_mode, atflag); +- return status; ++ return status == 0; + } + + + + static int +-open_output_file (char const *file_name, int typeflag, mode_t mode, ++open_output_file (char const *file_name, char typeflag, mode_t mode, + int file_created, mode_t *current_mode, + mode_t *current_mode_mask) + { +@@ -1189,11 +1189,11 @@ open_output_file (char const *file_name, int typeflag, mode_t mode, + + if (typeflag == CONTTYPE) + { +- static int conttype_diagnosed; ++ static bool conttype_diagnosed; + + if (!conttype_diagnosed) + { +- conttype_diagnosed = 1; ++ conttype_diagnosed = true; + WARNOPT (WARN_CONTIGUOUS_CAST, + (0, 0, _("Extracting contiguous files as regular files"))); + } +@@ -1245,8 +1245,8 @@ open_output_file (char const *file_name, int typeflag, mode_t mode, + return fd; + } + +-static int +-extract_file (char *file_name, int typeflag) ++static bool ++extract_file (char *file_name, char typeflag) + { + int fd; + off_t size; +@@ -1268,7 +1268,7 @@ extract_file (char *file_name, int typeflag) + if (fd < 0) + { + skip_member (); +- return 0; ++ return true; + } + } + else +@@ -1291,9 +1291,9 @@ extract_file (char *file_name, int typeflag) + { + skip_member (); + if (recover == RECOVER_SKIP) +- return 0; ++ return true; + open_error (file_name); +- return 1; ++ return false; + } + } + } +@@ -1344,7 +1344,7 @@ extract_file (char *file_name, int typeflag) + it doesn't exist, or we don't want to touch it anyway. */ + + if (to_stdout_option) +- return 0; ++ return true; + + if (! to_command_option) + set_stat (file_name, ¤t_stat_info, fd, +@@ -1359,7 +1359,7 @@ extract_file (char *file_name, int typeflag) + if (to_command_option) + sys_wait_command (); + +- return status; ++ return status == 0; + } + + /* Return true if NAME is a delayed link. This can happen only if the link +@@ -1399,7 +1399,7 @@ find_delayed_link_source (char const *name) + process. + */ + +-static int ++static bool + create_placeholder_file (char *file_name, bool is_symlink, bool *interdir_made) + { + int fd; +@@ -1413,7 +1413,7 @@ create_placeholder_file (char *file_name, bool is_symlink, bool *interdir_made) + that the link being extracted is a duplicate of an already + processed one. Skip it. + */ +- return 0; ++ return true; + } + + switch (maybe_recoverable (file_name, false, interdir_made)) +@@ -1422,11 +1422,11 @@ create_placeholder_file (char *file_name, bool is_symlink, bool *interdir_made) + continue; + + case RECOVER_SKIP: +- return 0; ++ return true; + + case RECOVER_NO: + open_error (file_name); +- return -1; ++ return false; + } + } + +@@ -1484,14 +1484,14 @@ create_placeholder_file (char *file_name, bool is_symlink, bool *interdir_made) + if ((h = find_direct_ancestor (file_name)) != NULL) + mark_after_links (h); + +- return 0; ++ return true; + } + +- return -1; ++ return false; + } + +-static int +-extract_link (char *file_name, MAYBE_UNUSED int typeflag) ++static bool ++extract_link (char *file_name, MAYBE_UNUSED char typeflag) + { + bool interdir_made = false; + char const *link_name; +@@ -1531,7 +1531,7 @@ extract_link (char *file_name, MAYBE_UNUSED int typeflag) + } + } + +- return 0; ++ return true; + } + else if ((e == EEXIST && strcmp (link_name, file_name) == 0) + || ((fstatat (chdir_fd, link_name, &st1, AT_SYMLINK_NOFOLLOW) +@@ -1540,7 +1540,7 @@ extract_link (char *file_name, MAYBE_UNUSED int typeflag) + == 0) + && st1.st_dev == st2.st_dev + && st1.st_ino == st2.st_ino)) +- return 0; ++ return true; + + errno = e; + } +@@ -1548,17 +1548,17 @@ extract_link (char *file_name, MAYBE_UNUSED int typeflag) + == RECOVER_OK); + + if (rc == RECOVER_SKIP) +- return 0; ++ return true; + if (!(incremental_option && errno == EEXIST)) + { + link_error (link_name, file_name); +- return 1; ++ return false; + } +- return 0; ++ return true; + } + +-static int +-extract_symlink (char *file_name, MAYBE_UNUSED int typeflag) ++static bool ++extract_symlink (char *file_name, MAYBE_UNUSED char typeflag) + { + #ifdef HAVE_SYMLINK + bool interdir_made = false; +@@ -1575,16 +1575,16 @@ extract_symlink (char *file_name, MAYBE_UNUSED int typeflag) + continue; + + case RECOVER_SKIP: +- return 0; ++ return true; + + case RECOVER_NO: + symlink_error (current_stat_info.link_name, file_name); +- return -1; ++ return false; + } + + set_stat (file_name, ¤t_stat_info, -1, 0, 0, + SYMTYPE, false, AT_SYMLINK_NOFOLLOW); +- return 0; ++ return true; + + #else + static int warned_once; +@@ -1601,8 +1601,8 @@ extract_symlink (char *file_name, MAYBE_UNUSED int typeflag) + } + + #if S_IFCHR || S_IFBLK +-static int +-extract_node (char *file_name, int typeflag) ++static bool ++extract_node (char *file_name, char typeflag) + { + bool interdir_made = false; + mode_t mode = (current_stat_info.stat.st_mode & (MODE_RWX | S_IFBLK | S_IFCHR) +@@ -1616,23 +1616,23 @@ extract_node (char *file_name, int typeflag) + continue; + + case RECOVER_SKIP: +- return 0; ++ return true; + + case RECOVER_NO: + mknod_error (file_name); +- return -1; ++ return false; + } + + set_stat (file_name, ¤t_stat_info, -1, + mode & ~ current_umask, MODE_RWX, + typeflag, false, AT_SYMLINK_NOFOLLOW); +- return 0; ++ return true; + } + #endif + + #if HAVE_MKFIFO || defined mkfifo +-static int +-extract_fifo (char *file_name, int typeflag) ++static bool ++extract_fifo (char *file_name, char typeflag) + { + bool interdir_made = false; + mode_t mode = (current_stat_info.stat.st_mode & MODE_RWX +@@ -1645,31 +1645,31 @@ extract_fifo (char *file_name, int typeflag) + continue; + + case RECOVER_SKIP: +- return 0; ++ return true; + + case RECOVER_NO: + mkfifo_error (file_name); +- return -1; ++ return false; + } + + set_stat (file_name, ¤t_stat_info, -1, + mode & ~ current_umask, MODE_RWX, + typeflag, false, AT_SYMLINK_NOFOLLOW); +- return 0; ++ return true; + } + #endif + +-typedef int (*tar_extractor_t) (char *file_name, int typeflag); ++typedef bool (*tar_extractor_t) (char *file_name, char typeflag); + + + /* Prepare to extract a file. Find extractor function. +- Return true to proceed with the extraction, false to skip the current +- member. */ ++ Return an extractor to proceed with the extraction, ++ a null pointer to skip the current member. */ + +-static bool +-prepare_to_extract (char const *file_name, int typeflag, tar_extractor_t *fun) ++static tar_extractor_t ++prepare_to_extract (char const *file_name, char typeflag) + { +- tar_extractor_t extractor = NULL; ++ tar_extractor_t extractor; + + /* Select the extractor */ + switch (typeflag) +@@ -1683,10 +1683,8 @@ prepare_to_extract (char const *file_name, int typeflag, tar_extractor_t *fun) + case CONTTYPE: + /* Appears to be a file. But BSD tar uses the convention that a slash + suffix means a directory. */ +- if (current_stat_info.had_trailing_slash) +- extractor = extract_dir; +- else +- extractor = extract_file; ++ extractor = (current_stat_info.had_trailing_slash ++ ? extract_dir : extract_file); + break; + + case SYMTYPE: +@@ -1725,18 +1723,18 @@ prepare_to_extract (char const *file_name, int typeflag, tar_extractor_t *fun) + break; + + case GNUTYPE_VOLHDR: +- return false; ++ return NULL; + + case GNUTYPE_MULTIVOL: + ERROR ((0, 0, + _("%s: Cannot extract -- file is continued from another volume"), + quotearg_colon (current_stat_info.file_name))); +- return false; ++ return NULL; + + case GNUTYPE_LONGNAME: + case GNUTYPE_LONGLINK: + ERROR ((0, 0, _("Unexpected long name header"))); +- return false; ++ return NULL; + + default: + WARNOPT (WARN_UNKNOWN_CAST, +@@ -1749,7 +1747,7 @@ prepare_to_extract (char const *file_name, int typeflag, tar_extractor_t *fun) + if (EXTRACT_OVER_PIPE) + { + if (extractor != extract_file) +- return false; ++ return NULL; + } + else + { +@@ -1763,7 +1761,7 @@ prepare_to_extract (char const *file_name, int typeflag, tar_extractor_t *fun) + && errno && errno != ENOENT) + { + unlink_error (file_name); +- return false; ++ return NULL; + } + break; + +@@ -1773,7 +1771,7 @@ prepare_to_extract (char const *file_name, int typeflag, tar_extractor_t *fun) + WARNOPT (WARN_IGNORE_NEWER, + (0, 0, _("Current %s is newer or same age"), + quote (file_name))); +- return false; ++ return NULL; + } + break; + +@@ -1781,9 +1779,7 @@ prepare_to_extract (char const *file_name, int typeflag, tar_extractor_t *fun) + break; + } + } +- *fun = extractor; +- +- return true; ++ return extractor; + } + + /* Extract a file from the archive. */ +@@ -1791,7 +1787,6 @@ void + extract_archive (void) + { + char typeflag; +- tar_extractor_t fun; + bool skip_dotdot_name; + + fatal_exit_hook = extract_finish; +@@ -1841,12 +1836,14 @@ extract_archive (void) + + /* Extract the archive entry according to its type. */ + /* KLUDGE */ +- typeflag = sparse_member_p (¤t_stat_info) ? +- GNUTYPE_SPARSE : current_header->header.typeflag; ++ typeflag = (sparse_member_p (¤t_stat_info) ++ ? GNUTYPE_SPARSE : current_header->header.typeflag); + +- if (prepare_to_extract (current_stat_info.file_name, typeflag, &fun)) ++ tar_extractor_t fun = prepare_to_extract (current_stat_info.file_name, ++ typeflag); ++ if (fun) + { +- if (fun (current_stat_info.file_name, typeflag) == 0) ++ if (fun (current_stat_info.file_name, typeflag)) + return; + } + else +-- +2.44.1 + diff --git a/meta/recipes-extended/tar/tar/CVE-2026-5704-dependent_p2.patch b/meta/recipes-extended/tar/tar/CVE-2026-5704-dependent_p2.patch new file mode 100644 index 0000000000..c3a94db5a0 --- /dev/null +++ b/meta/recipes-extended/tar/tar/CVE-2026-5704-dependent_p2.patch @@ -0,0 +1,169 @@ +From a934d62acc1edc202e93e9f1b38eff1d880568a0 Mon Sep 17 00:00:00 2001 +From: Sergey Poznyakoff <[email protected]> +Date: Mon, 12 May 2025 17:17:21 +0300 +Subject: [PATCH] Handle directory members consistently when listing and when + extracting. + +* src/list.c (skim_member): Recognize directory members using +the same rules as during extraction. +* tests/skipdir.at: New testcase. +* tests/testsuite.at: Add new test. +* tests/Makefile.am: Likewise. + +CVE: CVE-2026-5704 +Upstream-Status: Backport [https://cgit.git.savannah.gnu.org/cgit/tar.git/commit/?id=b009124ffde415515081db844d7a104e1d1c6c58] + +(cherry picked from commit b009124ffde415515081db844d7a104e1d1c6c58) +Signed-off-by: Himanshu Jadon <[email protected]> +--- + src/list.c | 22 ++++++++++++++++-- + tests/Makefile.am | 1 + + tests/skipdir.at | 56 ++++++++++++++++++++++++++++++++++++++++++++++ + tests/testsuite.at | 3 ++- + 4 files changed, 79 insertions(+), 3 deletions(-) + create mode 100644 tests/skipdir.at + +diff --git a/src/list.c b/src/list.c +index e9a68159..928779e1 100644 +--- a/src/list.c ++++ b/src/list.c +@@ -1440,6 +1440,23 @@ skip_member (void) + skim_member (false); + } + ++static bool ++member_is_dir (struct tar_stat_info *info, char typeflag) ++{ ++ switch (typeflag) { ++ case AREGTYPE: ++ case REGTYPE: ++ case CONTTYPE: ++ return info->had_trailing_slash; ++ ++ case DIRTYPE: ++ return true; ++ ++ default: ++ return false; ++ } ++} ++ + /* Skip the current member in the archive. + If MUST_COPY, always copy instead of skipping. */ + void +@@ -1447,14 +1464,15 @@ skim_member (bool must_copy) + { + if (!current_stat_info.skipped) + { +- char save_typeflag = current_header->header.typeflag; ++ bool is_dir = member_is_dir (¤t_stat_info, ++ current_header->header.typeflag); + set_next_block_after (current_header); + + mv_begin_read (¤t_stat_info); + + if (current_stat_info.is_sparse) + sparse_skim_file (¤t_stat_info, must_copy); +- else if (save_typeflag != DIRTYPE) ++ else if (!is_dir) + skim_file (current_stat_info.stat.st_size, must_copy); + + mv_end (); +diff --git a/tests/Makefile.am b/tests/Makefile.am +index 1884b722..6cf726c0 100644 +--- a/tests/Makefile.am ++++ b/tests/Makefile.am +@@ -240,6 +240,7 @@ TESTSUITE_AT = \ + shortrec.at\ + shortupd.at\ + sigpipe.at\ ++ skipdir.at\ + sparse01.at\ + sparse02.at\ + sparse03.at\ +diff --git a/tests/skipdir.at b/tests/skipdir.at +new file mode 100644 +index 00000000..7106ee74 +--- /dev/null ++++ b/tests/skipdir.at +@@ -0,0 +1,56 @@ ++# Process this file with autom4te to create testsuite. -*- Autotest -*- ++ ++# Test suite for GNU tar. ++# Copyright 2025 Free Software Foundation, Inc. ++ ++# This file is part of GNU tar. ++ ++# GNU tar is free software; you can redistribute it and/or modify ++# it under the terms of the GNU General Public License as published by ++# the Free Software Foundation; either version 3 of the License, or ++# (at your option) any later version. ++ ++# GNU tar is distributed in the hope that it will be useful, ++# but WITHOUT ANY WARRANTY; without even the implied warranty of ++# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++# GNU General Public License for more details. ++ ++# You should have received a copy of the GNU General Public License ++# along with this program. If not, see <http://www.gnu.org/licenses/>. ++ ++# Description: determining member type when listing and extracting ++# should follow the same principles. ++# ++# Until version 1.35 the same archive member could have been processed ++# as a directory when extracting and as a regular file when being ++# skipped during listing. ++# ++# References: https://savannah.gnu.org/patch/index.php?10100 ++ ++AT_SETUP([skip directory members]) ++AT_KEYWORDS([skipdir]) ++AT_DATA([archive.in], ++[/Td6WFoAAATm1rRGAgAhARwAAAAQz1jM4Cf/AG1dADedyh4ubnxHHIi7Cen6orusgKqY3paKeQwp ++3//HS9EIT7Hm+MsndXfRntXVt8mu8oDpLOfC+AB9VldyCtp2jqOfTwa455qfGAcONPn6WWDgsaAh ++O2Y6ptXuaF/vdaNkub7SkOBME8jHYITT5QAAAAAAHtdcflb5Zw8AAYkBgFAAAPYgb0axxGf7AgAA ++AAAEWVo= ++]) ++AT_CHECK([base64 --help >/dev/null 2>&1 || AT_SKIP_TEST ++xz --help >/dev/null 2>&1 || AT_SKIP_TEST ++base64 -d < archive.in | xz -c -d > archive.tar ++]) ++AT_CHECK([tar tf archive.tar], ++[0], ++[owo1/ ++owo2/ ++]) ++AT_CHECK([tar vxf archive.tar], ++[0], ++[owo1/ ++owo2/ ++]) ++AT_CHECK([tar -xvf archive.tar --exclude owo1], ++[0], ++[owo2/ ++]) ++AT_CLEANUP +diff --git a/tests/testsuite.at b/tests/testsuite.at +index 44ae773b..f2229be1 100644 +--- a/tests/testsuite.at ++++ b/tests/testsuite.at +@@ -464,7 +464,7 @@ AT_BANNER([Volume operations]) + m4_include([volume.at]) + m4_include([volsize.at]) + +-AT_BANNER() ++AT_BANNER([Various tests]) + m4_include([comprec.at]) + m4_include([shortfile.at]) + m4_include([shortupd.at]) +@@ -473,6 +473,7 @@ m4_include([truncate.at]) + m4_include([grow.at]) + m4_include([sigpipe.at]) + m4_include([comperr.at]) ++m4_include([skipdir.at]) + + AT_BANNER([Removing files after archiving]) + m4_include([remfiles01.at]) +-- +2.44.1 + diff --git a/meta/recipes-extended/tar/tar/CVE-2026-5704.patch b/meta/recipes-extended/tar/tar/CVE-2026-5704.patch new file mode 100644 index 0000000000..81c6df3712 --- /dev/null +++ b/meta/recipes-extended/tar/tar/CVE-2026-5704.patch @@ -0,0 +1,556 @@ +From b97bbb10103a911d418015b7ff41384af0419952 Mon Sep 17 00:00:00 2001 +From: Paul Eggert <[email protected]> +Date: Sun, 22 Mar 2026 12:19:40 -0700 +Subject: [PATCH] Fix more -t/-x discrepancies +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +Problem reported by Guillermo de Angel in: +https://lists.gnu.org/r/bug-tar/2026-03/msg00007.html +* THANKS: Add him, and sort. +* src/extract.c (extract_dir, extract_file): +* src/incremen.c (purge_directory): +Do not call skip_member, as the caller now does that, and does it +more reliably. +* src/extract.c (extract_file): +Mark file as skipped when we???ve read it. +(extract_archive): Always call skip_member after extracting, +as it suppresses the skip as needed. +* src/incremen.c (try_purge_directory): Remove; no longer +needed. Move internals to purge_directory. +* src/list.c (read_header): Do not treat LNKTYPE header as having +size zero, as it can be nonzero (e.g., ???pax -o linkdata???). +Set info->skipped field according to how the header was read. +(member_is_dir): Remove; no longer needed. +(skim_member): Skip directory data too, unless it???s already been +skipped (i.e., read). +* tests/extrac32.at: New file. +* tests/Makefile.am (TESTSUITE_AT): +* tests/testsuite.at: +Add it. +* tests/skipdir.at (skip directory members): +Fix test to match the correct behavior. +This fixes a bug introduced in commit +b009124ffde415515081db844d7a104e1d1c6c58 +dated 2025-05-12 17:17:21 +0300. + +CVE: CVE-2026-5704 +Upstream-Status: Backport [https://cgit.git.savannah.gnu.org/cgit/tar.git/commit/?id=b8d8a61b25588caca4efaf9bdd2e3f1a49da77e3] + +Backport Changes: +- Replaced UNNAMED(typeflag) with MAYBE_UNUSED char typeflag in extract_dir() + under src/extract.c, as the UNNAMED macro is not available in tar 1.35 + (introduced later in upstream commit 9280aa3807c1 "Prefer UNNAMED to + MAYBE_UNUSED"). +- THANKS file context difference: tar 1.35 has "Jurgen Botz" (ASCII), + while upstream has "J??rgen Botz" (UTF-8 umlaut), changed in upstream + commit 390950282d98 ("maint: fix some encodings and email addresses"); + the removed line in the diff reflects the tar 1.35 spelling. + +(cherry picked from commit b8d8a61b25588caca4efaf9bdd2e3f1a49da77e3) +Signed-off-by: Himanshu Jadon <[email protected]> +--- + THANKS | 30 +++++++++++++++-------------- + src/extract.c | 23 ++++++---------------- + src/incremen.c | 22 +++++++-------------- + src/list.c | 48 +++++++++++++++------------------------------- + tests/Makefile.am | 1 + + tests/extrac32.at | 47 +++++++++++++++++++++++++++++++++++++++++++++ + tests/skipdir.at | 6 +----- + tests/testsuite.at | 1 + + 8 files changed, 94 insertions(+), 84 deletions(-) + create mode 100644 tests/extrac32.at + +diff --git a/THANKS b/THANKS +index aee0a924..03fa49d5 100644 +--- a/THANKS ++++ b/THANKS +@@ -6,6 +6,7 @@ Many people further contributed to GNU tar by reporting problems, + suggesting various improvements or submitting actual code. Here is a + list of these people. Help me keep it complete and exempt of errors. + See various ChangeLogs for a detailed description of contributions. ++This listed is sorted via "LC_ALL=C sort". + + Aage Robeck [email protected] + Adam Borowski [email protected] +@@ -36,8 +37,8 @@ Andrew J. Schorr [email protected] + Andrew Torda [email protected] + Andrey A. Chernov [email protected] + Andy Gay [email protected] +-Antonio Jose Coutinho [email protected] + Anthony G. Basile [email protected] ++Antonio Jose Coutinho [email protected] + Ariel Faigon [email protected] + Arne Wichmann [email protected] + Arnold Robbins [email protected] +@@ -79,9 +80,9 @@ Cesar Romani [email protected] + Chad Hurwitz [email protected] + Chance Reschke [email protected] + Charles Fu [email protected] +-Charles McGarvey [email protected] + Charles Lopes [email protected] + Charles M. Hannum [email protected] ++Charles McGarvey [email protected] + Chip Salzenberg tct!chip + Chris Arthur [email protected] + Chris F.M. Verberne [email protected] +@@ -93,9 +94,9 @@ Christian Callsen [email protected] + Christian Kirsch [email protected] + Christian Laubscher [email protected] + Christian T. Dum [email protected] +-Christian von Roques [email protected] +-Christian Wetzel [email protected] + Christian Weisgerber [email protected] ++Christian Wetzel [email protected] ++Christian von Roques [email protected] + Christoph Litauer [email protected] + Christophe Colle [email protected] + Christophe Kalt [email protected] +@@ -117,8 +118,8 @@ Dan Bloch [email protected] + Dan Drake [email protected] + Dan Reish [email protected] + Daniel Hagerty [email protected] +-Daniel Quinlan [email protected] + Daniel Kahn Gillmor [email protected] ++Daniel Quinlan [email protected] + Daniel R. Guilderson [email protected] + Daniel S. Barclay [email protected] + Daniel Trinkle [email protected] +@@ -198,6 +199,7 @@ Greg Hudson [email protected] + Greg Maples [email protected] + Greg McGary [email protected] + Greg Schafer [email protected] ++Guillermo de Angel [email protected] + G??ran Uddeborg [email protected] + G??rkan Karaman [email protected] + Hans Guerth [email protected] +@@ -222,6 +224,7 @@ Indra Singhal [email protected] + J. Dean Brock [email protected] + J.J. Bailey [email protected] + J.T. Conklin [email protected] ++James Antill [email protected] + James Crawford Ralston [email protected] + James E. Carpenter [email protected] + James H Caldwell Jr [email protected] +@@ -233,15 +236,15 @@ Jan Carlson [email protected] + Jan Djarv [email protected] + Janice Burton [email protected] + Janne Snabb [email protected] +-Jason R. Mastaler [email protected] + Jason Armistead [email protected] ++Jason R. Mastaler [email protected] + Jay Fenlason [email protected] + Jean-Louis Martineau [email protected] +-Jean-Michel Soenen [email protected] + Jean-Loup Gailly [email protected] +-Jeff Moskow [email protected] ++Jean-Michel Soenen [email protected] + Jean-Ph. Martin-Flatin [email protected] + Jean-Pierre Demailly [email protected] ++Jeff Moskow [email protected] + Jeff Prothero [email protected] + Jeff Siegel [email protected] + Jeff Sorensen [email protected] +@@ -249,7 +252,6 @@ Jeffrey Goldberg [email protected] + Jeffrey Mark Siskind [email protected] + Jeffrey W. Parker [email protected] + Jens Henrik Jensen [email protected] +-J??r??my Bobbio [email protected] + Jim Blandy [email protected] + Jim Clausing [email protected] + Jim Farrell [email protected] +@@ -283,13 +285,14 @@ Joutsiniemi Tommi Il [email protected] + Joy Kendall [email protected] + Judy Ricker [email protected] + Juha Sarlin [email protected] +-Jurgen Botz [email protected] + Jyh-Shyang Wang [email protected] ++J??r??my Bobbio [email protected] + J??rg Schilling [email protected] +-J??rg Weule [email protected] + J??rg Weilbier [email protected] ++J??rg Weule [email protected] + J??rgen H??gg [email protected] + J??rgen Weigert [email protected] ++J??rgen Botz [email protected] + J??rgen L??ters [email protected] + J??rgen Reiss [email protected] + Kai Petzke [email protected] +@@ -311,7 +314,6 @@ Kimmy Posey [email protected] + Koji Kishi [email protected] + Konno Hiroharu [email protected] + Kurt Jaeger [email protected] +-James Antill [email protected] + Larry Creech [email protected] + Larry Schwimmer [email protected] + Lasse Collin [email protected] +@@ -394,7 +396,6 @@ Oswald P. Backus IV [email protected] + Pascal Meheut [email protected] + Patrick Fulconis [email protected] + Patrick Timmons [email protected] +-Pavel Raiskup [email protected] + Paul Eggert [email protected] + Paul Kanz [email protected] + Paul Mitchell [email protected] +@@ -402,6 +403,7 @@ Paul Nevai [email protected] + Paul Nordstrom [email protected] + Paul O'Connor [email protected] + Paul Siddall [email protected] ++Pavel Raiskup [email protected] + Peder Chr. Norgaard [email protected] + Pekka Janhunen [email protected] + Per Bojsen [email protected] +@@ -420,9 +422,9 @@ Piotr Rotter [email protected] + R. Kent Dybvig [email protected] + R. Scott Butler [email protected] + Rainer Orth [email protected] +-Ralf Wildenhues [email protected] + Ralf S. Engelschall [email protected] + Ralf Suckow [email protected] ++Ralf Wildenhues [email protected] + Ralph Corderoy [email protected] + Ralph Schleicher [email protected] + Randy Bias [email protected] +diff --git a/src/extract.c b/src/extract.c +index b384fed3..3bf0d77e 100644 +--- a/src/extract.c ++++ b/src/extract.c +@@ -1037,7 +1037,7 @@ safe_dir_mode (struct stat const *st) + /* Extractor functions for various member types */ + + static bool +-extract_dir (char *file_name, char typeflag) ++extract_dir (char *file_name, MAYBE_UNUSED char typeflag) + { + int status; + mode_t mode; +@@ -1060,8 +1060,6 @@ extract_dir (char *file_name, char typeflag) + if (incremental_option) + /* Read the entry and delete files that aren't listed in the archive. */ + purge_directory (file_name); +- else if (typeflag == GNUTYPE_DUMPDIR) +- skip_member (); + + mode = safe_dir_mode (¤t_stat_info.stat); + +@@ -1266,10 +1264,7 @@ extract_file (char *file_name, char typeflag) + { + fd = sys_exec_command (file_name, 'f', ¤t_stat_info); + if (fd < 0) +- { +- skip_member (); +- return true; +- } ++ return true; + } + else + { +@@ -1289,7 +1284,6 @@ extract_file (char *file_name, char typeflag) + int recover = maybe_recoverable (file_name, true, &interdir_made); + if (recover != RECOVER_OK) + { +- skip_member (); + if (recover == RECOVER_SKIP) + return true; + open_error (file_name); +@@ -1337,6 +1331,7 @@ extract_file (char *file_name, char typeflag) + } + + skim_file (size, false); ++ current_stat_info.skipped = true; + + mv_end (); + +@@ -1841,15 +1836,9 @@ extract_archive (void) + + tar_extractor_t fun = prepare_to_extract (current_stat_info.file_name, + typeflag); +- if (fun) +- { +- if (fun (current_stat_info.file_name, typeflag)) +- return; +- } +- else +- skip_member (); +- +- if (backup_option) ++ bool ok = fun && fun (current_stat_info.file_name, typeflag); ++ skip_member (); ++ if (!ok && backup_option) + undo_last_backup (); + } + +diff --git a/src/incremen.c b/src/incremen.c +index 7bcfdb93..194d5cb1 100644 +--- a/src/incremen.c ++++ b/src/incremen.c +@@ -1625,8 +1625,8 @@ dumpdir_ok (char *dumpdir) + + /* Examine the directories under directory_name and delete any + files that were not there at the time of the back-up. */ +-static bool +-try_purge_directory (char const *directory_name) ++void ++purge_directory (char const *directory_name) + { + char *current_dir; + char *cur, *arc, *p; +@@ -1634,18 +1634,18 @@ try_purge_directory (char const *directory_name) + struct dumpdir *dump; + + if (!is_dumpdir (¤t_stat_info)) +- return false; ++ return; + + current_dir = tar_savedir (directory_name, 0); + + if (!current_dir) + /* The directory doesn't exist now. It'll be created. In any + case, we don't have to delete any files out of it. */ +- return false; ++ return; + + /* Verify if dump directory is sane */ + if (!dumpdir_ok (current_stat_info.dumpdir)) +- return false; ++ return; + + /* Process renames */ + for (arc = current_stat_info.dumpdir; *arc; arc += strlen (arc) + 1) +@@ -1666,7 +1666,7 @@ try_purge_directory (char const *directory_name) + quote (temp_stub))); + free (temp_stub); + free (current_dir); +- return false; ++ return; + } + } + else if (*arc == 'R') +@@ -1700,7 +1700,7 @@ try_purge_directory (char const *directory_name) + free (current_dir); + /* FIXME: Make sure purge_directory(dst) will return + immediately */ +- return false; ++ return; + } + } + } +@@ -1758,14 +1758,6 @@ try_purge_directory (char const *directory_name) + dumpdir_free (dump); + + free (current_dir); +- return true; +-} +- +-void +-purge_directory (char const *directory_name) +-{ +- if (!try_purge_directory (directory_name)) +- skip_member (); + } + + void +diff --git a/src/list.c b/src/list.c +index 928779e1..c9623eb4 100644 +--- a/src/list.c ++++ b/src/list.c +@@ -437,20 +437,15 @@ read_header (union block **return_block, struct tar_stat_info *info, + if ((status = tar_checksum (header, false)) != HEADER_SUCCESS) + break; + +- /* Good block. Decode file size and return. */ +- +- if (header->header.typeflag == LNKTYPE) +- info->stat.st_size = 0; /* links 0 size on tape */ +- else ++ info->stat.st_size = OFF_FROM_HEADER (header->header.size); ++ if (info->stat.st_size < 0) + { +- info->stat.st_size = OFF_FROM_HEADER (header->header.size); +- if (info->stat.st_size < 0) +- { +- status = HEADER_FAILURE; +- break; +- } ++ status = HEADER_FAILURE; ++ break; + } + ++ info->skipped = false; ++ + if (header->header.typeflag == GNUTYPE_LONGNAME + || header->header.typeflag == GNUTYPE_LONGLINK + || header->header.typeflag == XHDTYPE +@@ -513,11 +508,15 @@ read_header (union block **return_block, struct tar_stat_info *info, + } + + *bp = '\0'; ++ info->skipped = true; + } + else if (header->header.typeflag == XHDTYPE + || header->header.typeflag == SOLARIS_XHDTYPE) +- xheader_read (&info->xhdr, header, +- OFF_FROM_HEADER (header->header.size)); ++ { ++ xheader_read (&info->xhdr, header, ++ OFF_FROM_HEADER (header->header.size)); ++ info->skipped = true; ++ } + else if (header->header.typeflag == XGLTYPE) + { + struct xheader xhdr; +@@ -531,6 +530,7 @@ read_header (union block **return_block, struct tar_stat_info *info, + OFF_FROM_HEADER (header->header.size)); + xheader_decode_global (&xhdr); + xheader_destroy (&xhdr); ++ info->skipped = true; + if (mode == read_header_x_global) + { + status = HEADER_SUCCESS_EXTENDED; +@@ -1440,23 +1440,6 @@ skip_member (void) + skim_member (false); + } + +-static bool +-member_is_dir (struct tar_stat_info *info, char typeflag) +-{ +- switch (typeflag) { +- case AREGTYPE: +- case REGTYPE: +- case CONTTYPE: +- return info->had_trailing_slash; +- +- case DIRTYPE: +- return true; +- +- default: +- return false; +- } +-} +- + /* Skip the current member in the archive. + If MUST_COPY, always copy instead of skipping. */ + void +@@ -1464,18 +1447,17 @@ skim_member (bool must_copy) + { + if (!current_stat_info.skipped) + { +- bool is_dir = member_is_dir (¤t_stat_info, +- current_header->header.typeflag); + set_next_block_after (current_header); + + mv_begin_read (¤t_stat_info); + + if (current_stat_info.is_sparse) + sparse_skim_file (¤t_stat_info, must_copy); +- else if (!is_dir) ++ else + skim_file (current_stat_info.stat.st_size, must_copy); + + mv_end (); ++ current_stat_info.skipped = true; + } + } + +diff --git a/tests/Makefile.am b/tests/Makefile.am +index 6cf726c0..baeb55bb 100644 +--- a/tests/Makefile.am ++++ b/tests/Makefile.am +@@ -133,6 +133,7 @@ TESTSUITE_AT = \ + extrac23.at\ + extrac24.at\ + extrac25.at\ ++ extrac32.at\ + filerem01.at\ + filerem02.at\ + grow.at\ +diff --git a/tests/extrac32.at b/tests/extrac32.at +new file mode 100644 +index 00000000..3829a483 +--- /dev/null ++++ b/tests/extrac32.at +@@ -0,0 +1,47 @@ ++# Check for file injection bug with symlinks. -*- Autotest -*- ++ ++# Copyright 2026 Free Software Foundation, Inc. ++ ++# This file is part of GNU tar. ++ ++# GNU tar is free software; you can redistribute it and/or modify ++# it under the terms of the GNU General Public License as published by ++# the Free Software Foundation; either version 3 of the License, or ++# (at your option) any later version. ++ ++# GNU tar is distributed in the hope that it will be useful, ++# but WITHOUT ANY WARRANTY; without even the implied warranty of ++# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++# GNU General Public License for more details. ++ ++# You should have received a copy of the GNU General Public License ++# along with this program. If not, see <http://www.gnu.org/licenses/>. ++ ++# Thanks to Guillermo de Angel for the bug report and test cases; see: ++# https://lists.gnu.org/r/bug-tar/2026-03/msg00007.html ++ ++AT_SETUP([skip file injection]) ++AT_KEYWORDS([injection]) ++AT_DATA([archive.in], ++[/Td6WFoAAATm1rRGBMDbAYAcIQEcAAAAAAAAACYr+9LgDf8A010AMZhKvfVdtHe4Rxjj7M03ek97 ++UgeKfJ0ORqYg0XDFntWxdTH4PYrTOo9CoqBrnTM2NcwFBrRVr7aFwdd56vddyAw2QGDjxgNexDU3 ++ImTi/+z8ZOLMi/+AybdEpd5aA/M9Maa+8tQ84bySzSAwrmxMWJJ6W9IKvsqfiRa3TrD51v44PZU/ ++KLVKpocS56n/O3g+b+hiZwaysR0eLO+tiU8FB/e3PEq3vTtDFVi/YfZMieBWSzomSX9eF13K1yPY ++UuWgp7VokXqduL0YGNVV40MTPG9oAAAApD6mpajengIAAfcBgBwAAOM4xw6xxGf7AgAAAAAEWVo= ++]) ++AT_CHECK([base64 --help >/dev/null 2>&1 || AT_SKIP_TEST ++xz --help >/dev/null 2>&1 || AT_SKIP_TEST ++base64 -d < archive.in | xz -c -d > archive.tar ++]) ++cp archive.tar /tmp ++AT_CHECK([tar tf archive.tar], ++[0], ++[carrier_entry ++marker.txt ++]) ++AT_CHECK([tar xvf archive.tar], ++[0], ++[carrier_entry ++marker.txt ++]) ++AT_CLEANUP +diff --git a/tests/skipdir.at b/tests/skipdir.at +index 7106ee74..a58a20fd 100644 +--- a/tests/skipdir.at ++++ b/tests/skipdir.at +@@ -42,15 +42,11 @@ base64 -d < archive.in | xz -c -d > archive.tar + AT_CHECK([tar tf archive.tar], + [0], + [owo1/ +-owo2/ + ]) + AT_CHECK([tar vxf archive.tar], + [0], + [owo1/ +-owo2/ + ]) + AT_CHECK([tar -xvf archive.tar --exclude owo1], +-[0], +-[owo2/ +-]) ++[0]) + AT_CLEANUP +diff --git a/tests/testsuite.at b/tests/testsuite.at +index f2229be1..58757005 100644 +--- a/tests/testsuite.at ++++ b/tests/testsuite.at +@@ -349,6 +349,7 @@ m4_include([extrac22.at]) + m4_include([extrac23.at]) + m4_include([extrac24.at]) + m4_include([extrac25.at]) ++m4_include([extrac32.at]) + + m4_include([backup01.at]) + +-- +2.44.1 + diff --git a/meta/recipes-extended/tar/tar_1.35.bb b/meta/recipes-extended/tar/tar_1.35.bb index c7bd1d195e..580dbf61ea 100644 --- a/meta/recipes-extended/tar/tar_1.35.bb +++ b/meta/recipes-extended/tar/tar_1.35.bb @@ -48,6 +48,9 @@ SRC_URI += " \ file://0001-tests-fix-TESTSUITE_AT.patch \ file://0002-tests-check-for-recently-fixed-bug.patch \ file://0003-Exclude-VCS-directory-with-writing-from-an-archive.patch \ + file://CVE-2026-5704-dependent_p1.patch \ + file://CVE-2026-5704-dependent_p2.patch \ + file://CVE-2026-5704.patch \ " inherit ptest -- 2.35.6
-=-=-=-=-=-=-=-=-=-=-=- Links: You receive all messages sent to this group. View/Reply Online (#238604): https://lists.openembedded.org/g/openembedded-core/message/238604 Mute This Topic: https://lists.openembedded.org/mt/119772217/21656 Group Owner: [email protected] Unsubscribe: https://lists.openembedded.org/g/openembedded-core/unsub [[email protected]] -=-=-=-=-=-=-=-=-=-=-=-
