Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add the CurrentUserOnly and CurrentSessionOnly options for named Mutex, Semaphore, and EventWaitHandle #112213

Open
wants to merge 7 commits into
base: main
Choose a base branch
from

Conversation

kouvel
Copy link
Member

@kouvel kouvel commented Feb 5, 2025

  • In Mutex, Semaphore, and EventWaitHandle, added overloads for methods with a name argument to also include a NamedWaitHandleOptions options argument. The CurrentUserOnly option indicates whether the object should be limited in access to the current user. The CurrentSessionOnly option indicates whether the object object is intended to be used only within the current session (an alternative to the name prefixes, such as Global\). The default value for NamedWaitHandleOptions has CurrentUserOnly=true and CurrentSessionOnly=true.
  • On Unixes, named semaphores and events are not supported. Named mutexes are have a functional implementation on Unixes in CoreCLR. NativeAOT and Mono on Unixes use an incomplete/temporary implementation where named mutexes are just process-local mutexes stored in a dictionary keyed by the name and can't be used to synchronize between processes. This change only briefly extends the NativeAOT/Mono implementation to add another prefix to the name when CurrentUserOnly=true. Sharing the CoreCLR Unix named mutex implementation with NativeAOT/Mono is left to a separate change. Most of what is described below when CurrentUserOnly=true on Unixes refers to the CoreCLR implementation.
  • The plan is to also deprecate the APIs with a name argument but without an options argument in a separate change
  • API review: [API Proposal]: New overloads for named wait handles that enable creating/opening user-specific synchronization primitives #102682 (comment)

Windows with CurrentUserOnly=true

When creating a named mutex, a security descriptor is created and assigned to the object.

  • The owner and group are set to the current user
  • The DACL has two ACEs:
    • One ACE allows the current user the relevant access rights
    • Another ACE allows the BUILTIN\Administrators group the READ_CONTROL access right to enable reading the security info for diagnostic purposes
  • The SACL has a mandatory label ACE
    • The access policy prevents principals with an integrity level lower than the object from opening the object
    • The integrity level assigned to the object is the same as what would be assigned by the system by default
  • When opening a named mutex, the owner and DACL are verified to see if they are as expected, and if not, a WaitHandleCannotBeOpenedException is thrown
  • Access controls are set when creating an object and checked when opening an object. Once a handle to the object is obtained, typical synchronization operations may not do further access checks. It's up to the caller to be careful about how the handle to the object is passed around.

Unixes with CurrentUserOnly=true

  • Files relevant to named mutexes (shared memory and lock files) go under /tmp/.dotnet-uidN/ where N is the effective user ID
    • /tmp/ (or equivalent) was chosen mainly for the automatic cleanup, as the number of files can add up in cases where mutexes are not disposed, particularly when randomly generated names are used upon each invocation of a process. Due to the use of a global location /tmp/ or equivalent, it would be possible for a user to deny access to the .dotnet-uidN directory of a different user if the directory hadn't been created yet. An alternative considered was to use the home directory and introduce some kind of periodic cleanup strategy, but there are also other tradeoffs.
  • /tmp/ or equivalent must either have the sticky bit, or it must be owned by the current user and without write access for any other user. Otherwise, an exception is thrown.
  • Permissions of the /tmp/.dotnet-uidN/ directory and files/directories under it are limited in access to the current user
  • When opening a named mutex, the owner and permissions of relevant files/directories are verified to see if they are as expected, and if not, an exception is thrown
  • Access controls are set when creating an object and checked when opening an object. Once a handle to the object is obtained, typical synchronization operations may not do further access checks. It's up to the caller to be careful about how the handle to the object is passed around.

Namespaces

  • On Windows, there is no namespace for kernel objects for each user. CurrentSessionOnly=true is close, but it is possible for multiple users to be running code simultaneously in the same session. There is a global namespace, and a namespace per session. When CurrentUserOnly=true, callers may need to ensure that the name used is distinguished for different users.
  • On Unixes, a different directory tree is used when CurrentUserOnly=true, so each user has a separate namespace for objects, including for session-scoped and session-unscoped objects.

Copy link

Note regarding the new-api-needs-documentation label:

This serves as a reminder for when your PR is modifying a ref *.cs file and adding/modifying public APIs, please make sure the API implementation in the src *.cs file is documented with triple slash comments, so the PR reviewers can sign off that change.

1 similar comment
Copy link

Note regarding the new-api-needs-documentation label:

This serves as a reminder for when your PR is modifying a ref *.cs file and adding/modifying public APIs, please make sure the API implementation in the src *.cs file is documented with triple slash comments, so the PR reviewers can sign off that change.

Copy link
Contributor

Tagging subscribers to this area: @mangod9
See info in area-owners.md if you want to be subscribed.

#define SHARED_MEMORY_UNIQUE_TEMP_NAME_TEMPLATE ".coreclr.XXXXXX"

#define SHARED_MEMORY_MAX_SESSION_ID_CHAR_COUNT (10)
#define SHARED_MEMORY_UNIQUE_TEMP_NAME_TEMPLATE ".dotnet.XXXXXX"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This means that .NET 10+ apps will no longer be able to sync with apps running using older .NET version. It seems we will need to document this.

Copy link
Member Author

@kouvel kouvel Feb 7, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is only used as the pattern for mkdtemp() when creating the .dotnet, .dotnet-uidN, and shm directories, where a directory with a unique name is created first and renamed to one of those. It shouldn't affect compatibility, the user-unscoped shared memory files and lock files are still stored under /tmp/.dotnet/.

/// </summary>
public struct NamedWaitHandleOptions
{
private bool _notCurrentUserOnly;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder what is the benefit of storing these as negative (_notXXX).

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We wanted a zero-initialized struct as in default(NamedWaitHandleOptions) to have CurrentUserOnly == true and CurrentSessionOnly == true, so the fields store the NOT of the property values.


if (myHandle.IsInvalid)
{
myHandle.Dispose();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the reason for disposing the handle when it is invalid?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would suppress the finalizer for the safe handle. It's on a path where the safe handle won't be used anymore, so it should ideally be disposed.

@danmoseley danmoseley requested a review from Copilot February 7, 2025 02:01

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot reviewed 23 out of 38 changed files in this pull request and generated no comments.

Files not reviewed (15)
  • src/coreclr/pal/inc/pal.h: Language not supported
  • src/coreclr/pal/src/include/pal/mutex.hpp: Language not supported
  • src/coreclr/pal/src/include/pal/sharedmemory.h: Language not supported
  • src/coreclr/pal/src/include/pal/sharedmemory.inl: Language not supported
  • src/coreclr/pal/src/init/pal.cpp: Language not supported
  • src/coreclr/pal/src/synchobj/mutex.cpp: Language not supported
  • src/coreclr/pal/tests/palsuite/threading/NamedMutex/test1/namedmutex.cpp: Language not supported
  • src/coreclr/pal/tests/palsuite/threading/NamedMutex/test1/nopal.cpp: Language not supported
  • src/libraries/System.Private.CoreLib/src/Resources/Strings.resx: Language not supported
  • src/libraries/Common/src/Interop/Windows/Advapi32/Interop.OpenThreadToken_SafeAccessTokenHandle.cs: Evaluated as low risk
  • src/libraries/Common/src/Interop/Windows/Interop.Errors.cs: Evaluated as low risk
  • src/libraries/Common/src/Interop/Windows/Kernel32/Interop.Constants.cs: Evaluated as low risk
  • src/libraries/Common/src/System/Threading/OpenExistingResult.cs: Evaluated as low risk
  • src/libraries/Common/src/Interop/Windows/Advapi32/Interop.GetSecurityInfoByHandle.cs: Evaluated as low risk
  • src/libraries/Common/src/Interop/Windows/Advapi32/Interop.OpenThreadToken.cs: Evaluated as low risk
@jkotas
Copy link
Member

jkotas commented Feb 7, 2025

Do we have people lined up to use these new APIs so that we can verify that they work well in practice?

@kouvel
Copy link
Member Author

kouvel commented Feb 7, 2025

Do we have people lined up to use these new APIs so that we can verify that they work well in practice?

PowerShell was a use case for user-scoped mutexes. I'll reach out to them to see if they'd be able to test some previews.

The plan was also to deprecate the old APIs with a name argument but no options argument, to push toward the better defaults and explicit choices. It would be nice to do that in .NET 10 as well, but there may be some benefits to wait a release before deprecating.

@kouvel
Copy link
Member Author

kouvel commented Feb 19, 2025

Rebased to update with main

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants