<feed xmlns='http://www.w3.org/2005/Atom'>
<title>rpcd, branch master</title>
<subtitle>OpenWrt ubus RPC daemon</subtitle>
<id>https://git.openwrt.org/project/rpcd/atom?h=master</id>
<link rel='self' href='https://git.openwrt.org/project/rpcd/atom?h=master'/>
<link rel='alternate' type='text/html' href='https://git.openwrt.org/project/rpcd/'/>
<updated>2026-07-19T12:57:18Z</updated>
<entry>
<title>file: re-authorize ACL against resolved path to close symlink bypass</title>
<updated>2026-07-19T12:57:18Z</updated>
<author>
<name>Jo-Philipp Wich</name>
</author>
<published>2026-07-19T12:57:18Z</published>
<link rel='alternate' type='text/html' href='https://git.openwrt.org/project/rpcd/commit/?id=e37ed9d814699098eb7e26c8b33c054840782dfb'/>
<id>urn:sha1:e37ed9d814699098eb7e26c8b33c054840782dfb</id>
<content type='text'>
The file plugin matched ACL grants against the textual, canonicalized
path but then let open()/stat()/opendir() follow symlinks unchecked.
A symlink placed inside an ACL-covered directory therefore let a grant
on the link authorize read/write/list/stat access to whatever file it
pointed to, including files entirely outside the granted scope.

Re-resolve the path with realpath() and re-run the ACL check against
the resolved target whenever it differs from the canonicalized string,
for every operation that dereferences the final component (read,
write, md5, list, stat). lstat and remove are left untouched since
they either need to see the link itself or already handle it safely
via unlink()'s no-follow semantics. Newly created files (file.write)
and dangling symlinks pointing outside the granted scope are handled
via the containing directory.

Also mask file.write's mode parameter to 0777 and drop the local
umask(0) override, and authorize recursive file.remove per-entry
instead of only at the top-level path.

Fixes GHSA-q5gr-86pq-vvwr.

Signed-off-by: Jo-Philipp Wich &lt;jo@mein.io&gt;
</content>
</entry>
<entry>
<title>cast char arguments to unsigned char for ctype.h functions</title>
<updated>2026-06-03T23:20:50Z</updated>
<author>
<name>Hauke Mehrtens</name>
</author>
<published>2026-05-31T16:19:22Z</published>
<link rel='alternate' type='text/html' href='https://git.openwrt.org/project/rpcd/commit/?id=28faf6403792d25b9826043aaf37880624c19568'/>
<id>urn:sha1:28faf6403792d25b9826043aaf37880624c19568</id>
<content type='text'>
isspace()/isxdigit()/isalnum()/tolower() are only defined for arguments
representable as unsigned char or EOF.  Several call sites passed a plain
(signed) char taken from client- or file-controlled data (uci names, the
session id, apk db lines, ACL list entries); a byte &gt;= 0x80 sign-extends
to a negative int, which is undefined behaviour and can index out of
bounds of a ctype lookup table on some C libraries.

Cast the argument to unsigned char at each site.

Assisted-by: Claude:claude-opus-4-8
Link: https://github.com/openwrt/rpcd/pull/34
Signed-off-by: Hauke Mehrtens &lt;hauke@hauke-m.de&gt;
</content>
</entry>
<entry>
<title>iwinfo: fix error handling and backend leak in survey</title>
<updated>2026-06-03T23:20:46Z</updated>
<author>
<name>Hauke Mehrtens</name>
</author>
<published>2026-05-31T16:16:22Z</published>
<link rel='alternate' type='text/html' href='https://git.openwrt.org/project/rpcd/commit/?id=2decaec3ef1bb6dc2caf7a4a63081aac82ff7d07'/>
<id>urn:sha1:2decaec3ef1bb6dc2caf7a4a63081aac82ff7d07</id>
<content type='text'>
rpc_iwinfo_survey() opened the iwinfo backend and the "results" blob
array, then on any error (open failed, survey call failed, negative
length) did "return UBUS_STATUS_OK" without sending a reply and without
calling rpc_iwinfo_close().  That leaked the iwinfo backend until the
next iwinfo call, abandoned a half-built reply, and masked an open
failure as success.

Restructure it like the freqlist/txpowerlist/countrylist handlers:
return the open error directly, otherwise emit the (possibly empty)
results array, send the reply and always release the backend.

Assisted-by: Claude:claude-opus-4-8
Link: https://github.com/openwrt/rpcd/pull/34
Signed-off-by: Hauke Mehrtens &lt;hauke@hauke-m.de&gt;
</content>
</entry>
<entry>
<title>ucode: bound recursion when converting blob arguments to ucode values</title>
<updated>2026-06-03T23:20:41Z</updated>
<author>
<name>Hauke Mehrtens</name>
</author>
<published>2026-05-31T16:14:05Z</published>
<link rel='alternate' type='text/html' href='https://git.openwrt.org/project/rpcd/commit/?id=cd1d9588da6368af1dbc99596ded90d7e5e027e1'/>
<id>urn:sha1:cd1d9588da6368af1dbc99596ded90d7e5e027e1</id>
<content type='text'>
rpc_ucode_blob_array_to_ucv()/rpc_ucode_blob_to_ucv() recurse once per
nesting level of the incoming request message, which is fully attacker
controlled.  ubus permits messages up to UBUS_MAX_MSGLEN (1 MiB) and
libubox enforces no nesting limit, so a deeply nested array/table
argument to a ucode plugin method can drive tens of thousands of
recursion levels and overflow the stack, crashing rpcd.

Thread a depth counter through the conversion and stop descending past
RPC_UCODE_MAX_NESTING (32) levels, which is far beyond any legitimate
ubus message.  Over-deep subtrees become null values instead of
crashing.

Assisted-by: Claude:claude-opus-4-8
Link: https://github.com/openwrt/rpcd/pull/34
Signed-off-by: Hauke Mehrtens &lt;hauke@hauke-m.de&gt;
</content>
</entry>
<entry>
<title>file: avoid zero-length b64_decode() on empty write data</title>
<updated>2026-06-03T23:20:36Z</updated>
<author>
<name>Hauke Mehrtens</name>
</author>
<published>2026-05-31T16:08:25Z</published>
<link rel='alternate' type='text/html' href='https://git.openwrt.org/project/rpcd/commit/?id=79c8087c8e8eb8933980fa6fc584703526388446'/>
<id>urn:sha1:79c8087c8e8eb8933980fa6fc584703526388446</id>
<content type='text'>
rpc_file_write() computes data_len as blobmsg_data_len() - 1, which is 0
for an empty "data" string.  With base64 enabled it then called
b64_decode(data, data, 0); b64_decode() asserts dest size &gt; 0, so a
request like file.write {"data":"","base64":true} aborts the daemon when
libubox is built with assertions enabled (and is a pointless call
otherwise).

Skip the decode when there is no data; the file is still created/
truncated and zero bytes are written, which is the correct result.

Assisted-by: Claude:claude-opus-4-8
Link: https://github.com/openwrt/rpcd/pull/34
Signed-off-by: Hauke Mehrtens &lt;hauke@hauke-m.de&gt;
</content>
</entry>
<entry>
<title>rpc-sys: packagelist: read world file instead of mmap to avoid SIGBUS</title>
<updated>2026-06-03T23:20:31Z</updated>
<author>
<name>Hauke Mehrtens</name>
</author>
<published>2026-05-31T15:56:20Z</published>
<link rel='alternate' type='text/html' href='https://git.openwrt.org/project/rpcd/commit/?id=e22aea1a51df4a5bc7197eb77a9194ef2c312d48'/>
<id>urn:sha1:e22aea1a51df4a5bc7197eb77a9194ef2c312d48</id>
<content type='text'>
The /etc/apk/world parser depends on a NUL byte one past the file
contents (world buffer is sized st_size + 1) to terminate its string
scans, and the world[] pointer walk and is_all_or_world() dereference a
sentinel that points at that trailing byte.  The file was mapped with
mmap(st_size + 1); when the file size is an exact multiple of the page
size, the byte at offset st_size lies in a page entirely beyond EOF and
accessing it raises SIGBUS, crashing the daemon.

Read the file into a malloc'd buffer and NUL terminate it explicitly so
the terminator is always backed by real memory.  This also drops the now
unused &lt;sys/mman.h&gt; include.

Assisted-by: Claude:claude-opus-4-8
Link: https://github.com/openwrt/rpcd/pull/34
Signed-off-by: Hauke Mehrtens &lt;hauke@hauke-m.de&gt;
</content>
</entry>
<entry>
<title>rpc-sys: packagelist: close status file on world parsing error paths</title>
<updated>2026-06-03T23:20:26Z</updated>
<author>
<name>Hauke Mehrtens</name>
</author>
<published>2026-05-31T15:53:19Z</published>
<link rel='alternate' type='text/html' href='https://git.openwrt.org/project/rpcd/commit/?id=fd4fcdeb186b83bfbe9db09f13994eec3d7162c9'/>
<id>urn:sha1:fd4fcdeb186b83bfbe9db09f13994eec3d7162c9</id>
<content type='text'>
rpc_sys_packagelist() opens /lib/apk/db/installed and then, for the
non-"all" case, opens and parses /etc/apk/world.  Every early error
return in that parsing block (missing world file, fstat failure, empty
or newline-less world, mmap failure) returned without fclose()ing the
already open status file, leaking a FILE/descriptor on each call.  The
most easily triggered case is a system whose installed db exists but
that has no /etc/apk/world.

Close the status file on all of these error paths.

Assisted-by: Claude:claude-opus-4-8
Link: https://github.com/openwrt/rpcd/pull/34
Signed-off-by: Hauke Mehrtens &lt;hauke@hauke-m.de&gt;
</content>
</entry>
<entry>
<title>rc: reap killed child on list "running" check timeout</title>
<updated>2026-06-03T23:20:21Z</updated>
<author>
<name>Hauke Mehrtens</name>
</author>
<published>2026-05-31T15:51:59Z</published>
<link rel='alternate' type='text/html' href='https://git.openwrt.org/project/rpcd/commit/?id=f5ffec54d7c74051286f9f6bca533271bd655c0b'/>
<id>urn:sha1:f5ffec54d7c74051286f9f6bca533271bd655c0b</id>
<content type='text'>
When a script's "running" check exceeds RC_LIST_EXEC_TIMEOUT_MS,
rpc_list_exec_timeout_cb() removes the process from uloop and SIGKILLs
it, but never waits on it.  Since uloop_process_delete() stops uloop
from reaping the child, it is left as a zombie for the lifetime of the
daemon.

Add a waitpid() after the kill to reap the just-killed child.

Assisted-by: Claude:claude-opus-4-8
Link: https://github.com/openwrt/rpcd/pull/34
Signed-off-by: Hauke Mehrtens &lt;hauke@hauke-m.de&gt;
</content>
</entry>
<entry>
<title>treat exec failures in forked children with _exit() instead of return</title>
<updated>2026-06-03T23:20:16Z</updated>
<author>
<name>Hauke Mehrtens</name>
</author>
<published>2026-05-31T15:51:23Z</published>
<link rel='alternate' type='text/html' href='https://git.openwrt.org/project/rpcd/commit/?id=3037a0e36856159075624fb00b5410d5577ed5fe'/>
<id>urn:sha1:3037a0e36856159075624fb00b5410d5577ed5fe</id>
<content type='text'>
Several handlers fork and, in the child, fall back to "return &lt;ubus
status&gt;" when open()/malloc()/execv() etc. fail.  Returning a ubus
status code from the child does not terminate it: control unwinds back
into the ubus dispatch loop and the child keeps running as a duplicate
rpcd, racing the parent on the ubus socket and pipes.

Replace these child-side returns with _exit(127) so a child that cannot
exec the target simply terminates, which is also what the parent's
WEXITSTATUS based reporting expects.  Affected paths: exec.c (rpc_exec),
file.c (rpc_file_exec_run), sys.c (rpc_cgi_password_set) and plugin.c
(rpc_plugin_register_exec).

Assisted-by: Claude:claude-opus-4-8
Link: https://github.com/openwrt/rpcd/pull/34
Signed-off-by: Hauke Mehrtens &lt;hauke@hauke-m.de&gt;
</content>
</entry>
<entry>
<title>rc: use a per-request blob_buf for the list reply</title>
<updated>2026-06-03T23:20:12Z</updated>
<author>
<name>Hauke Mehrtens</name>
</author>
<published>2026-05-31T15:42:56Z</published>
<link rel='alternate' type='text/html' href='https://git.openwrt.org/project/rpcd/commit/?id=75470f4b5124074ff078ed7052bb756e3b694712'/>
<id>urn:sha1:75470f4b5124074ff078ed7052bb756e3b694712</id>
<content type='text'>
rc_list() built the reply in a function-local "static struct blob_buf",
shared by every invocation, and kept using it across the deferred,
asynchronous directory walk.  Two overlapping "rc list" requests (the
walk waits on forked children, so a second request is easily interleaved)
would both blob_buf_init() and append to the same buffer, corrupting
each other's reply or crashing.

Move the blob_buf into the per-request context so each request owns its
own buffer, and free it when the request completes.

Assisted-by: Claude:claude-opus-4-8
Link: https://github.com/openwrt/rpcd/pull/34
Signed-off-by: Hauke Mehrtens &lt;hauke@hauke-m.de&gt;
</content>
</entry>
</feed>
