Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 0 additions & 12 deletions src/libraries/System.Private.CoreLib/src/Resources/Strings.resx
Original file line number Diff line number Diff line change
Expand Up @@ -4459,18 +4459,6 @@
<data name="IO_SharedMemory_PathExistsButNotDirectory" xml:space="preserve">
<value>The path '{0}' exists but is not a directory.</value>
</data>
<data name="IO_SharedMemory_DirectoryPermissionsIncorrect" xml:space="preserve">
<value>The directory '{0}' does not have the expected owner or permissions for system scope: {1}, {2}.</value>
</data>
<data name="IO_SharedMemory_DirectoryNotOwnedByUid" xml:space="preserve">
<value>The directory '{0}' is not owned by the current user with UID {1}.</value>
</data>
<data name="IO_SharedMemory_DirectoryPermissionsIncorrectUserScope" xml:space="preserve">
<value>The directory '{0}' does not have the expected permissions for user scope: {1}.</value>
</data>
<data name="IO_SharedMemory_DirectoryOwnerPermissionsIncorrect" xml:space="preserve">
<value>The directory '{0}' does not have the expected owner permissions: {1}.</value>
</data>
<data name="Argument_MustBeFunctionPointer" xml:space="preserve">
<value>Argument must represent a function pointer type.</value>
</data>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -372,10 +372,8 @@ internal static class SharedMemoryHelpers
{
private const UnixFileMode PermissionsMask_OwnerUser_ReadWrite = UnixFileMode.UserRead | UnixFileMode.UserWrite;
private const UnixFileMode PermissionsMask_OwnerUser_ReadWriteExecute = PermissionsMask_OwnerUser_ReadWrite | UnixFileMode.UserExecute;
private const UnixFileMode PermissionsMask_NonOwnerUsers_Write = UnixFileMode.GroupWrite | UnixFileMode.OtherWrite;
private const UnixFileMode PermissionsMask_AllUsers_ReadWrite = UnixFileMode.UserRead | UnixFileMode.UserWrite | UnixFileMode.GroupRead | UnixFileMode.GroupWrite | UnixFileMode.OtherRead | UnixFileMode.OtherWrite;
private const UnixFileMode PermissionsMask_AllUsers_ReadWriteExecute = PermissionsMask_AllUsers_ReadWrite | UnixFileMode.UserExecute | UnixFileMode.GroupExecute | UnixFileMode.OtherExecute;
private const UnixFileMode PermissionsMask_Sticky = UnixFileMode.StickyBit;

private const int TransientRetryCount = 10;

Expand Down Expand Up @@ -499,45 +497,7 @@ public static SafeFileHandle OpenSharedFilesSystemDirectory(SharedMemoryId id)

if (!directoryHandle.IsInvalid)
{
if (Interop.Sys.FStat(directoryHandle, out Interop.Sys.FileStatus fileStatus) != 0)
{
Interop.ErrorInfo fstatError = Interop.Sys.GetLastErrorInfo();
directoryHandle.Dispose();
if ((fstatError.Error == Interop.Error.ENOENT || fstatError.Error == Interop.Error.EACCES) &&
RetryOnTransientPermissionFailure(ref transientRetryCount))
{
continue;
}

throw Interop.GetExceptionForIoErrno(fstatError, directoryPath);
}

// For system directories (such as TEMP_DIRECTORY_PATH), require sufficient permissions only for the
// owner user. For instance, "docker run --mount ..." to mount /tmp to some directory on the host mounts the
// destination directory with the same permissions as the source directory, which may not include some permissions for
// other users. In the docker container, other user permissions are typically not relevant and relaxing the permissions
// requirement allows for that scenario to work without having to work around it by first giving sufficient permissions
// for all users.
//
// If the directory is being used for user-scoped shared memory data, also ensure that either it has the sticky bit or
// it's owned by the current user and without write access for other users.
if ((fileStatus.Mode & (int)PermissionsMask_OwnerUser_ReadWriteExecute) == (int)PermissionsMask_OwnerUser_ReadWriteExecute
&& (
!id.IsUserScope ||
(fileStatus.Mode & (int)PermissionsMask_Sticky) == (int)PermissionsMask_Sticky ||
(fileStatus.Uid == id.Uid && (fileStatus.Mode & (int)PermissionsMask_NonOwnerUsers_Write) == 0)
))
{
return directoryHandle;
}

directoryHandle.Dispose();
if (RetryOnTransientPermissionFailure(ref transientRetryCount))
{
continue;
}

throw new IOException(SR.Format(SR.IO_SharedMemory_DirectoryPermissionsIncorrect, directoryPath, fileStatus.Uid, Convert.ToString(fileStatus.Mode, 8)));
return directoryHandle;
}

Interop.ErrorInfo error = Interop.Sys.GetLastErrorInfo();
Expand Down Expand Up @@ -781,72 +741,10 @@ internal static SafeFileHandle EnsureDirectoryExists(
throw Interop.GetExceptionForIoErrno(openError, directoryPathForError);
}

if (ValidateDirectory(directoryHandle, directoryPathForError, id, ref transientRetryCount))
{
return directoryHandle;
}

directoryHandle.Dispose();
return directoryHandle;
}
}

private static bool ValidateDirectory(
SafeFileHandle directoryHandle,
string directoryPathForError,
SharedMemoryId id,
ref int transientRetryCount)
{
if (Interop.Sys.FStat(directoryHandle, out Interop.Sys.FileStatus fileStatus) != 0)
{
Interop.ErrorInfo error = Interop.Sys.GetLastErrorInfo();
if ((error.Error == Interop.Error.ENOENT || error.Error == Interop.Error.EACCES) &&
RetryOnTransientPermissionFailure(ref transientRetryCount))
{
return false;
}

throw Interop.GetExceptionForIoErrno(error, directoryPathForError);
}

UnixFileMode permissionsMask = id.IsUserScope
? PermissionsMask_OwnerUser_ReadWriteExecute
: PermissionsMask_AllUsers_ReadWriteExecute;

// For non-system directories (such as SharedFilesPath/UserUnscopedRuntimeTempDirectoryName),
// require the sufficient permissions, so that shared memory files may be shared according to its scope.

// For user-scoped directories, verify the owner UID.
if (id.IsUserScope && fileStatus.Uid != id.Uid)
{
if (RetryOnTransientPermissionFailure(ref transientRetryCount))
{
return false;
}

throw new IOException(SR.Format(SR.IO_SharedMemory_DirectoryNotOwnedByUid, directoryPathForError, id.Uid));
}

// Verify the permissions correct.
if ((fileStatus.Mode & (int)permissionsMask) == (int)permissionsMask)
{
return true;
}

if (RetryOnTransientPermissionFailure(ref transientRetryCount))
{
return false;
}

// We were not able to verify the necessary permissions.
// Throw an exception with a message that is specific to the scope of the shared memory data.
if (id.IsUserScope)
{
throw new IOException(SR.Format(SR.IO_SharedMemory_DirectoryPermissionsIncorrectUserScope, directoryPathForError, Convert.ToString(fileStatus.Mode, 8)));
}

throw new IOException(SR.Format(SR.IO_SharedMemory_DirectoryOwnerPermissionsIncorrect, directoryPathForError, Convert.ToString(fileStatus.Mode, 8)));
}

private static bool RetryOnTransientPermissionFailure(ref int transientRetryCount)
{
if (transientRetryCount >= TransientRetryCount)
Expand Down
56 changes: 52 additions & 4 deletions src/libraries/System.Threading/tests/MutexTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -394,6 +394,7 @@ public void NameOptionsApiCompatibilityTest(string namePrefix)
string name = Guid.NewGuid().ToString("N");
string prefixedName = namePrefix + name;
bool currentSessionOnly = namePrefix != @"Global\";
bool currentUserOnly = !PlatformDetection.IsWindows;

using (var m = new Mutex(initiallyOwned: false, prefixedName, out bool createdNew))
{
Expand All @@ -402,18 +403,18 @@ public void NameOptionsApiCompatibilityTest(string namePrefix)
new Mutex(
initiallyOwned: false,
name,
new() { CurrentUserOnly = false, CurrentSessionOnly = currentSessionOnly },
new() { CurrentUserOnly = currentUserOnly, CurrentSessionOnly = currentSessionOnly },
out createdNew).Dispose();
Assert.False(createdNew);

Mutex.OpenExisting(
name,
new() { CurrentUserOnly = false, CurrentSessionOnly = currentSessionOnly }).Dispose();
new() { CurrentUserOnly = currentUserOnly, CurrentSessionOnly = currentSessionOnly }).Dispose();

Assert.True(
Mutex.TryOpenExisting(
name,
new() { CurrentUserOnly = false, CurrentSessionOnly = currentSessionOnly },
new() { CurrentUserOnly = currentUserOnly, CurrentSessionOnly = currentSessionOnly },
out Mutex m2));
m2.Dispose();
}
Expand All @@ -422,7 +423,7 @@ public void NameOptionsApiCompatibilityTest(string namePrefix)
new Mutex(
initiallyOwned: false,
name,
new() { CurrentUserOnly = false, CurrentSessionOnly = currentSessionOnly },
new() { CurrentUserOnly = currentUserOnly, CurrentSessionOnly = currentSessionOnly },
out bool createdNew))
{
Assert.True(createdNew);
Expand Down Expand Up @@ -1100,6 +1101,53 @@ public void NamedMutex_OtherEvent_NotCompatible()
| UnixFileMode.UserWrite
| UnixFileMode.UserExecute;

[ConditionalTheory(nameof(IsRemoteExecutorAndCrossProcessNamedMutexSupported))]
[InlineData(OwnerUserRwx)]
[InlineData(AllUsersRwx)]
[PlatformSpecific(TestPlatforms.Linux)]
[UnsupportedOSPlatform("windows")]
public void NamedMutex_ExistingSharedMemoryDirectories_PermissionsAreUnchanged(UnixFileMode directoryMode)
{
string userRuntimeDirectory = GetTestFilePath();
Directory.CreateDirectory(userRuntimeDirectory);

RemoteInvokeOptions remoteInvokeOptions = new();
remoteInvokeOptions.StartInfo.EnvironmentVariables["XDG_RUNTIME_DIR"] = userRuntimeDirectory;

RemoteExecutor.Invoke(
(userRuntimeDirectory, mode) =>
{
UnixFileMode directoryMode = Enum.Parse<UnixFileMode>(mode);
string[] directories =
{
userRuntimeDirectory,
Path.Combine(userRuntimeDirectory, ".dotnet"),
Path.Combine(userRuntimeDirectory, ".dotnet", "shm"),
Path.Combine(userRuntimeDirectory, ".dotnet", "shm", "global")
};
foreach (string directory in directories)
{
Directory.CreateDirectory(directory);
File.SetUnixFileMode(directory, directoryMode);
}

string name = $"Global\\{Guid.NewGuid():N}";
using Mutex mutex = new(false, name, out bool createdNew);
Assert.True(createdNew);
using Mutex openedMutex = Mutex.OpenExisting(name);
Assert.True(Mutex.TryOpenExisting(name, out Mutex existingMutex));
existingMutex.Dispose();

foreach (string directory in directories)
{
Assert.Equal(directoryMode, File.GetUnixFileMode(directory));
}
},
userRuntimeDirectory,
directoryMode.ToString(),
remoteInvokeOptions).Dispose();
}

[ConditionalTheory(nameof(IsRemoteExecutorAndCrossProcessNamedMutexSupported))]
[InlineData(null)]
[InlineData(false)]
Expand Down
Loading