main: run sysupgrade backup through rpcd - #6
Open
JuliusBairaktaris wants to merge 2 commits into
Open
Conversation
The backup CGI forked sysupgrade, which runs it under uhttpd's uid. Once uhttpd runs as a non-root user, the generated archive silently shrinks from 87 to 38 entries, dropping /etc/shadow, /etc/config/network, /etc/config/system and the ssh host keys. The fork path also discarded the child's exit status, so a sysupgrade failure (tar cannot read a listed file) still produced HTTP 200 with a silently incomplete archive. Delegate to rpcd instead, which already validated the session and runs as root. The archive is streamed back through a memfd and the exit status is checked before any headers are sent, so a failed backup now returns an error instead of a truncated archive. Assisted-by: Claude:claude-opus-5 Signed-off-by: Julius Bairaktaris <julius@bairaktaris.de>
Author
|
It also fixes a pre-existing bug that is independent of the uid work: the old code printed Depends on openwrt/rpcd#40 (the |
This was referenced Aug 15, 2026
JuliusBairaktaris
force-pushed
the
backup-via-rpcd
branch
from
August 15, 2026 08:54
cf5b6ef to
5c4f080
Compare
cgi-exec forked the requested command in-process, running it as a child of uhttpd and therefore under uhttpd's uid. Once uhttpd runs as a non-root user, every fs.exec_direct() call in LuCI - network diagnostics, rrdtool graphs, package-manager - would lose the root privilege those commands need, and the in-process fork never surfaced a non-zero exit status, serving a partial 200 body instead. Delegate to rpcd's "file exec" like the backup path: the resolved executable and its arguments are handed to rpcd, which re-checks the session ACL against the canonical command line and runs the child as root, streaming stdout back through a memfd. The exit status is checked before any headers are sent, so a failed command now returns an error instead of a partial body, and an rpcd too old to support streaming fails loudly instead of silently truncating at the 256 KB inline cap. rpcd returns stderr inline, so when the caller requested it (stderr=1) it is prepended to the streamed stdout rather than interleaved with it as the in-process fork did. The backup and exec paths share the same reply handling. The cram tests can no longer run the in-process fork cases without a live rpcd, so the executable-not-found and invalid-filename cases now cover the argument parsing and failure paths main_exec still owns, keeping the result independent of whether a ubusd runs on the build host. Signed-off-by: Julius Bairaktaris <julius@bairaktaris.de>
JuliusBairaktaris
force-pushed
the
backup-via-rpcd
branch
from
August 15, 2026 08:57
5c4f080 to
f08fd00
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The
cgi-backupendpoint forked/sbin/sysupgrade --create-backup -and streamed its stdout while discarding the exit status. Two defects:sysupgradeexits non-zero whentarcannot read a listed file, but cgi-io returned HTTP 200 anyway and served the truncated archive. Restoring it destroys data with no error anywhere./etc/shadow,/etc/config/network,/etc/config/systemand the ssh host keys.This delegates the backup to rpcd's
file execmethod, which already validates the session ACL and runs as root, and requests the newstreammode: stdout goes to an anonymous memfd, the memfd is returned attached to the completion status, and the command's exit code is returned in the reply. cgi-io now checks the exit code and the presence of the fd before sending any headers, so a failed archive is never served as a good one.Depends on the companion rpcd change adding
streamto thefileobject'sexecmethod. Without it,streamis ignored and the reply comes back with an inline stdout capped at 256 KB and no fd; cgi-io detects the missing fd and fails loudly instead of serving a truncated archive.This is a prerequisite for running uhttpd unprivileged (openwrt/openwrt#24558).