<feed xmlns='http://www.w3.org/2005/Atom'>
<title>rpcd, branch master</title>
<subtitle>OpenWrt ubus RPC daemon</subtitle>
<id>https://git-03.infra.openwrt.org/project/rpcd/atom?h=master</id>
<link rel='self' href='https://git-03.infra.openwrt.org/project/rpcd/atom?h=master'/>
<link rel='alternate' type='text/html' href='https://git-03.infra.openwrt.org/project/rpcd/'/>
<updated>2026-10-03T23:54:11Z</updated>
<entry>
<title>session: stop naming runtime files after the session id</title>
<updated>2026-10-03T23:54:11Z</updated>
<author>
<name>Hauke Mehrtens</name>
</author>
<published>2026-09-20T09:46:33Z</published>
<link rel='alternate' type='text/html' href='https://git-03.infra.openwrt.org/project/rpcd/commit/?id=d99f703e4035d03fbcfbada3cb697db346c2b984'/>
<id>urn:sha1:d99f703e4035d03fbcfbada3cb697db346c2b984</id>
<content type='text'>
rpcd names the uci delta directory /var/run/rpcd/uci-&lt;SID&gt; and the
frozen session file /var/run/rpcd/sessions/&lt;SID&gt; after the raw session
id, which is a bearer credential. The file plugin lists directories as
root and only checks the "list" permission, and ACL paths are matched
with fnmatch() without FNM_PATHNAME, so the "/*" grant of luci-base
covers /var/run/rpcd. A restricted login can read a privileged
session's token from a directory entry and replay it. A default
installation is not affected, as its only login is root.

Give each session a separate random name, generated in
rpc_session_new(), and build both paths from it. rpcd does not purge
the delta directories on reload, so rpc_session_thaw() restores the
name from the frozen file's name to keep the staged changes. Files
frozen by an older rpcd are named after the session id, which also
matches their delta directory.

Reported-by: kiperZZZ &lt;https://github.com/kiperZZZ&gt;
Fixes: b3a5c08e087b ("uci: use per-session save directory and register session destroy callback to purge leftover deltas")
Fixes: aa2afdb739b4 ("session: add support for saving and restoring session data to disk")
Assisted-by: Claude:claude-opus-5
Link: https://github.com/openwrt/rpcd/pull/43
Signed-off-by: Hauke Mehrtens &lt;hauke@hauke-m.de&gt;
</content>
</entry>
<entry>
<title>ucode: add compatibility to newer ucode version</title>
<updated>2026-09-25T11:12:43Z</updated>
<author>
<name>Felix Fietkau</name>
</author>
<published>2026-09-25T11:12:42Z</published>
<link rel='alternate' type='text/html' href='https://git-03.infra.openwrt.org/project/rpcd/commit/?id=a6c6b63b47bd8f4f762ef2e6787210cd3e0fb8a0'/>
<id>urn:sha1:a6c6b63b47bd8f4f762ef2e6787210cd3e0fb8a0</id>
<content type='text'>
It adds STATUS_BREAK for breakpoints. Add a default switch/case fallback
to avoid running into the unhandled case warning/error.

Signed-off-by: Felix Fietkau &lt;nbd@nbd.name&gt;
</content>
</entry>
<entry>
<title>file: reply with empty data instead of error for empty files</title>
<updated>2026-09-17T13:52:51Z</updated>
<author>
<name>Jo-Philipp Wich</name>
</author>
<published>2026-09-17T13:52:06Z</published>
<link rel='alternate' type='text/html' href='https://git-03.infra.openwrt.org/project/rpcd/commit/?id=4062f666ecd17a03a153a40e76af6904d3eb6a78'/>
<id>urn:sha1:4062f666ecd17a03a153a40e76af6904d3eb6a78</id>
<content type='text'>
read() returns 0 for an empty file (st_size 0, e.g. right after touch),
which previously hit the &lt;= 0 check and replied UBUS_STATUS_NO_DATA (5),
so 'ubus call file read' on an empty file failed with 'No response'.

Treat a zero-byte read as a successful empty reply: only actual read
errors (&lt; 0) map to UBUS_STATUS_NO_DATA, while a zero-length read (0)
replies { "data": "" } with status 0.

Fixes #42

Signed-off-by: Jo-Philipp Wich &lt;jo@mein.io&gt;
</content>
</entry>
<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-03.infra.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-03.infra.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-03.infra.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-03.infra.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-03.infra.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-03.infra.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-03.infra.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>
</feed>
