Skip to content

Fix indefinite parent hang when child dies mid-handshake - #5391

Draft
nebojsaj1726 wants to merge 1 commit into
opencontainers:mainfrom
nebojsaj1726:5087-sync-socket-hang
Draft

Fix indefinite parent hang when child dies mid-handshake#5391
nebojsaj1726 wants to merge 1 commit into
opencontainers:mainfrom
nebojsaj1726:5087-sync-socket-hang

Conversation

@nebojsaj1726

@nebojsaj1726 nebojsaj1726 commented Aug 10, 2026

Copy link
Copy Markdown

Fixes #5087.

When the container's init process dies abnormally mid-handshake (e.g. killed by a seccomp filter) while some other reference to its end of the sync socket is still held open elsewhere, the parent can block forever in recvfrom(), since the kernel never delivers EOF on a socket with a live reference.

This adds waitForSyncReady, which polls the sync socket alongside the child's own /proc/ state before each read in parseSync, so the parent can detect the child's death directly rather than relying on the socket's own EOF behavior. If the child is confirmed dead without its socket ever becoming genuinely ready, parseSync now returns a clear error instead of hanging or silently reporting success.

Verified two ways:

  • A new test (TestParseSyncDeadChildDoesNotHang) exercises the real parseSync against a socket with a deliberately leaked duplicate fd and a real killed process - fails (5s timeout) on unpatched main, passes (~0.3s) with this fix.
  • Manual repro following the original issue's steps (seccomp filter killing write, runc run): hangs indefinitely on unpatched main; returns immediately with a clear error on this branch.

What's not yet done / open questions for maintainers:

  • I initially tried a pidfd-based fast path (mirroring waitForFifoReadyPidfd/waitForFifoReadyPolling in container_linux.go), which passed in isolated unit tests but hung unpredictably during real container startup - I suspect a race with runc's own child-reaping elsewhere in the process (see the caveat in pidfd_open(2) about exactly this). This draft ships polling-only for now; can dig further into the pidfd race if that's wanted.
  • Only the two parseSync call sites (initProcess/setnsProcess) are covered. I didn't find other parent-side callers with the same exposure, but would appreciate your insight.
  • Test coverage is currently one test exercising parseSync directly; it isn't wired through an actual runc run/exec integration test.
  • No CHANGELOG entry yet - wanted to confirm the direction is right first.

Opening as a draft to get feedback on the approach before finishing this up.

If the container's init process dies mid-handshake (e.g. killed
by a seccomp filter) while some other reference to its end of the
sync socket is still held open elsewhere, the parent can block
forever in recvfrom(), since the kernel never delivers EOF on a
socket with a live reference.

Add waitForSyncReady, which polls the sync socket alongside the
child's own /proc state before each read in parseSync, so the
parent can detect the child's death directly instead of relying on
the socket's own EOF behavior. If the child is confirmed dead
without its socket ever becoming ready, parseSync now returns a
clear error instead of hanging or silently reporting success.

Fixes opencontainers#5087

Signed-off-by: Nebojsa Jacovic <nebojsa.jacovic@gmail.com>
@kolyshkin

Copy link
Copy Markdown
Contributor

The description and the patch is somewhat misleading. What happens (I believe) is this: #5087 (comment)

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.

Indefinite parent hang in ReadPacket upon abnormal child exit after procRun

2 participants