Cipher found eight vulnerabilities in BusyBox. The most serious lets an unauthenticated network client crash the TLS server during the key exchange, with no credentials. The rest include a heap overflow from a crafted filesystem image that setuid tools parse at euid 0, a password check that treats a stored hash as the password, and an httpd deny rule that stops blocking anything.
BusyBox packages hundreds of common Unix utilities into one executable and is widely used across embedded Linux, router and set-top firmware, and container base images. Each applet still runs as its own process, so exposure to these findings depends on which applets a build enables and how they are reached. The ones that matter most are the applets a system exposes to untrusted input: a network service like ssl_server or httpd, or a parser handed attacker-controlled data.
Red Hat assigned CVEs for all eight and published them on 23 September 2026. Every one is fixed upstream. Six more BusyBox findings remain under embargo with the maintainers and will follow as their fixes ship.
Two of the eight are rated High and are covered in detail below. The other six are described against their fix commits.
IMPORTANT
Who’s exposed. These are separate bugs in separate applets. Exposure depends on which applets your build enables and how they are reached.
- Pre-auth TLS heap overflow (CVE-2026-88830) needs the
ssl_serverapplet. It is adefconfigdefault, but distributions do not ship it: it is absent from Debian trixie, Alpine, and Fedora. If you build BusyBox yourself and exposessl_server, an unauthenticated TCP client kills it. - romfs label overflow (CVE-2026-88832) fires when
blkid,findfs, ormountprobes a crafted image or device. It crosses a privilege boundary wheremountorfindfsare setuid, because those reach the parser at euid 0. - httpd bugs (CVE-2026-88837, CVE-2026-88831) apply only if you serve with BusyBox
httpdand use inline password hashes or IP allow/deny rules. - dpkg bugs (CVE-2026-88835, CVE-2026-88841) apply only if you use the BusyBox
dpkgapplet. - passwd/group parser (CVE-2026-88839) needs a malformed line in
/etc/groupor/etc/passwd, which normally means root already.
All eight are fixed in BusyBox master. The latest release, 1.38.0 from May 2026, predates every fix, so there is no release to upgrade to yet. Apply the commits listed at the end.
A pre-auth heap overflow in the TLS server (CVE-2026-88830)
Impact: an unauthenticated client that can open a TCP connection to ssl_server corrupts the process heap and crashes it before it forks the wrapped program. Availability only.
BusyBox carries its own TLS stack, adapted from MatrixSSL. pstm_montgomery_reduce() runs the modular reduction inside the RSA private-key operation. It sizes its working buffer in bytes and indexes it in pstm_digit units, which are four bytes wide in every shipped build:
// networking/tls_pstm_montgomery_reduce.c
if (paD && paDlen >= (uint32)2*pa+1) {
c = paD;
memset(c, 0x0, paDlen);
} else {
c = xzalloc(2*pa+1); // a digit count, allocated as bytes
}
oldused = a->used;
for (x = 0; x < oldused; x++) {
c[x] = a->dp[x]; // c is pstm_digit*: four bytes per index
}
Both branches are wrong. xzalloc(2*pa+1) omits sizeof(pstm_digit) and reserves a quarter of what the loop writes. The guard above it compares paDlen, a byte count, against 2*pa+1, a digit count, so it also accepts a caller buffer four times too small. The sibling allocations in tls_pstm_sqr_comba.c and tls_pstm_mul_comba.c scale correctly with sizeof(pstm_digit); only these two lines drop the factor.
It is reached when the server processes the client’s ClientKeyExchange, the message that carries the RSA-encrypted premaster secret. No authentication happens first: get_client_key_exchange() to psRsaDecryptPriv() to psRsaCrypt() to pstm_exptmod() to pstm_montgomery_reduce().
Why the fix calls it unreachable
The upstream fix, commit 89ac82774, corrects both lines. Its commit message says all callers pass a preallocated buffer, so the faulty code is unreachable, and that it is being fixed anyway. That is not correct, and the reason is in the caller.
paD is not sized from the modulus. It is sized from M[1].used, and M[1] is derived from the ciphertext the client sends. A degenerate ciphertext collapses M[1], which shrinks paD below the guard threshold and forces the xzalloc() branch, the one the commit message calls dead. The attacker sizes the buffer that decides which branch runs.
Against a 1024-bit server key, the reduction runs modulo a 512-bit CRT factor, so pa is 16 digits. The code needs (2*16+1)*4 = 132 bytes. The caller’s buffer arrives at 24 bytes, fails the guard, and xzalloc() reserves 33. The copy writes 128 bytes into it:
MR#0 after-copyin branch=B pa=16 paDlen=24 alloc=33 want=132 oob=99
c[10] = 0x0001eca1 <- glibc top-chunk size, read back through the overflow
On the release build, with the stock allocator and no sanitizer, the write past the chunk corrupts allocator metadata and glibc aborts:
Fatal glibc error: malloc.c:2601 (sysmalloc): assertion failed: ...
Aborted (core dumped)
Under AddressSanitizer the same input attributes the write:
==ERROR: AddressSanitizer: heap-buffer-overflow
#3 psRsaDecryptPriv networking/tls_rsa.c:372
#4 get_client_key_exchange networking/tls.c:2810
0x504000000031 is located 0 bytes after 33-byte region
allocated in pstm_montgomery_reduce networking/tls_pstm_montgomery_reduce.c:384
Controls
The ciphertext value alone decides the outcome. Holding the binary, key, and record shape constant and varying only the RSA ciphertext: a ciphertext of zero and one equal to the modulus both abort; ciphertexts of one, two, the modulus minus one, and six random values all return a clean pkcs1Unpad rejection and exit normally. A full handshake with a real PKCS#1 v1.5 premaster completes, and the wrapped program runs and returns output over the TLS channel. The path fails only on inputs that steer the reduction into the undersized buffer. Reproduced on master and on the 1.38.0 tag, with 1024-, 2048-, and 4096-bit keys, on both build types.
Where it stops
The bytes written past the chunk are the output of a modular reduction under a private key the attacker does not hold. The attacker picks the input, not the result, so no attacker-chosen byte lands at an attacker-chosen offset, and the decrypted value is never returned. Denial of service, which is what the published C:N/I:N/A:H reflects.
A heap overflow from a crafted filesystem label (CVE-2026-88832)
Impact: a crafted romfs image overflows a 65-byte heap field with an attacker-chosen length and content, and on an image with no terminator triggers an unbounded read, inside a parser that mount and findfs run at euid 0.
The romfs prober reads a fixed 512-byte window, then calls strlen() on the label inside it and copies that many bytes into a 65-byte field:
// util-linux/volume_id/romfs.c
rfs = volume_id_get_buffer(id, off, 0x200); // 512 bytes read
if (memcmp(rfs->magic, "-rom1fs-", 4) == 0) { // 4 of 8 magic bytes
size_t len = strlen((char *)rfs->name); // unbounded
volume_id_set_label_string(id, rfs->name, len);
}
// util-linux/volume_id/util.c
memcpy(id->label, buf, count); // no bound on count
Three omissions compound: the magic check reads four of eight bytes, strlen() is not limited to the 512 bytes actually read, and the copy is not limited to the destination. Two failures follow. A label over 64 bytes overflows the field. A superblock with no NUL makes strlen() run past the whole buffer, and that length becomes the copy length.
A 200-byte label:
==ERROR: AddressSanitizer: heap-buffer-overflow
WRITE of size 200 at 0x50e0000000e0
#1 volume_id_set_label_string util-linux/volume_id/util.c:119
#2 volume_id_probe_romfs util-linux/volume_id/romfs.c:52
0x50e0000000e0 is located 0 bytes after 160-byte region
An image with no NUL:
==ERROR: AddressSanitizer: heap-buffer-overflow
READ of size 69617 at 0x531000011800
#0 strlen
#1 volume_id_probe_romfs util-linux/volume_id/romfs.c:48
The superblock buffer is 69,632 bytes and only 512 were filled from disk. With no terminator, strlen() runs off the end of it, and the length it returns becomes the length of the copy that follows. On the release build the two images produce different allocator aborts, munmap_chunk(): invalid pointer and double free or corruption (out). The control is a well-formed image with a 9-byte label, which parses cleanly and prints the label; the only variable is the label length, the quantity the code does not bound.
Who runs this parser
blkid drops privilege before probing. mount and findfs reach the same parser at euid 0, which is what makes this a privilege question. We verified the reach against Alpine, the one distribution we could confirm ships a setuid BusyBox. It is narrower there than “any local user”: the setuid helper holds only mount and umount, and one of the three mount call sites is root-only. The two an unprivileged user can reach are in the loop that mounts an /etc/fstab entry by mountpoint, so the unprivileged path needs an fstab line with the user keyword naming its device by LABEL= or UUID=. Without that, the parser is still reached at euid 0, but only by root.
We demonstrated the corruption and the privileged reach. We did not turn the write into uid 0, and nothing read out of bounds is returned, since the process aborts before printing the label. The fix, commit 9073c1d15, reads the full 8-byte magic, uses strnlen() bounded to the label size, and clamps the count inside volume_id_set_label_string(), which every prober in that directory calls.
Two access-control failures in httpd
Neither is a memory-safety bug. Both are access checks that fail open.
A password hash accepted as the password (CVE-2026-88837). httpd decides whether a configured password is plaintext or a hash from its first two characters:
if (passwd[0] == '$' && isdigit(passwd[1])) {
That matches $1$, $5$, $6$, but not $y$, the yescrypt prefix, because y is not a digit. A yescrypt hash falls through to the plaintext comparison, so the stored hash string becomes the accepted password and the real password stops working. Anyone who reads the config, a backup, or an image can log in with the hash. Fedora 44 uses yescrypt as its default, so a hash generated the obvious way is vulnerable. Fixed in commit 9f5cbdcaa.
A deny rule that blocks no one (CVE-2026-88831). An IP deny rule with an out-of-range CIDR prefix, such as D:1.2.3/999, should mean deny-all. Instead the parser kept the address and zeroed the mask, producing a rule that matches no client. It is accepted with no error, and the addresses meant to be blocked are served. Fixed in commit 339e3c62f.
Two bugs in the dpkg parser
Reading past the terminator (CVE-2026-88835). read_package_field() can return an offset past the NUL that ends the buffer, so the next field read starts beyond the data. A crafted .deb crashes the applet or reads adjacent heap memory. Fixed in commit f5a4a02a1.
A cursor that is never reset (CVE-2026-88841). write_status_file() tracks its position in field_start, declared outside the per-package loop and never reset between entries, so after the first package the cursor is stale for every one that follows. Removing several packages in one run reads out of bounds and corrupts /var/lib/dpkg/status. The source carried the doubt in a comment: //FIXME: "int field_start = 0;" should be _here_, right?. It should have been. This is the mildest of the eight, rated Low, and it corrupts a database rather than the address space. Fixed in commits f5a4a02a1 and d5cc94063.
An out-of-bounds write in the passwd and group parser (CVE-2026-88839)
The shared parser for /etc/passwd and /etc/group trims whitespace by copying the remainder of the string backwards over it, then tests for end-of-record through a pointer the copy just invalidated:
if (p != end)
overlapping_strcpy(p, end);
num_fields++;
if (*end == '\0') { // end now points at moved data
After the trim, end no longer refers to what it did, so the test reads stale content and the parser walks past the end of the record, writing field pointers out of bounds. Fixed in commit c2dec52d3, which tests *p, the pointer the copy left valid. It needs a malformed entry with whitespace before a separator in /etc/group or /etc/passwd, files that are root-owned on a correct system, so it is rated Moderate with high privileges required. It matters where those files are generated from another source or have relaxed permissions.
A one-byte read past a truncated ClientHello (CVE-2026-88840)
The TLS server walks a ClientHello forward, decrementing a remaining-length counter. After the cipher list it reads the compression-methods length byte:
len -= 1 + p[0];
p += 1 + p[0];
The prior check was len < cipher_list_len, which allows the cipher list to consume every remaining byte; when it does, p[0] reads the byte after the packet. Fixed in commit e97224f8f, which uses <= so a ClientHello that ends at its cipher list is rejected first. One byte, confirmed at source level, rated Moderate.
Remediation
If you maintain the code. The TLS finding is a unit mismatch: a size written in digits and read as bytes. Allocate by digit count through one helper and convert digits to bytes through one macro, so a size cannot be written in one unit and read in another across the tls_pstm*.c family, where four sibling allocations already do it correctly. The romfs finding is a missing bound, on both the read and the copy. Clamp in the setter, since every prober calls it, and never call strlen() on input whose valid extent is known: the accessor was asked for 512 bytes, so pass that bound and use strnlen(). Probe attacker-authored on-disk metadata after the privilege drop blkid already performs, not before it as mount and findfs do.
If you operate it. There is no fixed release yet; 1.38.0 predates all eight fixes. Apply the commits below or track master. Meanwhile, ssl_server should never be reachable from an untrusted network, since BusyBox calls it a test server. If you use httpd with inline password hashes, check for any starting $y$ and reissue them. If you use httpd IP rules, check every prefix length is in range, since an out-of-range one is currently inert. Where mount or findfs are setuid, treat the ability to present removable media or a loopback image as a privilege boundary.
Disclosure
All eight were reported to Red Hat on 9 September 2026 and published on 23 September 2026. Red Hat assigned the CVEs and severities. Every fix is in BusyBox master; none is in a release.
| CVE | Finding | Severity | Fix |
|---|---|---|---|
| CVE-2026-88830 | TLS Montgomery reduction unit confusion, pre-auth heap overflow | High, CVSS 7.5 (AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H) | 89ac82774 |
| CVE-2026-88832 | romfs label heap overflow | High, CVSS 7.3 | 9073c1d15 |
| CVE-2026-88839 | passwd/group parser out-of-bounds pointer write | Moderate, CVSS 6.7 | c2dec52d3 |
| CVE-2026-88837 | httpd accepts a yescrypt hash as plaintext | Moderate, CVSS 6.5 | 9f5cbdcaa |
| CVE-2026-88835 | dpkg field parser reads past the NUL terminator | Moderate, CVSS 6.1 | f5a4a02a1 |
| CVE-2026-88831 | httpd IP deny rule with an invalid prefix fails open | Moderate, CVSS 5.3 | 339e3c62f |
| CVE-2026-88840 | truncated ClientHello read one byte out of bounds | Moderate, CVSS 5.3 | e97224f8f |
| CVE-2026-88841 | dpkg status file stale cursor | Low, CVSS 3.3 | f5a4a02a1, d5cc94063 |
- Software: BusyBox, confirmed on the 1.38.0 release tag and on master.
- Advisories: Red Hat, CVE-2026-88830 and the seven others above, 23 September 2026.
- Discovery: all eight were found and confirmed by Cipher. The reproduction detail attached to each finding is what we ran for that one. Six further BusyBox findings are under embargo with the maintainers.
How we found them
The most serious of these is not a missing check. The size check and the fallback allocation both count in the wrong unit: they measure a buffer in bytes and index it in four-byte digits, so the code reads like it bounds the write while it reserves a quarter of the space. The tell was the siblings: in the same directory, adapted in the same pass, four allocations scale by sizeof(pstm_digit) and two do not. We read allocation and indexing sites as a pair and looked for the odd one out.
This work is part of our vulnerability research into the open-source and embedded software that ships inside other people’s products.