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

Abierto Apto para principiantes
#136,802 1 comentario 0 reacciones 0 asignados Ver en GitHub

Los mantenedores suelen responder en 1 día

Nadie ha tomado este issue todavía.

Evaluación

Dificultad
2/5
Tiempo estimado
Medio día
Aptitud para principiantes
72/100
Tipo de issue
Error
Claridad
Bien especificado
Estado de actividad
Activo
Stack tecnológico
cpp, csharp

Línea de trabajo

Empieza en InitializeUserSpecificSharedFilesPath en src/coreclr/pal/src/sharedmemory/sharedmemory.cpp, donde el PathCharString path solo se rellena desde XDG_RUNTIME_DIR en Linux y luego se comprueba con IsEmpty(). Después lee StackString() e IsEmpty() en src/coreclr/pal/src/include/pal/stackstring.hpp para entender por qué un búfer que nunca se asignó puede parecer no vacío. Está terminado cuando se ejecuta el respaldo al directorio home, o cuando la ruta vacía nunca se pasa a lstat con XDG_RUNTIME_DIR sin definir.

Escrito por el modelo de indexación a partir del texto del issue.

Descripción

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).

Lenguaje dominante
C#
Estrellas
18.4k
Forks
5.8k
Merge medio
2 d 12 h
PR fusionados (30 d)
600

Preparar el entorno

Abrir en Codespaces

Inicia el contenedor de desarrollo del proyecto en tu navegador, con tu propia cuenta de GitHub.

Primeros pasos

  1. Lee el issue completo y luego la guía de contribución del proyecto.
  2. Comenta en el issue que vas a ocuparte — evita que dos personas hagan lo mismo.
  3. Haz un fork del repositorio y trabaja en una rama.
  4. Abre un pull request que haga referencia al número del issue.

Más de dotnet/runtime

Todos los issues de dotnet/runtime

Issues similares

Más issues de C#

Recibe los nuevos issues en tu correo

Un resumen breve de issues de GitHub para principiantes.