Linux: named mutex creation fails with lstat("") when XDG_RUNTIME_DIR is unset (StackString::IsEmpty reads an uninitialized buffer)

オープン 初心者向け
#136,802 コメント 1 件 リアクション 0 件 担当者 0 名 GitHub で見る

メンテナーはふだん 1 日以内に返信

まだ誰も着手していません。

評価

難易度
2/5
見積もり時間
半日
初心者へのやさしさ
72/100
issue の種類
バグ
明瞭さ
明確に書かれている
活発さ
活発
技術スタック
cpp, csharp

調査の方向性

src/coreclr/pal/src/sharedmemory/sharedmemory.cpp の InitializeUserSpecificSharedFilesPath から読み始めてください。Linux では PathCharString path は XDG_RUNTIME_DIR からのみ設定され、その後 IsEmpty() で確認されています。次に src/coreclr/pal/src/include/pal/stackstring.hpp の StackString() と IsEmpty() を読み、一度も設定されていないバッファがなぜ空でないと判定されうるのかを確認してください。XDG_RUNTIME_DIR が未設定の状態で、ホームディレクトリへのフォールバックが実行されるか、空のパスが lstat に渡されないことが確認できれば完了です。

索引モデルが issue の本文から書いたものです。

説明

area-System.IO untriaged
Description

On Linux, with XDG_RUNTIME_DIR unset, creating a user-scoped named mutex can fail with lstat("", ...) == -1; errno == ENOENT although the user has a valid, existing home directory. The .NET CLI hits it on first use, in NuGet's NuGet-Migrations mutex.

The cause appears to be in SharedMemoryHelpers::InitializeUserSpecificSharedFilesPath (src/coreclr/pal/src/sharedmemory/sharedmemory.cpp). On Linux, a freshly constructed PathCharString path is only set when XDG_RUNTIME_DIR is set. It is then tested with path.IsEmpty(), but StackString::IsEmpty() returns 0 == m_buffer[0], and the default constructor initializes m_count and m_buffer without writing m_innerBuffer[0]. IsEmpty() therefore reads an uninitialized byte:

  • If the byte happens to be 0, the home-directory fallback runs and ~/.dotnet/corefx/Runtime/SharedMemory is created.
  • If it isn't, the fallback is skipped, s_userSpecificSharedFilesPath is set from a zero-length path, and the later directory check runs lstat("").

Source, at release/10.0 425ba4b8e5abe47ff545f7158f95e57e0558d0a5:

Whether the byte is 0 depends on the build. Microsoft's SDK and Ubuntu's dotnet-sdk-10.0 take the fallback. Homebrew's source-built dotnet formula on linux-arm64 failed in every run I tried, including in Homebrew's own container image. I have not inspected the byte directly; the diagnosis rests on the source above and on strace (below).

Reproduction Steps

Both commands run on an arm64 host with Docker or Podman. They use Homebrew's official image, whose user linuxbrew (UID 1000) has home /home/linuxbrew in /etc/passwd, a real directory owned by that user. XDG_RUNTIME_DIR is unset.

  1. Homebrew's dotnet (SDK 10.0.403, runtime 10.0.14), without and with XDG_RUNTIME_DIR:
docker run --rm --platform linux/arm64 ghcr.io/homebrew/ubuntu24.04:latest bash -c '
brew install --quiet dotnet >/dev/null
mkdir -p ~/t && cd ~/t && echo "System.Console.WriteLine(\"ok\");" > a.cs
echo "--- XDG_RUNTIME_DIR unset"; DOTNET_NOLOGO=1 dotnet run a.cs
echo "--- XDG_RUNTIME_DIR set"; mkdir -p /tmp/xdg && XDG_RUNTIME_DIR=/tmp/xdg DOTNET_NOLOGO=1 dotnet run a.cs'
  1. Control: Microsoft's SDK 10.0.403 (runtime 10.0.14) in the same image, XDG_RUNTIME_DIR unset:
docker run --rm --platform linux/arm64 ghcr.io/homebrew/ubuntu24.04:latest bash -c '
sudo apt-get update -qq && sudo apt-get install -y -qq --no-install-recommends libicu74 >/dev/null
mkdir -p ~/ms && curl -sSfL https://builds.dotnet.microsoft.com/dotnet/Sdk/10.0.403/dotnet-sdk-10.0.403-linux-arm64.tar.gz | tar xz -C ~/ms
mkdir -p ~/t && cd ~/t && echo "System.Console.WriteLine(\"ok\");" > a.cs
DOTNET_NOLOGO=1 ~/ms/dotnet run a.cs
ls -d ~/.dotnet/corefx/Runtime/SharedMemory'
Expected behavior

With XDG_RUNTIME_DIR unset and a valid home directory, the runtime uses ~/.dotnet/corefx/Runtime/SharedMemory, as Microsoft's SDK does in step 2, and dotnet run prints ok.

Actual behavior

Step 1, XDG_RUNTIME_DIR unset:

System.IO.IOException: The system cannot open the device or file specified. : 'NuGet-Migrations'. One or more system calls failed: lstat("", ...) == -1; errno == ENOENT;
   at System.Threading.Mutex.CreateMutexCore(Boolean initiallyOwned, String name, NamedWaitHandleOptionsInternal options, Boolean& createdNew)
   at System.Threading.Mutex..ctor(Boolean initiallyOwned, String name)
   at NuGet.Common.Migrations.MigrationRunner.Run(String migrationsDirectory, IEnvironmentVariableReader environmentVariableReader)
   at Microsoft.DotNet.Configurer.DotnetFirstTimeUseConfigurer.Configure()
   ...

Step 1, XDG_RUNTIME_DIR set: prints ok.

Step 2: gets past first-use configuration and creates ~/.dotnet/corefx/Runtime/SharedMemory. (In my environment its NuGet restore then failed because of a TLS-intercepting proxy, which is unrelated.)

strace -f -e trace=newfstatat,mkdir,openat of the failing first run shows no attempt to create ~/.dotnet/corefx/...; right after the first-use sentinel check it does:

newfstatat(AT_FDCWD, "/home/ubuntu/.dotnet/10.0.403.dotnetFirstUseSentinel", ..., AT_SYMLINK_NOFOLLOW) = -1 ENOENT
newfstatat(AT_FDCWD, "", ..., AT_SYMLINK_NOFOLLOW) = -1 ENOENT (No such file or directory)

Ubuntu's dotnet-sdk-10.0 (10.0.114, runtime 10.0.14) in the same container instead does:

newfstatat(AT_FDCWD, "/home/ubuntu/.dotnet/corefx/Runtime/SharedMemory", {st_mode=S_IFDIR|0700, ...}, AT_SYMLINK_NOFOLLOW) = 0
newfstatat(AT_FDCWD, "/home/ubuntu/.dotnet/corefx/Runtime/SharedMemory/.dotnet", ...) = -1 ENOENT
openat(AT_FDCWD, "/home/ubuntu/.dotnet/corefx/Runtime/SharedMemory/.dotnet.rVZsu7", O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 78

(These strace runs were in an Ubuntu 24.04 arm64 container whose user is ubuntu, home /home/ubuntu; Homebrew's dotnet behaved the same there as in Homebrew's image.)

Pre-creating ~/.dotnet/corefx/Runtime/SharedMemory with mode 0700 does not help, which fits the fallback branch not being entered at all.

Regression?

Yes. InitializeUserSpecificSharedFilesPath and its home-directory fallback arrived with the user-scoped named-mutex default in .NET 10.0.13 (dotnet/docs#56396). Homebrew's dotnet started failing on linux-arm64 with 10.0.402 / runtime 10.0.13.

Known Workarounds

Set XDG_RUNTIME_DIR to an existing directory owned by the user. This avoids the faulty branch, which is also why it hides the bug: it is the workaround the breaking-change notice recommends, and Homebrew adopted it in Homebrew/homebrew-core#319219 and Homebrew/homebrew-core#318259, where the failure was attributed to the documented scope change.

Configuration
  • .NET runtime 10.0.14, SDK 10.0.403, from Homebrew's dotnet formula, which source-builds the runtime from dotnet/dotnet v10.0.114 with two OpenSSL patches
  • linux-arm64, Ubuntu 24.04 container (ghcr.io/homebrew/ubuntu24.04:latest, Homebrew 7.0.11)
  • Podman 5.7 on macOS (Apple Silicon, applehv)
  • Not tested: linux-x64, and Homebrew's x86_64 bottle
Other information

Possible fixes, either of:

  • test path.GetCount() == 0 instead of path.IsEmpty() in InitializeUserSpecificSharedFilesPath;
  • initialize m_innerBuffer[0] = 0 in the StackString() default constructor, which also protects other callers of IsEmpty() on a never-set string.

main replaced this code with a managed implementation (SharedMemoryManager.Unix.cs), so this may affect .NET 10 only. I haven't checked release/8.0 or release/9.0, which received the same scope change in 8.0.32 and 9.0.21.

Related but different: #135726 (scope-change fallout and the XDG_RUNTIME_DIR guidance) and #136236 (concurrent creation race on 10.0.13).

主要言語
C#
スター
18.4k
フォーク
5.8k
平均マージ
2日 12時間
マージ済み PR(30日)
600

環境構築

Codespaces で開く

このプロジェクトの開発コンテナを、あなたの GitHub アカウントでブラウザ上に起動します。

はじめの一歩

  1. issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
  2. 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
  3. リポジトリをフォークし、ブランチを切って変更します。
  4. issue 番号を参照したプルリクエストを送ります。

dotnet/runtime のほかの issue

dotnet/runtime の issue をすべて見る

似ている issue

C# の issue をもっと見る

新しい issue をメールで受け取る

初心者向けの GitHub issue を短くまとめたダイジェスト。