CVE-2019-19945: uhttpd, OpenWrt 18.06.5 to 18.06.6
OpenWrt 18.06.6 updates uhttpd to 2019-12-22, with three upstream
commits
(OpenWrt commit b901563):
5f9ae57, “client: fix invalid data access through invalid content-length values”: the fix. Jan-Niklas Sohn found that a POST to a CGI script withTransfer-Encoding: chunkedand aContent-Lengthabove 2^31 reads as a negative length:client_parse_headerstores it, andclient_poll_post_datapasses it on toustream_consume(advisory 2020-01-13-1).6b03f96, “ubus: increase maximum ubus request size to 64KB”.91fcac3, “uhttpd: Fix multiple format string problems”.
OpenWrt runs sstrip on what it ships, so only the segments of the file
are left.
Get the binaries
armvirt before 21.02 ships an empty rootfs.cpio.gz, so the files come
from the tar of the root file system:
$ for v in 18.06.5 18.06.6; do
mkdir -p $v
curl -s https://downloads.openwrt.org/releases/$v/targets/armvirt/64/openwrt-$v-armvirt-64-default-rootfs.tar.gz \
| tar -xz -C $v ./usr/sbin/uhttpd
done
Compare
$ hereticus --format markdown 18.06.5/usr/sbin/uhttpd 18.06.6/usr/sbin/uhttpd
| FUNCTION | CHANGE | SCORE | +/-/~ | TOUCHED | MATCHED BY |
|---|---|---|---|---|---|
sub_4076f8 -> sub_4076fc | changed | 0.91 | 6/1/0 | 1 branch, 1 jump | similar |
sub_4040fc | changed | 0.97 | 3/2/0 | 1 branch, 2 mem | similar |
sub_404288 -> sub_404290 | changed | 0.99 | 3/1/0 | 1 branch | similar |
sub_404bfc -> sub_404c08 | changed | 0.97 | 1/2/0 | 2 mem | similar |
sub_404ca4 -> sub_404cac | changed | 0.98 | 1/2/0 | 2 mem | similar |
228 matched (222 unchanged, 6 changed), 0 gone, 0 new
sub_4040fc is client_poll_post_data, which calls ustream_consume,
and sub_404288 is client_parse_header, which reads "chunked": the
fix is on rows 2 and 3. The other rows hold the two other commits, or
code that moved with them; this report does not tell them apart. The
build for ARMv7 reads the same, with the fix on rows 2 and
3 of 5. The
MIPS16e build for malta, from the initramfs of its kernel, has the fix
on rows 1 and 2 of
6. Its code is not PIC, so the literal pool after each
function holds the absolute addresses it loads, which move with each
release; the matching reads them as data.
Read the fix
$ hereticus --view il --diff sub_404288 18.06.5/usr/sbin/uhttpd 18.06.6/usr/sbin/uhttpd
...
+ w1 = load.8 [x1]
+ if (w1 != 0) goto $+0x8
+ if ((w0 & 0x80000000) == 0) goto $-0x80
x2 = &page
w1 = 0x190
x2 = &"Bad Request"
...
The new test of bit 31 refuses a length that reads as negative: the code after it answers 400 Bad Request.