<feed xmlns='http://www.w3.org/2005/Atom'>
<title>uhttpd/tests/ubus_list_encoding.py, branch master</title>
<subtitle>Tiny HTTP server</subtitle>
<id>https://git.openwrt.org/project/uhttpd/atom?h=master</id>
<link rel='self' href='https://git.openwrt.org/project/uhttpd/atom?h=master'/>
<link rel='alternate' type='text/html' href='https://git.openwrt.org/project/uhttpd/'/>
<updated>2026-11-05T21:40:21Z</updated>
<entry>
<title>ubus: keep ubus list response encoding within encodable bounds</title>
<updated>2026-11-05T21:40:21Z</updated>
<author>
<name>Jo-Philipp Wich</name>
</author>
<published>2026-11-05T18:48:03Z</published>
<link rel='alternate' type='text/html' href='https://git.openwrt.org/project/uhttpd/commit/?id=93b05e0b65d91c23c5aa8fdde378df1f174ec71d'/>
<id>urn:sha1:93b05e0b65d91c23c5aa8fdde378df1f174ec71d</id>
<content type='text'>
uh_ubus_list_cb() opened one nesting level per signature method of every
object returned by a ubus lookup and closed the cookie returned by
blobmsg_open_table() without checking it:

	t = blobmsg_open_table(data-&gt;buf, blobmsg_name(sig));
	(...)
	blobmsg_close_table(data-&gt;buf, t);

Blob attribute lengths are 24 bit wide, so a blob can never grow beyond
BLOB_ATTR_LEN_MASK (16 MiB) bytes. Once the accumulated "list" result
approaches that limit, blob_new() fails and blobmsg_open_nested() returns
NULL, which libubox encodes as an invalid offset into the buffer.
blobmsg_close_table() then handed (char *)buf-&gt;buf - BLOB_COOKIE to
blob_nest_end(), which both read and wrote through that pointer,
corrupting the blob header and the heap around it.

No authentication is required to reach this code. A request such as

	{"jsonrpc":"2.0","method":"list","params":["*","*", ...]}

grew the response by the size of the whole object signature list per
repeated path; AddressSanitizer reports the resulting SEGV in
blob_nest_end() called from uh_ubus_list_cb(). The reporter shaped the
blob header corruption into unauthenticated remote command execution
running as root.

Fix the nesting bookkeeping and bound the response:

- Check the cookie returned by blobmsg_open_table() / blobmsg_open_array()
  before encoding into the nesting level and before closing it, in the
  list callback as well as in the remaining response builders.
- Track encoding failures in struct list_data, stop looking up further
  objects and discard a partial result instead of formatting it.
- Cap the accumulated list result at UH_UBUS_MAX_LIST_SIZE and answer with
  a JSON-RPC "Internal error" (-32603), or an HTTP 500 for the
  unparameterized GET list, instead of continuing to encode into a blob
  which no longer fits.
- Skip duplicate lookup paths, which resolve to the very same objects and
  used to duplicate result keys while growing the blob without a bound,
  and cap the number of paths a single request may name at
  UH_UBUS_MAX_LIST_PATHS ("Invalid parameters", -32602).
- Handle blob_buf_init(), blobmsg_add_blob() and blobmsg_format_json()
  failures in the affected code paths (blobmsg_add_* return negative
  values, not pointers). When formatting a response fails, still emit a
  JSON-RPC "Internal error" object instead of nothing, which left an empty
  body for single requests and a dangling separator within batches.
- The per-request bound does not bound a JSON-RPC batch: a 64 KiB POST
  holds well over a thousand "list" calls, each answered with up to
  UH_UBUS_MAX_LIST_SIZE, and all of it was queued into the unbounded
  write buffer whether or not the peer read it. Hold back the next batch
  element while more than UH_UBUS_MAX_PENDING_WRITE bytes are still
  queued for the peer, resume from the write callback once it caught up,
  and drop the connection if it does not drain within script_timeout.

Add tests/ubus_list_encoding.py, which runs uhttpd against a throwaway
ubusd holding synthetic objects with large signatures: the request from
the report makes the unpatched build die with SIGSEGV in blob_nest_end(),
while the patched build encodes every object once, answers oversized
requests with a bounded error and keeps serving normal requests.

Reported-by: @jro-calif
Link: https://github.com/openwrt/uhttpd/security/advisories/GHSA-788h-w5m4-j5xv
Signed-off-by: Jo-Philipp Wich &lt;jo@mein.io&gt;
</content>
</entry>
</feed>
