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
11 changes: 11 additions & 0 deletions debian/changelog
Original file line number Diff line number Diff line change
@@ -1,3 +1,14 @@
busybox (1:1.37.0-10.1) unstable; urgency=medium

* Non-maintainer upload.
* CVE-2026-26157: Incomplete path sanitization in archive
extraction utilities
* CVE-2026-26158: File modification outside of the intended
extraction directory in tar
* (Closes: #1127782)

-- Adrian Bunk <bunk@debian.org> Wed, 04 Mar 2026 19:42:01 +0200

busybox (1:1.37.0-10) unstable; urgency=medium

* Revert "initramfs-tools/conf-hooks.d/busybox:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
From 0c20d6b353b058ab910dd3a0211e2b906802b105 Mon Sep 17 00:00:00 2001
From: Denys Vlasenko <vda.linux@googlemail.com>
Date: Thu, 29 Jan 2026 11:48:02 +0100
Subject: tar: strip unsafe hardlink components - GNU tar does the same

Defends against files like these (python reproducer):

import tarfile
ti = tarfile.TarInfo("leak_hosts")
ti.type = tarfile.LNKTYPE
ti.linkname = "/etc/hosts" # or "../etc/hosts" or ".."
ti.size = 0
with tarfile.open("/tmp/hardlink.tar", "w") as t:
t.addfile(ti)

function old new delta
skip_unsafe_prefix - 127 +127
get_header_tar 1752 1754 +2
.rodata 106861 106856 -5
unzip_main 2715 2706 -9
strip_unsafe_prefix 102 18 -84
------------------------------------------------------------------------------
(add/remove: 1/0 grow/shrink: 1/3 up/down: 129/-98) Total: 31 bytes

Signed-off-by: Denys Vlasenko <vda.linux@googlemail.com>
---
archival/libarchive/data_extract_all.c | 7 +++--
archival/libarchive/get_header_tar.c | 11 ++++++--
archival/libarchive/unsafe_prefix.c | 30 +++++++++++++++++----
archival/libarchive/unsafe_symlink_target.c | 1 +
archival/tar.c | 2 +-
archival/unzip.c | 2 +-
include/bb_archive.h | 3 ++-
7 files changed, 42 insertions(+), 14 deletions(-)

diff --git a/archival/libarchive/data_extract_all.c b/archival/libarchive/data_extract_all.c
index 8a69711c1..b84b960c4 100644
--- a/archival/libarchive/data_extract_all.c
+++ b/archival/libarchive/data_extract_all.c
@@ -66,8 +66,8 @@ void FAST_FUNC data_extract_all(archive_handle_t *archive_handle)
}
#endif
#if ENABLE_FEATURE_PATH_TRAVERSAL_PROTECTION
- /* Strip leading "/" and up to last "/../" path component */
- dst_name = (char *)strip_unsafe_prefix(dst_name);
+ /* Skip leading "/" and past last ".." path component */
+ dst_name = (char *)skip_unsafe_prefix(dst_name);
#endif
// ^^^ This may be a problem if some applets do need to extract absolute names.
// (Probably will need to invent ARCHIVE_ALLOW_UNSAFE_NAME flag).
@@ -185,8 +185,7 @@ void FAST_FUNC data_extract_all(archive_handle_t *archive_handle)

/* To avoid a directory traversal attack via symlinks,
* do not restore symlinks with ".." components
- * or symlinks starting with "/", unless a magic
- * envvar is set.
+ * or symlinks starting with "/"
*
* For example, consider a .tar created via:
* $ tar cvf bug.tar anything.txt
diff --git a/archival/libarchive/get_header_tar.c b/archival/libarchive/get_header_tar.c
index cc6f3f0ad..1c40ecedb 100644
--- a/archival/libarchive/get_header_tar.c
+++ b/archival/libarchive/get_header_tar.c
@@ -454,8 +454,15 @@ char FAST_FUNC get_header_tar(archive_handle_t *archive_handle)
#endif

/* Everything up to and including last ".." component is stripped */
- overlapping_strcpy(file_header->name, strip_unsafe_prefix(file_header->name));
-//TODO: do the same for file_header->link_target?
+ strip_unsafe_prefix(file_header->name);
+ if (file_header->link_target) {
+ /* GNU tar 1.34 examples:
+ * tar: Removing leading '/' from hard link targets
+ * tar: Removing leading '../' from hard link targets
+ * tar: Removing leading 'etc/../' from hard link targets
+ */
+ strip_unsafe_prefix(file_header->link_target);
+ }

/* Strip trailing '/' in directories */
/* Must be done after mode is set as '/' is used to check if it's a directory */
diff --git a/archival/libarchive/unsafe_prefix.c b/archival/libarchive/unsafe_prefix.c
index 667081195..89a371a7f 100644
--- a/archival/libarchive/unsafe_prefix.c
+++ b/archival/libarchive/unsafe_prefix.c
@@ -5,11 +5,11 @@
#include "libbb.h"
#include "bb_archive.h"

-const char* FAST_FUNC strip_unsafe_prefix(const char *str)
+const char* FAST_FUNC skip_unsafe_prefix(const char *str)
{
const char *cp = str;
while (1) {
- char *cp2;
+ const char *cp2;
if (*cp == '/') {
cp++;
continue;
@@ -22,10 +22,25 @@ const char* FAST_FUNC strip_unsafe_prefix(const char *str)
cp += 3;
continue;
}
- cp2 = strstr(cp, "/../");
+ cp2 = cp;
+ find_dotdot:
+ cp2 = strstr(cp2, "/..");
if (!cp2)
- break;
- cp = cp2 + 4;
+ break; /* No (more) malicious components */
+
+ /* We found "/..something" */
+ cp2 += 3;
+ if (*cp2 != '/') {
+ if (*cp2 == '\0') {
+ /* Trailing "/..": malicious, return "" */
+ /* (causes harmless errors trying to create or hardlink a file named "") */
+ return cp2;
+ }
+ /* "/..name" is not malicious, look for next "/.." */
+ goto find_dotdot;
+ }
+ /* Found "/../": malicious, advance past it */
+ cp = cp2 + 1;
}
if (cp != str) {
static smallint warned = 0;
@@ -37,3 +52,8 @@ const char* FAST_FUNC strip_unsafe_prefix(const char *str)
}
return cp;
}
+
+void FAST_FUNC strip_unsafe_prefix(char *str)
+{
+ overlapping_strcpy(str, skip_unsafe_prefix(str));
+}
diff --git a/archival/libarchive/unsafe_symlink_target.c b/archival/libarchive/unsafe_symlink_target.c
index f8dc8033d..d764c89ab 100644
--- a/archival/libarchive/unsafe_symlink_target.c
+++ b/archival/libarchive/unsafe_symlink_target.c
@@ -36,6 +36,7 @@ void FAST_FUNC create_links_from_list(llist_t *list)
*list->data ? "hard" : "sym",
list->data + 1, target
);
+ /* Note: GNU tar 1.34 errors out only _after_ all links are (attempted to be) created */
}
list = list->link;
}
diff --git a/archival/tar.c b/archival/tar.c
index d6ca6c1e0..d42dcfc26 100644
--- a/archival/tar.c
+++ b/archival/tar.c
@@ -475,7 +475,7 @@ static int FAST_FUNC writeFileToTarball(struct recursive_state *state,
DBG("writeFileToTarball('%s')", fileName);

/* Strip leading '/' and such (must be before memorizing hardlink's name) */
- header_name = strip_unsafe_prefix(fileName);
+ header_name = skip_unsafe_prefix(fileName);

if (header_name[0] == '\0')
return TRUE;
diff --git a/archival/unzip.c b/archival/unzip.c
index 71a302915..8a9a90f7d 100644
--- a/archival/unzip.c
+++ b/archival/unzip.c
@@ -860,7 +860,7 @@ int unzip_main(int argc, char **argv)

/* Guard against "/abspath", "/../" and similar attacks */
// NB: UnZip 6.00 has option -: to disable this
- overlapping_strcpy(dst_fn, strip_unsafe_prefix(dst_fn));
+ strip_unsafe_prefix(dst_fn);

/* Filter zip entries */
if (find_list_entry(zreject, dst_fn)
diff --git a/include/bb_archive.h b/include/bb_archive.h
index e0ef8fc4e..1dc77f31d 100644
--- a/include/bb_archive.h
+++ b/include/bb_archive.h
@@ -202,7 +202,8 @@ char get_header_tar_xz(archive_handle_t *archive_handle) FAST_FUNC;
void seek_by_jump(int fd, off_t amount) FAST_FUNC;
void seek_by_read(int fd, off_t amount) FAST_FUNC;

-const char *strip_unsafe_prefix(const char *str) FAST_FUNC;
+const char *skip_unsafe_prefix(const char *str) FAST_FUNC;
+void strip_unsafe_prefix(char *str) FAST_FUNC;
void create_or_remember_link(llist_t **link_placeholders,
const char *target,
const char *linkname,
--
2.47.3

Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
From 038e0e4d791ea4e8a8da5e06904756142fc6b8dc Mon Sep 17 00:00:00 2001
From: Radoslav Kolev <radoslav.kolev@suse.com>
Date: Mon, 16 Feb 2026 11:50:04 +0200
Subject: tar: only strip unsafe components from hardlinks, not symlinks

commit 3fb6b31c7 introduced a check for unsafe components in
tar archive hardlinks, but it was being applied to symlinks too
which broke "Symlinks and hardlinks coexist" tar test.

Signed-off-by: Radoslav Kolev <radoslav.kolev@suse.com>
Signed-off-by: Denys Vlasenko <vda.linux@googlemail.com>
---
archival/libarchive/get_header_tar.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/archival/libarchive/get_header_tar.c b/archival/libarchive/get_header_tar.c
index 1c40ecedb..606d8067f 100644
--- a/archival/libarchive/get_header_tar.c
+++ b/archival/libarchive/get_header_tar.c
@@ -455,7 +455,7 @@ char FAST_FUNC get_header_tar(archive_handle_t *archive_handle)

/* Everything up to and including last ".." component is stripped */
strip_unsafe_prefix(file_header->name);
- if (file_header->link_target) {
+ if (file_header->link_target && !S_ISLNK(file_header->mode)) {
/* GNU tar 1.34 examples:
* tar: Removing leading '/' from hard link targets
* tar: Removing leading '../' from hard link targets
--
2.47.3

2 changes: 2 additions & 0 deletions debian/patches/series
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,5 @@ wget-disallow-control-chars-in-URLs-CVE-2025-60876.patch
archival-libarchive-sanitize-filenames-on-output-CVE-2025-46394.patch
archival-libarchive-sanitize-filenames-on-output-CVE-2025-46394-2.patch
netstat-sanitize-argv0-for-p-CVE-2024-58251.patch
0001-tar-strip-unsafe-hardlink-components-GNU-tar-does-th.patch
0002-tar-only-strip-unsafe-components-from-hardlinks-not-.patch
Loading