Skip to content

Fix possible deadlock in UnixCertificateManager and MacOSCertificateManager#66727

Open
Youssef1313 wants to merge 4 commits into
mainfrom
dev/ygerges/fix-deadlock
Open

Fix possible deadlock in UnixCertificateManager and MacOSCertificateManager#66727
Youssef1313 wants to merge 4 commits into
mainfrom
dev/ygerges/fix-deadlock

Conversation

@Youssef1313
Copy link
Copy Markdown
Member

Fixes #65518

When stdout/stderr are redirected, the child process writes to buffer (typically 4kb, at least on Windows), and the parent process is expected to be reading the stdout/stderr. If the parent process didn't read stdout/stderr, then the child process will be blocked forever on any Console.Write* call when the buffer is full (4kb of data is already written).

The child process can only get unblocked when the parent process consumes the output, but the parent process never attempts to read the output.

RunAndCaptureText is a new API in .NET 11 that attempts to resolve that. See also the very recent blogpost about this: https://devblogs.microsoft.com/dotnet/process-api-improvements-in-dotnet-11/

@adamsitnik I'm less familiar with the new APIs and haven't used them much yet. I was hesitating between RunAndCaptureText (keeping the current behavior of redirecting stdout/stderr which is likely done to avoid the avoid from showing on the running terminal) versus using ProcessStartInfo.StartDetached which prevents the handle inheritance altogether and uses null/nop handles instead (which is probably faster but more deviating from the original code).

Copilot AI review requested due to automatic review settings May 18, 2026 16:30
@github-actions github-actions Bot added the needs-area-label Used by the dotnet-issue-labeler to label those issues which couldn't be triaged automatically label May 18, 2026
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR addresses a possible Unix development-certificate startup hang by replacing a redirected certutil process wait with Process.RunAndCaptureText, allowing stdout/stderr to be consumed while waiting.

Changes:

  • Updates the NSS database certificate lookup path to use Process.RunAndCaptureText.
  • Preserves existing exit-code-based success behavior for the lookup.

Comment thread src/Shared/CertificateGeneration/UnixCertificateManager.cs Outdated
@Youssef1313 Youssef1313 force-pushed the dev/ygerges/fix-deadlock branch from 9991cd8 to cf3abff Compare May 18, 2026 16:40
@Youssef1313 Youssef1313 force-pushed the dev/ygerges/fix-deadlock branch from 56470e3 to 5326fb3 Compare May 18, 2026 16:56
@Youssef1313 Youssef1313 changed the title Fix possible deadlock in UnixCertificateManager Fix possible deadlock in UnixCertificateManager and MacOSCertificateManager May 18, 2026
@Youssef1313 Youssef1313 requested a review from javiercn May 18, 2026 19:53
Copy link
Copy Markdown
Member

@adamsitnik adamsitnik left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I really like the fact that you have found other places that could run into the same bug and fixed them @Youssef1313 🥇

I also love the fact that you have tried the new APIs! But please keep in mind that in case you need to backport these changes to older versions, they won't be available.

PTAL at my comments, thanks!

}
using (var process = Process.Start(MacOSTrustCertificateCommandLine, MacOSTrustCertificateCommandLineArguments + tmpFile))

var exitStatus = Process.Run(new ProcessStartInfo(MacOSTrustCertificateCommandLine, MacOSTrustCertificateCommandLineArguments + tmpFile));
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thank you for using the new APIs @Youssef1313 👍

return checkTrustProcess.ExitCode == 0 ? TrustLevel.Full : TrustLevel.None;
};

var checkTrustProcessOutput = Process.RunAndCaptureText(checkTrustProcessStartInfo);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For this particular use case the best solution is to redirect standard output and error to null file.

What we get:

  • no output is being printed
  • no extra work is being performed to capture the text just to ignore it

This is how it could look like (pseudocode, I've not compiled it)

            using SafeFileHandle nullHandle = File.OpenNullHandle();
            var checkTrustProcessStartInfo = new ProcessStartInfo(
                MacOSVerifyCertificateCommandLine,
                string.Format(CultureInfo.InvariantCulture, MacOSVerifyCertificateCommandLineArgumentsFormat, tmpFile))
            {
                // Do this to avoid showing output to the console when the cert is not trusted. It is trivial to export
                // the cert and replicate the command to see details.
                StandardOutputHandle = nullHandle,
                StandardErrorHandle= nullHandle
            });

            var checkTrustProcessOutput = Process.Run(checkTrustProcessStartInfo);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've created dotnet/runtime#128453 in order to make it one-liner

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it different from StartDetached?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

StartDetached

Yes, start detached focuses on ensuring the process can outlive the parent (it creates new session on Unix etc) and in addition to that it redirects to null


{output}");
}
{processOutput.StandardOutput}{processOutput.StandardError}");
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This problem has existed before this PR, I just want to mention it: joining standard output and error like this may confuse the users when it produced invalid order.

Imagine that the child process produces following output:

OUT: A
ERR: B
OUT: C

The combined output will be:

A
C
B

In the past I was considering introducing a method that would redirect std out and err to the same pipe and producing a single string (and preserving original ordering). I think it would be very useful in such scenarios.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yup. As it's a pre-existing different issue, I would say it's out of scope for this PR for now.

using var process = Process.Start(startInfo)!;
process.WaitForExit();
return process.ExitCode == 0;
return Process.RunAndCaptureText(startInfo).ExitStatus.ExitCode == 0;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similar to above, it would be better to redirect to null file and then just .Run

using var process = Process.Start(startInfo)!;
process.WaitForExit();
return process.ExitCode == 0;
return Process.RunAndCaptureText(startInfo).ExitStatus.ExitCode == 0;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similar to above, it would be better to redirect to null file and then just .Run

using var process = Process.Start(startInfo)!;
process.WaitForExit();
return process.ExitCode == 0;
return Process.RunAndCaptureText(startInfo).ExitStatus.ExitCode == 0;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similar to above, it would be better to redirect to null file and then just .Run

Copy link
Copy Markdown
Member

@adamsitnik adamsitnik left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM (assuming the tests are going to pass), thank you for addressing my feedback @Youssef1313 !

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

needs-area-label Used by the dotnet-issue-labeler to label those issues which couldn't be triaged automatically

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ASP.NET fails to start on Linux

3 participants