Sync child TID and retain CoW across nested fork#101
Merged
Merged
Conversation
glibc's fork wrapper clones with CLONE_CHILD_{SETTID | CLEARTID} |
SIGCHLD, but the posix_spawn fork path could not see the original clone
arguments, so the child never wrote its new TID into the guest ctid
address. The child kept the parent's cached TID and modern glibc tripped
its stack-canary / TLS checks ("stack smashing detected"), which
surfaced on nested forks.
Forward the relevant clone flags and the ctid address through
ipc_header_t. In fork_child_main, after the main thread is registered,
honor CLONE_CHILD_SETTID by writing the child TID into ctid_gva. A
faulting address is the guest's own bad pointer, so warn and continue,
matching how the kernel ignores a child_tidptr fault. CLONE_CHILD_CLEARTID
is intentionally not honored: a fork child is a separate process whose
ctid no other process can observe, and the parent reaps it via
wait4/SIGCHLD rather than a cross-process futex.
A fork child also closed its inherited shm fd and mapped it MAP_PRIVATE,
so any nested grandchild fork dropped off the copy-on-write fast path
into the slow region-copy path. When the inherited fd is an independent
fclonefileat clone (the new shm_is_clone header flag), map it MAP_SHARED
and retain it in g->shm_fd so the child can clone it again for its own
nested fork; guest_destroy closes it. The live-fd fallback keeps the
MAP_PRIVATE behavior so the child does not share writes with the parent.
guest_init_from_shm gains a retain_shared parameter and closes the
inherited fd on every error path so the ownership contract holds.
Close #99
Contributor
Author
|
@doanbaotrung , Please validate this PR. |
Collaborator
|
I've tested. It works. |
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.
glibc's fork wrapper clones with CLONE_CHILD_{SETTID | CLEARTID} | SIGCHLD, but the posix_spawn fork path could not see the original clone arguments, so the child never wrote its new TID into the guest ctid address. The child kept the parent's cached TID and modern glibc tripped its stack-canary / TLS checks ("stack smashing detected"), which surfaced on nested forks.
Forward the relevant clone flags and the ctid address through ipc_header_t. In fork_child_main, after the main thread is registered, honor CLONE_CHILD_SETTID by writing the child TID into ctid_gva. A faulting address is the guest's own bad pointer, so warn and continue, matching how the kernel ignores a child_tidptr fault. CLONE_CHILD_CLEARTID is intentionally not honored: a fork child is a separate process whose ctid no other process can observe, and the parent reaps it via wait4/SIGCHLD rather than a cross-process futex.
A fork child also closed its inherited shm fd and mapped it MAP_PRIVATE, so any nested grandchild fork dropped off the copy-on-write fast path into the slow region-copy path. When the inherited fd is an independent fclonefileat clone (the new shm_is_clone header flag), map it MAP_SHARED and retain it in g->shm_fd so the child can clone it again for its own nested fork; guest_destroy closes it. The live-fd fallback keeps the MAP_PRIVATE behavior so the child does not share writes with the parent.
guest_init_from_shm gains a retain_shared parameter and closes the inherited fd on every error path so the ownership contract holds.
Close #99
Summary by cubic
Sync the child TID on the posix_spawn-based fork path and retain copy-on-write across nested forks. Fixes glibc crashes (“stack smashing detected”) and keeps grandchild forks on the fast CoW path.
CLONE_CHILD_{SETTID,CLEARTID}andctid_gvaviaipc_header_t; infork_child_mainhonorCLONE_CHILD_SETTIDby writing the child TID, warn on badctid_gva, intentionally ignoreCLEARTID.fclonefileatclone (shm_is_clone), mapMAP_SHAREDand retain it ing->shm_fd; otherwise keepMAP_PRIVATEand close it.guest_init_from_shmaddsretain_sharedand closes the fd on all error paths.test-clone-childtidand wire it intotests/manifest.txtandtest-matrix.sh.Written for commit b8e578c. Summary will update on new commits.