Skip to content

session: enforce session ACLs for non-root ubus callers - #38

Open
JuliusBairaktaris wants to merge 1 commit into
openwrt:masterfrom
JuliusBairaktaris:session-acl-nonroot
Open

session: enforce session ACLs for non-root ubus callers#38
JuliusBairaktaris wants to merge 1 commit into
openwrt:masterfrom
JuliusBairaktaris:session-acl-nonroot

Conversation

@JuliusBairaktaris

Copy link
Copy Markdown

Reaching rpcd over ubus is treated as authorisation. rpc_file_access() and its three uci.c siblings return true outright when a request carries no ubus_rpc_session, and rc, sys and iwinfo never look at a session at all. That is safe only while ubusd's uid 0 exemption is the sole way in.

It stops being safe as soon as a /usr/share/acl.d entry lets a non-root uid reach one of those objects — something OpenWrt is about to do for a jailed uhttpd (openwrt/openwrt#24558). With such a grant in place, "no session" means unrestricted read, write and exec as root, and rc init and system.password_set are open to whoever holds that uid.

This guards every object rpcd registers: a caller ubusd reports as non-root has to satisfy rpc_session_access(sid, "ubus", object, method) — the same check uhttpd's ubus proxy and cgi-io already perform before forwarding — with the default session when the request carries no id, so rpcd's own unauthenticated group governs pre-auth access. Callers at uid 0 are unaffected, so every existing shell and script path behaves exactly as before.

The check is installed by walking ctx->objects once after the api_init calls and swapping each method's handler for the guard. That keeps it in one place and covers the file, sys, iwinfo, ucode and exec plugins without an ops or ABI change.

The session object keeps the six methods that act on nothing but the id handed to them — access, destroy, get, login, set, unset — reachable without an ACL, since they are how a caller authenticates and reaches its own session. create, grant, revoke and list are guarded like everything else; grant in particular would otherwise let anyone holding the default session id escalate it.

Testing

ipq807x router, OpenWrt snapshot with LuCI.

As uid 0, unchanged: ubus call file exec, ubus call uci get and ubus call system board all behave as before.

As a non-root uid holding an acl.d grant for the file and uci objects and presenting no session:

ubus call uci get  '{"config":"system"}'   ->  Permission denied
ubus call file read '{"path":"/etc/shadow"}' ->  Permission denied

Without this commit the second call returns the contents of /etc/shadow.

LuCI is unaffected end to end — login, page render, and /ubus-proxied uci get, file read and luci-rpc getNetworkDevices all carry a session id already, which is what the guard checks.

No ubusd ACL file shipped by OpenWrt today grants a non-root uid access to an rpcd object — luci-base.json, dnsmasq_acl.json, ntpd.json, logd.json and wpad_acl.json name procd, netifd and hostapd objects only — so nothing changes for existing installs.

Reaching rpcd over ubus is treated as authorisation: rpc_file_access()
and its three uci.c siblings return true outright when a request
carries no ubus_rpc_session, and rc, sys and iwinfo never look at a
session at all. That holds only while ubusd's uid 0 exemption is the
sole way in. The moment a /usr/share/acl.d entry lets a non-root uid
reach the file object, "no session" means unrestricted read, write and
exec as root, and rc init and system.password_set are open to whoever
holds that uid.

Guard every object rpcd registers: a caller ubusd reports as non-root
must satisfy the session ACL the ubus proxies already check before
forwarding - rpc_session_access(sid, "ubus", object, method) - with the
default session when the request carries no id, so the unauthenticated
group governs pre-auth access. Callers at uid 0 are unaffected.

The check is installed by walking ctx->objects once after the api_init
calls and swapping each method's handler for the guard, which keeps it
in one place and covers the file, sys, iwinfo, ucode and exec plugins
without an ops or ABI change.

The session object keeps the six methods that act on nothing but the id
handed to them - access, destroy, get, login, set and unset - reachable
without an ACL, because they are how a caller authenticates and reaches
its own session; create, grant, revoke and list are guarded like
everything else.

Assisted-by: Claude:claude-opus-5
Signed-off-by: Julius Bairaktaris <julius@bairaktaris.de>
Copilot AI lite review requested due to automatic review settings August 6, 2026 23:07

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@JuliusBairaktaris

Copy link
Copy Markdown
Author

@jow- @hauke — review ping.

This is the prerequisite for granting any non-root daemon an acl.d entry that reaches rpcd. rpc_file_access() returns true when no session id is passed, which is only safe while ubusd's uid-0 exemption is the sole way in; once a non-root daemon holds a grant to rpcd's file object it can call file exec with no token at all.

Blocks openwrt/openwrt#24558 (running uhttpd, pppd and odhcpd unprivileged), and is a prerequisite for #40.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants