Repository navigation
Description
Description
Since .NET SDK 9.0.321 (released 2026-11-10, VSTest 17.14 17.1400.226.52835), dotnet test exits with code 1 on linux-arm64 even when every test passes. SDK 9.0.320 is not affected, and linux-x64 is not affected.
Passed! - Failed: 0, Passed: 1006, Skipped: 3, Total: 1009, Duration: 1 s - MyTests.dll (net9.0)
The test session artifacts were not saved: Could not open '/tmp/<guid>/1_<guid>', errno: 22. Set VSTEST_DISABLE_ARTIFACTS_DIRECTORY_VERIFICATION=1 to skip this check.
The test session directory '/tmp/<guid>/1_<guid>' can be accessed by users other than its owner, it should allow the owner only. Set VSTEST_DISABLE_ARTIFACTS_DIRECTORY_VERIFICATION=1 to skip this check.
We hit it in CI on every multi-arch Docker build (mcr.microsoft.com/dotnet/sdk:9.0-alpine, arm64 agent, dotnet test --collect:"XPlat Code Coverage").
Root cause
ArtifactProcessingManager.VerifyDirectoryHasUserOnlyAccess opens the session directory with flags from GetOpenNoFollowDirectoryFlags(). Decompiled from Microsoft.TestPlatform.CrossPlatEngine.dll in SDK 9.0.321:
private static int GetOpenNoFollowDirectoryFlags()
{
if (!RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
return 196608; // 0x30000
return 1048832;
}0x30000 is O_DIRECTORY | O_NOFOLLOW only on the asm-generic layout used by x86/x64 and others. On arm/arm64 (and powerpc) Linux uses different values:
| Architecture | O_DIRECTORY |
O_NOFOLLOW |
Meaning of 0x30000 |
|---|---|---|---|
| x86_64 | 0x10000 |
0x20000 |
O_DIRECTORY | O_NOFOLLOW (intended) |
| aarch64 | 0x4000 |
0x8000 |
O_DIRECT | O_LARGEFILE |
Opening a directory with O_DIRECT returns EINVAL (22), so the check always fails on arm64. Because fchmod is never reached, the directory keeps mode 0755, which then triggers the second message ("can be accessed by users other than its owner").
Minimal repro (no .NET needed)
import os, errno
d = "/tmp/probe"; os.makedirs(d, exist_ok=True)
for name, fl in [("vstest 0x30000", 0x30000), ("O_DIRECTORY|O_NOFOLLOW", os.O_DIRECTORY | os.O_NOFOLLOW)]:
try: os.close(os.open(d, fl)); print(name, "OK")
except OSError as e: print(name, "errno", e.errno, errno.errorcode[e.errno])$ docker run --rm --platform linux/amd64 -v $PWD/p.py:/p.py python:3-alpine python /p.py
vstest 0x30000 OK
O_DIRECTORY|O_NOFOLLOW OK
$ docker run --rm --platform linux/arm64 -v $PWD/p.py:/p.py python:3-alpine python /p.py
vstest 0x30000 errno 22 EINVAL
O_DIRECTORY|O_NOFOLLOW OK
Suggested fix
Use architecture-specific Linux values:
private static int GetOpenNoFollowDirectoryFlags()
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
{
return 0x100000 | 0x100; // O_DIRECTORY | O_NOFOLLOW
}
// Linux: arm, arm64 and powerpc use different values than the asm-generic layout (x86, x64, riscv64, loongarch64, s390x).
return RuntimeInformation.ProcessArchitecture switch
{
Architecture.Arm or Architecture.Arm64 or Architecture.Ppc64le => 0x4000 | 0x8000,
_ => 0x10000 | 0x20000,
};
}A regression test that runs VerifyDirectoryHasUserOnlyAccess on a freshly created directory in the arm64 Linux CI leg would have caught this.
Workarounds
- Set
VSTEST_DISABLE_ARTIFACTS_DIRECTORY_VERIFICATION=1. - Pin the SDK to
9.0.320.
Environment
- .NET SDK 9.0.321 (
mcr.microsoft.com/dotnet/sdk:9.0-alpine, digestsha256:7bfcc502abdb2483b9b8d0ada3ac2b77fb888aa1824ffe0bb2ad400bc6a9b339) - VSTest 17.14.2 (
17.1400.226.52835) - linux-arm64 (Alpine 3.24, Docker/BuildKit)
Activity
github-actions commentedon Nov 11, 2026
🤖 Automated triage: thanks for the detailed report. The analysis is consistent with the flags described:
0x30000isO_DIRECTORY | O_NOFOLLOWonly on the asm-generic layout, and meansO_DIRECT | O_LARGEFILEon arm/arm64.GetOpenNoFollowDirectoryFlagsisn't inmainyet. The artifact-directory hardening appears to be in the open PRs #16656 and #16653, so a fix PR can't be built onmainnow. The architecture-specific flags suggested here should be applied in those PRs, or in a follow-up once one lands. A regression test on the arm64 Linux leg would also help.Skip VSTest artifacts directory check on ARM test runs