Linux: named mutex creation fails with lstat("") when XDG_RUNTIME_DIR is unset (StackString::IsEmpty reads an uninitialized buffer)
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
- Área
- operating-systems
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
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/SharedMemoryis created. - If it isn't, the fallback is skipped,
s_userSpecificSharedFilesPathis set from a zero-lengthpath, and the later directory check runslstat("").
Source, at release/10.0 425ba4b8e5abe47ff545f7158f95e57e0558d0a5:
sharedmemory.cppL234:PathCharString path;sharedmemory.cppL251-L258:pathis set only fromXDG_RUNTIME_DIRon Linux.sharedmemory.cppL261:if (path.IsEmpty())sharedmemory.cppL297:userSpecificSharedFilesPath->Set(path)copiesm_count(0) characters.stackstring.hppL110-L113:StackString() : m_buffer(m_innerBuffer), m_size(STACKCOUNT+1), m_count(0) { }stackstring.hppL229-L232:BOOL IsEmpty() { return 0 == m_buffer[0]; }
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.
- Homebrew's
dotnet(SDK 10.0.403, runtime 10.0.14), without and withXDG_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'
- Control: Microsoft's SDK 10.0.403 (runtime 10.0.14) in the same image,
XDG_RUNTIME_DIRunset:
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
dotnetformula, which source-builds the runtime from dotnet/dotnetv10.0.114with 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() == 0instead ofpath.IsEmpty()inInitializeUserSpecificSharedFilesPath; - initialize
m_innerBuffer[0] = 0in theStackString()default constructor, which also protects other callers ofIsEmpty()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
Inicia el contenedor de desarrollo del proyecto en tu navegador, con tu propia cuenta de GitHub.
- Sin Dockerfile ni archivo de Docker Compose
- Tiene una plantilla de pull request
- Leer la guía de contribución
Primeros pasos
- Lee el issue completo y luego la guía de contribución del proyecto.
- Comenta en el issue que vas a ocuparte — evita que dos personas hagan lo mismo.
- Haz un fork del repositorio y trabaja en una rama.
- Abre un pull request que haga referencia al número del issue.
Más de dotnet/runtime
-
[wasi] .NET 10: WasmEnableSIMD with AOT fails because WasiApp.targets has -msimd128 commented outAbiertoarea-Build-mono untriaged
Dificultad 2/5 1-3 horas Aptitud para principiantes 78/100
Los mantenedores suelen responder en 1 día
-
area-System.ComponentModel untriaged
Dificultad 2/5 1-3 horas Aptitud para principiantes 65/100
dotnet/runtime#136608 · 2 comentarios ·
Los mantenedores suelen responder en 1 día
-
MemoryMappedFile (Windows): failed MEM_COMMIT is ignored, accessor over reserved memory crashes the processPosiblemente ocupada @RutulPatel8 la tomó hoy. Abiertoarea-System.IO bug untriaged
Dificultad 2/5 1-3 horas Aptitud para principiantes 78/100
dotnet/runtime#136599 · 1 comentario ·
Los mantenedores suelen responder en 1 día
-
area-System.IO untriaged
Dificultad 2/5 1-3 horas Aptitud para principiantes 76/100
dotnet/runtime#136577 · 1 comentario ·
Los mantenedores suelen responder en 1 día
-
[mono] Native memory leak on the first invoke of every open-instance delegate to a virtual methodAbiertoarea-VM-meta-mono untriaged
Dificultad 2/5 1-3 horas Aptitud para principiantes 72/100
dotnet/runtime#136284 · 6 reacciones ·
Los mantenedores suelen responder en 1 día
Todos los issues de dotnet/runtime
Issues similares
-
Dificultad 2/5 1-3 horas Aptitud para principiantes 70/100
libnativeapi/nativeapi#21 ·
-
Dificultad 2/5 1-3 horas Aptitud para principiantes 76/100
Los mantenedores suelen responder en 3 días
-
enhancement P3 performance
Dificultad 2/5 1-3 horas Aptitud para principiantes 75/100
thomhurst/TUnit#7380 · 2 comentarios ·
Los mantenedores suelen responder en 1 día
-
Dificultad 2/5 1-3 horas Aptitud para principiantes 82/100
-
Dificultad 1/5 Menos de una hora Aptitud para principiantes 68/100
Los mantenedores suelen responder en 1 día