Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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_header compared Transfer-Encoding with case, so Chunked read 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_data kept 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
FUNCTIONCHANGESCORE+/-/~TOUCHEDMATCHED BY
sub_8c68 -> sub_8da0changed0.7332/0/22 branch, 2 call, 2 jump, 7 mem, 3 textsimilar
sub_5b9c -> sub_5ba0changed0.8124/7/63 branch, 4 call, 1 callee, 14 memsimilar
sub_6c84 -> sub_6cfcchanged0.8519/12/133 branch, 7 jump, 3 memsimilar
sub_8dcc -> sub_8f80changed0.8917/11/67 branch, 3 call, 2 jump, 5 memsimilar
sub_5d78 -> sub_5dbcchanged0.9616/3/32 branch, 1 call, 3 callee, 1 jump, 6 mem, 1 textsimilar
sub_7394 -> sub_7428changed0.976/7/12 branch, 2 jump, 3 memsimilar
sub_9f50 -> sub_a10cchanged0.965/2/11 call, 3 mem, 1 constsimilar
sub_6ea4 -> sub_6f38changed0.960/0/11 branchsimilar
sub_9b08 -> sub_9cd0changed0.932/5/26 memsimilar
sub_3dd0changed1.001/0/0-similar
sub_9c3c -> sub_9df8changed0.970/0/1-similar
sub_9d50 -> sub_9f0cchanged0.980/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:

RowFunctionWhat changed
sub_8c68uh_auth_add, by "$p$"a $p$ user is looked up by the state of the account
sub_5b9cclient_poll_post_data, by the MIPS16e buildCVE-2026-55612, and chunk sizes parsed safely
sub_6c84uh_urldecode, by the MIPS16e buildthe one-byte overflow
sub_8dccuh_auth_check, by "Authorization Required"no stored hash as a password, and a constant-time compare
sub_5d78client_parse_header, by "Transfer-Encoding"CVE-2026-55614, and Content-Length parsed safely
sub_3dd0main, by its option string
sub_8cecnew, by sizelikely 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

BuildRowsRow of the fix
armsr/armv8, AArch64135
armsr/armv7, A32135
malta/be, MIPS16e big endian135
malta/le, MIPS16e little endian145

hereticus-cli/tests/truth_cves.rs locks these ranks, and fixtures/cve_binaries.py fetches and pins the binaries.