Summary
The JSON-RPC list handler iterates the params array with the raw iterator, which bounds
the walk but performs no type check, then hands each element to ubus_lookup() as a C string:
rem = blobmsg_data_len(dup);
data.verbose = true;
__blob_for_each_attr(cur, blobmsg_data(dup), rem)
ubus_lookup(ctx, blobmsg_data(cur), uh_ubus_list_cb, &data);
For any element that is not BLOBMSG_TYPE_STRING the payload carries no NUL, and
blob_memdup() allocates exactly blob_pad_len(attr) bytes — so the strlen() inside
blob_put_string() runs off the end of the allocation.
list reaches this with no ACL check at all: uh_ubus_handle_request_object() calls
uh_ubus_allowed() only for "call".
Reproducer
POST /ubus HTTP/1.1
Host: 192.168.1.1
Content-Length: 59
{"jsonrpc":"2.0","id":1,"method":"list","params":[16843009]}
16843009 == 0x01010101, chosen so the 4-byte INT32 payload contains no zero byte and strlen()
cannot terminate inside it. [true], [{}], [[]], [1.5] behave the same.
ERROR: AddressSanitizer: heap-buffer-overflow
READ of size 5 at 0x71fe46be020c
#0 strlen
#1 blob_put_string libubox/blob.h:209
#2 ubus_lookup ubus/libubus.c:187
#3 uh_ubus_send_list ubus.c:677
#4 uh_ubus_handle_request_object ubus.c:807
0x71fe46be020c is located 0 bytes after 28-byte region [0x71fe46be01f0,0x71fe46be020c)
allocated by blob_memdup libubox/blob.c:343
Why I am filing this publicly rather than as an advisory
I could not turn it into any consequence beyond the read, and I tried:
- The over-read bytes are
memcpy'd into the outgoing blob and sent to ubusd as
UBUS_ATTR_OBJPATH. They are not reflected to the attacker — the lookup simply fails — so
this is not an information-disclosure primitive.
blob_memdup()'s allocation is exactly 28 + 12·(n−1) for an n-element array, and the
64 KiB POST cap allows n up to ~32000 (~384 KiB), comfortably past glibc's mmap threshold.
To fault, the allocation must end flush with its mapping. Measuring the real layout via
/proc/self/maps across n = 9000…20000: for mmap-backed chunks glibc places the user region
at offset 16 and rounds the mapping up to a whole extra page (e.g. n=10920, alloc=131056,
mapsz=135168, slack 4096). Minimum slack reachable in that range was 24 bytes, on a
heap-backed chunk.
- A sweep of
n = 11000…13000 plus the specific boundary triples on a non-ASan build all
returned 200 OK with the daemon alive.
So on x86-64/glibc the read lands in mapped heap and strlen() terminates almost immediately.
Scored strictly on what is demonstrated that is C:N/I:N/A:N.
Open question I could not close: musl/mallocng and 32-bit targets, which is where the OpenWrt
fleet actually lives and where the layout differs. I have no evidence either way there. If
someone demonstrates the fault on musl or 32-bit, this should be re-filed privately.
It is still a genuine memory-safety violation in an unauthenticated network parser reachable
with no ACL check, which is why it is worth fixing regardless of score.
Fix
One line, inside the loop:
if (blobmsg_type(cur) != BLOBMSG_TYPE_STRING)
continue;
Summary
The JSON-RPC
listhandler iterates theparamsarray with the raw iterator, which boundsthe walk but performs no type check, then hands each element to
ubus_lookup()as a C string:For any element that is not
BLOBMSG_TYPE_STRINGthe payload carries no NUL, andblob_memdup()allocates exactlyblob_pad_len(attr)bytes — so thestrlen()insideblob_put_string()runs off the end of the allocation.listreaches this with no ACL check at all:uh_ubus_handle_request_object()callsuh_ubus_allowed()only for"call".Reproducer
16843009 == 0x01010101, chosen so the 4-byte INT32 payload contains no zero byte andstrlen()cannot terminate inside it.
[true],[{}],[[]],[1.5]behave the same.Why I am filing this publicly rather than as an advisory
I could not turn it into any consequence beyond the read, and I tried:
memcpy'd into the outgoing blob and sent to ubusd asUBUS_ATTR_OBJPATH. They are not reflected to the attacker — the lookup simply fails — sothis is not an information-disclosure primitive.
blob_memdup()'s allocation is exactly28 + 12·(n−1)for ann-element array, and the64 KiB POST cap allows
nup to ~32000 (~384 KiB), comfortably past glibc's mmap threshold.To fault, the allocation must end flush with its mapping. Measuring the real layout via
/proc/self/mapsacrossn = 9000…20000: for mmap-backed chunks glibc places the user regionat offset 16 and rounds the mapping up to a whole extra page (e.g.
n=10920,alloc=131056,mapsz=135168, slack 4096). Minimum slack reachable in that range was 24 bytes, on aheap-backed chunk.
n = 11000…13000plus the specific boundary triples on a non-ASan build allreturned
200 OKwith the daemon alive.So on x86-64/glibc the read lands in mapped heap and
strlen()terminates almost immediately.Scored strictly on what is demonstrated that is
C:N/I:N/A:N.Open question I could not close: musl/mallocng and 32-bit targets, which is where the OpenWrt
fleet actually lives and where the layout differs. I have no evidence either way there. If
someone demonstrates the fault on musl or 32-bit, this should be re-filed privately.
It is still a genuine memory-safety violation in an unauthenticated network parser reachable
with no ACL check, which is why it is worth fixing regardless of score.
Fix
One line, inside the loop: