Skip to content

Return empty string instead of throwing when getpwuid_r fails unexpectedly - #132396

Open
HarnageaGabriel wants to merge 2 commits into
dotnet:mainfrom
HarnageaGabriel:fix-getusername-passwd-missing
Open

Return empty string instead of throwing when getpwuid_r fails unexpectedly#132396
HarnageaGabriel wants to merge 2 commits into
dotnet:mainfrom
HarnageaGabriel:fix-getusername-passwd-missing

Conversation

@HarnageaGabriel

Copy link
Copy Markdown
Contributor

Fixes#119216

Problem

GetUserNameFromPasswd's XML doc already documents that it returns an empty string on failure. That contract already holds when /etc/passwd is readable but the current UID isn't present (getpwuid_r returns -1/ENOENT-equivalent → TryGetUserNameFromPasswd returns null).

But when getpwuid_r fails for any other reason — notably when /etc/passwd itself doesn't exist, as can happen in restricted sandboxes/containers — TryGetUserNameFromPasswd threw an IOException instead. That exception propagates up through Environment.UserName and crashes the calling process (e.g. dotnet build, dotnet new) instead of degrading gracefully.

Repro (from the issue)

docker run -it ubuntu:24.04
apt update && apt install -y dotnet-sdk-8.0
rm /etc/passwd
cd /root
dotnet new create console

Fix

src/libraries/Common/src/Interop/Unix/System.Native/Interop.GetPwUid.cs: in TryGetUserNameFromPasswd, the fallback branch after the ERANGE (buffer-too-small) check no longer throws — it now sets username = null and returns true, same as the "current user not found" path, so GetUserNameFromPasswd returns string.Empty instead of propagating the exception. The ERANGE retry-with-larger-buffer behavior is unchanged.

Also removed the now-unused using System.IO;.

Testing

This is a small interop fix in src/libraries/Common (shared source, no dedicated isolated test project); given the size of the dotnet/runtime repo I did not run a full runtime build. I verified the change by inspecting the diff and confirming no other caller relies on the previously-thrown IOException (checked Environment.UserName in Environment.Unix.cs, the only caller).

…tedly
GetUserNameFromPasswd's doc contract already states it returns an empty
string on failure, matching the existing behavior when the current user
has no passwd entry. But any other getpwuid_r error (e.g. ENOENT when
/etc/passwd is missing, as in restricted sandboxes/containers) fell
through to an IOException, crashing callers like Environment.UserName
and, transitively, dotnet build/dotnet new.
Fixesdotnet#119216
@dotnet-policy-servicedotnet-policy-serviceBot added the community-contribution Indicates that the PR has been added by a community member label Aug 17, 2026
@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.

@HarnageaGabriel

Copy link
Copy Markdown
ContributorAuthor

@dotnet-policy-service agree

@dotnet-policy-service

Copy link
Copy Markdown
Contributor

Tagging subscribers to this area: @dotnet/area-system-runtime
See info in area-owners.md if you want to be subscribed.

@jeffhandleyjeffhandley left a comment

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.

@jeffhandleyjeffhandley added this to the 12.0.0 milestone Aug 20, 2026
throw new IOException(errorInfo.GetErrorMessage(), errorInfo.RawErrno);
// Otherwise, give back null.
username = null;
return true;

@AaronRobinsonMSFTAaronRobinsonMSFTAug 20, 2026

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 seems like a user visible behavioral change that should have a test. Clearly this was a test hole when it was written and now that we're addressing it here we might consider taking the opportunity to add some verification?

/cc @jeffhandley

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Good call — added coverage in ee86dae.

I extracted the error-code decision out of TryGetUserNameFromPasswd into a small internal helper, ShouldRetryGetUserNameFromPasswd(int error), which only decides "retry with a larger buffer" (true, ERANGE) vs. "no user name" (false, everything else including -1/not-found and unexpected errors). The native getpwuid_r call and all other logic are unchanged.

Since a real "unexpected getpwuid_r failure" (e.g. EIO) isn't something you can reliably force through the actual syscall in a portable xunit test, and there's no existing DI seam for this P/Invoke shim, I unit-tested the extracted helper directly instead — same pattern already used by Win32Marshal.Tests in this test project for similar error-code-to-outcome logic. New tests in GetPwUidTests.cs cover:

  • -1 (user not found) → no retry
  • ERANGE → retry
  • EIO (stand-in for an unexpected error) → no retry, which is what now surfaces as Environment.UserName returning "" instead of throwing.

I wasn't able to run the full test suite locally (this repo's local build requires enabling Windows long paths system-wide, which I didn't want to do unilaterally in this environment), but the change is a pure extraction with no behavioral difference, and I reviewed the resulting logic carefully against the original branching.

Extract the error-code decision in TryGetUserNameFromPasswd into an
internal ShouldRetryGetUserNameFromPasswd helper so it can be unit
tested independent of the native getpwuid_r call. Adds tests
covering the not-found (-1), ERANGE (retry), and unexpected-error
(e.g. EIO) cases, addressing review feedback that the
throw-to-empty-string behavior change was untested.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area-System.Runtimecommunity-contributionIndicates that the PR has been added by a community member

Projects

None yet

Development

Successfully merging this pull request may close these issues.

GetUserNameFromPasswd errors instead of returning empty string if /etc/passwd is missing

4 participants

@HarnageaGabriel@jeffhandley@AaronRobinsonMSFT@jkotas