CVE-2026-55614: uhttpd, OpenWrt 24.10.7 to 24.10.8
OpenWrt 24.10.8 takes 17 patches to uhttpd, its web server
(OpenWrt commit f68fa73),
three of them for CVEs:
- CVE-2026-55614:
client_parse_headercomparedTransfer-Encodingwith case, soChunkedread as no encoding, and a proxy in front could read the body another way: request smuggling. The fix rejects every encoding it does not handle. - CVE-2026-55612:
client_poll_post_datakept the state of a chunk after an invalid chunk length. The fix closes the connection. - CVE-2026-55613: the ubus plugin,
uhttpd_ubus.so, kept reading after a POST body it could not parse. The fix closes the connection.
The others parse Content-Length and chunk sizes safely, fix a one-byte
overflow in uh_urldecode and an out-of-bounds read in uh_b64decode,
compare passwords in constant time, and keep the status of a CGI per
client. This report follows CVE-2026-55614, the first fix in
client_parse_header.
OpenWrt runs sstrip on what it ships, so only the segments of the file
are left. On MIPS the binary is MIPS16e, and it exports the functions
the plugins call, so some rows there have names.
Get the binaries
$ for v in 24.10.7 24.10.8; do
mkdir -p $v
curl -s https://downloads.openwrt.org/releases/$v/targets/armsr/armv8/openwrt-$v-armsr-armv8-rootfs.cpio.gz \
| gunzip | (cd $v && cpio -id usr/sbin/uhttpd)
done
Compare
$ hereticus --format markdown 24.10.7/usr/sbin/uhttpd 24.10.8/usr/sbin/uhttpd
| FUNCTION | CHANGE | SCORE | +/-/~ | TOUCHED | MATCHED BY |
|---|---|---|---|---|---|
sub_8c68 -> sub_8da0 | changed | 0.73 | 32/0/2 | 2 branch, 2 call, 2 jump, 7 mem, 3 text | similar |
sub_5b9c -> sub_5ba0 | changed | 0.81 | 24/7/6 | 3 branch, 4 call, 1 callee, 14 mem | similar |
sub_6c84 -> sub_6cfc | changed | 0.85 | 19/12/13 | 3 branch, 7 jump, 3 mem | similar |
sub_8dcc -> sub_8f80 | changed | 0.89 | 17/11/6 | 7 branch, 3 call, 2 jump, 5 mem | similar |
sub_5d78 -> sub_5dbc | changed | 0.96 | 16/3/3 | 2 branch, 1 call, 3 callee, 1 jump, 6 mem, 1 text | similar |
sub_7394 -> sub_7428 | changed | 0.97 | 6/7/1 | 2 branch, 2 jump, 3 mem | similar |
sub_9f50 -> sub_a10c | changed | 0.96 | 5/2/1 | 1 call, 3 mem, 1 const | similar |
sub_6ea4 -> sub_6f38 | changed | 0.96 | 0/0/1 | 1 branch | similar |
sub_9b08 -> sub_9cd0 | changed | 0.93 | 2/5/2 | 6 mem | similar |
sub_3dd0 | changed | 1.00 | 1/0/0 | - | similar |
sub_9c3c -> sub_9df8 | changed | 0.97 | 0/0/1 | - | similar |
sub_9d50 -> sub_9f0c | changed | 0.98 | 0/0/1 | - | similar |
sub_8cec: new
120 matched (106 unchanged, 14 changed), 0 gone, 1 new
The texts each function points at name most rows. Where a function
points at none, the row is named by the MIPS16e build, which names
client_poll_post_data, uh_auth_add, uh_auth_check and
uh_urldecode, and by the size of the edit there:
| Row | Function | What changed |
|---|---|---|
sub_8c68 | uh_auth_add, by "$p$" | a $p$ user is looked up by the state of the account |
sub_5b9c | client_poll_post_data, by the MIPS16e build | CVE-2026-55612, and chunk sizes parsed safely |
sub_6c84 | uh_urldecode, by the MIPS16e build | the one-byte overflow |
sub_8dcc | uh_auth_check, by "Authorization Required" | no stored hash as a password, and a constant-time compare |
sub_5d78 | client_parse_header, by "Transfer-Encoding" | CVE-2026-55614, and Content-Length parsed safely |
sub_3dd0 | main, by its option string | |
sub_8cec | new, by size | likely the constant-time compare |
The fix of CVE-2026-55614 is on row 5. Every patched function sits
above it or near it, so the fix is among the first rows a reader sees,
but --sort added ranks the larger edits first.
Read the fix
$ hereticus --view il --diff sub_5d78 24.10.7/usr/sbin/uhttpd 24.10.8/usr/sbin/uhttpd
...
x1 = &"transfer-encoding"
call strcmp
if (w0 != 0) goto $+0x3c
+ x2 = &page
+ x2 = load.64 [@strcasecmp]
x1 = &page
x0 = x21
x1 = &"chunked"
- call strcmp
+ call strcasecmp
if (w0 != 0) goto $+0x10
w0 = 1
store.8 [x19 + 0x2e7] = w0
goto $-0xfc
+ x2 = &page
+ w1 = 0x1f5
+ x2 = &"Not Implemented"
+ goto $-0x4c0
...
The value of Transfer-Encoding is now compared without case, and any
value but chunked answers 501 Not Implemented, where before it was
ignored. Just above, Content-Length is read by strtol in base 10,
with errno and the top of an int checked, where strtoul in base 0
read 010 as eight.
The other architectures
| Build | Rows | Row of the fix |
|---|---|---|
armsr/armv8, AArch64 | 13 | 5 |
armsr/armv7, A32 | 13 | 5 |
malta/be, MIPS16e big endian | 13 | 5 |
malta/le, MIPS16e little endian | 14 | 5 |
hereticus-cli/tests/truth_cves.rs locks these ranks, and
fixtures/cve_binaries.py fetches and pins the binaries.