File 0001-tar-strip-unsafe-hardlink-components-GNU-tar-does-th.patch of Package busybox
From b847292170b4f976133932e0eadb50c5f307b1a4 Mon Sep 17 00:00:00 2001
From: Denys Vlasenko <vda.linux@googlemail.com>
Date: Thu, 29 Jan 2026 11:48:02 +0100
Subject: [PATCH 1/2] 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 | 3 +--
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, 40 insertions(+), 12 deletions(-)
diff --git a/archival/libarchive/data_extract_all.c b/archival/libarchive/data_extract_all.c
index 049c2c156..0e57a7b03 100644
--- a/archival/libarchive/data_extract_all.c
+++ b/archival/libarchive/data_extract_all.c
@@ -177,8 +177,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 33e487bf9..62f676ea6 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;
@@ -18,10 +18,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;
@@ -33,3 +48,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.52.0