[packages/busybox] - release 2; backport CVE fixes: 2023-42366, 2024-58251, 2025-46394, 2025-60876, 2026-38752..38755
arekm
arekm at pld-linux.org
Mon Oct 19 16:32:53 CEST 2026
commit 168af84b55992613f8030537edae977674860da4
Author: Arkadiusz Miśkiewicz <arekm at maven.pl>
Date: Mon Oct 19 16:31:48 2026 +0200
- release 2; backport CVE fixes: 2023-42366, 2024-58251, 2025-46394,
2025-60876, 2026-38752..38755, 2026-88830..88842, 2026-108119
(CVE-2026-88833 and CVE-2026-88838 are own fixes - no upstream or
distro patch exists)
- enable CONFIG_FEATURE_PATH_TRAVERSAL_PROTECTION (CVE-2023-39810,
CVE-2026-26157)
- CVE-2026-88829 and CVE-2026-90954 remain unfixed (no fix available)
busybox-CVE-2023-42366.patch | 32 +++
busybox-CVE-2024-58251.patch | 47 ++++
busybox-CVE-2025-46394.patch | 19 ++
busybox-CVE-2025-60876.patch | 38 ++++
busybox-CVE-2026-108119.patch | 200 +++++++++++++++++
busybox-CVE-2026-38752.patch | 80 +++++++
busybox-CVE-2026-38753.patch | 51 +++++
busybox-CVE-2026-38754.patch | 154 +++++++++++++
busybox-CVE-2026-38755.patch | 69 ++++++
busybox-CVE-2026-88830.patch | 55 +++++
busybox-CVE-2026-88831.patch | 134 ++++++++++++
busybox-CVE-2026-88832.patch | 48 +++++
busybox-CVE-2026-88833.patch | 25 +++
busybox-CVE-2026-88834.patch | 55 +++++
busybox-CVE-2026-88835.patch | 492 ++++++++++++++++++++++++++++++++++++++++++
busybox-CVE-2026-88837.patch | 40 ++++
busybox-CVE-2026-88838.patch | 110 ++++++++++
busybox-CVE-2026-88839.patch | 30 +++
busybox-CVE-2026-88840.patch | 38 ++++
busybox-CVE-2026-88842.patch | 82 +++++++
busybox.config | 3 +-
busybox.spec | 49 ++++-
22 files changed, 1846 insertions(+), 5 deletions(-)
---
diff --git a/busybox.spec b/busybox.spec
index b2f8f99..bb25ce1 100644
--- a/busybox.spec
+++ b/busybox.spec
@@ -1,6 +1,4 @@
# TODO:
-# - review patch 3. Updated to 1.17.3, but the code changed so much it's unclear
-# if it still serves a purpose
# - sparc64 modules support in sparc(32), x86_64 modules support in i386 version
# - make internal commands work even if busybox is not in /bin/busybox (initrd)
# or when /proc is not mounted (static / normal)
@@ -36,9 +34,8 @@ Summary: Set of common Unix utilities for embeded systems
Summary(pl.UTF-8): Zestaw narzędzi uniksowych dla systemów wbudowanych
Summary(pt_BR.UTF-8): BusyBox é um conjunto de utilitários UNIX em um único binário
Name: busybox
-# stable line only
Version: 1.38.0
-Release: 1
+Release: 2
License: GPL v2
Group: Applications
Source0: https://www.busybox.net/downloads/%{name}-%{version}.tar.bz2
@@ -48,6 +45,7 @@ Source2: %{name}-initrd.config
%{?with_altconfig:Source3: %{cfgfile}}
Patch1: %{name}-logconsole.patch
Patch2: %{name}-printf-gettext.patch
+# needed with Linux >= 6.8 headers, which removed the CBQ uapi definitions
Patch3: busybox-1.36.1-kernel-6.8.patch
Patch4: %{name}-kernel_headers.patch
Patch5: %{name}-insmod-morearchs.patch
@@ -56,6 +54,29 @@ Patch7: %{name}-fix_64_archs.patch
Patch8: busybox-1.31.1-stime-fix.patch
Patch9: %{name}-ash-export-PATH.patch
Patch12: %{name}-inetd-rpc-portno.patch
+Patch13: %{name}-CVE-2023-42366.patch
+Patch14: %{name}-CVE-2025-46394.patch
+Patch15: %{name}-CVE-2024-58251.patch
+Patch16: %{name}-CVE-2025-60876.patch
+Patch17: %{name}-CVE-2026-38752.patch
+Patch18: %{name}-CVE-2026-38753.patch
+Patch19: %{name}-CVE-2026-38754.patch
+Patch20: %{name}-CVE-2026-38755.patch
+Patch21: %{name}-CVE-2026-88830.patch
+Patch22: %{name}-CVE-2026-88831.patch
+Patch23: %{name}-CVE-2026-88832.patch
+Patch24: %{name}-CVE-2026-88835.patch
+Patch25: %{name}-CVE-2026-88837.patch
+Patch26: %{name}-CVE-2026-88834.patch
+Patch27: %{name}-CVE-2026-88839.patch
+Patch28: %{name}-CVE-2026-88840.patch
+Patch29: %{name}-CVE-2026-88842.patch
+Patch30: %{name}-CVE-2026-108119.patch
+# local backports, no upstream or distro fix available
+Patch31: %{name}-CVE-2026-88833.patch
+Patch32: %{name}-CVE-2026-88838.patch
+# No fix available (upstream, Debian, SUSE, Fedora) for CVE-2026-88829
+# (httpd proxy auth and pipelining) and CVE-2026-90954 (dpkg-deb ar link flush).
URL: https://www.busybox.net/
BuildRequires: gcc >= 3.2
BuildRequires: perl-tools-pod
@@ -203,6 +224,26 @@ Statycznie skonsolidowany busybox dla initrd.
%patch -P8 -p1
%patch -P9 -p1
%patch -P12 -p1
+%patch -P13 -p1
+%patch -P14 -p1
+%patch -P15 -p1
+%patch -P16 -p1
+%patch -P17 -p1
+%patch -P18 -p1
+%patch -P19 -p1
+%patch -P20 -p1
+%patch -P21 -p1
+%patch -P22 -p1
+%patch -P23 -p1
+%patch -P24 -p1
+%patch -P25 -p1
+%patch -P26 -p1
+%patch -P27 -p1
+%patch -P28 -p1
+%patch -P29 -p1
+%patch -P30 -p1
+%patch -P31 -p1
+%patch -P32 -p1
%build
install -d built
diff --git a/busybox-CVE-2023-42366.patch b/busybox-CVE-2023-42366.patch
new file mode 100644
index 0000000..a129c90
--- /dev/null
+++ b/busybox-CVE-2023-42366.patch
@@ -0,0 +1,32 @@
+From 5cf8b332429a1dd9afef3337bae92aeddaeff993 Mon Sep 17 00:00:00 2001
+From: Valery Ushakov <uwe at stderr.spb.ru>
+Date: Wed, 24 Jan 2024 22:24:41 +0300
+Subject: [PATCH] awk.c: fix CVE-2023-42366 (bug #15874)
+
+Make sure we don't read past the end of the string in next_token()
+when backslash is the last character in an (invalid) regexp.
+---
+ editors/awk.c | 6 ++++--
+ 1 file changed, 4 insertions(+), 2 deletions(-)
+
+diff --git a/editors/awk.c b/editors/awk.c
+index 728ee8685..be48df7c7 100644
+--- a/editors/awk.c
++++ b/editors/awk.c
+@@ -1165,9 +1165,11 @@ static uint32_t next_token(uint32_t expected)
+ s[-1] = bb_process_escape_sequence((const char **)&pp);
+ if (*p == '\\')
+ *s++ = '\\';
+- if (pp == p)
++ if (pp == p) {
++ if (*p == '\0')
++ syntax_error(EMSG_UNEXP_EOS);
+ *s++ = *p++;
+- else
++ } else
+ p = pp;
+ }
+ }
+--
+2.34.1
+
diff --git a/busybox-CVE-2024-58251.patch b/busybox-CVE-2024-58251.patch
new file mode 100644
index 0000000..12ff357
--- /dev/null
+++ b/busybox-CVE-2024-58251.patch
@@ -0,0 +1,47 @@
+From: Valery Ushakov <valery.ushakov at bell-sw.com>
+Date: Thu, 21 Aug 2025 12:31:53 +0000
+Subject: netstat: CVE-2024-58251 - sanitize argv0 for -p
+Bug-Debian: https://bugs.debian.org/1104009
+
+Signed-off-by: Valery Ushakov <valery.ushakov at bell-sw.com>
+---
+ networking/netstat.c | 7 ++++++-
+ 1 file changed, 6 insertions(+), 1 deletion(-)
+
+diff --git a/networking/netstat.c b/networking/netstat.c
+index 807800a62..d979f6079 100644
+--- a/networking/netstat.c
++++ b/networking/netstat.c
+@@ -41,6 +41,7 @@
+
+ #include "libbb.h"
+ #include "inet_common.h"
++#include "unicode.h"
+
+ //usage:#define netstat_trivial_usage
+ //usage: "[-"IF_ROUTE("r")"al] [-tuwx] [-en"IF_FEATURE_NETSTAT_WIDE("W")IF_FEATURE_NETSTAT_PRG("p")"]"
+@@ -314,9 +315,12 @@ static int FAST_FUNC dir_act(struct recursive_state *state,
+ return FALSE;
+ cmdline_buf[n] = '\0';
+
++ /* don't write process-controlled argv[0] to the user's terminal as-is */
++ const char *argv0base = printable_string(bb_basename(cmdline_buf));
++
+ /* go through all files in /proc/PID/fd and check whether they are sockets */
+ strcpy(proc_pid_fname + len - (sizeof("cmdline")-1), "fd");
+- pid_slash_progname = concat_path_file(pid, bb_basename(cmdline_buf)); /* "PID/argv0" */
++ pid_slash_progname = concat_path_file(pid, argv0base); /* "PID/argv0" */
+ n = recursive_action(proc_pid_fname,
+ ACTION_RECURSE | ACTION_QUIET,
+ add_to_prg_cache_if_socket,
+@@ -686,6 +690,7 @@ int netstat_main(int argc UNUSED_PARAM, char **argv)
+ unsigned opt;
+
+ INIT_G();
++ init_unicode();
+
+ /* Option string must match NETSTAT_xxx constants */
+ opt = getopt32(argv, NETSTAT_OPTS);
+--
+2.34.1
+
diff --git a/busybox-CVE-2025-46394.patch b/busybox-CVE-2025-46394.patch
new file mode 100644
index 0000000..2db8130
--- /dev/null
+++ b/busybox-CVE-2025-46394.patch
@@ -0,0 +1,19 @@
+From: Michael Tokarev <mjt at tls.msk.ru>
+Subject: missing part of CVE-2025-46394 fix
+Date: Sun, 01 Feb 2026 19:21:37 +0300
+Bug-Debian: https://bugs.debian.org/1104008
+Forwarded: yes, https://lists.busybox.net/pipermail/busybox/2026-February/091910.html
+
+diff --git a/archival/libarchive/header_verbose_list.c b/archival/libarchive/header_verbose_list.c
+index a575a08a0..e4ee939c0 100644
+--- a/archival/libarchive/header_verbose_list.c
++++ b/archival/libarchive/header_verbose_list.c
+@@ -40,7 +40,7 @@ void FAST_FUNC header_verbose_list(const file_header_t *file_header)
+ ptm->tm_hour,
+ ptm->tm_min,
+ ptm->tm_sec,
+- file_header->name);
++ printable_string(file_header->name));
+
+ #else /* !FEATURE_TAR_UNAME_GNAME */
+
diff --git a/busybox-CVE-2025-60876.patch b/busybox-CVE-2025-60876.patch
new file mode 100644
index 0000000..639cb37
--- /dev/null
+++ b/busybox-CVE-2025-60876.patch
@@ -0,0 +1,38 @@
+From 9904185c589bb43f0c7f129bd16e885b140ae931 Mon Sep 17 00:00:00 2001
+From: Radoslav Kolev <radoslav.kolev at suse.com>
+Date: Wed, 12 Nov 2025 19:46:15 +0200
+Subject: [PATCH v2 1/1] wget: don't allow control characters or spaces in the
+ URL
+
+Fixes CVE-2025-60876 malicious URL can be used to inject
+HTTP headers in the request.
+
+Signed-off-by: Radoslav Kolev <radoslav.kolev at suse.com>
+Reviewed-by: Emmanuel Deloget <logout at free.fr>
+---
+ networking/wget.c | 9 +++++++++
+ 1 file changed, 9 insertions(+)
+
+diff --git a/networking/wget.c b/networking/wget.c
+index ec3767793..fa555427b 100644
+--- a/networking/wget.c
++++ b/networking/wget.c
+@@ -536,6 +536,15 @@ static void parse_url(const char *src_url, struct host_info *h)
+ {
+ char *url, *p, *sp;
+
++ /* Fix for CVE-2025-60876 - don't allow control characters or spaces in the URL */
++ /* otherwise a malicious URL can be used to inject HTTP headers in the request */
++ const unsigned char *u = (void *) src_url;
++ while (*u) {
++ if (*u <= ' ')
++ bb_simple_error_msg_and_die("Unencoded control character found in the URL!");
++ u++;
++ }
++
+ free(h->allocated);
+ h->allocated = url = xstrdup(src_url);
+
+--
+2.51.1
+
diff --git a/busybox-CVE-2026-108119.patch b/busybox-CVE-2026-108119.patch
new file mode 100644
index 0000000..a9dc1d8
--- /dev/null
+++ b/busybox-CVE-2026-108119.patch
@@ -0,0 +1,200 @@
+From 5da20102b5db249c76edddd2cf608394a1ad34dc Mon Sep 17 00:00:00 2001
+From: Denys Vlasenko <vda.linux at googlemail.com>
+Date: Fri, 9 Oct 2026 15:09:26 +0200
+Subject: [PATCH] tar: ".." is also an unsafe filename, treat as such
+
+Signed-off-by: Denys Vlasenko <vda.linux at googlemail.com>
+---
+ archival/libarchive/unsafe_prefix.c | 6 ++++++
+ 1 file changed, 6 insertions(+)
+
+diff --git a/archival/libarchive/unsafe_prefix.c b/archival/libarchive/unsafe_prefix.c
+index 89a371a7f..ab70ff816 100644
+--- a/archival/libarchive/unsafe_prefix.c
++++ b/archival/libarchive/unsafe_prefix.c
+@@ -8,6 +8,12 @@
+ const char* FAST_FUNC skip_unsafe_prefix(const char *str)
+ {
+ const char *cp = str;
++
++ /* if "..": malicious, return "" */
++ /* (causes harmless errors trying to create or hardlink a file named "") */
++ if (cp[0] == '.' && cp[1] == '.' && cp[2] == '\0')
++ return cp + 2;
++
+ while (1) {
+ const char *cp2;
+ if (*cp == '/') {
+--
+2.56.0
+
+From 382008b3f07d78e3abbc50d00bb857622dbbfac6 Mon Sep 17 00:00:00 2001
+From: Denys Vlasenko <vda.linux at googlemail.com>
+Date: Fri, 9 Oct 2026 16:37:30 +0200
+Subject: [PATCH] tar: protect against Symlink+Hardlink attack
+
+function old new delta
+create_links_from_list 93 190 +97
+
+Signed-off-by: Denys Vlasenko <vda.linux at googlemail.com>
+---
+ archival/libarchive/unsafe_symlink_target.c | 54 ++++++++++++++++++---
+ testsuite/tar.tests | 39 +++++++++++++--
+ 2 files changed, 81 insertions(+), 12 deletions(-)
+
+diff --git a/archival/libarchive/unsafe_symlink_target.c b/archival/libarchive/unsafe_symlink_target.c
+index d764c89ab..65c782bdd 100644
+--- a/archival/libarchive/unsafe_symlink_target.c
++++ b/archival/libarchive/unsafe_symlink_target.c
+@@ -17,26 +17,64 @@ void FAST_FUNC create_or_remember_link(llist_t **link_placeholders,
+ return;
+ }
+ if (symlink(target, linkname) != 0) {
+- /* shared message */
++ // shared message
+ bb_perror_msg_and_die("can't create %slink '%s' to '%s'",
+ "sym", linkname, target
+ );
+ }
+ }
+
++static int all_dir_components_are_dirs(char *path)
++{
++ struct stat sb;
++ char *slash;
++
++ slash = path;
++ while ((slash = strchr(slash, '/')) != NULL) {
++ int ok;
++ *slash = '\0';
++ ok = (lstat(path, &sb) == 0 && S_ISDIR(sb.st_mode));
++ *slash++ = '/';
++ if (!ok)
++ return 0;
++ }
++ return 1;
++}
++
+ void FAST_FUNC create_links_from_list(llist_t *list)
+ {
++ // This idea sounds better (fewer file ops):
++ // Create hardlinks first, then symlinks.
++ // ^^^ but it breaks the "hardlink to symlink" case:
++ // mkdir dir
++ // >dir/a
++ // ln -s ../dir/a dir/b
++ // ln dir/b dir/c
++ // mkdir new; tar cf - dir/a dir/b dir/c | tar -C new -xvf -
++ // On extract, 'c' will have no 'b' to be a hardlink to.
++
+ while (list) {
+ char *target;
++ char *linkname = list->data + 1;
+
+- target = list->data + 1 + strlen(list->data + 1) + 1;
+- if ((*list->data ? link : symlink) (target, list->data + 1)) {
+- /* shared message */
+- bb_error_msg_and_die("can't create %slink '%s' to '%s'",
+- *list->data ? "hard" : "sym",
+- list->data + 1, target
++ target = linkname + strlen(linkname) + 1;
++
++ // Check whether the linkname has suspicious components:
++ // SYMLINK_OUTSIDE_EXTRACT_DIR/etc/passwd sort of thing.
++ // (Our delayed creation of symlinks is not defending against it,
++ // we could create SYMLINK_OUTSIDE_EXTRACT_DIR ourself!)
++ if (!all_dir_components_are_dirs(linkname)) {
++ errno = ENOTDIR;
++ goto err;
++ }
++ if ((list->data[0] ? link : symlink) (target, linkname)) {
++ // shared message
++ err:
++ bb_perror_msg_and_die("can't create %slink '%s' to '%s'",
++ list->data[0] ? "hard" : "sym",
++ linkname, target
+ );
+- /* Note: GNU tar 1.34 errors out only _after_ all links are (attempted to be) created */
++ // Note: GNU tar 1.34 errors out only _after_ all links are (attempted to be) created
+ }
+ list = list->link;
+ }
+diff --git a/testsuite/tar.tests b/testsuite/tar.tests
+index 83c8c2edf..efd8f4855 100755
+--- a/testsuite/tar.tests
++++ b/testsuite/tar.tests
+@@ -304,7 +304,7 @@ optional UUDECODE FEATURE_TAR_AUTODETECT FEATURE_SEAMLESS_BZ2
+ testing "tar does not extract into symlinks" "\
+ >>/tmp/passwd && uudecode -o input && tar xf input 2>&1 && rm passwd; cat /tmp/passwd; echo \$?
+ " "\
+-tar: can't create symlink 'passwd' to '/tmp/passwd'
++tar: can't create symlink 'passwd' to '/tmp/passwd': File exists
+ 0
+ " \
+ "" "\
+@@ -324,7 +324,7 @@ optional UUDECODE FEATURE_TAR_AUTODETECT FEATURE_SEAMLESS_BZ2
+ testing "tar -k does not extract into symlinks" "\
+ >>/tmp/passwd && uudecode -o input && tar xf input -k 2>&1 && rm passwd; cat /tmp/passwd; echo \$?
+ " "\
+-tar: can't create symlink 'passwd' to '/tmp/passwd'
++tar: can't create symlink 'passwd' to '/tmp/passwd': File exists
+ 0
+ " \
+ "" "\
+@@ -374,7 +374,7 @@ ls symlink/bb_test_evilfile
+ anything.txt
+ symlink
+ symlink/bb_test_evilfile
+-tar: can't create symlink 'symlink' to '/tmp'
++tar: can't create symlink 'symlink' to '/tmp': File exists
+ 1
+ ls: /tmp/bb_test_evilfile: No such file or directory
+ ls: bb_test_evilfile: No such file or directory
+@@ -400,7 +400,7 @@ mkdir dir
+ ln -s ../dir/a dir/b
+ ln dir/b dir/c
+ mkdir new
+-tar cf - dir/* | tar -C new -xvf - 2>&1
++tar cf - dir/a dir/b dir/c | tar -C new -xvf - 2>&1
+ ' "\
+ dir/a
+ dir/b
+@@ -410,4 +410,35 @@ dir/c
+ SKIP=
+ cd .. || exit 1; rm -rf tar.tempdir 2>/dev/null
+
++mkdir tar.tempdir && cd tar.tempdir || exit 1
++testing "tar Symlink+Hardlink attack: create symlink-to-outside and then create a hardlink in it" '\
++exec 2>&1
++mkdir 2tar || exit 1
++(
++cd 2tar || exit 1
++ln -s .. gate
++ln -s gate alias
++ln -s alias portal
++ > seed
++ln seed portal/ESCAPED
++#^^^ the ESCAPED file is actually in ..!
++tar cf 2tar.tar \
++ alias \
++ gate \
++ portal \
++ seed \
++ portal/ESCAPED
++rm portal/ESCAPED alias gate portal seed
++tar xf 2tar.tar
++echo $?
++)
++test -e ESCAPED && echo "ESCAPED!"
++' "\
++tar: can't create directory 'portal': No such file or directory
++tar: can't create hardlink 'portal/ESCAPED' to 'seed': Not a directory
++1
++" \
++"" ""
++cd .. || exit 1; rm -rf tar.tempdir 2>/dev/null
++
+ exit $FAILCOUNT
+--
+2.56.0
+
diff --git a/busybox-CVE-2026-38752.patch b/busybox-CVE-2026-38752.patch
new file mode 100644
index 0000000..baee6d8
--- /dev/null
+++ b/busybox-CVE-2026-38752.patch
@@ -0,0 +1,80 @@
+From sanghyun.park.cnu at gmail.com Tue Jun 16 03:16:05 2026
+From: sanghyun.park.cnu at gmail.com (Sanghyun Park)
+Date: Tue, 16 Jun 2026 12:16:05 +0900
+Subject: [PATCH] awk: fix stack overflow in evaluate()
+Message-ID: <20260616031605.1178760-3-sanghyun.park.cnu at gmail.com>
+
+Recursive awk functions can keep re-entering evaluate() until the
+process stack is exhausted. Track active function-call depth and report a
+normal awk error once the limit is reached instead of overflowing the
+stack.
+
+Signed-off-by: Sanghyun Park <sanghyun.park.cnu at gmail.com>
+---
+ editors/awk.c | 9 +++++++++
+ 1 file changed, 9 insertions(+)
+
+diff --git a/editors/awk.c b/editors/awk.c
+index dd8f4ac..f15832b 100644
+--- a/editors/awk.c
++++ b/editors/awk.c
+@@ -633,6 +633,7 @@ struct globals {
+ smalluint exitcode;
+
+ unsigned evaluate__seed;
++ unsigned evaluate__func_depth;
+ var *evaluate__fnargs;
+ regex_t evaluate__sreg;
+
+@@ -703,6 +704,7 @@ static const char EMSG_UNEXP_TOKEN[] ALIGN1 = "Unexpected token";
+ static const char EMSG_DIV_BY_ZERO[] ALIGN1 = "Division by zero";
+ static const char EMSG_INV_FMT[] ALIGN1 = "Invalid format specifier";
+ static const char EMSG_TOO_FEW_ARGS[] ALIGN1 = "Too few arguments";
++static const char EMSG_RECURSION[] ALIGN1 = "Recursion limit exceeded";
+ static const char EMSG_NOT_ARRAY[] ALIGN1 = "Not an array";
+ static const char EMSG_POSSIBLE_ERROR[] ALIGN1 = "Possible syntax error";
+ static const char EMSG_UNDEF_FUNC[] ALIGN1 = "Call to undefined function";
+@@ -2988,6 +2990,7 @@ static var *evaluate(node *op, var *res)
+ #define fnargs (G.evaluate__fnargs)
+ /* seed is initialized to 1 */
+ #define seed (G.evaluate__seed)
++#define func_depth (G.evaluate__func_depth)
+ #define sreg (G.evaluate__sreg)
+
+ var *tmpvars;
+@@ -3299,13 +3302,17 @@ static var *evaluate(node *op, var *res)
+ var *argvars, *sv_fnargs;
+ const char *sv_progname;
+ int nargs, i;
++ enum { MAX_AWK_FUNC_DEPTH = 1000 };
+
+ debug_printf_eval("FUNC\n");
+
+ if (!op->r.f->defined)
+ syntax_error(EMSG_UNDEF_FUNC);
++ if (func_depth >= MAX_AWK_FUNC_DEPTH)
++ syntax_error(EMSG_RECURSION);
+
+ /* The body might be empty, still has to eval the args */
++ func_depth++;
+ nargs = op->r.f->nargs;
+ argvars = nvalloc(nargs);
+ i = 0;
+@@ -3333,6 +3340,7 @@ static var *evaluate(node *op, var *res)
+
+ g_progname = sv_progname;
+ fnargs = sv_fnargs;
++ func_depth--;
+
+ break;
+ }
+@@ -3690,6 +3698,7 @@ static var *evaluate(node *op, var *res)
+ return res;
+ #undef fnargs
+ #undef seed
++#undef func_depth
+ #undef sreg
+ }
+
+--
+2.48.1
diff --git a/busybox-CVE-2026-38753.patch b/busybox-CVE-2026-38753.patch
new file mode 100644
index 0000000..4ef3b42
--- /dev/null
+++ b/busybox-CVE-2026-38753.patch
@@ -0,0 +1,51 @@
+From sanghyun.park.cnu at gmail.com Tue Jun 16 03:18:43 2026
+From: sanghyun.park.cnu at gmail.com (Sanghyun Park)
+Date: Tue, 16 Jun 2026 12:18:43 +0900
+Subject: [PATCH] awk: fix use-after-free in awk_sub()
+Message-ID: <20260616031845.1181463-1-sanghyun.park.cnu at gmail.com>
+
+awk_sub() receives the replacement text as a pointer into awk variable
+storage. Evaluating the regular expression argument can change that
+storage through as_regex(), leaving the replacement pointer dangling
+before strlen() and the replacement loop use it.
+
+Copy the replacement string before calling as_regex() so substitution
+uses stable storage for the duration of the operation.
+
+Signed-off-by: Sanghyun Park <sanghyun.park.cnu at gmail.com>
+---
+ editors/awk.c | 4 ++++
+ 1 file changed, 4 insertions(+)
+
+diff --git a/editors/awk.c b/editors/awk.c
+index f15832b..8a51827 100644
+--- a/editors/awk.c
++++ b/editors/awk.c
+@@ -2555,6 +2555,7 @@ static char *awk_printf(node *n, size_t *len)
+ static int awk_sub(node *rn, const char *repl, int nm, var *src, var *dest /*,int subexp*/)
+ {
+ char *resbuf;
++ char *repl_copy;
+ const char *sp;
+ int match_no, residx, replen, resbufsize;
+ int regexec_flags;
+@@ -2572,7 +2573,9 @@ static int awk_sub(node *rn, const char *repl, int nm, var *src, var *dest /*,in
+ resbuf = NULL;
+ residx = 0;
+ match_no = 0;
++ repl_copy = xstrdup(repl);
+ regex = as_regex(rn, &sreg);
++ repl = repl_copy;
+ sp = getvar_s(src ? src : intvar[F0]);
+ #if defined(REG_STARTEND)
+ src_string = sp;
+@@ -2662,6 +2665,7 @@ static int awk_sub(node *rn, const char *repl, int nm, var *src, var *dest /*,in
+ setvar_p(dest ? dest : intvar[F0], resbuf);
+ if (regex == &sreg)
+ regfree(regex);
++ free(repl_copy);
+ return match_no;
+ }
+
+--
+2.48.1
diff --git a/busybox-CVE-2026-38754.patch b/busybox-CVE-2026-38754.patch
new file mode 100644
index 0000000..8bfe71a
--- /dev/null
+++ b/busybox-CVE-2026-38754.patch
@@ -0,0 +1,154 @@
+From a448b6d5b21e5b21249391389b6f0551d9bea136 Mon Sep 17 00:00:00 2001
+From: Sanghyun Park <sanghyun.park.cnu at gmail.com>
+Date: Thu, 18 Jun 2026 17:04:20 +0900
+Subject: [PATCH] ash: fix out-of-bounds read in ifsbreakup()
+
+ifsfree() does not only release allocated ifsregion nodes; it also clears
+the global IFS region state used by ifsbreakup(). If argstr() raises an
+error while expanding an argument, ash longjmps out of expandarg() before
+that cleanup runs, leaving stale IFS split offsets behind.
+
+A later expansion can reuse the stack for a shorter string. ifsbreakup()
+then sees the stale IFS state, trusts the old offsets, and can walk past
+the current stack block before dereferencing p.
+
+Follow dash's root-cause fix: when an expansion-related handler catches
+EXERROR and continues, restore the handler and call ifsfree(). Apply
+the cleanup to redirectsafe(), expandstr(), and evaltree().
+
+Upstream commit:
+
+ Date: Mon Dec 5 23:02:01 2022 +0800
+ expand: Add ifsfree to expand to fix a logic error that causes a buffer over-read
+
+ On Mon, Jun 20, 2022 at 02:27:10PM -0400, Alex Gorinson wrote:
+ > Due to a logic error in the ifsbreakup function in expand.c if a
+ > heredoc and normal command is run one after the other by means of a
+ > semi-colon, when the second command drops into ifsbreakup the command
+ > will be evaluated with the ifslastp/ifsfirst struct that was set when
+ > the here doc was evaluated. This results in a buffer over-read that
+ > can leak the program's heap, stack, and arena addresses which can be
+ > used to beat ASLR.
+ >
+ > Steps to Reproduce:
+ > First bug:
+ > cmd args: ~/exampleDir/example> dash
+ > $ M='AAAAAAAAAAAAAAAAA' <note: 17 A's>
+ > $ q00(){
+ > $ <<000;echo
+ > $ ${D?$M$M$M$M$M$M} <note: 6 $M's>
+ > $ 000
+ > $ }
+ > $ q00 <note: After the q00 is typed in, the leak
+ > should be echo'd out; this works with ash, busybox ash, and dash and
+ > with all option args.>
+ >
+ > Patch:
+ > Adding the following to expand.c will fix both bugs in one go.
+ > (Thank you to Harald van Dijk and Michael Greenberg for doing the
+ > heavy lifting for this patch!)
+ > ==========================
+ > --- a/src/expand.c
+ > +++ b/src/expand.c
+ > @@ -859,6 +859,7 @@
+ > if (discard)
+ > return -1;
+ >
+ > +ifsfree();
+ > sh_error("Bad substitution");
+ > }
+ >
+ > @@ -1739,6 +1740,7 @@
+ > } else
+ > msg = umsg;
+ > }
+ > +ifsfree();
+ > sh_error("%.*s: %s%s", end - var - 1, var, msg, tail);
+ > }
+ > ==========================
+
+ Thanks for the report!
+
+ I think it's better to add the ifsfree() call to the exception
+ handling path as other sh_error calls may trigger this too.
+
+function old new delta
+restore_handler_expandarg - 33 +33
+evaltree 725 711 -14
+static.redirectsafe 141 124 -17
+expandstr 262 242 -20
+------------------------------------------------------------------------------
+(add/remove: 1/0 grow/shrink: 0/3 up/down: 36/-45) Total: -18 bytes
+
+Signed-off-by: Sanghyun Park <sanghyun.park.cnu at gmail.com>
+Signed-off-by: Denys Vlasenko <vda.linux at googlemail.com>
+---
+ shell/ash.c | 24 +++++++++++++++---------
+ 1 file changed, 15 insertions(+), 9 deletions(-)
+
+diff --git a/shell/ash.c b/shell/ash.c
+index fb887f31b..b8ff67b16 100644
+--- a/shell/ash.c
++++ b/shell/ash.c
+@@ -5574,6 +5574,7 @@ write2pipe(int pip[2], const char *p, size_t len)
+
+ /* openhere needs this forward reference */
+ static void expandhere(union node *arg);
++static void ifsfree(void);
+ static int
+ openhere(union node *redir)
+ {
+@@ -5998,6 +5999,17 @@ redirect(union node *redir, int flags)
+ // preverrout_fd = copied_fd2;
+ }
+
++static void
++restore_handler_expandarg(struct jmploc *savehandler, int err)
++{
++ exception_handler = savehandler;
++ if (err) {
++ if (exception_type != EXERROR)
++ longjmp(exception_handler->loc, 1);
++ ifsfree();
++ }
++}
++
+ static int
+ redirectsafe(union node *redir, int flags)
+ {
+@@ -6013,9 +6025,7 @@ redirectsafe(union node *redir, int flags)
+ exception_handler = &jmploc;
+ redirect(redir, flags);
+ }
+- exception_handler = savehandler;
+- if (err && exception_type != EXERROR)
+- longjmp(exception_handler->loc, 1);
++ restore_handler_expandarg(savehandler, err);
+ RESTORE_INT(saveint);
+ return err;
+ }
+@@ -9792,9 +9802,7 @@ evaltree(union node *n, int flags)
+ trap_depth--;
+ in_trap_ERR = 0;
+
+- exception_handler = savehandler;
+- if (err && exception_type != EXERROR)
+- longjmp(exception_handler->loc, 1);
++ restore_handler_expandarg(savehandler, err);
+
+ exitstatus = savestatus;
+ }
+@@ -14009,9 +14017,7 @@ expandstr(const char *ps, int syntax_type)
+ result = stackblock();
+
+ out:
+- exception_handler = savehandler;
+- if (err && exception_type != EXERROR)
+- longjmp(exception_handler->loc, 1);
++ restore_handler_expandarg(savehandler, err);
+
+ doprompt = saveprompt;
+ /* Try: PS1='`xxx(`' */
+--
+2.52.0
+
diff --git a/busybox-CVE-2026-38755.patch b/busybox-CVE-2026-38755.patch
new file mode 100644
index 0000000..75022c6
--- /dev/null
+++ b/busybox-CVE-2026-38755.patch
@@ -0,0 +1,69 @@
+From sanghyun.park.cnu at gmail.com Tue Jun 16 03:19:27 2026
+From: sanghyun.park.cnu at gmail.com (Sanghyun Park)
+Date: Tue, 16 Jun 2026 12:19:27 +0900
+Subject: [PATCH] ash: fix stack overflow in evalfun()
+Message-ID: <20260616031928.1182292-1-sanghyun.park.cnu at gmail.com>
+
+Recursive shell functions can repeatedly enter evalfun() until the
+process stack is exhausted. Track active shell function calls and raise a
+normal shell error when the recursion limit is reached.
+
+The counter is decremented through the existing funcdone cleanup path so
+errors raised from inside the function body unwind it correctly.
+
+Signed-off-by: Sanghyun Park <sanghyun.park.cnu at gmail.com>
+---
+ shell/ash.c | 8 ++++++++
+ 1 file changed, 8 insertions(+)
+
+diff --git a/shell/ash.c b/shell/ash.c
+index cda6c85..2e319cc 100644
+--- a/shell/ash.c
++++ b/shell/ash.c
+@@ -481,6 +481,7 @@ struct globals_misc {
+ int shlvl;
+ #define rootshell (!shlvl)
+ int errlinno;
++ unsigned func_depth;
+
+ char *minusc; /* argument to -c option */
+
+@@ -588,6 +589,7 @@ extern struct globals_misc *BB_GLOBAL_CONST ash_ptr_to_globals_misc;
+ #define rootpid (G_misc.rootpid )
+ #define shlvl (G_misc.shlvl )
+ #define errlinno (G_misc.errlinno )
++#define func_depth (G_misc.func_depth )
+ #define minusc (G_misc.minusc )
+ #define curdir (G_misc.curdir )
+ #define physdir (G_misc.physdir )
+@@ -10321,6 +10323,10 @@ evalfun(struct funcnode *func, int argc, char **argv, int flags)
+ int savefuncline;
+ char *savefuncname;
+ char *savetrap = NULL;
++ enum { MAX_ASH_FUNC_DEPTH = 1000 };
++
++ if (func_depth >= MAX_ASH_FUNC_DEPTH)
++ ash_msg_and_raise_error("function recursion limit exceeded");
+
+ if (!Eflag) {
+ savetrap = trap[NTRAP_ERR];
+@@ -10339,6 +10345,7 @@ evalfun(struct funcnode *func, int argc, char **argv, int flags)
+ exception_handler = &jmploc;
+ shellparam.malloced = 0;
+ func->count++;
++ func_depth++;
+ funcname = func->n.ndefun.text;
+ funcline = func->n.ndefun.linno;
+ INTON;
+@@ -10360,6 +10367,7 @@ evalfun(struct funcnode *func, int argc, char **argv, int flags)
+ }
+ funcline = savefuncline;
+ lineno = savelineno;
++ func_depth--;
+ freefunc(func);
+ freeparam(&shellparam);
+ shellparam = saveparam;
+--
+2.48.1
+
+
diff --git a/busybox-CVE-2026-88830.patch b/busybox-CVE-2026-88830.patch
new file mode 100644
index 0000000..9a71fb1
--- /dev/null
+++ b/busybox-CVE-2026-88830.patch
@@ -0,0 +1,55 @@
+From 89ac82774f90e270d04ed5299e5cf05dfbc314c1 Mon Sep 17 00:00:00 2001
+From: Denys Vlasenko <vda.linux at googlemail.com>
+Date: Mon, 21 Sep 2026 03:19:28 +0200
+Subject: [PATCH] tls: fix undersized buffer calculation
+
+All callers of pstm_montgomery_reduce() are currently in pstm_exptmod()
+and they all pass a preallocated temporary buffer, so the incorrect code
+is unreachable. But fix it anyway.
+
+function old new delta
+pstm_montgomery_reduce 431 430 -1
+
+Signed-off-by: Denys Vlasenko <vda.linux at googlemail.com>
+---
+ networking/tls_pstm_montgomery_reduce.c | 10 +++++++---
+ 1 file changed, 7 insertions(+), 3 deletions(-)
+
+diff --git a/networking/tls_pstm_montgomery_reduce.c b/networking/tls_pstm_montgomery_reduce.c
+index e63e590db..36882425a 100644
+--- a/networking/tls_pstm_montgomery_reduce.c
++++ b/networking/tls_pstm_montgomery_reduce.c
+@@ -370,6 +370,7 @@ int32 FAST_FUNC pstm_montgomery_reduce(psPool_t *pool, pstm_int *a, pstm_int *m,
+ pstm_digit *c, *_c, *tmpm, mu;
+ int32 oldused, x, y;
+ int pa; //bbox: was int16
++ uint32 cSize;
+
+ pa = m->used;
+ if (pa > a->alloc) {
+@@ -377,11 +378,13 @@ int32 FAST_FUNC pstm_montgomery_reduce(psPool_t *pool, pstm_int *a, pstm_int *m,
+ return PS_LIMIT_FAIL;
+ }
+
+- if (paD && paDlen >= (uint32)2*pa+1) {
++ cSize = (2 * pa + 1) * sizeof(pstm_digit);
++ if (paD && paDlen >= cSize) {
+ c = paD;
+ memset(c, 0x0, paDlen);
+ } else {
+- c = xzalloc(2*pa+1);//bbox
++ //bbox: dead code, all callers always pass paD:
++ c = xzalloc(cSize); //bbox
+ }
+ /* copy the input */
+ oldused = a->used;
+@@ -441,7 +444,8 @@ int32 FAST_FUNC pstm_montgomery_reduce(psPool_t *pool, pstm_int *a, pstm_int *m,
+ x = PS_MEM_FAIL;
+ }
+ }
+- if (paDlen < (uint32)2*pa+1) {
++ //bbox: dead code, all callers always pass paD:
++ if (c != paD) {
+ psFree(c, pool);
+ }
+ return x;
diff --git a/busybox-CVE-2026-88831.patch b/busybox-CVE-2026-88831.patch
new file mode 100644
index 0000000..37052f6
--- /dev/null
+++ b/busybox-CVE-2026-88831.patch
@@ -0,0 +1,134 @@
+From 339e3c62f5bcf252839c8abca5325f48a22f21aa Mon Sep 17 00:00:00 2001
+From: Denys Vlasenko <vda.linux at googlemail.com>
+Date: Mon, 21 Sep 2026 04:40:37 +0200
+Subject: [PATCH] httpd: fix handling of "D:1.2.3/999"
+
+An error in allow/deny rule should be interpreted as D:0/0
+("deny all") but was interpreted as D:IP/0. Fix this.
+
+function old new delta
+parse_conf 1323 1330 +7
+
+Signed-off-by: Denys Vlasenko <vda.linux at googlemail.com>
+---
+ networking/httpd.c | 32 ++++++++++++++++++--------------
+ 1 file changed, 18 insertions(+), 14 deletions(-)
+
+diff --git a/networking/httpd.c b/networking/httpd.c
+index ad59493d7..bc446a838 100644
+--- a/networking/httpd.c
++++ b/networking/httpd.c
+@@ -41,7 +41,7 @@
+ * H:/serverroot # define the server root. It will override -h
+ * A:172.20. # Allow address from 172.20.0.0/16
+ * A:10.0.0.0/25 # Allow any address from 10.0.0.0-10.0.0.127
+- * A:10.0.0.0/255.255.255.128 # Allow any address that previous set
++ * A:10.0.0.0/255.255.255.128 # Same as the previous
+ * A:127.0.0.1 # Allow local loopback connections
+ * D:* # Deny from other IP connections
+ * E404:/path/e404.html # /path/e404.html is the 404 (not found) error page
+@@ -63,7 +63,7 @@
+ * Deny/Allow IP logic:
+ * - Default is to allow all (Allow all (A:*) is a no-op).
+ * - Deny rules take precedence over allow rules.
+- * - "Deny all" rule (D:*) is applied last.
++ * - However, "Deny all" rule (D:*) is applied last.
+ *
+ * Example:
+ * 1. Allow only specified addresses
+@@ -381,8 +381,8 @@ typedef struct Htaccess {
+ /* Must have "next" as a first member */
+ typedef struct Htaccess_IP {
+ struct Htaccess_IP *next;
+- unsigned ip;
+- unsigned mask;
++ uint32_t ip; /* host-endian */
++ uint32_t mask; /* host-endian */
+ int allow_deny;
+ } Htaccess_IP;
+ #endif
+@@ -653,11 +653,11 @@ static ALWAYS_INLINE void free_Htaccess_IP_list(Htaccess_IP **pptr)
+ #if ENABLE_FEATURE_HTTPD_ACL_IP
+ /* Returns presumed mask width in bits or < 0 on error.
+ * Updates strp, stores IP at provided pointer */
+-static int scan_ip(const char **strp, unsigned *ipp, unsigned char endc)
++static int scan_ip(const char **strp, uint32_t *ipp, unsigned char endc)
+ {
+ const char *p = *strp;
+ int auto_mask = 8;
+- unsigned ip = 0;
++ uint32_t ip = 0;
+ int j;
+
+ if (*p == '/')
+@@ -695,10 +695,10 @@ static int scan_ip(const char **strp, unsigned *ipp, unsigned char endc)
+ }
+
+ /* Returns 0 on success. Stores IP and mask at provided pointers */
+-static int scan_ip_mask(const char *str, unsigned *ipp, unsigned *maskp)
++static int scan_ip_mask(const char *str, uint32_t *ipp, uint32_t *maskp)
+ {
+ int i;
+- unsigned mask;
++ uint32_t mask;
+ char *p;
+
+ i = scan_ip(&str, ipp, '/');
+@@ -713,15 +713,16 @@ static int scan_ip_mask(const char *str, unsigned *ipp, unsigned *maskp)
+ /* (return 0 (success) only if it has N.N.N.N form) */
+ return scan_ip(&str, maskp, '\0') - 32;
+ }
+- if (*p)
++ if (*p) /* 'xxx' had something apart from just digits */
+ return -1;
+ }
++ //else: i = "automask" (the count of explicit IP components: "10.0[.]" = 16)
+
+ if (i > 32)
+ return -1;
+
+- if (sizeof(unsigned) == 4 && i == 32) {
+- /* mask >>= 32 below may not work */
++ if (i == 32) {
++ /* mask >>= 32 below may not work (according to C standard) */
+ mask = 0;
+ } else {
+ mask = 0xffffffff;
+@@ -890,15 +891,18 @@ static int parse_conf(const char *path, int flag)
+ if (scan_ip_mask(after_colon, &pip->ip, &pip->mask)) {
+ /* IP{/mask} syntax error detected, protect all */
+ ch = 'D';
++ //bb_error_msg("ERR");
++ pip->ip = 0; /* could be set before error is detected - zero it (again) */
+ pip->mask = 0;
+ }
++ //bb_error_msg_and_die("ip:0x%08x mask:0x%08x", pip->ip, pip->mask);
+ pip->allow_deny = ch;
+ if (ch == 'D') {
+ /* Deny:from_IP - prepend */
+ pip->next = G.ip_a_d;
+ G.ip_a_d = pip;
+ } else {
+- /* A:from_IP - append (thus all D's precedes A's) */
++ /* A:from_IP - append (thus all D's precede A's) */
+ Htaccess_IP *prev_IP = G.ip_a_d;
+ if (prev_IP == NULL) {
+ G.ip_a_d = pip;
+@@ -2045,7 +2049,7 @@ static NOINLINE void send_file_and_exit(const char *url, int what)
+ }
+
+ #if ENABLE_FEATURE_HTTPD_ACL_IP
+-static void if_ip_denied_send_HTTP_FORBIDDEN_and_exit(unsigned remote_ip)
++static void if_ip_denied_send_HTTP_FORBIDDEN_and_exit(uint32_t remote_ip)
+ {
+ Htaccess_IP *cur;
+
+@@ -2337,7 +2341,7 @@ static void handle_incoming_and_exit(const len_and_sockaddr *fromAddr)
+ char *urlp;
+ char *tptr;
+ #if ENABLE_FEATURE_HTTPD_ACL_IP
+- unsigned remote_ip;
++ uint32_t remote_ip;
+ #endif
+ #if ENABLE_FEATURE_HTTPD_CGI
+ unsigned total_headers_len;
diff --git a/busybox-CVE-2026-88832.patch b/busybox-CVE-2026-88832.patch
new file mode 100644
index 0000000..a8cdc75
--- /dev/null
+++ b/busybox-CVE-2026-88832.patch
@@ -0,0 +1,48 @@
+From 9073c1d15c9953c6f1e39145815fc5dccfa6081c Mon Sep 17 00:00:00 2001
+From: Denys Vlasenko <vda.linux at googlemail.com>
+Date: Mon, 21 Sep 2026 05:34:19 +0200
+Subject: [PATCH] volume_id/romfs: limit the maximum size of label to
+ VOLUME_ID_LABEL_SIZE
+
+function old new delta
+volume_id_set_label_string 71 81 +10
+volume_id_probe_romfs 94 97 +3
+------------------------------------------------------------------------------
+(add/remove: 0/0 grow/shrink: 2/0 up/down: 13/0) Total: 13 bytes
+
+Signed-off-by: Denys Vlasenko <vda.linux at googlemail.com>
+---
+ util-linux/volume_id/romfs.c | 4 ++--
+ util-linux/volume_id/util.c | 4 ++++
+ 2 files changed, 6 insertions(+), 2 deletions(-)
+
+diff --git a/util-linux/volume_id/romfs.c b/util-linux/volume_id/romfs.c
+index bd74fda8d..475144c43 100644
+--- a/util-linux/volume_id/romfs.c
++++ b/util-linux/volume_id/romfs.c
+@@ -44,8 +44,8 @@ int FAST_FUNC volume_id_probe_romfs(struct volume_id *id /*,uint64_t off*/)
+ if (rfs == NULL)
+ return -1;
+
+- if (memcmp(rfs->magic, "-rom1fs-", 4) == 0) {
+- size_t len = strlen((char *)rfs->name);
++ if (memcmp(rfs->magic, "-rom1fs-", 8) == 0) {
++ size_t len = strnlen((char *)rfs->name, VOLUME_ID_LABEL_SIZE);
+
+ if (len) {
+ // volume_id_set_label_raw(id, rfs->name, len);
+diff --git a/util-linux/volume_id/util.c b/util-linux/volume_id/util.c
+index b59aa99b2..a2bee28ab 100644
+--- a/util-linux/volume_id/util.c
++++ b/util-linux/volume_id/util.c
+@@ -116,6 +116,10 @@ void volume_id_set_label_string(struct volume_id *id, const uint8_t *buf, size_t
+ {
+ unsigned i;
+
++ /* Do not overflow label[VOLUME_ID_LABEL_SIZE+1] */
++ if (count > VOLUME_ID_LABEL_SIZE)
++ count = VOLUME_ID_LABEL_SIZE;
++
+ memcpy(id->label, buf, count);
+
+ /* remove trailing whitespace */
diff --git a/busybox-CVE-2026-88833.patch b/busybox-CVE-2026-88833.patch
new file mode 100644
index 0000000..58c37a4
--- /dev/null
+++ b/busybox-CVE-2026-88833.patch
@@ -0,0 +1,25 @@
+udhcpc: reject packets whose IP total length is below the header size (CVE-2026-88833)
+
+d4_recv_raw_packet() replaces the received byte count with the
+attacker-controlled ip.tot_len and only checks it against the received
+size. A tot_len smaller than sizeof(ip)+sizeof(udp) makes the following
+"bytes -= sizeof(packet.ip) + sizeof(packet.udp)" underflow, and the
+memcpy() at the end then copies a huge amount of memory, crashing udhcpc.
+Validate the minimum length before switching to ip.tot_len.
+
+diff -ruN a/networking/udhcp/dhcpc.c b/networking/udhcp/dhcpc.c
+--- a/networking/udhcp/dhcpc.c 2026-10-19 16:19:14.050304928 +0200
++++ b/networking/udhcp/dhcpc.c 2026-10-19 16:19:14.051130457 +0200
+@@ -913,6 +913,12 @@
+
+ /* ignore any extra garbage bytes */
+ bytes = ntohs(packet.ip.tot_len);
++ if (bytes < (int) (sizeof(packet.ip) + sizeof(packet.udp))) {
++ /* ip.tot_len must at least cover the IP and UDP headers,
++ * otherwise the subtraction below underflows */
++ log1s("packet is too short, ignoring");
++ return -2;
++ }
+
+ /* make sure its the right packet for us, and that it passes sanity checks */
+ if (packet.ip.protocol != IPPROTO_UDP
diff --git a/busybox-CVE-2026-88834.patch b/busybox-CVE-2026-88834.patch
new file mode 100644
index 0000000..6e9c987
--- /dev/null
+++ b/busybox-CVE-2026-88834.patch
@@ -0,0 +1,55 @@
+From b829bba95095b87834628adb38ea14b5885c0fbc Mon Sep 17 00:00:00 2001
+From: Denys Vlasenko <vda.linux at googlemail.com>
+Date: Mon, 5 Oct 2026 22:59:11 +0200
+Subject: [PATCH] httpd: do not try to use obviously bogus password from
+ /etc/passwd
+
+If password is 'x' or '*' and yet, /etc/shadow has no entry,
+do not run the crypt machinery - go to the next entry.
+
+function old new delta
+check_user_passwd 444 462 +18
+
+Signed-off-by: Denys Vlasenko <vda.linux at googlemail.com>
+---
+ networking/httpd.c | 10 ++++------
+ 1 file changed, 4 insertions(+), 6 deletions(-)
+
+diff --git a/networking/httpd.c b/networking/httpd.c
+index 7469de1de..a6daba044 100644
+--- a/networking/httpd.c
++++ b/networking/httpd.c
+@@ -2181,7 +2181,6 @@ static int check_user_passwd(const char *path, char *user_and_passwd)
+ # if ENABLE_FEATURE_SHADOWPASSWDS && !ENABLE_PAM
+ char sp_buf[256];
+ # endif
+-
+ colon_after_user = strchr(user_and_passwd, ':');
+ if (!colon_after_user)
+ goto bad_input;
+@@ -2236,8 +2235,9 @@ static int check_user_passwd(const char *path, char *user_and_passwd)
+ * At least glibc 2.4 does this. Be extra paranoid here. */
+ struct spwd *result = NULL;
+ r = getspnam_r(pw->pw_name, &spw, sp_buf, sizeof(sp_buf), &result);
+- if (r == 0 && result)
+- passwd = result->sp_pwdp;
++ if (r != 0 || !result)
++ continue; /* no shadow, and passwd entry is bogus one-char string */
++ passwd = result->sp_pwdp;
+ }
+ # endif
+ /* In this case, passwd is ALWAYS encrypted:
+@@ -2250,9 +2250,7 @@ static int check_user_passwd(const char *path, char *user_and_passwd)
+
+ if (passwd[0] == '$' && (isdigit(passwd[1]) || passwd[1] == 'y')) {
+ char *encrypted;
+-# if !ENABLE_PAM
+- check_encrypted:
+-# endif
++ IF_NOT_PAM(check_encrypted:)
+ /* encrypt pwd from peer and check match with local one */
+ encrypted = pw_encrypt(
+ /* pwd (from peer): */ colon_after_user + 1,
+--
+2.56.0
+
diff --git a/busybox-CVE-2026-88835.patch b/busybox-CVE-2026-88835.patch
new file mode 100644
index 0000000..82db94c
--- /dev/null
+++ b/busybox-CVE-2026-88835.patch
@@ -0,0 +1,492 @@
+From e2c5cc04206008acc82d28dac4fcb1963e8f0acf Mon Sep 17 00:00:00 2001
+From: Denys Vlasenko <vda.linux at googlemail.com>
+Date: Mon, 21 Sep 2026 16:16:47 +0200
+Subject: [PATCH] dpkg: free results of read_package_field(), preliminary
+ cleanups in read_package_field()
+
+function old new delta
+dpkg_main 3910 3943 +33
+write_buffer_no_status 92 114 +22
+read_package_field 204 184 -20
+------------------------------------------------------------------------------
+(add/remove: 0/0 grow/shrink: 2/1 up/down: 55/-20) Total: 35 bytes
+
+Signed-off-by: Denys Vlasenko <vda.linux at googlemail.com>
+---
+ archival/dpkg.c | 80 +++++++++++++++++++++++++++----------------------
+ 1 file changed, 45 insertions(+), 35 deletions(-)
+
+diff --git a/archival/dpkg.c b/archival/dpkg.c
+index eda5ec7eb..4ca1daf9b 100644
+--- a/archival/dpkg.c
++++ b/archival/dpkg.c
+@@ -531,26 +531,31 @@ static void free_package(common_node_t *node)
+ }
+
+ /*
+- * Gets the next package field from package_buffer, separated into the field name
+- * and field value, it returns the int offset to the first character of the next field
++ * Gets the next package field from package_buffer:
++ * "Name:<whitespace>VALUE{\n|NUL}"
++ * "Name:<whitespace>\n
++ * " VALUE{\n|NUL}"
++ * separated into "NAME" and "VALUE", both strdup()ed.
++ * Returns the int offset to the first character of the next field.
++ * The package_buffer parameter is NUL-terminated.
+ */
+ static int read_package_field(const char *package_buffer, char **field_name, char **field_value)
+ {
+ int offset_name_start = 0;
+ int offset_name_end = 0;
+ int offset_value_start = 0;
+- int offset_value_end = 0;
+ int offset = 0;
+ int next_offset;
+ int name_length;
+ int value_length;
+ int exit_flag = FALSE;
+
++ *field_name = NULL;
++ *field_value = NULL;
+ if (package_buffer == NULL) {
+- *field_name = NULL;
+- *field_value = NULL;
+ return -1;
+ }
++
+ while (1) {
+ next_offset = offset + 1;
+ switch (package_buffer[offset]) {
+@@ -566,14 +571,14 @@ static int read_package_field(const char *package_buffer, char **field_name, cha
+ * immediately after name */
+ break;
+ case '\n':
+- /* TODO: The char next_offset may be out of bounds */
+ if (package_buffer[next_offset] != ' ') {
+ exit_flag = TRUE;
+ break;
+ }
++ /* fall through */
+ case '\t':
+ case ' ':
+- /* increment the value start point if its a just filler */
++ /* increment start points if it is just a filler */
+ if (offset_name_start == offset) {
+ offset_name_start++;
+ }
+@@ -584,9 +589,8 @@ static int read_package_field(const char *package_buffer, char **field_name, cha
+ }
+ if (exit_flag) {
+ /* Check that the names are valid */
+- offset_value_end = offset;
+ name_length = offset_name_end - offset_name_start;
+- value_length = offset_value_end - offset_value_start;
++ value_length = offset - offset_value_start;
+ if (name_length == 0) {
+ break;
+ }
+@@ -594,27 +598,26 @@ static int read_package_field(const char *package_buffer, char **field_name, cha
+ break;
+ }
+
+- /* If not valid, start fresh with next field */
++ /* Not valid: start fresh with next field */
+ exit_flag = FALSE;
+ offset_name_start = offset + 1;
+ offset_name_end = 0;
+ offset_value_start = offset + 1;
+- offset_value_end = offset + 1;
+ offset++;
+ }
+ offset++;
+- }
+- *field_name = NULL;
+- if (name_length) {
++ } /* while (1) */
++
++ if (name_length > 0) {
+ *field_name = xstrndup(&package_buffer[offset_name_start], name_length);
+ }
+- *field_value = NULL;
+ if (value_length > 0) {
+ *field_value = xstrndup(&package_buffer[offset_value_start], value_length);
+ }
+ return next_offset;
+ }
+
++/* The parameter is NUL-terminated */
+ static unsigned fill_package_struct(char *control_buffer)
+ {
+ static const char field_names[] ALIGN1 =
+@@ -623,14 +626,14 @@ static unsigned fill_package_struct(char *control_buffer)
+ "Conflicts\0""Suggests\0""Recommends\0""Enhances\0";
+
+ common_node_t *new_node = xzalloc(sizeof(common_node_t));
+- char *field_name;
+- char *field_value;
+ int field_start = 0;
+- int num = -1;
+ int buffer_length = strlen(control_buffer);
++ int num;
+
+ new_node->version = search_name_hashtable("unknown");
+ while (field_start < buffer_length) {
++ char *field_name;
++ char *field_value;
+ unsigned field_num;
+
+ field_start += read_package_field(&control_buffer[field_start],
+@@ -799,7 +802,7 @@ static void write_buffer_no_status(FILE *new_status_file, const char *control_bu
+ char *name;
+ char *value;
+ int start = 0;
+- while (1) {
++ while (control_buffer[start]) {
+ start += read_package_field(&control_buffer[start], &name, &value);
+ if (name == NULL) {
+ break;
+@@ -807,6 +810,8 @@ static void write_buffer_no_status(FILE *new_status_file, const char *control_bu
+ if (strcmp(name, "Status") != 0) {
+ fprintf(new_status_file, "%s: %s\n", name, value);
+ }
++ free(name);
++ free(value);
+ }
+ }
+
+@@ -815,34 +820,35 @@ static void write_status_file(deb_file_t **deb_file)
+ {
+ FILE *old_status_file = xfopen_for_read("/var/lib/dpkg/status");
+ FILE *new_status_file = xfopen_for_write("/var/lib/dpkg/status.udeb");
+- char *package_name;
+- char *status_from_file;
+- char *control_buffer = NULL;
+- char *tmp_string;
+- int status_num;
++ char *control_buffer;
+ int field_start = 0;
+- int write_flag;
+- int i = 0;
++ int status_num;
++ int i;
+
+ /* Update previously known packages */
+ while ((control_buffer = xmalloc_fgetline_str(old_status_file, "\n\n")) != NULL) {
++ char *package_name;
++ char *status_from_file;
++ char *tmp_string;
++ int write_flag;
++//FIXME: "int field_start = 0;" should be _here_, right?
++
+ tmp_string = strstr(control_buffer, "Package:");
+ if (tmp_string == NULL) {
++ free(control_buffer);
+ continue;
+ }
+-
+ tmp_string += 8;
+ tmp_string += strspn(tmp_string, " \n\t");
+ package_name = xstrndup(tmp_string, strcspn(tmp_string, "\n"));
++
+ write_flag = FALSE;
++ status_from_file = NULL;
+ tmp_string = strstr(control_buffer, "Status:");
+ if (tmp_string != NULL) {
+- /* Separate the status value from the control buffer */
+ tmp_string += 7;
+ tmp_string += strspn(tmp_string, " \n\t");
+ status_from_file = xstrndup(tmp_string, strcspn(tmp_string, "\n"));
+- } else {
+- status_from_file = NULL;
+ }
+
+ /* Find this package in the status hashtable */
+@@ -870,7 +876,7 @@ static void write_status_file(deb_file_t **deb_file)
+ }
+ i++;
+ }
+- /* This is temperary, debugging only */
++ /* This is temporary, debugging only */
+ if (deb_file[i] == NULL) {
+ bb_error_msg_and_die("ALERT: cannot find a control file, "
+ "your status file may be broken, status may be "
+@@ -882,7 +888,7 @@ static void write_status_file(deb_file_t **deb_file)
+ fprintf(new_status_file, "Package: %s\n", package_name);
+ fprintf(new_status_file, "Status: %s\n", status_from_hashtable);
+
+- while (1) {
++ while (control_buffer[field_start]) {
+ char *field_name;
+ char *field_value;
+ field_start += read_package_field(&control_buffer[field_start], &field_name, &field_value);
+@@ -894,13 +900,15 @@ static void write_status_file(deb_file_t **deb_file)
+ ) {
+ fprintf(new_status_file, "%s: %s\n", field_name, field_value);
+ }
++ free(field_name);
++ free(field_value);
+ }
+ write_flag = TRUE;
+ fputs("\n", new_status_file);
+ }
+ else if (strcmp("config-files", name_hashtable[state_status]) == 0) {
+ /* only change the status line */
+- while (1) {
++ while (control_buffer[field_start]) {
+ char *field_name;
+ char *field_value;
+ field_start += read_package_field(&control_buffer[field_start], &field_name, &field_value);
+@@ -913,13 +921,15 @@ static void write_status_file(deb_file_t **deb_file)
+ } else {
+ fprintf(new_status_file, "%s: %s\n", field_name, field_value);
+ }
++ free(field_name);
++ free(field_value);
+ }
+ write_flag = TRUE;
+ fputs("\n", new_status_file);
+ }
+ }
+ }
+- /* If the package from the status file wasn't handle above, do it now*/
++ /* If the package from the status file wasn't handled above, do it now */
+ if (!write_flag) {
+ fprintf(new_status_file, "%s\n\n", control_buffer);
+ }
+@@ -927,7 +937,7 @@ static void write_status_file(deb_file_t **deb_file)
+ free(status_from_file);
+ free(package_name);
+ free(control_buffer);
+- }
++ } /* while (control_buffer) */
+
+ /* Write any new packages */
+ for (i = 0; deb_file[i] != NULL; i++) {
+From 43a9d7d31560ad4e9d6a341207b1aa191cb9b0db Mon Sep 17 00:00:00 2001
+From: Denys Vlasenko <vda.linux at googlemail.com>
+Date: Mon, 21 Sep 2026 16:32:43 +0200
+Subject: [PATCH] dpkg: reformat code in read_package_field(), exposing why
+ it's buggy
+
+function old new delta
+read_package_field 184 169 -15
+
+Signed-off-by: Denys Vlasenko <vda.linux at googlemail.com>
+---
+ archival/dpkg.c | 56 +++++++++++++++++++------------------------------
+ 1 file changed, 22 insertions(+), 34 deletions(-)
+
+diff --git a/archival/dpkg.c b/archival/dpkg.c
+index 4ca1daf9b..860256f0e 100644
+--- a/archival/dpkg.c
++++ b/archival/dpkg.c
+@@ -548,20 +548,13 @@ static int read_package_field(const char *package_buffer, char **field_name, cha
+ int next_offset;
+ int name_length;
+ int value_length;
+- int exit_flag = FALSE;
+
+ *field_name = NULL;
+ *field_value = NULL;
+- if (package_buffer == NULL) {
+- return -1;
+- }
+
+ while (1) {
+ next_offset = offset + 1;
+ switch (package_buffer[offset]) {
+- case '\0':
+- exit_flag = TRUE;
+- break;
+ case ':':
+ if (offset_name_end == 0) {
+ offset_name_end = offset;
+@@ -572,8 +565,7 @@ static int read_package_field(const char *package_buffer, char **field_name, cha
+ break;
+ case '\n':
+ if (package_buffer[next_offset] != ' ') {
+- exit_flag = TRUE;
+- break;
++ goto end_of_value;
+ }
+ /* fall through */
+ case '\t':
+@@ -586,35 +578,31 @@ static int read_package_field(const char *package_buffer, char **field_name, cha
+ offset_value_start++;
+ }
+ break;
+- }
+- if (exit_flag) {
+- /* Check that the names are valid */
+- name_length = offset_name_end - offset_name_start;
+- value_length = offset - offset_value_start;
+- if (name_length == 0) {
+- break;
+- }
+- if ((name_length > 0) && (value_length > 0)) {
++ case '\0':
++ end_of_value:
++ /* Check that the names are valid */
++ name_length = offset_name_end - offset_name_start;
++ value_length = offset - offset_value_start;
++ if ((name_length > 0) && (value_length > 0)) {
++ if (name_length > 0) {
++ *field_name = xstrndup(&package_buffer[offset_name_start], name_length);
++ }
++ if (value_length > 0) {
++ *field_value = xstrndup(&package_buffer[offset_value_start], value_length);
++ }
++ return next_offset;
++ }
++ if (!package_buffer[offset])
++ return next_offset; //BUG! past NUL
++ /* Not valid: start fresh with next field */
++ offset_name_start = offset + 1;
++ offset_name_end = 0;
++ offset_value_start = offset + 1;
++ offset++; //BUG?
+ break;
+- }
+-
+- /* Not valid: start fresh with next field */
+- exit_flag = FALSE;
+- offset_name_start = offset + 1;
+- offset_name_end = 0;
+- offset_value_start = offset + 1;
+- offset++;
+ }
+ offset++;
+ } /* while (1) */
+-
+- if (name_length > 0) {
+- *field_name = xstrndup(&package_buffer[offset_name_start], name_length);
+- }
+- if (value_length > 0) {
+- *field_value = xstrndup(&package_buffer[offset_value_start], value_length);
+- }
+- return next_offset;
+ }
+
+ /* The parameter is NUL-terminated */
+From f5a4a02a157e250d28976e2ba4ea61a2fa7eb51f Mon Sep 17 00:00:00 2001
+From: Denys Vlasenko <vda.linux at googlemail.com>
+Date: Mon, 21 Sep 2026 16:59:56 +0200
+Subject: [PATCH] dpkg: fix cases where read_package_field() returns offset
+ past NUL
+
+function old new delta
+read_package_field 169 211 +42
+
+Signed-off-by: Denys Vlasenko <vda.linux at googlemail.com>
+---
+ archival/dpkg.c | 63 +++++++++++++++++++++++--------------------------
+ 1 file changed, 30 insertions(+), 33 deletions(-)
+
+diff --git a/archival/dpkg.c b/archival/dpkg.c
+index 860256f0e..b8d8eb648 100644
+--- a/archival/dpkg.c
++++ b/archival/dpkg.c
+@@ -532,8 +532,8 @@ static void free_package(common_node_t *node)
+
+ /*
+ * Gets the next package field from package_buffer:
+- * "Name:<whitespace>VALUE{\n|NUL}"
+- * "Name:<whitespace>\n
++ * "<whitespace>Name:<whitespace>VALUE{\n|NUL}"
++ * "<whitespace>Name:<whitespace>\n
+ * " VALUE{\n|NUL}"
+ * separated into "NAME" and "VALUE", both strdup()ed.
+ * Returns the int offset to the first character of the next field.
+@@ -541,64 +541,61 @@ static void free_package(common_node_t *node)
+ */
+ static int read_package_field(const char *package_buffer, char **field_name, char **field_value)
+ {
+- int offset_name_start = 0;
+- int offset_name_end = 0;
+- int offset_value_start = 0;
+- int offset = 0;
+- int next_offset;
+- int name_length;
+- int value_length;
++ int offset_name_start;
++ int offset_name_end;
++ int offset_value_start = offset_value_start;
++ int offset;
+
+ *field_name = NULL;
+ *field_value = NULL;
+
++ offset = 0;
++ offset_name_start = 0;
++ offset_name_end = 0; /* "we did not see ':' yet" */
+ while (1) {
+- next_offset = offset + 1;
+- switch (package_buffer[offset]) {
++ char ch = package_buffer[offset];
++ switch (ch) {
+ case ':':
+ if (offset_name_end == 0) {
+- offset_name_end = offset;
+- offset_value_start = next_offset;
++ offset_name_end = offset + 1; /* points AFTER ':' - think of empty NAME case */
++ offset_value_start = offset + 1;
+ }
+- /* TODO: Name might still have trailing spaces if ':' isn't
++ /* TODO: NAME might still have trailing spaces if ':' isn't
+ * immediately after name */
+ break;
+ case '\n':
+- if (package_buffer[next_offset] != ' ') {
++ if (package_buffer[offset + 1] != ' ')
+ goto end_of_value;
+- }
+ /* fall through */
+ case '\t':
+ case ' ':
+ /* increment start points if it is just a filler */
+- if (offset_name_start == offset) {
++ if (offset_name_start == offset)
+ offset_name_start++;
+- }
+- if (offset_value_start == offset) {
++ if (offset_value_start == offset)
+ offset_value_start++;
+- }
+ break;
+ case '\0':
+ end_of_value:
+- /* Check that the names are valid */
+- name_length = offset_name_end - offset_name_start;
+- value_length = offset - offset_value_start;
+- if ((name_length > 0) && (value_length > 0)) {
+- if (name_length > 0) {
++ /* Did we see the ':'? */
++ if (offset_name_end != 0) {
++ /* Yes. Check that NAME and VALUE exist and not empty */
++ int name_length = (offset_name_end - 1) - offset_name_start;
++ int value_length = offset - offset_value_start;
++ if ((name_length > 0) && (value_length > 0)) {
+ *field_name = xstrndup(&package_buffer[offset_name_start], name_length);
+- }
+- if (value_length > 0) {
+ *field_value = xstrndup(&package_buffer[offset_value_start], value_length);
++ if (ch)
++ offset++; /* skip '\n' */
++ return offset;
+ }
+- return next_offset;
+ }
+- if (!package_buffer[offset])
+- return next_offset; //BUG! past NUL
++ if (!ch)
++ return offset; /* stop at NUL */
++
+ /* Not valid: start fresh with next field */
+ offset_name_start = offset + 1;
+- offset_name_end = 0;
+- offset_value_start = offset + 1;
+- offset++; //BUG?
++ offset_name_end = 0; /* "we did not see ':' yet" */
+ break;
+ }
+ offset++;
diff --git a/busybox-CVE-2026-88837.patch b/busybox-CVE-2026-88837.patch
new file mode 100644
index 0000000..d46815a
--- /dev/null
+++ b/busybox-CVE-2026-88837.patch
@@ -0,0 +1,40 @@
+From 9f5cbdcaa5972d58503c59cd0da13da1e5bb272b Mon Sep 17 00:00:00 2001
+From: Denys Vlasenko <vda.linux at googlemail.com>
+Date: Tue, 22 Sep 2026 01:14:47 +0200
+Subject: [PATCH] httpd: allow yescrypt passwords ("$y$....")
+
+function old new delta
+check_user_passwd 439 444 +5
+
+Signed-off-by: Denys Vlasenko <vda.linux at googlemail.com>
+---
+ loginutils/Config.src | 2 +-
+ networking/httpd.c | 2 +-
+ 2 files changed, 2 insertions(+), 2 deletions(-)
+
+diff --git a/loginutils/Config.src b/loginutils/Config.src
+index a7812bd32..5559992da 100644
+--- a/loginutils/Config.src
++++ b/loginutils/Config.src
+@@ -96,7 +96,7 @@ config USE_BB_CRYPT_YES
+ default y
+ depends on USE_BB_CRYPT
+ help
+- Enable this if you have passwords starting with "$y$" or
++ Enable this if you have passwords starting with "$y$"
+ in your /etc/passwd or /etc/shadow files. These passwords
+ are hashed using yescrypt algorithms.
+ With this option off, login will fail password check for any
+diff --git a/networking/httpd.c b/networking/httpd.c
+index a5dfb38af..842574e0e 100644
+--- a/networking/httpd.c
++++ b/networking/httpd.c
+@@ -2247,7 +2247,7 @@ static int check_user_passwd(const char *path, char *user_and_passwd)
+ }
+ /* Else: passwd is from httpd.conf, it is either plaintext or encrypted */
+
+- if (passwd[0] == '$' && isdigit(passwd[1])) {
++ if (passwd[0] == '$' && (isdigit(passwd[1]) || passwd[1] == 'y')) {
+ char *encrypted;
+ # if !ENABLE_PAM
+ check_encrypted:
diff --git a/busybox-CVE-2026-88838.patch b/busybox-CVE-2026-88838.patch
new file mode 100644
index 0000000..b293dc9
--- /dev/null
+++ b/busybox-CVE-2026-88838.patch
@@ -0,0 +1,110 @@
+mount: reject clearing options for non-root users (CVE-2026-88838)
+
+Options such as suid/dev/exec/rw only clear flag bits and never set any,
+so parse_mount_options() returns just MS_SILENT for them and the non-root
+"cannot specify flags" check misses them. They are merged with the fstab
+options as a string; when the combined string is re-parsed they clear the
+nosuid/nodev/noexec the administrator set. Report the clearing options too
+and reject them for non-root.
+
+diff -ruN a/util-linux/mount.c b/util-linux/mount.c
+--- a/util-linux/mount.c 2026-10-19 16:19:14.065346691 +0200
++++ b/util-linux/mount.c 2026-10-19 16:19:14.066286273 +0200
+@@ -595,7 +595,7 @@
+
+ // Use the mount_options list to parse options into flags.
+ // Also update list of unrecognized options if unrecognized != NULL
+-static unsigned long parse_mount_options(char *options, char **unrecognized, uint32_t *opt)
++static unsigned long parse_mount_options(char *options, char **unrecognized, uint32_t *opt, unsigned long *inverted)
+ {
+ unsigned long flags = MS_SILENT;
+
+@@ -619,9 +619,11 @@
+ )
+ ) {
+ unsigned long fl = mount_options[i];
+- if (fl & BB_MS_INVERTED_VALUE)
++ if (fl & BB_MS_INVERTED_VALUE) {
+ flags &= fl;
+- else
++ if (inverted)
++ *inverted |= fl;
++ } else
+ flags |= fl;
+ /* If we see "-o rw" on command line, it's the same as -w:
+ * "do not try to fall back to RO mounts"
+@@ -1984,7 +1986,7 @@
+
+ errno = 0;
+
+- vfsflags = parse_mount_options(mp->mnt_opts, &filteropts, NULL);
++ vfsflags = parse_mount_options(mp->mnt_opts, &filteropts, NULL, NULL);
+
+ // Treat fstype "auto" as unspecified
+ if (mp->mnt_type && strcmp(mp->mnt_type, "auto") == 0)
+@@ -2058,7 +2060,7 @@
+ len, share,
+ share + len + 1 /* "dir1/dir2" */
+ );
+- parse_mount_options(unc, &filteropts, NULL);
++ parse_mount_options(unc, &filteropts, NULL, NULL);
+ if (ENABLE_FEATURE_CLEAN_UP) free(unc);
+ }
+
+@@ -2083,7 +2085,7 @@
+ // (instead of _numeric_ iface_id) with glibc.
+ // This probably should be fixed in glibc, not here.
+ // The workaround is to manually specify correct "ip=ADDR%n" option.
+- parse_mount_options(ip, &filteropts, NULL);
++ parse_mount_options(ip, &filteropts, NULL, NULL);
+ if (ENABLE_FEATURE_CLEAN_UP) free(ip);
+ }
+
+@@ -2365,9 +2367,17 @@
+ // Past this point, we are handling either "mount -a [opts]"
+ // or "mount [opts] single_param"
+
+- cmdopt_flags = parse_mount_options(cmdopts, NULL, &option_mask32);
+- if (nonroot && (cmdopt_flags & ~MS_SILENT)) // Non-root users cannot specify flags
+- bb_simple_error_msg_and_die(bb_msg_you_must_be_root);
++ {
++ unsigned long inverted_flags = 0;
++
++ cmdopt_flags = parse_mount_options(cmdopts, NULL, &option_mask32, &inverted_flags);
++ // Non-root users cannot specify flags. Clearing options ("suid", "dev",
++ // "exec", "rw", "atime", ...) do not set any flag, they are reported
++ // via inverted_flags; without this they could undo nosuid/nodev/noexec
++ // set in fstab when the combined options are re-parsed.
++ if (nonroot && ((cmdopt_flags & ~MS_SILENT) || inverted_flags))
++ bb_simple_error_msg_and_die(bb_msg_you_must_be_root);
++ }
+
+ // If we have a shared subtree flag, don't worry about fstab or mtab.
+ if (ENABLE_FEATURE_MOUNT_FLAGS
+@@ -2439,7 +2449,7 @@
+ continue;
+
+ // Skip noauto and swap anyway
+- if ((parse_mount_options(mtcur->mnt_opts, NULL, NULL) & (MOUNT_NOAUTO | MOUNT_SWAP))
++ if ((parse_mount_options(mtcur->mnt_opts, NULL, NULL, NULL) & (MOUNT_NOAUTO | MOUNT_SWAP))
+ // swap is bogus "fstype", parse_mount_options can't check fstypes
+ || strcasecmp(mtcur->mnt_type, "swap") == 0
+ ) {
+@@ -2500,7 +2510,7 @@
+ // exit_group(32) = ?
+ #if 0
+ // In case we want to simply skip swap partitions:
+- l = parse_mount_options(mtcur->mnt_opts, NULL, NULL);
++ l = parse_mount_options(mtcur->mnt_opts, NULL, NULL, NULL);
+ if ((l & MOUNT_SWAP)
+ // swap is bogus "fstype", parse_mount_options can't check fstypes
+ || strcasecmp(mtcur->mnt_type, "swap") == 0
+@@ -2510,7 +2520,7 @@
+ #endif
+ if (nonroot) {
+ // fstab must have "users" or "user"
+- l = parse_mount_options(mtcur->mnt_opts, NULL, NULL);
++ l = parse_mount_options(mtcur->mnt_opts, NULL, NULL, NULL);
+ if (!(l & MOUNT_USERS))
+ bb_simple_error_msg_and_die(bb_msg_you_must_be_root);
+ }
diff --git a/busybox-CVE-2026-88839.patch b/busybox-CVE-2026-88839.patch
new file mode 100644
index 0000000..db94095
--- /dev/null
+++ b/busybox-CVE-2026-88839.patch
@@ -0,0 +1,30 @@
+From c2dec52d37380d27d89827cf01a770f6b3b04e3a Mon Sep 17 00:00:00 2001
+From: Denys Vlasenko <vda.linux at googlemail.com>
+Date: Tue, 22 Sep 2026 02:19:06 +0200
+Subject: [PATCH] libpwdgrp: tokenizer fix for trailing whitespace removeal.
+
+The tokenize() function trims trailing whitespace via
+overlapping_strcpy() but continues to reference the stale *end
+pointer. Fixing this.
+
+function old new delta
+tokenize 120 119 -1
+
+Signed-off-by: Denys Vlasenko <vda.linux at googlemail.com>
+---
+ libpwdgrp/pwd_grp.c | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/libpwdgrp/pwd_grp.c b/libpwdgrp/pwd_grp.c
+index 10debbcdb..48a49fc24 100644
+--- a/libpwdgrp/pwd_grp.c
++++ b/libpwdgrp/pwd_grp.c
+@@ -171,7 +171,7 @@ static int tokenize(char *buffer, int ch)
+ if (p != end)
+ overlapping_strcpy(p, end);
+ num_fields++;
+- if (*end == '\0') {
++ if (*p == '\0') {
+ S.tokenize_end = p + 1;
+ return num_fields;
+ }
diff --git a/busybox-CVE-2026-88840.patch b/busybox-CVE-2026-88840.patch
new file mode 100644
index 0000000..e179691
--- /dev/null
+++ b/busybox-CVE-2026-88840.patch
@@ -0,0 +1,38 @@
+From e97224f8fbad11a4d00feaec3391c27ed773f147 Mon Sep 17 00:00:00 2001
+From: Denys Vlasenko <vda.linux at googlemail.com>
+Date: Tue, 22 Sep 2026 02:38:38 +0200
+Subject: [PATCH] tls: fix off-by-one access past the end of the received
+ packet.
+
+Signed-off-by: Denys Vlasenko <vda.linux at googlemail.com>
+---
+ networking/tls.c | 6 ++++--
+ 1 file changed, 4 insertions(+), 2 deletions(-)
+
+diff --git a/networking/tls.c b/networking/tls.c
+index 91172f8bc..05dda491a 100644
+--- a/networking/tls.c
++++ b/networking/tls.c
+@@ -2467,7 +2467,9 @@ static void get_client_hello(tls_state_t *tls)
+ p += 2;
+ len -= 2;
+
+- if (len < cipher_list_len) {
++ if (len <= cipher_list_len) {
++ /* The "<=" is subtle: it aborts even if we do have cipher list,
++ * but have no compression size byte after it */
+ bb_simple_error_msg_and_die("malformed ClientHello");
+ }
+
+@@ -2522,7 +2524,7 @@ static void get_client_hello(tls_state_t *tls)
+ len -= cipher_list_len;
+
+ /* Skip compression methods */
+- len -= 1 + p[0];
++ len -= 1 + p[0]; /* touching p[0] is allowed: len was > cipher_list_len */
+ p += 1 + p[0];
+
+ /* Parse extensions if present */
+--
+2.56.0
+
diff --git a/busybox-CVE-2026-88842.patch b/busybox-CVE-2026-88842.patch
new file mode 100644
index 0000000..44bfae7
--- /dev/null
+++ b/busybox-CVE-2026-88842.patch
@@ -0,0 +1,82 @@
+From d5cc94063b2bb5b54fe1021e54600cbcbce6a350 Mon Sep 17 00:00:00 2001
+From: Denys Vlasenko <vda.linux at googlemail.com>
+Date: Tue, 22 Sep 2026 01:04:32 +0200
+Subject: [PATCH] dpkg: fix field handling in write_status_file()
+
+function old new delta
+dpkg_main 3943 3934 -9
+
+Signed-off-by: Denys Vlasenko <vda.linux at googlemail.com>
+---
+[Backport note: dropped the upstream comment-only hunk that renames
+"Name" to "NAME" in the read_package_field() description, as it depends
+on the unrelated variable-renaming commit 7c611860d.]
+---
+ archival/dpkg.c | 14 ++++++++------
+ 1 file changed, 8 insertions(+), 6 deletions(-)
+
+diff --git a/archival/dpkg.c b/archival/dpkg.c
+index 3abc810c8..6a65f16a4 100644
+--- a/archival/dpkg.c
++++ b/archival/dpkg.c
+@@ -809,7 +809,6 @@ static void write_status_file(deb_file_t **deb_file)
+ FILE *old_status_file = xfopen_for_read("/var/lib/dpkg/status");
+ FILE *new_status_file = xfopen_for_write("/var/lib/dpkg/status.udeb");
+ char *control_buffer;
+- int field_start = 0;
+ int status_num;
+ int i;
+
+@@ -819,23 +818,20 @@ static void write_status_file(deb_file_t **deb_file)
+ char *status_from_file;
+ char *tmp_string;
+ int write_flag;
+-//FIXME: "int field_start = 0;" should be _here_, right?
+
+ tmp_string = strstr(control_buffer, "Package:");
+ if (tmp_string == NULL) {
+ free(control_buffer);
+ continue;
+ }
+- tmp_string += 8;
+- tmp_string += strspn(tmp_string, " \n\t");
++ tmp_string = skip_whitespace(tmp_string + 8);
+ package_name = xstrndup(tmp_string, strcspn(tmp_string, "\n"));
+
+ write_flag = FALSE;
+ status_from_file = NULL;
+ tmp_string = strstr(control_buffer, "Status:");
+ if (tmp_string != NULL) {
+- tmp_string += 7;
+- tmp_string += strspn(tmp_string, " \n\t");
++ tmp_string = skip_whitespace(tmp_string + 7);
+ status_from_file = xstrndup(tmp_string, strcspn(tmp_string, "\n"));
+ }
+
+@@ -872,6 +868,8 @@ static void write_status_file(deb_file_t **deb_file)
+ }
+ }
+ else if (strcmp("not-installed", name_hashtable[state_status]) == 0) {
++ int field_start = 0;
++
+ /* Only write the Package, Status, Priority and Section lines */
+ fprintf(new_status_file, "Package: %s\n", package_name);
+ fprintf(new_status_file, "Status: %s\n", status_from_hashtable);
+@@ -880,6 +878,8 @@ static void write_status_file(deb_file_t **deb_file)
+ char *field_name;
+ char *field_value;
+ field_start += read_package_field(&control_buffer[field_start], &field_name, &field_value);
++//FIXME: the questionable ": VALUE" lines (empty NAME)
++//probably should not stop parsing of the entire file?
+ if (field_name == NULL) {
+ break;
+ }
+@@ -895,6 +895,8 @@ static void write_status_file(deb_file_t **deb_file)
+ fputs("\n", new_status_file);
+ }
+ else if (strcmp("config-files", name_hashtable[state_status]) == 0) {
++ int field_start = 0;
++
+ /* only change the status line */
+ while (control_buffer[field_start]) {
+ char *field_name;
diff --git a/busybox.config b/busybox.config
index 010d612..c5f98db 100644
--- a/busybox.config
+++ b/busybox.config
@@ -195,7 +195,8 @@ CONFIG_UNXZ=y
CONFIG_XZCAT=y
CONFIG_XZ=y
# CONFIG_FEATURE_LZMA_FAST is not set
-# CONFIG_FEATURE_PATH_TRAVERSAL_PROTECTION is not set
+# strip unsafe paths from cpio/ar/rpm extraction (CVE-2023-39810, CVE-2026-26157)
+CONFIG_FEATURE_PATH_TRAVERSAL_PROTECTION=y
CONFIG_LZMA=y
CONFIG_UNZIP=y
CONFIG_FEATURE_UNZIP_CDF=y
================================================================
---- gitweb:
http://git.pld-linux.org/gitweb.cgi/packages/busybox.git/commitdiff/168af84b55992613f8030537edae977674860da4
More information about the pld-cvs-commit
mailing list