Skip to content

[release/11.0] [CreateDump] Use _exit after createdump launch failures - #133928

Merged
steveisok merged 1 commit into
release/11.0from
backport/pr-133742-to-release/11.0
Sep 18, 2026
Merged

steveisok merged 1 commit into
release/11.0from
backport/pr-133742-to-release/11.0

Conversation

@github-actions

@github-actions github-actions Bot commented Sep 15, 2026 •

Copy link
Copy Markdown
Contributor

Backport of #133742 to release/11.0

/cc @steveisok @mdh1418

Customer Impact

  • Customer reported
  • Found internally

Customers collecting dumps via dotnet-dump with a companion createdump missing exec permissions will trigger cleanup that normally runs during shutdown (e.g. closing diagnostic ports) even though the app is still running.
The expected behavior is that cleanup logic does not run when the forked child process fails to execve into createdump, and just exits without closing diagnostic ports for example.

Regression

  • Yes
  • No

Testing

Before the fix, ran a live CoreCLR target with it's colocated createdump set to mode  0644 .
The first dotnet-dump collect reached the runtime and failed at execve with EACCES.
The target remained alive, but its default diagnostic socket disappeared, reproducing the issue.

After rebuilding with the fix (_exit(EXIT_FAILURE)), two consecutive collections both reached the runtime and returned the expected permission error.
After each failure, the target remained alive and the same diagnostic socket remained present.
After restoring execute permission, a subsequent minidump completed successfully.

Risk

Low.
Successful execve/execv behavior is unchanged.
Parent process behavior is unchanged: it reads the error pipe, calls waitpid, and treats any nonzero child exit status as failure.
_exit is the standard operation for abandoning a forked child process before exec, especially in a multithreaded process. It prevents inherited atexit handlers and destructors from operating on copied parent-runtime state.
Kernel cleanup still closes all child descriptors, releases mappings, signals pipe EOF, records the exit status, and permits waitpid to reap the child process.

IMPORTANT: If this backport is for a servicing release, please verify that:

  • For .NET 8 and .NET 9: The PR target branch is release/X.0-staging, not release/X.0.
  • For .NET 10+: The PR target branch is release/X.0 (no -staging suffix).

Package authoring no longer needed in .NET 9

IMPORTANT: Starting with .NET 9, you no longer need to edit a NuGet package's csproj to enable building and bump the version.
Keep in mind that we still need package authoring in .NET 8 and older versions.

Fixes #133733

Use `_exit(EXIT_FAILURE)` instead of `exit(-1)` when the forked
`createdump` launcher child fails before completing `execve`.

On Unix, CoreCLR forks a temporary child and expects it to replace
itself with `createdump`. If the parent/child synchronization handshake
fails or `execve(createdump)` fails, the child is still a forked copy of
the managed runtime.

Calling `exit()` in that child runs inherited process destructors. In
particular, the PAL process-shutdown destructor invokes the CoreCLR
shutdown callback, which cleans up the debugger transport and diagnostic
server. Because the child shares the parent's filesystem namespace, that
cleanup unlinks paths belonging to the still-running parent:

```text
/tmp/clr-debug-pipe-<pid>-<key>-in
/tmp/clr-debug-pipe-<pid>-<key>-out
/tmp/dotnet-diagnostic-<pid>-<key>-socket
```

The parent runtime remains alive and retains its diagnostic listener
file descriptor, but new diagnostic clients cannot connect after the
Unix-domain socket pathname has been removed.

## Cleanup behavior before this change

On a child failure:

```text
forked child
    |
    | handshake or execve failure
    v
exit(-1)
    |
    | runs inherited destructors
    v
PAL shutdown callback
    |
    +-- closes the child's inherited descriptor references
    +-- runs debugger transport cleanup
    +-- runs diagnostic server shutdown
    +-- unlinks debugger pipe and diagnostic socket paths
```

The kernel eventually closed the child's file descriptors when the child
terminated, but `exit()` first ran userspace cleanup that belonged to
the parent runtime. Unlinking the shared endpoint paths affected the
parent even though closing the child's descriptor references alone would
not have affected it.

## Cleanup behavior after this change

On a child failure:

```text
forked child
    |
    | handshake or execve failure
    v
_exit(EXIT_FAILURE)
    |
    +-- kernel closes the child's file descriptors
    +-- kernel releases the child's mappings and other process resources
    +-- parent observes EOF on the child stderr pipe
    +-- parent reaps the child with waitpid()
    |
    +-- does not run inherited destructors
    +-- does not invoke the PAL shutdown callback
    +-- does not unlink the parent's diagnostic endpoint paths
```

The child still closes all of its file descriptor references as part of
kernel process termination. The difference is that it no longer performs
inherited userspace teardown against copied runtime state.

On a successful `execve`, the existing `SOCK_CLOEXEC` and `FD_CLOEXEC`
flags continue to close inherited diagnostic descriptors in the new
`createdump` process. This change affects only failure paths before a
successful exec.

Both forked-child failure paths are updated:

1. Failure to read the one-byte synchronization signal from the parent.
2. Failure to execute the external `createdump` binary.

## Validation

The issue was reproduced by running a target with its colocated
`createdump` set to mode `0644`.

Before the change:

1. `dotnet-dump collect` reached the target runtime.
2. `execve(createdump)` failed with `EACCES`.
3. The target process remained alive.
4. The target's default diagnostic socket disappeared.

After the change:

1. Two consecutive dump requests failed with the expected
execute-permission error.
2. The target process remained alive after both requests.
3. The default diagnostic socket remained present after both requests.
4. The second request successfully connected to the same runtime,
confirming that the listener remained reachable.
5. After restoring execute permission, a subsequent minidump completed
successfully.
6. The diagnostic socket remained present after successful collection.

CoreCLR Release was rebuilt successfully with the change.

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 3 pipeline(s).
13 pipeline(s) were filtered out due to trigger conditions.
There may be pipelines that require an authorized user to comment /azp run to run.

@github-actions github-actions Bot added the area-PAL-coreclr only for closed issues label Sep 15, 2026
@ViveliDuCh

Copy link
Copy Markdown
Member

Hi,

the code complete date for RC2 is Monday 21st September. Make sure to merge this PR on that date at the latest, or it won't make it into that release.

As a reminder, if this is a product change, you also need Tactics approval before merging this PR (test-only or infra-only changes don't require Tactics approval).

@steveisok steveisok added the Servicing-approved Approved for servicing release label Sep 18, 2026
@steveisok
steveisok merged commit a5acd60 into release/11.0 Sep 18, 2026
113 of 114 checks passed
@steveisok
steveisok deleted the backport/pr-133742-to-release/11.0 branch September 18, 2026 13:17
@dotnet-milestone-bot dotnet-milestone-bot Bot added this to the 11.0-rc2 milestone Sep 18, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area-PAL-coreclr only for closed issues Servicing-approved Approved for servicing release

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants