tar/SOURCES/tar-1.34-CVE-2026-5704.patch
2026-08-31 10:42:38 -04:00

505 lines
15 KiB
Diff
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

diff --git a/NEWS b/NEWS
index 41a3c4e2..b4bb185a 100644
--- a/NEWS
+++ b/NEWS
@@ -1,4 +1,4 @@
-GNU tar NEWS - User visible changes. 2026-02-23
+GNU tar NEWS - User visible changes. 2026-08-21
Please send GNU tar bug reports to <bug-tar@gnu.org>
version 1.35.90 (git)
@@ -16,6 +16,11 @@ version 1.35.90 (git)
** tar no longer fails merely if an extraction directory is unreadable
on Linux kernels.
+** tar no longer behaves erratically when reading nonzero size fields
+ in archive headers representing special files, fifos, and symlinks.
+ Although these size fields are typically zero, POSIX allows some to
+ be nonzero, and in practice they do not count data blocks.
+
version 1.34 - Sergey Poznyakoff, 2021-02-13
diff --git a/THANKS b/THANKS
index aee0a924..b9e4ce54 100644
--- a/THANKS
+++ b/THANKS
@@ -38,6 +38,7 @@ Andrey A. Chernov ache@astral.msk.su
Andy Gay andy@rdl.co.uk
Antonio Jose Coutinho ajc@di.uminho.pt
Anthony G. Basile blueness@gentoo.org
+Antonio Teixeira antonio.teixeira@suse.com
Ariel Faigon ariel@engr.sgi.com
Arne Wichmann aw@math.uni-sb.de
Arnold Robbins arnold@gnu.org
diff --git a/src/extract.c b/src/extract.c
index ddc5c206..1c50c1f8 100644
--- a/src/extract.c
+++ b/src/extract.c
@@ -1025,7 +1025,7 @@ safe_dir_mode (struct stat const *st)
/* Extractor functions for various member types */
static int
-extract_dir (char *file_name, int typeflag)
+extract_dir (char *file_name, int typeflag __attribute__((unused)))
{
int status;
mode_t mode;
@@ -1050,8 +1050,6 @@ extract_dir (char *file_name, int 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 (&current_stat_info.stat);
@@ -1229,10 +1227,7 @@ extract_file (char *file_name, int typeflag)
{
fd = sys_exec_command (file_name, 'f', &current_stat_info);
if (fd < 0)
- {
- skip_member ();
- return 0;
- }
+ return 0;
}
else
{
@@ -1249,7 +1244,6 @@ extract_file (char *file_name, int typeflag)
int recover = maybe_recoverable (file_name, true, &interdir_made);
if (recover != RECOVER_OK)
{
- skip_member ();
if (recover == RECOVER_SKIP)
return 0;
open_error (file_name);
@@ -1297,6 +1291,7 @@ extract_file (char *file_name, int typeflag)
}
skip_file (size);
+ current_stat_info.skipped = true;
mv_end ();
@@ -1839,15 +1834,10 @@ extract_archive (void)
typeflag = sparse_member_p (&current_stat_info) ?
GNUTYPE_SPARSE : current_header->header.typeflag;
- if (prepare_to_extract (current_stat_info.file_name, typeflag, &fun))
- {
- if (fun (current_stat_info.file_name, typeflag) == 0)
- return;
- }
- else
- skip_member ();
-
- if (backup_option)
+ bool ok = prepare_to_extract (current_stat_info.file_name, typeflag, &fun)
+ && fun (current_stat_info.file_name, typeflag) == 0;
+ skip_member ();
+ if (!ok && backup_option)
undo_last_backup ();
}
diff --git a/src/incremen.c b/src/incremen.c
index 4f909602..0a11acc9 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 (&current_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 b3370115..0d1c1c10 100644
--- a/src/list.c
+++ b/src/list.c
@@ -441,20 +441,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
@@ -517,11 +512,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;
@@ -535,6 +534,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;
@@ -551,6 +551,22 @@ read_header (union block **return_block, struct tar_stat_info *info,
struct posix_header const *h = &header->header;
char namebuf[sizeof h->prefix + 1 + NAME_FIELD_SIZE + 1];
+ switch (h->typeflag)
+ {
+ /* For these file types, although POSIX does not specify the
+ meaning of the size, it does say there should be no data,
+ so treat the size as zero. */
+ case BLKTYPE: case CHRTYPE: case FIFOTYPE:
+
+ /* For these file types, POSIX requires that the size be zero.
+ Be generous and accept any size as zero, as some
+ nonconforming programs generate nonzero size fields along
+ with no data. */
+ case LNKTYPE: case SYMTYPE:
+
+ info->stat.st_size = 0;
+ }
+
free (recent_long_name);
if (next_long_name)
@@ -1434,6 +1450,24 @@ skip_file (off_t size)
}
}
+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.
NOTE: Current header must be decoded before calling this function. */
void
@@ -1441,17 +1475,18 @@ skip_member (void)
{
if (!current_stat_info.skipped)
{
- char save_typeflag = current_header->header.typeflag;
set_next_block_after (current_header);
mv_begin_read (&current_stat_info);
if (current_stat_info.is_sparse)
sparse_skip_file (&current_stat_info);
- else if (save_typeflag != DIRTYPE)
+ else if (!member_is_dir (&current_stat_info,
+ current_header->header.typeflag))
skip_file (current_stat_info.stat.st_size);
mv_end ();
+ current_stat_info.skipped = true;
}
}
diff --git a/tests/Makefile.am b/tests/Makefile.am
index 9005b02b..f61bd8ac 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -125,6 +125,8 @@ TESTSUITE_AT = \
extrac24.at\
extrac30.at\
extrac31.at\
+ extrac32.at\
+ extrac34.at\
filerem01.at\
filerem02.at\
dirrem01.at\
@@ -232,6 +234,7 @@ TESTSUITE_AT = \
shortupd.at\
shortrec.at\
sigpipe.at\
+ skipdir.at\
sparse01.at\
sparse02.at\
sparse03.at\
diff --git a/tests/extrac32.at b/tests/extrac32.at
new file mode 100644
index 00000000..af40916d
--- /dev/null
+++ b/tests/extrac32.at
@@ -0,0 +1,48 @@
+# 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
+])
+AT_CHECK([tar tf archive.tar],
+[0],
+[carrier_entry
+injected.txt
+marker.txt
+])
+AT_CHECK([tar xvf archive.tar],
+[0],
+[carrier_entry
+injected.txt
+marker.txt
+])
+AT_CLEANUP
diff --git a/tests/extrac34.at b/tests/extrac34.at
new file mode 100644
index 00000000..1cedb463
--- /dev/null
+++ b/tests/extrac34.at
@@ -0,0 +1,40 @@
+# Check hard link with nonzero size field in tarball. -*- 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/>.
+
+# Check extracting from a nonconforming tarball that has
+# a hard link with nonzero size.
+
+AT_SETUP([hard link with a nonzero size field])
+AT_KEYWORDS([extract extrac34 hard link])
+AT_DATA([archive.in],
+[/Td6WFoAAATm1rRGBMBwgFAhARwAAAAAAAAAAAbKrCjgJ/8AaF0AMIAzUBhoiawFdYeRen4lxlj0
+QWRLpUM+28ArRsXVY5bGv4H5kijsqiJ4Z9YIVhZd01+IppF+AkltS60aB8fuUW35Tp/3XzUx9Mq2
+4ypJFzvcgHXsSIvc9L+mmTDuHzhvJQ/oe7ya8QAASbOfD7CgZcIAAYwBgFAAAEWxohaxxGf7AgAA
+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([mkdir dir
+])
+AT_CHECK([tar -C dir -xf archive.tar || exit 1
+cmp dir/a dir/b
+])
+AT_CLEANUP
diff --git a/tests/skipdir.at b/tests/skipdir.at
new file mode 100644
index 00000000..0bc38e4f
--- /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-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/>.
+
+# 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 17daae8e..fe5e7cd4 100644
--- a/tests/testsuite.at
+++ b/tests/testsuite.at
@@ -347,6 +347,8 @@ m4_include([extrac23.at])
m4_include([extrac24.at])
m4_include([extrac30.at])
m4_include([extrac31.at])
+m4_include([extrac32.at])
+m4_include([extrac34.at])
m4_include([backup01.at])
@@ -469,6 +471,7 @@ m4_include([shortupd.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])