Skip to content

Sync child TID and retain CoW across nested fork#101

Merged
jserv merged 1 commit into
mainfrom
nested-clone
Jun 17, 2026
Merged

Sync child TID and retain CoW across nested fork#101
jserv merged 1 commit into
mainfrom
nested-clone

Conversation

@jserv

@jserv jserv commented Jun 17, 2026

Copy link
Copy Markdown
Contributor

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.

  • Bug Fixes
    • Forward CLONE_CHILD_{SETTID,CLEARTID} and ctid_gva via ipc_header_t; in fork_child_main honor CLONE_CHILD_SETTID by writing the child TID, warn on bad ctid_gva, intentionally ignore CLEARTID.
    • Preserve CoW for nested forks: when the inherited shm fd is an independent fclonefileat clone (shm_is_clone), map MAP_SHARED and retain it in g->shm_fd; otherwise keep MAP_PRIVATE and close it. guest_init_from_shm adds retain_shared and closes the fd on all error paths.
    • Add test-clone-childtid and wire it into tests/manifest.txt and test-matrix.sh.

Written for commit b8e578c. Summary will update on new commits.

Review in cubic

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
@jserv

jserv commented Jun 17, 2026

Copy link
Copy Markdown
Contributor Author

@doanbaotrung , Please validate this PR.

cubic-dev-ai[bot]

This comment was marked as resolved.

@doanbaotrung

Copy link
Copy Markdown
Collaborator

I've tested. It works.

@jserv jserv merged commit a2fb25a into main Jun 17, 2026
5 checks passed
@jserv jserv deleted the nested-clone branch June 17, 2026 16:20
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.

Crash on nested fork/clone in guest processes due to missing TID sync and SHM fd closure

2 participants