-
Notifications
You must be signed in to change notification settings - Fork 28
Let a pidfd name any relative kill can name #392
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
94067f8
aff1cc5
655d6a0
dbb1f57
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -105,7 +105,7 @@ static void *pidfd_monitor_thread(void *arg) | |
| return NULL; | ||
| } | ||
|
|
||
| int pidfd_create(guest_t *g, int64_t target_pid) | ||
| int pidfd_create(guest_t *g, int64_t target_pid, pid_t host_pid) | ||
| { | ||
| (void) g; | ||
| int pfd[2]; | ||
|
|
@@ -141,9 +141,16 @@ int pidfd_create(guest_t *g, int64_t target_pid) | |
| entry->write_end = pfd[1]; | ||
| pthread_mutex_unlock(&pidfd_lock); | ||
|
|
||
| pid_t host_pid = proc_guest_to_host_pid(target_pid); | ||
| if (host_pid > 0) { | ||
| bool monitor_ok = false; | ||
| /* host_pid <= 0 means the target lives inside this host process -- the | ||
| * caller itself, or a CLONE_VM child, which holds a guest tid but no host | ||
| * pid of its own. Neither can be watched from here, and neither has exited, | ||
| * so the fd stays unreadable rather than being completed. | ||
| */ | ||
| if (host_pid <= 0) | ||
| return gfd; | ||
|
|
||
| bool monitor_ok = false; | ||
| { | ||
| int64_t *ctx = malloc(2 * sizeof(int64_t)); | ||
| if (ctx) { | ||
| ctx[0] = target_pid; | ||
|
|
@@ -164,10 +171,16 @@ int pidfd_create(guest_t *g, int64_t target_pid) | |
| free(ctx); | ||
| } | ||
| } | ||
| if (!monitor_ok) | ||
| proc_pidfd_notify_exit(target_pid); | ||
| } | ||
|
|
||
| /* Nothing will ever mark this fd readable without a monitor behind it, so | ||
| * complete it rather than leave the guest polling forever. A target that | ||
| * has already exited needs no special case: the monitor thread finds it | ||
| * gone and completes the fd the same way. | ||
| */ | ||
| if (!monitor_ok) | ||
| proc_pidfd_notify_exit(target_pid); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Both halves are right, and a follow-up suits them better than this PR — they are pre-existing, and fixing them properly means changing what What I will do there: complete a single entry by fd for the creation-failure case, and make
I will open it once this merges, unless you would rather have it here. |
||
|
|
||
| return gfd; | ||
| } | ||
|
|
||
|
|
@@ -204,11 +217,16 @@ int64_t sys_pidfd_open(guest_t *g, int64_t pid, unsigned int flags) | |
| if (flags != 0) | ||
| return -LINUX_EINVAL; | ||
|
|
||
| /* The kernel rejects a non-positive pid before it looks anything up. */ | ||
| if (pid <= 0) | ||
| return -LINUX_EINVAL; | ||
|
|
||
| if (pid == proc_get_pid()) | ||
| return pidfd_create(g, pid); | ||
| return pidfd_create(g, pid, 0); | ||
|
|
||
| if (proc_guest_to_host_pid(pid) > 0) | ||
| return pidfd_create(g, pid); | ||
| pid_t host_pid = proc_resolve_guest_pid(pid); | ||
| if (host_pid > 0) | ||
| return pidfd_create(g, pid, host_pid); | ||
|
|
||
| return -LINUX_ESRCH; | ||
| } | ||
|
|
@@ -238,7 +256,7 @@ int64_t sys_pidfd_send_signal(guest_t *g, | |
| return 0; | ||
| } | ||
|
|
||
| pid_t host_pid = proc_guest_to_host_pid(pid); | ||
| pid_t host_pid = proc_resolve_guest_pid(pid); | ||
| if (host_pid > 0) { | ||
| if (sig == 0) { | ||
| if (kill(host_pid, 0) < 0) | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -1814,6 +1814,12 @@ pid_t proc_namespace_host_pid(int64_t guest_pid) | |
| : -1; | ||
| } | ||
|
|
||
| pid_t proc_resolve_guest_pid(int64_t guest_pid) | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. P2: The registry fallback trusts a record after only a same-binary-name check (registry_collect compares proc_pidpath against this process's path), not the record's original process identity. If the host pid in a record is reused by another process that also runs this elfuse binary (any other elfuse instance of the same uid), the reuse passes the check, and proc_resolve_guest_pid returns the recycled host pid. sys_pidfd_open / pidfd_send_signal / kill then address a process that never carried the guest pid, with an exit monitor watching the wrong process. Validate each registry hit against the recorded process start time (proc_pidinfo start time) before returning its host pid, per the registry-record validation guidance (matching the same rule already recommended for fork-family records). Prompt for AI agents
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This validation is already in place, so there is nothing to change here.
uint64_t live_us;
if (!host_start_us((pid_t) hp, &live_us) || live_us != (uint64_t) st)
return;A record naming a reused host pid therefore never reaches the |
||
| { | ||
| pid_t host_pid = proc_guest_to_host_pid(guest_pid); | ||
| return host_pid > 0 ? host_pid : proc_namespace_host_pid(guest_pid); | ||
| } | ||
|
|
||
| int64_t proc_host_to_guest_pid(pid_t host_pid) | ||
| { | ||
| pthread_mutex_lock(&pid_lock); | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This completion now runs for a target that never resolved, and
clone3(CLONE_PIDFD | CLONE_VM)reachespidfd_createwith exactly that:sys_clone_vmallocates the child tid in the thread table and never callsproc_register_childor publishes to the registry, soproc_resolve_guest_pidreturns -1 for a child that is running. The guest then gets a pidfd that reads as exited from the momentclone3returns. Gate the completion on the target having resolved, and handle the unresolvable non-self case separately.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Confirmed and fixed in
655d6a0. Reproduced first:clone3(CLONE_PIDFD | CLONE_VM)thenppollon the pidfd while the child slept gavepoll=1 revents=0x1— the fd read as exited immediately, exactly as described.Rather than gate the completion,
pidfd_createno longer resolves at all: the caller passes the host pid to watch, and 0 means the target lives in this host process and has nothing to watch, so the fd is left unreadable as before.sys_pidfd_openpasses what it already resolved, and the clone3 path passesproc_resolve_guest_pid(ret), which is the child's host pid for a fork child and -1 for a CLONE_VM child.That also removes the second lookup cubic's earlier finding was about, so the window it described no longer exists. A target that died in the meantime is handled where it belongs: the monitor thread's
kill(hpid, 0)ESRCH check and thekeventfailure path both complete the fd.Covered by a new case in test-pidfd, which asserts the CLONE_VM child's pidfd is not readable while it runs; restoring the old behavior turns it red. It went to test-pidfd rather than the cross-checked test because a CLONE_VM child resumes on a fresh stack, where the compiler's copy of the clone3 return value is undefined — the
if (ret == 0)branch is only reliable under elfuse, and against qemu the child took the parent branch and died. The same reason your existing CLONE_VM tests sit in QEMU_SKIP, I think.Unrelated observation from writing it: a VM-clone child calling
exit(93) directly takes the whole host process down with it, before stdio is flushed — the parent's output disappears entirely and the process still exits 0. I parked the child in a nanosleep loop instead. Happy to file that separately if it is not already known.