Accept FIOASYNC ioctl and F_SETOWN/F_GETOWN fcntl#81
Open
Max042004 wants to merge 1 commit into
Open
Conversation
nginx's ngx_spawn_process arms the master->worker channel socket with
ioctl(FIOASYNC) immediately followed by fcntl(F_SETOWN), right before
fork(), and treats failure of either as fatal: it logs an alert, closes
the channel, and returns NGX_INVALID_PID without ever forking the worker.
elfuse answered FIOASYNC with ENOTTY and F_SETOWN with EINVAL, so nginx
in its default master/worker mode silently ended up with zero workers:
the listen socket still accepted connections at the host kernel, but no
guest worker ever accept()ed them, so every request hung. (With
"master_process off" nginx served fine, which masked the issue.)
elfuse does not forward host SIGIO into the guest, and nginx workers
receive both client I/O and channel commands via epoll rather than
SIGIO, so both calls are safe to accept as no-ops:
- sys_ioctl: FIOASYNC reads the int arg (for EFAULT parity) and
returns success without arming host O_ASYNC.
- sys_fcntl: F_SETOWN / F_SETOWN_EX accept and track no owner;
F_GETOWN / F_GETOWN_EX report "no owner". glibc implements
fcntl(F_GETOWN) on top of F_GETOWN_EX, so the EX form writes a
struct f_owner_ex{type=F_OWNER_PID, pid=0} to stay coherent.
With this, "nginx -g 'daemon off;'" forks its worker and serves HTTP
(GET/HEAD/404, keep-alive, concurrency) on the default config.
Add tests/test-ioctl-fioasync.c, which replays nginx's pre-fork channel
arming on a socketpair and a TCP socket.
There was a problem hiding this comment.
2 issues found across 5 files
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="src/syscall/fs.c">
<violation number="1" location="src/syscall/fs.c:804">
P2: New F_SETOWN/F_GETOWN handlers can return success for closed FDs because they skip closed-descriptor validation.</violation>
<violation number="2" location="src/syscall/fs.c:805">
P2: F_SETOWN_EX no-op path skips user-pointer read/validation, so bad guest pointers incorrectly succeed.</violation>
</file>
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
| return 0; | ||
| } | ||
| case 8: /* F_SETOWN */ | ||
| case 15: /* F_SETOWN_EX */ |
There was a problem hiding this comment.
P2: F_SETOWN_EX no-op path skips user-pointer read/validation, so bad guest pointers incorrectly succeed.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/syscall/fs.c, line 805:
<comment>F_SETOWN_EX no-op path skips user-pointer read/validation, so bad guest pointers incorrectly succeed.</comment>
<file context>
@@ -801,6 +801,29 @@ int64_t sys_fcntl(guest_t *g, int fd, int cmd, uint64_t arg)
return 0;
}
+ case 8: /* F_SETOWN */
+ case 15: /* F_SETOWN_EX */
+ /* SIGIO/SIGURG delivery owner. nginx's ngx_spawn_process pairs
+ * ioctl(FIOASYNC) with fcntl(F_SETOWN) on the channel socket before
</file context>
| host_fd_ref_close(&host_ref); | ||
| return 0; | ||
| } | ||
| case 8: /* F_SETOWN */ |
There was a problem hiding this comment.
P2: New F_SETOWN/F_GETOWN handlers can return success for closed FDs because they skip closed-descriptor validation.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/syscall/fs.c, line 804:
<comment>New F_SETOWN/F_GETOWN handlers can return success for closed FDs because they skip closed-descriptor validation.</comment>
<file context>
@@ -801,6 +801,29 @@ int64_t sys_fcntl(guest_t *g, int fd, int cmd, uint64_t arg)
host_fd_ref_close(&host_ref);
return 0;
}
+ case 8: /* F_SETOWN */
+ case 15: /* F_SETOWN_EX */
+ /* SIGIO/SIGURG delivery owner. nginx's ngx_spawn_process pairs
</file context>
jserv
requested changes
Jun 6, 2026
Contributor
jserv
left a comment
There was a problem hiding this comment.
Rebase latest 'main` branch and resolve conflicts.
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.
Problem
Running nginx (aarch64 Linux, e.g.
nginx:alpine) under elfuse in its default master/worker mode never serves requests: the listen socket binds, the host kernel completes TCP handshakes, but every request hangs and times out. Withmaster_process off;(single process, no fork) nginx serves correctly, which masks the real issue.Root cause
nginx's
ngx_spawn_processarms the master→worker channel socket right beforefork()with:and treats failure of either as fatal — it logs an alert,
ngx_close_channel(), andreturn NGX_INVALID_PIDwithout ever callingfork(). elfuse answeredFIOASYNCwithENOTTYandF_SETOWNwithEINVAL, so the master silently ended up with zero workers. A verbose trace shows noclone(220), noaccept4(242), noepoll_pwait(22)after the FIOASYNC failure — the master just spins inrt_sigsuspend, and the accepted connections are never serviced.Fix
elfuse does not forward host
SIGIOinto the guest, and nginx workers receive both client I/O and channel commands viaepollrather thanSIGIO, so both calls are safe to accept as no-ops:sys_ioctl:FIOASYNCreads theintarg (forEFAULTparity) and returns success without arming hostO_ASYNC.sys_fcntl:F_SETOWN/F_SETOWN_EXaccept and track no owner;F_GETOWN/F_GETOWN_EXreport "no owner". glibc implementsfcntl(F_GETOWN)on top ofF_GETOWN_EX, so the EX form writes astruct f_owner_ex{type=F_OWNER_PID, pid=0}to stay coherent.With this,
nginx -g "daemon off;"forks its worker and serves HTTP on the stock config: GET/HEAD/404, keep-alive, and 100-way concurrency all return 200 with byte-identical bodies.Testing
tests/test-ioctl-fioasync.c(registered intests/manifest.txt) replays nginx's pre-fork channel arming on a socketpair and a TCP socket, assertingFIOASYNC,F_SETOWN, andF_GETOWNall succeed.make check: the full guest driver suite passes (the only failure observed was the pre-existing, unrelated flakytest-fork— "unexpected exit reason 0x3" — which is racy onmainand untouched by this change, which only adds switch cases insys_ioctl/sys_fcntl).Summary by cubic
Accepts
FIOASYNCioctl andF_SETOWN/F_GETOWN(and EX variants) fcntl as safe no-ops so nginx master/worker mode forks workers and serves requests under elfuse. Fixes the hang where requests timed out because no workers were spawned.LINUX_FIOASYNCas a no-op (reads int arg; no hostSIGIO). Adds constant inabi.h.F_SETOWN/F_SETOWN_EXas no-ops;F_GETOWNreturns 0;F_GETOWN_EXwrites{type=F_OWNER_PID, pid=0}.tests/test-ioctl-fioasync.cand manifest entry; checks socketpair and TCP socket paths.Written for commit b6260b6. Summary will update on new commits.