Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
339 changes: 339 additions & 0 deletions SPECS/tar/CVE-2026-5704.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,339 @@
From 4c323ad56be72f0083c5ee78aa7f44f052f869af Mon Sep 17 00:00:00 2001
From: AllSpark <allspark@microsoft.com>
Date: Tue, 1 Sep 2026 15:21:38 +0000
Subject: [PATCH] Fix more -t/-x discrepancies

Signed-off-by: Azure Linux Security Servicing Account <azurelinux-security@microsoft.com>
Upstream-reference: AI Backport of https://cgit.git.savannah.gnu.org/cgit/tar.git/patch/?id=b8d8a61b25588caca4efaf9bdd2e3f1a49da77e3
---
src/extract.c | 24 +++++++-----------------
src/incremen.c | 36 ++++++++++++++----------------------
src/list.c | 23 ++++++++++-------------
tests/extrac32.at | 47 +++++++++++++++++++++++++++++++++++++++++++++++
4 files changed, 78 insertions(+), 52 deletions(-)
create mode 100644 tests/extrac32.at

diff --git a/src/extract.c b/src/extract.c
index 314d8bc..0ddb587 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 int
-extract_dir (char *file_name, int typeflag)
+extract_dir (char *file_name, int UNUSED_PARAM (typeflag))
{
int status;
mode_t mode;
@@ -1060,8 +1060,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);

@@ -1266,10 +1264,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
{
@@ -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 ();

@@ -1844,15 +1839,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 7bcfdb9..39bf215 100644
--- a/src/incremen.c
+++ b/src/incremen.c
@@ -1154,7 +1154,7 @@ read_num (FILE *fp, char const *fieldname,
FATAL_ERROR ((0, 0, "%s: %s",
quotearg_colon (listed_incremental_option),
_("Unexpected EOF in snapshot file")));
- return false;
+ return;
}

if (c)
@@ -1551,7 +1551,7 @@ dumpdir_ok (char *dumpdir)
ERROR ((0, 0,
_("Malformed dumpdir: expected '%c' but found %#3o"),
expect, uc));
- return false;
+ return;
}
switch (*p)
{
@@ -1560,7 +1560,7 @@ dumpdir_ok (char *dumpdir)
{
ERROR ((0, 0,
_("Malformed dumpdir: 'X' duplicated")));
- return false;
+ return;
}
else
has_tempdir = 1;
@@ -1573,7 +1573,7 @@ dumpdir_ok (char *dumpdir)
{
ERROR ((0, 0,
_("Malformed dumpdir: empty name in 'R'")));
- return false;
+ return;
}
else
has_tempdir = 0;
@@ -1586,13 +1586,13 @@ dumpdir_ok (char *dumpdir)
{
ERROR ((0, 0,
_("Malformed dumpdir: 'T' not preceded by 'R'")));
- return false;
+ return;
}
if (p[1] == 0 && !has_tempdir)
{
ERROR ((0, 0,
_("Malformed dumpdir: empty name in 'T'")));
- return false;
+ return;
}
expect = 0;
break;
@@ -1613,7 +1613,7 @@ dumpdir_ok (char *dumpdir)
ERROR ((0, 0,
_("Malformed dumpdir: expected '%c' but found end of data"),
expect));
- return false;
+ return;
}

if (has_tempdir)
@@ -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 e9a6815..81b5e4f 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,6 +508,7 @@ 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)
@@ -531,6 +527,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;
@@ -1447,17 +1444,17 @@ 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 (&current_stat_info);

if (current_stat_info.is_sparse)
sparse_skim_file (&current_stat_info, must_copy);
- else if (save_typeflag != DIRTYPE)
+ else
skim_file (current_stat_info.stat.st_size, must_copy);

mv_end ();
+ current_stat_info.skipped = true;
}
}

diff --git a/tests/extrac32.at b/tests/extrac32.at
new file mode 100644
index 0000000..3829a48
--- /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
--
2.45.4

6 changes: 5 additions & 1 deletion SPECS/tar/tar.spec
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
Summary: Archiving program
Name: tar
Version: 1.35
Release: 2%{?dist}
Release: 3%{?dist}
License: GPLv3+
URL: https://www.gnu.org/software/tar
Group: Applications/System
Expand All @@ -13,6 +13,7 @@ Source0: https://ftp.gnu.org/gnu/%{name}/%{name}-%{version}.tar.xz
# https://web.git.kernel.org/pub/scm/libs/libcap/libcap.git/commit/?id=177cd418031b1acfcf73fe3b1af9f3279828681c
Patch1: tar-1.33-fix-capabilities-test.patch
Patch2: tar-1.35-add-forgotten-tests-from-upstream.patch
Patch3: CVE-2026-5704.patch

%description
Contains GNU archiving program
Expand Down Expand Up @@ -50,6 +51,9 @@ make %{?_smp_mflags} check
%{_mandir}/*/*

%changelog
* Tue Sep 01 2026 Azure Linux Security Servicing Account <azurelinux-security@microsoft.com> - 1.35-3
- Patch for CVE-2026-5704

* Wed Apr 02 2025 Riken Maharjan <rmaharjan@microsoft.com> - 1.35-2
- import patches from fedora (LICENSE:MIT) to fix libcap issue.
* Mon Nov 27 2023 Andrew Phelps <anphel@microsoft.com> - 1.35-1
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ libcap-ng-devel-0.8.4-1.azl3.aarch64.rpm
util-linux-2.40.2-5.azl3.aarch64.rpm
util-linux-devel-2.40.2-5.azl3.aarch64.rpm
util-linux-libs-2.40.2-5.azl3.aarch64.rpm
tar-1.35-2.azl3.aarch64.rpm
tar-1.35-3.azl3.aarch64.rpm
xz-5.4.4-3.azl3.aarch64.rpm
xz-devel-5.4.4-3.azl3.aarch64.rpm
xz-lang-5.4.4-3.azl3.aarch64.rpm
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ libcap-ng-devel-0.8.4-1.azl3.x86_64.rpm
util-linux-2.40.2-5.azl3.x86_64.rpm
util-linux-devel-2.40.2-5.azl3.x86_64.rpm
util-linux-libs-2.40.2-5.azl3.x86_64.rpm
tar-1.35-2.azl3.x86_64.rpm
tar-1.35-3.azl3.x86_64.rpm
xz-5.4.4-3.azl3.x86_64.rpm
xz-devel-5.4.4-3.azl3.x86_64.rpm
xz-lang-5.4.4-3.azl3.x86_64.rpm
Expand Down
4 changes: 2 additions & 2 deletions toolkit/resources/manifests/package/toolchain_aarch64.txt
Original file line number Diff line number Diff line change
Expand Up @@ -585,8 +585,8 @@ systemd-bootstrap-debuginfo-250.3-20.azl3.aarch64.rpm
systemd-bootstrap-devel-250.3-20.azl3.aarch64.rpm
systemd-bootstrap-libs-250.3-20.azl3.aarch64.rpm
systemd-bootstrap-rpm-macros-250.3-20.azl3.noarch.rpm
tar-1.35-2.azl3.aarch64.rpm
tar-debuginfo-1.35-2.azl3.aarch64.rpm
tar-1.35-3.azl3.aarch64.rpm
tar-debuginfo-1.35-3.azl3.aarch64.rpm
tdnf-3.5.8-8.azl3.aarch64.rpm
tdnf-autoupdate-3.5.8-8.azl3.aarch64.rpm
tdnf-cli-libs-3.5.8-8.azl3.aarch64.rpm
Expand Down
Loading
Loading