Files
buildroot/package/busybox/0017-tar-strip-unsafe-hardlink-components.patch
Thomas Perale ceb6f2d234 package/busybox: patch CVE-2026-2615{7, 8}
Based on the work of the OpenEmbedded community. This commit patches the
following vulnerabilities:

- CVE-2026-26157:
    A flaw was found in BusyBox. Incomplete path sanitization in its
    archive extraction utilities allows an attacker to craft malicious
    archives that when extracted, and under specific conditions, may write
    to files outside the intended directory. This can lead to arbitrary
    file overwrite, potentially enabling code execution through the
    modification of sensitive system files.

    https://www.cve.org/CVERecord?id=CVE-2026-26157

- CVE-2026-26158:
    A flaw was found in BusyBox. This vulnerability allows an attacker to
    modify files outside of the intended extraction directory by crafting
    a malicious tar archive containing unvalidated hardlink or symlink
    entries. If the tar archive is extracted with elevated privileges,
    this flaw can lead to privilege escalation, enabling an attacker to
    gain unauthorized access to critical system files.

    https://www.cve.org/CVERecord?id=CVE-2026-26158

Signed-off-by: Thomas Perale <thomas.perale@mind.be>
(alternative to commit 5a27004cff)
Signed-off-by: Titouan Christophe <titouan.christophe@mind.be>
2026-08-21 17:04:01 +02:00

199 lines
7.3 KiB
Diff

From 3fb6b31c716669e12f75a2accd31bb7685b1a1cb 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] 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>
CVE: CVE-2026-26157
CVE: CVE-2026-26158
Upstream: https://git.busybox.net/busybox/commit/?id=3fb6b31c716669e12f75a2accd31bb7685b1a1cb
Upstream: https://gogs.librecmc.org/OWEALS/busybox/commit/3fb6b31c716669e12f75a2accd31bb7685b1a1cb
Signed-off-by: Hitendra Prajapati <hprajapati@mvista.com>
Signed-off-by: Thomas Perale <thomas.perale@mind.be>
---
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 8a69711..b84b960 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 cc6f3f0..1c40ece 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 6670811..89a371a 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 f8dc803..d764c89 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 9de3759..cf8c2d1 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 691a2d8..5844215 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 e0ef8fc..1dc77f3 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.50.1