Skip to content

file: allow exec to return stdout as a file descriptor - #40

Open
JuliusBairaktaris wants to merge 1 commit into
openwrt:masterfrom
JuliusBairaktaris:file-exec-stream-fd
Open

file: allow exec to return stdout as a file descriptor#40
JuliusBairaktaris wants to merge 1 commit into
openwrt:masterfrom
JuliusBairaktaris:file-exec-stream-fd

Conversation

@JuliusBairaktaris

Copy link
Copy Markdown

Extend the file exec method with an optional stream field. When set, the child's stdout is written to an anonymous memfd_create() file instead of a ustream buffer, and the fd is handed back to the caller via ubus SCM_RIGHTS fd passing. This lifts the 256 KB RPC_FILE_MAX_SIZE cap that currently applies to captured command output and makes the method usable for carrying a config backup archive.

A non-root uhttpd cannot build a LuCI config backup: cgi-io forks /sbin/sysupgrade --create-backup as a CGI child inheriting uhttpd's uid, which cannot read /etc/shadow or 0600 configs. With this change rpcd runs the command and returns the output as a file descriptor opened by root, so the receiver needs no DAC bypass. The caller is expected to check code first; the fd is attached whenever the fork succeeded so partial output can be inspected.

When stream is false or absent, behaviour is byte-identical to before. stderr is still returned inline and capped as today.

The memfd is capped at 16 MB; a child exceeding it is killed and the request fails with UBUS_STATUS_NOT_SUPPORTED rather than silently truncating.

Prerequisite for openwrt/openwrt#24558; pairs with #38.

Copilot AI lite review requested due to automatic review settings August 15, 2026 07:52

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.

rpc_file_exec captures stdout and stderr into ustreams capped at
RPC_FILE_MAX_SIZE (256 KB); exceeding the cap fails the request with
UBUS_STATUS_NOT_SUPPORTED and no output at all. That cap makes the method
unusable for carrying a config backup archive.

A non-root uhttpd cannot build a LuCI config backup because cgi-io forks
/sbin/sysupgrade --create-backup as a CGI child inheriting uhttpd's uid,
which cannot read /etc/shadow or 0600 configs. Letting rpcd run the
command and hand the output back as a file descriptor keeps those reads
in the root daemon.

The new optional "stream" field (default false) writes the child's stdout
to an anonymous memfd instead of a ustream buffer. When stream is false
or absent the behaviour is byte-identical to before; stderr is still
returned inline and capped as today. The memfd is attached to the deferred
reply and carried to the caller via ubus fd passing; it is the same open
file description opened by root, so the receiver needs no DAC bypass.

The memfd is capped at 16 MB; the kernel enforces the cap via
RLIMIT_FSIZE, killing a child that writes past the limit with SIGXFSZ at
exactly the cap. Exit codes follow the shell convention: a signal-killed
child reports 128 plus the signal number, so this surfaces as 153
(128 + SIGXFSZ) rather than the misleading 0 that WEXITSTATUS() yields
for a signal death or a silently truncated stream.

Assisted-by: Claude:claude-opus-5
Signed-off-by: Julius Bairaktaris <julius@bairaktaris.de>
@JuliusBairaktaris

Copy link
Copy Markdown
Author

@jow- @hauke — review ping.

Adds stream: true to file exec, returning the child's stdout as a file descriptor over SCM_RIGHTS instead of inline in the reply, so callers are not bound by the 256 KB RPC_FILE_MAX_SIZE cap (which errors out rather than truncating). The non-stream path is unchanged.

This is the primitive that lets cgi-io hand back a sysupgrade backup archive without being root (openwrt/cgi-io#6), and so blocks openwrt/openwrt#24558. Best reviewed after #38.

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