import CS git tar-1.35-13.el10_2
This commit is contained in:
parent
dfaff86057
commit
d00c4815b2
99
tar-1.35-Avoid-acl_-prefix-for-functions.patch
Normal file
99
tar-1.35-Avoid-acl_-prefix-for-functions.patch
Normal file
@ -0,0 +1,99 @@
|
||||
From f1ecf60ea59c1503e63e887e455756b68db04806 Mon Sep 17 00:00:00 2001
|
||||
From: Pavel Cahyna <pcahyna@redhat.com>
|
||||
Date: Wed, 24 Jun 2026 18:45:30 +0200
|
||||
Subject: [PATCH] Avoid acl_ prefix for functions
|
||||
|
||||
The acl.h header from libacl uses acl_ prefix for its functions. Avoid
|
||||
defining functions with the same name in order to protect its namespace.
|
||||
---
|
||||
src/xattrs.c | 30 +++++++++++++++---------------
|
||||
1 file changed, 15 insertions(+), 15 deletions(-)
|
||||
|
||||
diff --git a/src/xattrs.c b/src/xattrs.c
|
||||
index 4f5872c1..f98e8e0f 100644
|
||||
--- a/src/xattrs.c
|
||||
+++ b/src/xattrs.c
|
||||
@@ -133,13 +133,13 @@ static struct
|
||||
#ifdef HAVE_POSIX_ACLS
|
||||
|
||||
/* acl-at wrappers, TODO: move to gnulib in future? */
|
||||
-static acl_t acl_get_file_at (int, const char *, acl_type_t);
|
||||
-static int acl_set_file_at (int, const char *, acl_type_t, acl_t);
|
||||
+static acl_t tar_acl_get_file_at (int, const char *, acl_type_t);
|
||||
+static int tar_acl_set_file_at (int, const char *, acl_type_t, acl_t);
|
||||
static int file_has_acl_at (int, char const *, struct stat const *);
|
||||
-static int acl_delete_def_file_at (int, char const *);
|
||||
+static int tar_acl_delete_def_file_at (int, char const *);
|
||||
|
||||
-/* acl_get_file_at */
|
||||
-#define AT_FUNC_NAME acl_get_file_at
|
||||
+/* tar_acl_get_file_at */
|
||||
+#define AT_FUNC_NAME tar_acl_get_file_at
|
||||
#define AT_FUNC_RESULT acl_t
|
||||
#define AT_FUNC_FAIL (acl_t)NULL
|
||||
#define AT_FUNC_F1 acl_get_file
|
||||
@@ -153,8 +153,8 @@ static int acl_delete_def_file_at (int, char const *);
|
||||
#undef AT_FUNC_POST_FILE_PARAM_DECLS
|
||||
#undef AT_FUNC_POST_FILE_ARGS
|
||||
|
||||
-/* acl_set_file_at */
|
||||
-#define AT_FUNC_NAME acl_set_file_at
|
||||
+/* tar_acl_set_file_at */
|
||||
+#define AT_FUNC_NAME tar_acl_set_file_at
|
||||
#define AT_FUNC_F1 acl_set_file
|
||||
#define AT_FUNC_POST_FILE_PARAM_DECLS , acl_type_t type, acl_t acl
|
||||
#define AT_FUNC_POST_FILE_ARGS , type, acl
|
||||
@@ -164,8 +164,8 @@ static int acl_delete_def_file_at (int, char const *);
|
||||
#undef AT_FUNC_POST_FILE_PARAM_DECLS
|
||||
#undef AT_FUNC_POST_FILE_ARGS
|
||||
|
||||
-/* acl_delete_def_file_at */
|
||||
-#define AT_FUNC_NAME acl_delete_def_file_at
|
||||
+/* tar_acl_delete_def_file_at */
|
||||
+#define AT_FUNC_NAME tar_acl_delete_def_file_at
|
||||
#define AT_FUNC_F1 acl_delete_def_file
|
||||
#define AT_FUNC_POST_FILE_PARAM_DECLS
|
||||
#define AT_FUNC_POST_FILE_ARGS
|
||||
@@ -294,10 +294,10 @@ xattrs__acls_set (struct tar_stat_info const *st,
|
||||
FILE_NAME may already have inherited default acls from parent
|
||||
directory; clean them up. */
|
||||
struct fdbase f1 = fdbase (file_name);
|
||||
- if (f1.fd == BADFD || acl_delete_def_file_at (f1.fd, f1.base))
|
||||
+ if (f1.fd == BADFD || tar_acl_delete_def_file_at (f1.fd, f1.base))
|
||||
WARNOPT (WARN_XATTR_WRITE,
|
||||
(0, errno,
|
||||
- _("acl_delete_def_file_at: Cannot drop default POSIX ACLs "
|
||||
+ _("tar_acl_delete_def_file_at: Cannot drop default POSIX ACLs "
|
||||
"for file '%s'"),
|
||||
file_name));
|
||||
return;
|
||||
@@ -311,11 +311,11 @@ xattrs__acls_set (struct tar_stat_info const *st,
|
||||
}
|
||||
|
||||
struct fdbase f = fdbase (file_name);
|
||||
- if (f.fd == BADFD || acl_set_file_at (f.fd, f.base, type, acl) == -1)
|
||||
+ if (f.fd == BADFD || tar_acl_set_file_at (f.fd, f.base, type, acl) == -1)
|
||||
/* warn even if filesystem does not support acls */
|
||||
WARNOPT (WARN_XATTR_WRITE,
|
||||
(0, errno,
|
||||
- _ ("acl_set_file_at: Cannot set POSIX ACLs for file '%s'"),
|
||||
+ _ ("tar_acl_set_file_at: Cannot set POSIX ACLs for file '%s'"),
|
||||
file_name));
|
||||
|
||||
acl_free (acl);
|
||||
@@ -351,10 +351,10 @@ acls_get_text (int parentfd, const char *file_name, acl_type_t type,
|
||||
char *val = NULL;
|
||||
acl_t acl;
|
||||
|
||||
- if (!(acl = acl_get_file_at (parentfd, file_name, type)))
|
||||
+ if (!(acl = tar_acl_get_file_at (parentfd, file_name, type)))
|
||||
{
|
||||
if (errno != ENOTSUP)
|
||||
- call_arg_warn ("acl_get_file_at", file_name);
|
||||
+ call_arg_warn ("tar_acl_get_file_at", file_name);
|
||||
return;
|
||||
}
|
||||
|
||||
--
|
||||
2.47.3
|
||||
|
||||
445
tar-1.35-CVE-2026-18477.patch
Normal file
445
tar-1.35-CVE-2026-18477.patch
Normal file
@ -0,0 +1,445 @@
|
||||
diff --git a/NEWS b/NEWS
|
||||
index 45fedbf5..f4621d7f 100644
|
||||
--- a/NEWS
|
||||
+++ b/NEWS
|
||||
@@ -13,6 +13,10 @@ version 1.35.90 (git)
|
||||
** When extracting, tar no longer follows symbolic links to targets
|
||||
outside the working directory.
|
||||
|
||||
+** When extracting from an incremental dump, tar now strips leading '/'
|
||||
+ from names of temporary directories specified by 'X' entries,
|
||||
+ unless --absolute-names (-P) is used.
|
||||
+
|
||||
** tar no longer fails merely if an extraction directory is unreadable
|
||||
on Linux kernels.
|
||||
|
||||
diff --git a/THANKS b/THANKS
|
||||
index b9e4ce54..f12d98c7 100644
|
||||
--- a/THANKS
|
||||
+++ b/THANKS
|
||||
@@ -330,6 +330,7 @@ Manuel Munier Manuel.Munier@loria.fr
|
||||
Marc Boucher marc@cam.org
|
||||
Marc Ewing marc@redhat.com
|
||||
Marcin Matuszewski marcin@frodo.nask.org.pl
|
||||
+Marcin Wyczechowski mwyczechowski@afine.com
|
||||
Marcus Daniels marcus@sysc.pdx.edu
|
||||
Mark Bynum bynum@cennas.nhmfl.gov
|
||||
Mark Clements mpc@mbsmm.com
|
||||
@@ -367,6 +368,7 @@ Michael Schmidt michael@muc.de
|
||||
Michael Schwingen m.schwingen@stochastik.rwth-aachen.de
|
||||
Michael Smolsky fnsiguc@astro.weizmann.ac.il
|
||||
Michal Žejdl zejdl@suas.cz
|
||||
+Michał Majchrowicz mmajchrowicz@afine.com
|
||||
Mike Muuss mike@brl.mil
|
||||
Mike Nolan nolan@lpl.arizona.edu
|
||||
Mike Rogers mike@demon.net
|
||||
diff --git a/lib/Makefile.am b/lib/Makefile.am
|
||||
index 38645b2c..45152c49 100644
|
||||
--- a/lib/Makefile.am
|
||||
+++ b/lib/Makefile.am
|
||||
@@ -30,6 +30,7 @@ AM_CPPFLAGS = -I$(top_srcdir)/gnu -I../ -I../gnu
|
||||
AM_CFLAGS = $(GNULIB_WARN_CFLAGS) $(WERROR_CFLAGS)
|
||||
|
||||
noinst_HEADERS = \
|
||||
+ mkdtempat.h\
|
||||
paxlib.h\
|
||||
rmt.h\
|
||||
system.h\
|
||||
@@ -38,6 +39,7 @@ noinst_HEADERS = \
|
||||
xattr-at.h
|
||||
|
||||
libtar_a_SOURCES = \
|
||||
+ mkdtempat.c\
|
||||
paxerror.c paxexit-status.c paxlib.h paxnames.c \
|
||||
rtapelib.c \
|
||||
rmt.h \
|
||||
diff --git a/lib/mkdtempat.c b/lib/mkdtempat.c
|
||||
new file mode 100644
|
||||
index 00000000..e35b9bba
|
||||
--- /dev/null
|
||||
+++ b/lib/mkdtempat.c
|
||||
@@ -0,0 +1,45 @@
|
||||
+/* Copyright 2026 Free Software Foundation, Inc.
|
||||
+
|
||||
+ This program 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.
|
||||
+
|
||||
+ This program 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/>.
|
||||
+
|
||||
+ Written by Paul Eggert. */
|
||||
+
|
||||
+#include <config.h>
|
||||
+
|
||||
+#include "mkdtempat.h"
|
||||
+
|
||||
+#include <tempname.h>
|
||||
+
|
||||
+#include <stddef.h>
|
||||
+#include <sys/stat.h>
|
||||
+
|
||||
+static int
|
||||
+try_dir (char *tmpl, void *flags)
|
||||
+{
|
||||
+ int *pdirfd = flags;
|
||||
+ return mkdirat (*pdirfd, tmpl, S_IRUSR | S_IWUSR | S_IXUSR);
|
||||
+}
|
||||
+
|
||||
+/* Relative to the directory DIRFD if XTEMPLATE is relative,
|
||||
+ generate a unique temporary directory from XTEMPLATE.
|
||||
+ The last six characters of XTEMPLATE must be "XXXXXX";
|
||||
+ replace them with a string that makes the generated directory unique.
|
||||
+ Create the directory mode 700, and return its name.
|
||||
+ On failure, return NULL and set errno. */
|
||||
+char *
|
||||
+mkdtempat (int dirfd, char *xtemplate)
|
||||
+{
|
||||
+ return (try_tempname_len (xtemplate, 0, &dirfd, try_dir, 6) < 0
|
||||
+ ? NULL : xtemplate);
|
||||
+}
|
||||
diff --git a/lib/mkdtempat.h b/lib/mkdtempat.h
|
||||
new file mode 100644
|
||||
index 00000000..03a18dc1
|
||||
--- /dev/null
|
||||
+++ b/lib/mkdtempat.h
|
||||
@@ -0,0 +1 @@
|
||||
+char *mkdtempat (int, char *);
|
||||
diff --git a/src/incremen.c b/src/incremen.c
|
||||
index 194d5cb1..a808e1c4 100644
|
||||
--- a/src/incremen.c
|
||||
+++ b/src/incremen.c
|
||||
@@ -19,6 +19,7 @@
|
||||
|
||||
#include <system.h>
|
||||
#include <hash.h>
|
||||
+#include <mkdtempat.h>
|
||||
#include <quotearg.h>
|
||||
#include "common.h"
|
||||
|
||||
@@ -1653,13 +1654,16 @@ purge_directory (char const *directory_name)
|
||||
if (*arc == 'X')
|
||||
{
|
||||
#define TEMP_DIR_TEMPLATE "tar.XXXXXX"
|
||||
- size_t len = strlen (arc + 1);
|
||||
+ char *d = safer_name_suffix (arc + 1, false, absolute_names_option);
|
||||
+ size_t len = strlen (d);
|
||||
temp_stub = xrealloc (temp_stub, len + 1 + sizeof TEMP_DIR_TEMPLATE);
|
||||
- memcpy (temp_stub, arc + 1, len);
|
||||
- temp_stub[len] = '/';
|
||||
- memcpy (temp_stub + len + 1, TEMP_DIR_TEMPLATE,
|
||||
+ char *copy_end = mempcpy (temp_stub, d, len);
|
||||
+ *copy_end = '/';
|
||||
+ memcpy (copy_end + !ISSLASH (copy_end[-1]), TEMP_DIR_TEMPLATE,
|
||||
sizeof TEMP_DIR_TEMPLATE);
|
||||
- if (!mkdtemp (temp_stub))
|
||||
+ struct fdbase f = fdbase (temp_stub);
|
||||
+ if (f.fd == BADFD
|
||||
+ || !mkdtempat (f.fd, temp_stub + (f.base - temp_stub)))
|
||||
{
|
||||
ERROR ((0, errno,
|
||||
_("Cannot create temporary directory using template %s"),
|
||||
diff --git a/tests/Makefile.am b/tests/Makefile.am
|
||||
index b9ed8270..6031d7a1 100644
|
||||
--- a/tests/Makefile.am
|
||||
+++ b/tests/Makefile.am
|
||||
@@ -209,6 +209,8 @@ TESTSUITE_AT = \
|
||||
rename04.at\
|
||||
rename05.at\
|
||||
rename06.at\
|
||||
+ rename08.at\
|
||||
+ rename09.at\
|
||||
remfiles01.at\
|
||||
remfiles02.at\
|
||||
remfiles03.at\
|
||||
diff --git a/tests/rename08.at b/tests/rename08.at
|
||||
new file mode 100644
|
||||
index 00000000..5452fb4f
|
||||
--- /dev/null
|
||||
+++ b/tests/rename08.at
|
||||
@@ -0,0 +1,131 @@
|
||||
+# Process this file with autom4te to create testsuite. -*- Autotest -*-
|
||||
+
|
||||
+# Test suite for GNU tar.
|
||||
+# Copyright 2006-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: Handling of cyclic renames in incremental archives.
|
||||
+
|
||||
+AT_SETUP([cyclic renames])
|
||||
+AT_KEYWORDS([incremental rename rename08 cyclic-rename])
|
||||
+
|
||||
+AT_TAR_CHECK([
|
||||
+AT_SORT_PREREQ
|
||||
+
|
||||
+mkdir foo
|
||||
+genfile --file foo/file1
|
||||
+genfile --file foo/file2
|
||||
+
|
||||
+mkdir foo/a
|
||||
+genfile --file foo/a/filea
|
||||
+
|
||||
+mkdir foo/b
|
||||
+genfile --file foo/b/fileb
|
||||
+
|
||||
+mkdir foo/c
|
||||
+genfile --file foo/c/filec
|
||||
+
|
||||
+sleep 1
|
||||
+
|
||||
+echo "First dump"
|
||||
+echo "First dump">&2
|
||||
+tar -g incr -cf arch.1 -v foo 2>tmperr
|
||||
+sort tmperr >&2
|
||||
+
|
||||
+# Shuffle directories:
|
||||
+(cd foo
|
||||
+mv a $$
|
||||
+mv c a
|
||||
+mv b c
|
||||
+mv $$ b)
|
||||
+
|
||||
+echo "Second dump"
|
||||
+echo "Second dump" >&2
|
||||
+tar -g incr -cf arch.2 -v foo 2>tmperr
|
||||
+sort tmperr >&2
|
||||
+
|
||||
+mkdir -p restoreparent/restore
|
||||
+cd restoreparent
|
||||
+tar xfg ../arch.1 /dev/null --warning=no-timestamp -C restore
|
||||
+
|
||||
+echo "Begin directory listing 1"
|
||||
+( cd restore; find foo ) | sort
|
||||
+echo "End directory listing 1"
|
||||
+
|
||||
+tar xfgv ../arch.2 /dev/null --warning=no-timestamp -C restore
|
||||
+echo Begin directory listing 2
|
||||
+( cd restore ; find foo ) | sort
|
||||
+echo End directory listing 2
|
||||
+],
|
||||
+[0],
|
||||
+[First dump
|
||||
+foo/
|
||||
+foo/a/
|
||||
+foo/b/
|
||||
+foo/c/
|
||||
+foo/file1
|
||||
+foo/file2
|
||||
+foo/a/filea
|
||||
+foo/b/fileb
|
||||
+foo/c/filec
|
||||
+Second dump
|
||||
+foo/
|
||||
+foo/a/
|
||||
+foo/b/
|
||||
+foo/c/
|
||||
+Begin directory listing 1
|
||||
+foo
|
||||
+foo/a
|
||||
+foo/a/filea
|
||||
+foo/b
|
||||
+foo/b/fileb
|
||||
+foo/c
|
||||
+foo/c/filec
|
||||
+foo/file1
|
||||
+foo/file2
|
||||
+End directory listing 1
|
||||
+foo/
|
||||
+foo/a/
|
||||
+foo/b/
|
||||
+foo/c/
|
||||
+Begin directory listing 2
|
||||
+foo
|
||||
+foo/a
|
||||
+foo/a/filec
|
||||
+foo/b
|
||||
+foo/b/filea
|
||||
+foo/c
|
||||
+foo/c/fileb
|
||||
+foo/file1
|
||||
+foo/file2
|
||||
+End directory listing 2
|
||||
+],
|
||||
+[First dump
|
||||
+tar: foo/a: Directory is new
|
||||
+tar: foo/b: Directory is new
|
||||
+tar: foo/c: Directory is new
|
||||
+tar: foo: Directory is new
|
||||
+Second dump
|
||||
+tar: foo/a: Directory has been renamed from 'foo/c'
|
||||
+tar: foo/b: Directory has been renamed from 'foo/a'
|
||||
+tar: foo/c: Directory has been renamed from 'foo/b'
|
||||
+],
|
||||
+[],[],[gnu, oldgnu, posix])
|
||||
+
|
||||
+AT_CLEANUP
|
||||
+
|
||||
+# End of rename03.at
|
||||
diff --git a/tests/rename09.at b/tests/rename09.at
|
||||
new file mode 100644
|
||||
index 00000000..8346ff0a
|
||||
--- /dev/null
|
||||
+++ b/tests/rename09.at
|
||||
@@ -0,0 +1,129 @@
|
||||
+# Process this file with autom4te to create testsuite. -*- Autotest -*-
|
||||
+
|
||||
+# Test suite for GNU tar.
|
||||
+# Copyright 2006-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: Handling of cyclic renames in incremental archives.
|
||||
+
|
||||
+AT_SETUP([cyclic renames])
|
||||
+AT_KEYWORDS([incremental rename rename09 cyclic-rename])
|
||||
+
|
||||
+AT_TAR_CHECK([
|
||||
+AT_SORT_PREREQ
|
||||
+
|
||||
+mkdir foo
|
||||
+genfile --file foo/file1
|
||||
+genfile --file foo/file2
|
||||
+
|
||||
+mkdir foo/a
|
||||
+genfile --file foo/a/filea
|
||||
+
|
||||
+mkdir foo/b
|
||||
+genfile --file foo/b/fileb
|
||||
+
|
||||
+mkdir foo/c
|
||||
+genfile --file foo/c/filec
|
||||
+
|
||||
+sleep 1
|
||||
+
|
||||
+echo "First dump"
|
||||
+echo "First dump">&2
|
||||
+tar -g incr -cf arch.1 -v foo 2>tmperr
|
||||
+sort tmperr >&2
|
||||
+
|
||||
+# Shuffle directories:
|
||||
+(cd foo
|
||||
+mv a $$
|
||||
+mv c a
|
||||
+mv b c
|
||||
+mv $$ b)
|
||||
+
|
||||
+echo "Second dump"
|
||||
+echo "Second dump" >&2
|
||||
+tar -g incr -cf arch.2 -v foo 2>tmperr
|
||||
+sort tmperr >&2
|
||||
+
|
||||
+tar xfg arch.1 /dev/null --warning=no-timestamp --one-top-level=restore
|
||||
+
|
||||
+echo "Begin directory listing 1"
|
||||
+( cd restore; find foo ) | sort
|
||||
+echo "End directory listing 1"
|
||||
+
|
||||
+tar xfgv arch.2 /dev/null --warning=no-timestamp --one-top-level=restore
|
||||
+echo Begin directory listing 2
|
||||
+( cd restore ; find foo ) | sort
|
||||
+echo End directory listing 2
|
||||
+],
|
||||
+[0],
|
||||
+[First dump
|
||||
+foo/
|
||||
+foo/a/
|
||||
+foo/b/
|
||||
+foo/c/
|
||||
+foo/file1
|
||||
+foo/file2
|
||||
+foo/a/filea
|
||||
+foo/b/fileb
|
||||
+foo/c/filec
|
||||
+Second dump
|
||||
+foo/
|
||||
+foo/a/
|
||||
+foo/b/
|
||||
+foo/c/
|
||||
+Begin directory listing 1
|
||||
+foo
|
||||
+foo/a
|
||||
+foo/a/filea
|
||||
+foo/b
|
||||
+foo/b/fileb
|
||||
+foo/c
|
||||
+foo/c/filec
|
||||
+foo/file1
|
||||
+foo/file2
|
||||
+End directory listing 1
|
||||
+foo/
|
||||
+foo/a/
|
||||
+foo/b/
|
||||
+foo/c/
|
||||
+Begin directory listing 2
|
||||
+foo
|
||||
+foo/a
|
||||
+foo/a/filec
|
||||
+foo/b
|
||||
+foo/b/filea
|
||||
+foo/c
|
||||
+foo/c/fileb
|
||||
+foo/file1
|
||||
+foo/file2
|
||||
+End directory listing 2
|
||||
+],
|
||||
+[First dump
|
||||
+tar: foo/a: Directory is new
|
||||
+tar: foo/b: Directory is new
|
||||
+tar: foo/c: Directory is new
|
||||
+tar: foo: Directory is new
|
||||
+Second dump
|
||||
+tar: foo/a: Directory has been renamed from 'foo/c'
|
||||
+tar: foo/b: Directory has been renamed from 'foo/a'
|
||||
+tar: foo/c: Directory has been renamed from 'foo/b'
|
||||
+],
|
||||
+[],[],[gnu, oldgnu, posix])
|
||||
+
|
||||
+AT_CLEANUP
|
||||
+
|
||||
+# End of rename03.at
|
||||
diff --git a/tests/testsuite.at b/tests/testsuite.at
|
||||
index 0cc7adda..e4edaba5 100644
|
||||
--- a/tests/testsuite.at
|
||||
+++ b/tests/testsuite.at
|
||||
@@ -401,6 +401,8 @@ m4_include([rename03.at])
|
||||
m4_include([rename04.at])
|
||||
m4_include([rename05.at])
|
||||
m4_include([rename06.at])
|
||||
+m4_include([rename08.at])
|
||||
+m4_include([rename09.at])
|
||||
m4_include([chtype.at])
|
||||
|
||||
AT_BANNER([Ignore failing reads])
|
||||
497
tar-1.35-CVE-2026-5704.patch
Normal file
497
tar-1.35-CVE-2026-5704.patch
Normal file
@ -0,0 +1,497 @@
|
||||
diff --git a/NEWS b/NEWS
|
||||
index 5eadfe9d..ffb4f54f 100644
|
||||
--- a/NEWS
|
||||
+++ b/NEWS
|
||||
@@ -20,6 +20,10 @@ version 1.35.90 (git)
|
||||
not even temporarily. This matches the documentation better and avoids
|
||||
some permissions glitches.
|
||||
|
||||
+** 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.35 - Sergey Poznyakoff, 2023-07-18
|
||||
|
||||
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 cfaf5daf..4685f83c 100644
|
||||
--- a/src/extract.c
|
||||
+++ b/src/extract.c
|
||||
@@ -1056,7 +1056,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, MAYBE_UNUSED int typeflag)
|
||||
{
|
||||
int status;
|
||||
mode_t mode;
|
||||
@@ -1081,8 +1081,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 (¤t_stat_info.stat);
|
||||
|
||||
@@ -1266,10 +1264,7 @@ extract_file (char *file_name, int typeflag)
|
||||
{
|
||||
fd = sys_exec_command (file_name, 'f', ¤t_stat_info);
|
||||
if (fd < 0)
|
||||
- {
|
||||
- skip_member ();
|
||||
- return 0;
|
||||
- }
|
||||
+ return 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -1289,7 +1284,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);
|
||||
@@ -1337,6 +1331,7 @@ extract_file (char *file_name, int typeflag)
|
||||
}
|
||||
|
||||
skim_file (size, false);
|
||||
+ current_stat_info.skipped = true;
|
||||
|
||||
mv_end ();
|
||||
|
||||
@@ -1869,15 +1864,10 @@ extract_archive (void)
|
||||
typeflag = sparse_member_p (¤t_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 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 e9a68159..7d7cde5a 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;
|
||||
@@ -547,6 +547,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)
|
||||
@@ -1440,6 +1456,24 @@ 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,17 +1481,18 @@ skim_member (bool must_copy)
|
||||
{
|
||||
if (!current_stat_info.skipped)
|
||||
{
|
||||
- char save_typeflag = 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 (!member_is_dir (¤t_stat_info,
|
||||
+ current_header->header.typeflag))
|
||||
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 311a4315..b9ed8270 100644
|
||||
--- a/tests/Makefile.am
|
||||
+++ b/tests/Makefile.am
|
||||
@@ -128,6 +128,8 @@ TESTSUITE_AT = \
|
||||
extrac27.at\
|
||||
extrac30.at\
|
||||
extrac31.at\
|
||||
+ extrac32.at\
|
||||
+ extrac34.at\
|
||||
filerem01.at\
|
||||
filerem02.at\
|
||||
dirrem01.at\
|
||||
@@ -235,6 +237,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 fd8b8b59..0cc7adda 100644
|
||||
--- a/tests/testsuite.at
|
||||
+++ b/tests/testsuite.at
|
||||
@@ -352,6 +352,8 @@ m4_include([extrac25.at])
|
||||
m4_include([extrac27.at])
|
||||
m4_include([extrac30.at])
|
||||
m4_include([extrac31.at])
|
||||
+m4_include([extrac32.at])
|
||||
+m4_include([extrac34.at])
|
||||
|
||||
m4_include([backup01.at])
|
||||
|
||||
@@ -475,6 +477,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])
|
||||
793
tar-1.35-fix-absolute-one-top-level.patch
Normal file
793
tar-1.35-fix-absolute-one-top-level.patch
Normal file
@ -0,0 +1,793 @@
|
||||
diff --git a/NEWS b/NEWS
|
||||
index ffb4f54f..45fedbf5 100644
|
||||
--- a/NEWS
|
||||
+++ b/NEWS
|
||||
@@ -1,12 +1,12 @@
|
||||
-GNU tar NEWS - User visible changes. 2026-01-30
|
||||
+GNU tar NEWS - User visible changes. 2026-07-22
|
||||
Please send GNU tar bug reports to <bug-tar@gnu.org>
|
||||
|
||||
version 1.35.90 (git)
|
||||
|
||||
* Changes to behavior
|
||||
|
||||
-** --one-top-level=DIR now requires DIR to be relative.
|
||||
- Previously this restriction was alluded to in the manual but not enforced.
|
||||
+** The --one-top-level option now requires either -x or -d mode.
|
||||
+ Previously the behavior was unspecified in other operation modes.
|
||||
|
||||
* Bug fixes
|
||||
|
||||
diff --git a/doc/tar.texi b/doc/tar.texi
|
||||
index 9b04b94b..91302f05 100644
|
||||
--- a/doc/tar.texi
|
||||
+++ b/doc/tar.texi
|
||||
@@ -3271,14 +3271,14 @@ directory.
|
||||
|
||||
@opsummary{one-top-level}
|
||||
@item --one-top-level[=@var{dir}]
|
||||
-Tells @command{tar} to create a new directory beneath the extraction directory
|
||||
-(or the one passed to @option{-C}) and use it to prevent @command{tar}
|
||||
-from modifying files outside that directory.
|
||||
-If @var{dir} is present, it must be a relative file name.
|
||||
-If it is absent, the name of the new directory
|
||||
+Tells @command{tar} to use a directory beneath the extraction directory
|
||||
+(or the one passed to @option{-C}) to prevent @command{tar}
|
||||
+from accessing files outside that directory.
|
||||
+If @var{dir} is absent, the name of the directory
|
||||
is the base name of the archive minus any recognized archive suffix.
|
||||
-If multiple @option{-C} options are present,
|
||||
-each has its own subdirectory with the same name.
|
||||
+If @var{dir} is an absolute file name, that is the only such directory;
|
||||
+otherwise, the working directory and the directories specified by
|
||||
+any @option{-C} options each have the named subdirectory.
|
||||
Any member names that do not begin
|
||||
with that directory name (after
|
||||
transformations from @option{--transform} and
|
||||
@@ -3286,6 +3286,9 @@ transformations from @option{--transform} and
|
||||
file name suffixes are @samp{.tar}, and any compression suffixes
|
||||
recognizable by @xref{--auto-compress}.
|
||||
|
||||
+This option can be used only when extracting, comparing, or reading
|
||||
+from the archive.
|
||||
+
|
||||
@opsummary{overwrite}
|
||||
@item --overwrite
|
||||
|
||||
diff --git a/src/common.h b/src/common.h
|
||||
index ac0e7eef..fb732f0b 100644
|
||||
--- a/src/common.h
|
||||
+++ b/src/common.h
|
||||
@@ -246,7 +246,6 @@ GLOBAL bool numeric_owner_option;
|
||||
GLOBAL bool one_file_system_option;
|
||||
|
||||
/* Create a top-level directory for extracting based on the archive name. */
|
||||
-GLOBAL bool one_top_level_option;
|
||||
GLOBAL char *one_top_level_dir;
|
||||
|
||||
/* Specified value to be put into tar file in place of stat () results, or
|
||||
@@ -545,6 +544,7 @@ void verify_volume (void);
|
||||
/* Module extract.c. */
|
||||
|
||||
void extr_init (void);
|
||||
+int make_directories (char *file_name, bool *interdir_made);
|
||||
void extract_archive (void);
|
||||
void extract_finish (void);
|
||||
bool rename_directory (char *src, char *dst);
|
||||
@@ -635,6 +635,20 @@ void skim_member (bool must_copy);
|
||||
#define min(a, b) ((a) < (b) ? (a) : (b))
|
||||
#define max(a, b) ((a) < (b) ? (b) : (a))
|
||||
|
||||
+/* A directory FD, and a file name BASE that matches the regexp "[^/]*[/]*".
|
||||
+ If BASE is absolute (i.e., matches "/+"), FD is valid but irrelevant.
|
||||
+ Otherwise if FD == AT_FDCWD, BASE is relative to the current directory.
|
||||
+ Otherwise if FD == BADFD, the parent directory could not be opened
|
||||
+ and BASE is merely the original file name's basename.
|
||||
+ Otherwise, FD is open to a parent directory,
|
||||
+ and BASE is relative to that directory.
|
||||
+ BASE points to storage managed elsewhere; do not free it directly. */
|
||||
+struct fdbase
|
||||
+ {
|
||||
+ int fd;
|
||||
+ char const *base;
|
||||
+ };
|
||||
+
|
||||
char const *quote_n_colon (int n, char const *arg);
|
||||
void assign_string_or_null (char **dest, const char *src)
|
||||
ATTRIBUTE_NONNULL ((1));
|
||||
@@ -657,6 +671,8 @@ void namebuf_free (namebuf_t buf);
|
||||
char *namebuf_name (namebuf_t buf, const char *name);
|
||||
|
||||
const char *tar_dirname (void);
|
||||
+char *transform_top_level (const char *name)
|
||||
+ _GL_ATTRIBUTE_MALLOC _GL_ATTRIBUTE_DEALLOC_FREE;
|
||||
|
||||
/* Represent N using a signed integer I such that (uintmax_t) I == N.
|
||||
With a good optimizing compiler, this is equivalent to (intmax_t) i
|
||||
@@ -728,10 +744,10 @@ size_t blocking_write (int fd, void const *buf, size_t count);
|
||||
enum { BADFD = AT_FDCWD == -1 ? -2 : -1 };
|
||||
|
||||
extern int chdir_current;
|
||||
-int chdir_arg (char const *dir);
|
||||
-void chdir_do (int dir);
|
||||
+int chdir_arg (char const *dir, bool one_top_level);
|
||||
+void chdir_do (int dir, bool create);
|
||||
struct chdir_id { int err; dev_t st_dev; ino_t st_ino; } chdir_id (void);
|
||||
-struct fdbase { int fd; char const *base; } fdbase (char const *);
|
||||
+struct fdbase fdbase (char const *);
|
||||
struct fdbase fdbase1 (char const *);
|
||||
void fdbase_clear (void);
|
||||
int chdir_count (void);
|
||||
diff --git a/src/create.c b/src/create.c
|
||||
index 51c3f213..582ca4c6 100644
|
||||
--- a/src/create.c
|
||||
+++ b/src/create.c
|
||||
@@ -1266,7 +1266,7 @@ ensure_slash (char **pstr)
|
||||
/* If we just ran out of file descriptors, release a file descriptor
|
||||
in the directory chain somewhere leading from DIR->parent->parent
|
||||
up through the root. Return true if successful, false (preserving
|
||||
- errno == EMFILE) otherwise.
|
||||
+ errno) otherwise.
|
||||
|
||||
Do not release DIR's file descriptor, or DIR's parent, as other
|
||||
code assumes that they work. On some operating systems, another
|
||||
@@ -1277,7 +1277,8 @@ ensure_slash (char **pstr)
|
||||
static bool
|
||||
open_failure_recover (struct tar_stat_info const *dir)
|
||||
{
|
||||
- if (errno == EMFILE && dir && dir->parent)
|
||||
+ int err = errno;
|
||||
+ if ((err == EMFILE || err == ENFILE) && dir && dir->parent)
|
||||
{
|
||||
struct tar_stat_info *p;
|
||||
for (p = dir->parent->parent; p; p = p->parent)
|
||||
@@ -1286,7 +1287,7 @@ open_failure_recover (struct tar_stat_info const *dir)
|
||||
tar_stat_close (p);
|
||||
return true;
|
||||
}
|
||||
- errno = EMFILE;
|
||||
+ errno = err;
|
||||
}
|
||||
|
||||
return false;
|
||||
diff --git a/src/extract.c b/src/extract.c
|
||||
index 4685f83c..a31f560b 100644
|
||||
--- a/src/extract.c
|
||||
+++ b/src/extract.c
|
||||
@@ -698,7 +698,7 @@ fixup_delayed_set_stat (char const *src, char const *dst)
|
||||
create all required directories. Return zero if all the required
|
||||
directories were created, nonzero (issuing a diagnostic) otherwise.
|
||||
Set *INTERDIR_MADE (unless NULL) if at least one directory was created. */
|
||||
-static int
|
||||
+int
|
||||
make_directories (char *file_name, bool *interdir_made)
|
||||
{
|
||||
char *cursor0 = file_name + FILE_SYSTEM_PREFIX_LEN (file_name);
|
||||
@@ -741,7 +741,7 @@ make_directories (char *file_name, bool *interdir_made)
|
||||
repair_delayed_set_stat may need to update the struct. */
|
||||
delay_set_stat (file_name,
|
||||
0, mode & ~ current_umask, MODE_RWX,
|
||||
- desired_mode, AT_SYMLINK_NOFOLLOW);
|
||||
+ desired_mode, AT_SYMLINK_NOFOLLOW);
|
||||
if (interdir_made)
|
||||
*interdir_made = true;
|
||||
print_for_mkdir (file_name, desired_mode);
|
||||
@@ -974,7 +974,7 @@ apply_nonancestor_delayed_set_stat (char const *file_name, bool after_links)
|
||||
&& memcmp (file_name, data->file_name, data->file_name_len) == 0))
|
||||
break;
|
||||
|
||||
- chdir_do (data->change_dir);
|
||||
+ chdir_do (data->change_dir, false);
|
||||
|
||||
if (check_for_renamed_directories)
|
||||
{
|
||||
@@ -1844,7 +1844,7 @@ extract_archive (void)
|
||||
{
|
||||
int dir = chdir_current;
|
||||
apply_nonancestor_delayed_set_stat (current_stat_info.file_name, 0);
|
||||
- chdir_do (dir);
|
||||
+ chdir_do (dir, false);
|
||||
}
|
||||
|
||||
/* Take a safety backup of a previously existing file. */
|
||||
@@ -1864,8 +1864,35 @@ extract_archive (void)
|
||||
typeflag = sparse_member_p (¤t_stat_info) ?
|
||||
GNUTYPE_SPARSE : current_header->header.typeflag;
|
||||
|
||||
- bool ok = prepare_to_extract (current_stat_info.file_name, typeflag, &fun)
|
||||
- && fun (current_stat_info.file_name, typeflag) == 0;
|
||||
+ bool ok = false;
|
||||
+ if (prepare_to_extract (current_stat_info.file_name, typeflag, &fun))
|
||||
+ {
|
||||
+ if (one_top_level_dir)
|
||||
+ {
|
||||
+ /* Create one_top_level dir if it does not exist. */
|
||||
+ chdir_do (chdir_current, true);
|
||||
+ /* Flush delayed stat to mirror the code above that does it
|
||||
+ before extracting a new entry. Creating the one_top_level
|
||||
+ dir may have created new delayed_set_stat interdir
|
||||
+ entries, so repeat the operation. Ideally this should not
|
||||
+ be needed, but the newly-created interdir entries have
|
||||
+ st_dev/st_ino uninitialized, which would be a problem if
|
||||
+ there is a "." entry afterwards:
|
||||
+ apply_nonancestor_delayed_set_stat would use the
|
||||
+ uninitialized values. Ideally, st_dev/st_ino would be
|
||||
+ initialized by mark_metadata_set, but this one does not
|
||||
+ take chdir into account, so it stats a wrong file. */
|
||||
+ if (!delay_directory_restore_option)
|
||||
+ {
|
||||
+ int dir = chdir_current;
|
||||
+ apply_nonancestor_delayed_set_stat (current_stat_info.file_name,
|
||||
+ false);
|
||||
+ chdir_do (dir, false);
|
||||
+ }
|
||||
+ }
|
||||
+ if (fun (current_stat_info.file_name, typeflag) == 0)
|
||||
+ ok = true;
|
||||
+ }
|
||||
skip_member ();
|
||||
if (!ok && backup_option)
|
||||
undo_last_backup ();
|
||||
@@ -1878,7 +1905,7 @@ apply_delayed_link (struct delayed_link *ds)
|
||||
struct string_list *sources = ds->sources;
|
||||
char const *valid_source = NULL;
|
||||
|
||||
- chdir_do (ds->change_dir);
|
||||
+ chdir_do (ds->change_dir, false);
|
||||
|
||||
for (sources = ds->sources; sources; sources = sources->next)
|
||||
{
|
||||
diff --git a/src/list.c b/src/list.c
|
||||
index 7d7cde5a..068ca5fb 100644
|
||||
--- a/src/list.c
|
||||
+++ b/src/list.c
|
||||
@@ -128,16 +128,28 @@ enforce_one_top_level (char **pfile_name)
|
||||
int pos = strlen (one_top_level_dir);
|
||||
if (strncmp (p, one_top_level_dir, pos) == 0)
|
||||
{
|
||||
- if (ISSLASH (p[pos]) || p[pos] == 0)
|
||||
- return;
|
||||
+ /* Remove the one_top_level_dir prefix if it ends at
|
||||
+ component boundary. */
|
||||
+ if (ISSLASH (p[pos]))
|
||||
+ {
|
||||
+ *pfile_name = xstrdup (p[pos+1] ? &p[pos+1] : ".");
|
||||
+ free (file_name);
|
||||
+ return;
|
||||
+ }
|
||||
+ else if (p[pos] == 0)
|
||||
+ {
|
||||
+ *pfile_name = xstrdup (".");
|
||||
+ free (file_name);
|
||||
+ return;
|
||||
+ }
|
||||
}
|
||||
-
|
||||
- *pfile_name = make_file_name (one_top_level_dir, file_name);
|
||||
- normalize_filename_x (*pfile_name);
|
||||
+ /* If the prefix does not match, do nothing. */
|
||||
}
|
||||
else
|
||||
- *pfile_name = xstrdup (one_top_level_dir);
|
||||
- free (file_name);
|
||||
+ {
|
||||
+ *pfile_name = xstrdup (".");
|
||||
+ free (file_name);
|
||||
+ }
|
||||
}
|
||||
|
||||
void
|
||||
@@ -158,8 +170,15 @@ transform_stat_info (int typeflag, struct tar_stat_info *stat_info)
|
||||
transform_member_name (&stat_info->link_name, XFORM_LINK);
|
||||
}
|
||||
|
||||
- if (one_top_level_option)
|
||||
- enforce_one_top_level (¤t_stat_info.file_name);
|
||||
+ if (one_top_level_dir)
|
||||
+ {
|
||||
+ enforce_one_top_level (&stat_info->file_name);
|
||||
+ /* Hard links are interpreted relative to cwd, and --one-top-level
|
||||
+ works by means of a hidden change of cwd to the requested directory.
|
||||
+ Adjust hard link targets as well. */
|
||||
+ if (typeflag == LNKTYPE)
|
||||
+ enforce_one_top_level (&stat_info->link_name);
|
||||
+ }
|
||||
}
|
||||
|
||||
/* Main loop for reading an archive. */
|
||||
@@ -1159,9 +1178,9 @@ simple_print_header (struct tar_stat_info *st, union block *blk,
|
||||
int sizelen;
|
||||
|
||||
if (show_transformed_names_option)
|
||||
- temp_name = st->file_name ? st->file_name : st->orig_file_name;
|
||||
+ temp_name = transform_top_level (st->file_name ? st->file_name : st->orig_file_name);
|
||||
else
|
||||
- temp_name = st->orig_file_name ? st->orig_file_name : st->file_name;
|
||||
+ temp_name = xstrdup (st->orig_file_name ? st->orig_file_name : st->file_name);
|
||||
|
||||
if (block_number_option)
|
||||
{
|
||||
@@ -1354,6 +1373,7 @@ simple_print_header (struct tar_stat_info *st, union block *blk,
|
||||
}
|
||||
fflush (stdlis);
|
||||
xattrs_print (st);
|
||||
+ free (temp_name);
|
||||
}
|
||||
|
||||
|
||||
diff --git a/src/misc.c b/src/misc.c
|
||||
index 575d13d9..f6c9a819 100644
|
||||
--- a/src/misc.c
|
||||
+++ b/src/misc.c
|
||||
@@ -23,6 +23,7 @@
|
||||
#include <xgetcwd.h>
|
||||
#include <unlinkdir.h>
|
||||
#include <utimens.h>
|
||||
+#include <assert.h>
|
||||
|
||||
#ifndef DOUBLE_SLASH_IS_DISTINCT_ROOT
|
||||
# define DOUBLE_SLASH_IS_DISTINCT_ROOT 0
|
||||
@@ -946,6 +947,7 @@ struct wd
|
||||
to be used. */
|
||||
int fd;
|
||||
|
||||
+ bool one_top_level;
|
||||
/* If ID.err is zero, the directory's identity;
|
||||
if positive, a failure indication with errno = ID.err;
|
||||
if negative, no attempt has been made yet to get the identity. */
|
||||
@@ -977,9 +979,17 @@ static size_t wdcache_count;
|
||||
int
|
||||
chdir_count (void)
|
||||
{
|
||||
- if (wd_count == 0)
|
||||
- return wd_count;
|
||||
- return wd_count - 1;
|
||||
+ int count = 0;
|
||||
+ if (wd_count)
|
||||
+ {
|
||||
+ /* Do not count the initial CWD entry -> start at 1. */
|
||||
+ for (int i = 1; i < wd_count; i++)
|
||||
+ {
|
||||
+ if (! wd[i].one_top_level)
|
||||
+ count++;
|
||||
+ }
|
||||
+ }
|
||||
+ return count;
|
||||
}
|
||||
|
||||
/* Grow the WD table by at least one entry. */
|
||||
@@ -996,15 +1006,27 @@ grow_wd (void)
|
||||
wd[wd_count].abspath = NULL;
|
||||
wd[wd_count].fd = AT_FDCWD;
|
||||
wd[wd_count].id.err = -1;
|
||||
+ wd[wd_count].one_top_level = false;
|
||||
wd_count++;
|
||||
+ if (one_top_level_dir)
|
||||
+ {
|
||||
+ wd[wd_count].name = one_top_level_dir;
|
||||
+ wd[wd_count].abspath = NULL;
|
||||
+ wd[wd_count].fd = 0;
|
||||
+ wd[wd_count].id.err = -1;
|
||||
+ wd[wd_count].one_top_level = true;
|
||||
+ wd_count++;
|
||||
+ }
|
||||
}
|
||||
}
|
||||
|
||||
/* DIR is the operand of a -C option; add it to vector of chdir targets,
|
||||
and return the index of its location. */
|
||||
int
|
||||
-chdir_arg (char const *dir)
|
||||
+chdir_arg (char const *dir, bool one_top_level)
|
||||
{
|
||||
+ if (one_top_level)
|
||||
+ chdir_arg (dir, false);
|
||||
if (wd_count == wd_alloc)
|
||||
grow_wd ();
|
||||
|
||||
@@ -1014,13 +1036,22 @@ chdir_arg (char const *dir)
|
||||
{
|
||||
dir += dotslashlen (dir);
|
||||
if (! dir[dir[0] == '.'])
|
||||
- return wd_count - 1;
|
||||
+ {
|
||||
+ if (wd[wd_count - 1].one_top_level == one_top_level)
|
||||
+ return wd_count - 1;
|
||||
+ else
|
||||
+ return wd_count - 2;
|
||||
+ }
|
||||
}
|
||||
|
||||
+ if (one_top_level)
|
||||
+ dir = one_top_level_dir;
|
||||
+
|
||||
wd[wd_count].name = dir;
|
||||
wd[wd_count].abspath = NULL;
|
||||
wd[wd_count].fd = 0;
|
||||
wd[wd_count].id.err = -1;
|
||||
+ wd[wd_count].one_top_level = one_top_level;
|
||||
return wd_count++;
|
||||
}
|
||||
|
||||
@@ -1039,21 +1070,74 @@ static int chdir_fd = AT_FDCWD;
|
||||
working directory; otherwise, I must be a value returned by
|
||||
chdir_arg. */
|
||||
void
|
||||
-chdir_do (int i)
|
||||
+chdir_do (int i, bool create)
|
||||
{
|
||||
- if (chdir_current != i)
|
||||
- {
|
||||
- struct wd *curr = &wd[i];
|
||||
- int fd = curr->fd;
|
||||
+ struct wd *curr = &wd[i];
|
||||
+ int fd = curr->fd;
|
||||
|
||||
- if (! fd)
|
||||
+ /* Nothing to create unless we are at the one_top_level dir that has
|
||||
+ not been created yet. */
|
||||
+ create = create && curr->one_top_level && (fd == BADFD || fd == 0);
|
||||
+
|
||||
+ if (chdir_current != i || create)
|
||||
+ {
|
||||
+ if (! fd || create)
|
||||
{
|
||||
if (! IS_ABSOLUTE_FILE_NAME (curr->name))
|
||||
- chdir_do (i - 1);
|
||||
+ {
|
||||
+ int j = i - 1;
|
||||
+ if (wd[j].one_top_level)
|
||||
+ {
|
||||
+ j--;
|
||||
+ assert (! wd[j].one_top_level);
|
||||
+ }
|
||||
+ chdir_do (j, false);
|
||||
+ }
|
||||
fd = openat (chdir_fd, curr->name,
|
||||
open_searchdir_how.flags & ~O_NOFOLLOW);
|
||||
if (fd < 0)
|
||||
- open_fatal (curr->name);
|
||||
+ {
|
||||
+ if (create)
|
||||
+ {
|
||||
+ char *dir_with_dot;
|
||||
+ struct open_how saved_open_searchdir_how = open_searchdir_how;
|
||||
+ /* Don't use O_BENEATH during creation of the
|
||||
+ directory. The one-top-level directory is
|
||||
+ allowed to be given as an absolute path. */
|
||||
+ open_searchdir_how.resolve = 0;
|
||||
+ /* Append a dot. make_directories creates
|
||||
+ directories up to and excluding the last
|
||||
+ component of the path. So, in order to create
|
||||
+ "a/b", we need to pass "a/b/." to it. */
|
||||
+ {
|
||||
+ namebuf_t nbuf = namebuf_create (curr->name);
|
||||
+ namebuf_add_dir (nbuf, ".");
|
||||
+ dir_with_dot = namebuf_finish (nbuf);
|
||||
+ }
|
||||
+ if (make_directories (dir_with_dot, NULL) == 0)
|
||||
+ /* Directory created, retry */
|
||||
+ fd = openat (chdir_fd, curr->name,
|
||||
+ open_searchdir_how.flags & ~O_NOFOLLOW);
|
||||
+ open_searchdir_how = saved_open_searchdir_how;
|
||||
+ free (dir_with_dot);
|
||||
+ /* Either the creation or open failed */
|
||||
+ if (fd < 0)
|
||||
+ open_fatal (curr->name);
|
||||
+ }
|
||||
+ else if (errno == ENOENT && curr->one_top_level)
|
||||
+ {
|
||||
+ /* We are requested to not create the directory now. Mark it
|
||||
+ as to be created later when called with create == true. */
|
||||
+ chdir_fd = curr->fd = BADFD;
|
||||
+ chdir_current = i;
|
||||
+ /* Do not add it to the cache */
|
||||
+ return;
|
||||
+ }
|
||||
+ else
|
||||
+ {
|
||||
+ open_fatal (curr->name);
|
||||
+ }
|
||||
+ }
|
||||
|
||||
curr->fd = fd;
|
||||
|
||||
@@ -1071,7 +1155,7 @@ chdir_do (int i)
|
||||
}
|
||||
}
|
||||
|
||||
- if (0 < fd)
|
||||
+ if (0 < fd && /* no assumption about sign of BADFD */ fd != BADFD)
|
||||
{
|
||||
/* Move the i value to the front of the cache. This is
|
||||
O(CHDIR_CACHE_SIZE), but the cache is small. */
|
||||
@@ -1175,6 +1259,14 @@ fdbase_opendir (char const *file_name, bool alternate)
|
||||
{
|
||||
char const *name = file_name;
|
||||
|
||||
+ if (chdir_fd == BADFD && ! IS_ABSOLUTE_FILE_NAME (file_name))
|
||||
+ {
|
||||
+ /* BADFD is a sentinel value meaning that the chdir directory
|
||||
+ needs to be created lazily, therefore if we encounter it, the
|
||||
+ directory does not exist yet. */
|
||||
+ errno = ENOENT;
|
||||
+ return (struct fdbase) { .fd = chdir_fd, .base = name };
|
||||
+ }
|
||||
/* Skip past leading "./"s,
|
||||
but not past the last "./" if that ends the name. */
|
||||
idx_t dslen = dotslashlen (name);
|
||||
@@ -1219,13 +1311,13 @@ fdbase_opendir (char const *file_name, bool alternate)
|
||||
if (subfd < 0)
|
||||
{
|
||||
/* Keep the old directory cached and report open failure,
|
||||
- unless EMFILE means it's possible that falling
|
||||
+ unless EMFILE/ENFILE means it's possible that falling
|
||||
through to close the old directory would mean we
|
||||
could successfully retry from the chdir_fd level.
|
||||
When reporting failure, there is no need to
|
||||
null-terminate the old directory, since the code does
|
||||
not assume null termination. */
|
||||
- if (errno != EMFILE)
|
||||
+ if (errno != EMFILE && errno != ENFILE)
|
||||
return (struct fdbase) { .fd = BADFD, .base = base };
|
||||
}
|
||||
else
|
||||
@@ -1279,6 +1371,29 @@ tar_dirname (void)
|
||||
return wd[chdir_current].name;
|
||||
}
|
||||
|
||||
+/* Return a newly allocated string that shows NAME from the user's
|
||||
+ viewpoint, given that --one-top-level may be in effect. */
|
||||
+char *
|
||||
+transform_top_level (const char *name)
|
||||
+{
|
||||
+ if (wd[chdir_current].one_top_level)
|
||||
+ {
|
||||
+ if (strcmp (name, ".") == 0)
|
||||
+ {
|
||||
+ /* nothing to append - .../. is the same as ... */
|
||||
+ return xstrdup (wd[chdir_current].name);
|
||||
+ }
|
||||
+ else
|
||||
+ {
|
||||
+ namebuf_t nbuf = namebuf_create (wd[chdir_current].name);
|
||||
+ namebuf_add_dir (nbuf, name);
|
||||
+ return namebuf_finish (nbuf);
|
||||
+ }
|
||||
+ }
|
||||
+ else
|
||||
+ return xstrdup (name);
|
||||
+}
|
||||
+
|
||||
/* Return the absolute path that represents the working
|
||||
directory referenced by IDX.
|
||||
|
||||
@@ -1307,12 +1422,13 @@ tar_getcdpath (int idx)
|
||||
int save_cwdi = chdir_current;
|
||||
|
||||
for (i = idx; i >= 0; i--)
|
||||
- if (wd[i].abspath)
|
||||
+ if (wd[i].abspath && !wd[i].one_top_level)
|
||||
break;
|
||||
|
||||
while (++i <= idx)
|
||||
{
|
||||
- chdir_do (i);
|
||||
+ if (!wd[i].one_top_level)
|
||||
+ chdir_do (i, false);
|
||||
if (i == 0)
|
||||
{
|
||||
if ((wd[i].abspath = xgetcwd ()) == NULL)
|
||||
@@ -1325,13 +1441,18 @@ tar_getcdpath (int idx)
|
||||
wd[i].abspath = xstrdup (wd[i].name);
|
||||
else
|
||||
{
|
||||
- namebuf_t nbuf = namebuf_create (wd[i - 1].abspath);
|
||||
+ int j = i - 1;
|
||||
+ if (wd[j].one_top_level)
|
||||
+ {
|
||||
+ j--;
|
||||
+ assert (! wd[j].one_top_level);
|
||||
+ }
|
||||
+ namebuf_t nbuf = namebuf_create (wd[j].abspath);
|
||||
namebuf_add_dir (nbuf, wd[i].name);
|
||||
wd[i].abspath = namebuf_finish (nbuf);
|
||||
}
|
||||
}
|
||||
-
|
||||
- chdir_do (save_cwdi);
|
||||
+ chdir_do (save_cwdi, false);
|
||||
}
|
||||
|
||||
return wd[idx].abspath;
|
||||
diff --git a/src/names.c b/src/names.c
|
||||
index 403192be..56cee63c 100644
|
||||
--- a/src/names.c
|
||||
+++ b/src/names.c
|
||||
@@ -867,6 +867,7 @@ name_init (void)
|
||||
{
|
||||
name_buffer = xmalloc (NAME_FIELD_SIZE + 2);
|
||||
name_buffer_length = NAME_FIELD_SIZE;
|
||||
+ chdir_do (chdir_arg (".", !!one_top_level_dir), false);
|
||||
name_list_adjust ();
|
||||
}
|
||||
|
||||
@@ -1119,7 +1120,8 @@ name_next_elt (int change_dirs)
|
||||
case NELT_CHDIR:
|
||||
if (change_dirs)
|
||||
{
|
||||
- chdir_do (chdir_arg (xstrdup (ep->v.name)));
|
||||
+ chdir_do (chdir_arg (xstrdup (ep->v.name), !!one_top_level_dir),
|
||||
+ false);
|
||||
name_list_advance ();
|
||||
break;
|
||||
}
|
||||
@@ -1182,7 +1184,7 @@ name_gather (void)
|
||||
static int change_dir;
|
||||
|
||||
while ((ep = name_next_elt (0)) && ep->type == NELT_CHDIR)
|
||||
- change_dir = chdir_arg (xstrdup (ep->v.name));
|
||||
+ change_dir = chdir_arg (xstrdup (ep->v.name), !!one_top_level_dir);
|
||||
|
||||
if (ep)
|
||||
{
|
||||
@@ -1211,7 +1213,7 @@ name_gather (void)
|
||||
{
|
||||
int change_dir0 = change_dir;
|
||||
while ((ep = name_next_elt (0)) && ep->type == NELT_CHDIR)
|
||||
- change_dir = chdir_arg (xstrdup (ep->v.name));
|
||||
+ change_dir = chdir_arg (xstrdup (ep->v.name), !!one_top_level_dir);
|
||||
|
||||
if (ep)
|
||||
addname (ep->v.name, change_dir, true, NULL);
|
||||
@@ -1326,7 +1328,7 @@ name_match (const char *file_name)
|
||||
|
||||
if (cursor->name[0] == 0)
|
||||
{
|
||||
- chdir_do (cursor->change_dir);
|
||||
+ chdir_do (cursor->change_dir, false);
|
||||
namelist = NULL;
|
||||
nametail = NULL;
|
||||
return true;
|
||||
@@ -1348,7 +1350,7 @@ name_match (const char *file_name)
|
||||
if (!(ISSLASH (file_name[cursor->length]) && recursion_option)
|
||||
|| cursor->found_count == 0)
|
||||
cursor->found_count++; /* remember it matched */
|
||||
- chdir_do (cursor->change_dir);
|
||||
+ chdir_do (cursor->change_dir, false);
|
||||
/* We got a match. */
|
||||
return ISFOUND (cursor);
|
||||
}
|
||||
@@ -1778,7 +1780,7 @@ collect_and_sort_names (void)
|
||||
if (name->found_count || name->directory)
|
||||
continue;
|
||||
|
||||
- chdir_do (name->change_dir);
|
||||
+ chdir_do (name->change_dir, false);
|
||||
|
||||
if (name->name[0] == 0)
|
||||
continue;
|
||||
@@ -1924,7 +1926,7 @@ name_from_list (void)
|
||||
{
|
||||
if (!gnu_list_name->is_wildcard)
|
||||
gnu_list_name->found_count++;
|
||||
- chdir_do (gnu_list_name->change_dir);
|
||||
+ chdir_do (gnu_list_name->change_dir, false);
|
||||
return gnu_list_name;
|
||||
}
|
||||
return NULL;
|
||||
diff --git a/src/tar.c b/src/tar.c
|
||||
index 9a5fbd0b..56c9ed2a 100644
|
||||
--- a/src/tar.c
|
||||
+++ b/src/tar.c
|
||||
@@ -1572,7 +1572,6 @@ parse_opt (int key, char *arg, struct argp_state *state)
|
||||
|
||||
case ONE_TOP_LEVEL_OPTION:
|
||||
optloc_save (OC_ONE_TOP_LEVEL, args->loc);
|
||||
- one_top_level_option = true;
|
||||
one_top_level_dir = arg;
|
||||
break;
|
||||
|
||||
@@ -2576,9 +2575,11 @@ decode_options (int argc, char **argv)
|
||||
same_order_option = false;
|
||||
}
|
||||
|
||||
- if (one_top_level_option)
|
||||
+ if (optloc_lookup (OC_ONE_TOP_LEVEL))
|
||||
{
|
||||
- char *base;
|
||||
+ if (!IS_SUBCOMMAND_CLASS (SUBCL_READ))
|
||||
+ option_conflict_error ("--one-top-level",
|
||||
+ subcommand_string (subcommand_option));
|
||||
|
||||
if (absolute_names_option)
|
||||
{
|
||||
@@ -2589,17 +2590,18 @@ decode_options (int argc, char **argv)
|
||||
|
||||
if (optloc_eq (one_top_level_loc, absolute_names_loc))
|
||||
option_conflict_error ("--one-top-level", "--absolute-names");
|
||||
- else if (one_top_level_loc->source == OPTS_COMMAND_LINE)
|
||||
+ if (one_top_level_loc->source == OPTS_COMMAND_LINE)
|
||||
absolute_names_option = false;
|
||||
else
|
||||
- one_top_level_option = false;
|
||||
+ one_top_level_dir = NULL;
|
||||
}
|
||||
|
||||
- if (!one_top_level_dir && one_top_level_option)
|
||||
+ if (!absolute_names_option && !one_top_level_dir)
|
||||
{
|
||||
- /* If the user wants to guarantee that everything is under one
|
||||
- directory, determine its name now and let it be created later. */
|
||||
- base = base_name (archive_name_array[0]);
|
||||
+ /* Determine name now; the directory (or directories, if -C
|
||||
+ means there are multiple top-level directories) are
|
||||
+ created later if needed. */
|
||||
+ char *base = base_name (archive_name_array[0]);
|
||||
one_top_level_dir = strip_compression_suffix (base);
|
||||
free (base);
|
||||
|
||||
@@ -2609,9 +2611,7 @@ decode_options (int argc, char **argv)
|
||||
"please set it explicitly with --one-top-level=DIR")));
|
||||
}
|
||||
|
||||
- if (one_top_level_dir && !IS_RELATIVE_FILE_NAME (one_top_level_dir))
|
||||
- USAGE_ERROR ((0, 0,
|
||||
- _("--one-top-level=DIR must use a relative file name")));
|
||||
+ normalize_filename_x (one_top_level_dir);
|
||||
}
|
||||
|
||||
/* If ready to unlink hierarchies, so we are for simpler files. */
|
||||
diff --git a/src/unlink.c b/src/unlink.c
|
||||
index 33d2ccd1..e199bab3 100644
|
||||
--- a/src/unlink.c
|
||||
+++ b/src/unlink.c
|
||||
@@ -103,7 +103,7 @@ flush_deferred_unlinks (bool force)
|
||||
if (force
|
||||
|| records_written > p->records_written + deferred_unlink_delay)
|
||||
{
|
||||
- chdir_do (p->dir_idx);
|
||||
+ chdir_do (p->dir_idx, false);
|
||||
if (p->is_dir)
|
||||
{
|
||||
const char *fname;
|
||||
@@ -175,11 +175,11 @@ flush_deferred_unlinks (bool force)
|
||||
struct deferred_unlink *next = p->next;
|
||||
const char *fname;
|
||||
|
||||
- chdir_do (p->dir_idx);
|
||||
+ chdir_do (p->dir_idx, false);
|
||||
if (p->dir_idx && IS_CWD (p))
|
||||
{
|
||||
fname = tar_dirname ();
|
||||
- chdir_do (p->dir_idx - 1);
|
||||
+ chdir_do (p->dir_idx - 1, false);
|
||||
}
|
||||
else
|
||||
fname = p->file_name;
|
||||
@@ -195,9 +195,9 @@ flush_deferred_unlinks (bool force)
|
||||
p = next;
|
||||
}
|
||||
dunlink_head = dunlink_tail = NULL;
|
||||
- }
|
||||
-
|
||||
- chdir_do (saved_chdir);
|
||||
+ }
|
||||
+
|
||||
+ chdir_do (saved_chdir, false);
|
||||
}
|
||||
|
||||
void
|
||||
diff --git a/src/update.c b/src/update.c
|
||||
index 2bc38207..24167778 100644
|
||||
--- a/src/update.c
|
||||
+++ b/src/update.c
|
||||
@@ -141,7 +141,7 @@ update_archive (void)
|
||||
{
|
||||
struct stat s;
|
||||
|
||||
- chdir_do (name->change_dir);
|
||||
+ chdir_do (name->change_dir, false);
|
||||
if (deref_stat (current_stat_info.file_name, &s) == 0)
|
||||
{
|
||||
if (S_ISDIR (s.st_mode))
|
||||
42
tar.spec
42
tar.spec
@ -10,7 +10,7 @@ Summary: GNU file archiving program
|
||||
Name: tar
|
||||
Epoch: 2
|
||||
Version: 1.35
|
||||
Release: 11%{?dist}
|
||||
Release: 13%{?dist}
|
||||
License: GPL-3.0-or-later
|
||||
URL: https://www.gnu.org/software/tar/
|
||||
|
||||
@ -59,6 +59,32 @@ Patch22: tar-1.35-no-overwrite-dir-no-overwrite-even-temporarily.patch
|
||||
# 45b6e6898d1f931bfca41d961289bd6ac33238e5
|
||||
Patch23: tar-1.35-CVE-2025-45582.patch
|
||||
Patch24: tar-1.35-tar-one-top-level-DIR-must-be-relative.patch
|
||||
# Source: https://cgit.git.savannah.gnu.org/cgit/tar.git/diff/?id=08c3fc2e9337094aff01a511170fd35fdb8f1ee3
|
||||
Patch25: tar-1.35-Avoid-acl_-prefix-for-functions.patch
|
||||
#Upstream commits
|
||||
# b009124ffde415515081db844d7a104e1d1c6c58
|
||||
# b8d8a61b25588caca4efaf9bdd2e3f1a49da77e3
|
||||
# 67981bbb1587803bb1e029393d2228492cef8c4f
|
||||
# 19a3a73e8c48bd3c59cbea9b5ed6780fc6836c6d
|
||||
Patch26: tar-1.35-CVE-2026-5704.patch
|
||||
#Upstream commits
|
||||
# bfc33463942060316f70f275471ed202b0076972
|
||||
# b4fc9ca13617411c5db57286a3ff534bd40acdc1
|
||||
# 67c8dff6968aae58fcdfb05268eb1b4c07308f1a
|
||||
# 55e8233438b3c13294109df502a9b220a9a3f4f5
|
||||
# 0470c109c08f466d8332ba3326070554b4d81aa1
|
||||
# 1b91f5f66f8e6c490eef0fdee50f652cfe155844
|
||||
# 325b899214ac13519153318e66c889a31c15342d
|
||||
# 1980e032afe60c5fe0e5df573457885ece4e69d5
|
||||
# e335e2c8b102f83b63ffd373f0b88401da3ebe6c
|
||||
# part of 941f62b2
|
||||
# Also "by the way" fixes CVE-2026-18508.
|
||||
Patch27: tar-1.35-fix-absolute-one-top-level.patch
|
||||
#Upstream commits
|
||||
# 0714d2f082104005a1c70ee6ec4175194943ea88
|
||||
# d479b2cc9160d9c2fb61afbc9ee70c2faadf80db
|
||||
# b17665b2c0548c77b6cd8d2d5b61e4c4fcc4f770
|
||||
Patch28: tar-1.35-CVE-2026-18477.patch
|
||||
|
||||
BuildRequires: autoconf
|
||||
BuildRequires: automake
|
||||
@ -157,6 +183,20 @@ make check || (
|
||||
|
||||
|
||||
%changelog
|
||||
* Mon Aug 24 2026 Pavel Cahyna <pcahyna@redhat.com> - 2:1.35-13
|
||||
- Backport upstream patches for CVE-2026-18477, fixes a bug
|
||||
where incremental restore with cyclic renames between backups
|
||||
may create a temporary directory at an archive-controlled path
|
||||
outside the extraction tree.
|
||||
The fix for CVE-2025-45582 already prevents exploiting
|
||||
this problem, so it is more a correctness and hardening change.
|
||||
|
||||
* Fri Aug 21 2026 Pavel Cahyna <pcahyna@redhat.com> - 2:1.35-12
|
||||
- Backport upstream fix for CVE-2026-5704 (file injection hidden from -t)
|
||||
- Fix --one-top-level with absolute path (broken by the CVE-2025-45582 fix)
|
||||
Also fixes CVE-2026-18508 (escape from --one-top-level via hardlinks).
|
||||
- Upstream fix for build with libacl 2.4.0
|
||||
|
||||
* Mon Feb 23 2026 Pavel Cahyna <pcahyna@redhat.com> - 2:1.35-11
|
||||
- Add upstream patch to improve error message for --one-top-level
|
||||
with absolute path (unsupported as a result of CVE-2025-45582 fix)
|
||||
|
||||
Loading…
Reference in New Issue
Block a user