public static void SetAccessControl(this System.Threading.Mutex mutex, System.Security.AccessControl.MutexSecurity mutexSecurity) { }
public static void SetAccessControl(this System.Threading.Semaphore semaphore, System.Security.AccessControl.SemaphoreSecurity semaphoreSecurity) { }
}
+ public static class EventWaitHandleAcl
+ {
+ public static System.Threading.EventWaitHandle Create(bool initialState, System.Threading.EventResetMode mode, string name, out bool createdNew, System.Security.AccessControl.EventWaitHandleSecurity eventSecurity) { throw null; }
+ }
public static class MutexAcl
{
public static System.Threading.Mutex Create(bool initiallyOwned, string name, out bool createdNew, System.Security.AccessControl.MutexSecurity mutexSecurity) { throw null; }
<Compile Include="$(CommonPath)\Interop\Windows\Interop.Errors.cs" Link="Common\Interop\Windows\Interop.Errors.cs" />
<Compile Include="$(CommonPath)\CoreLib\Interop\Windows\Interop.BOOL.cs" Link="Common\CoreLib\Interop\Windows\Interop.BOOL.cs" />
<Compile Include="$(CommonPath)\CoreLib\Interop\Windows\Interop.Libraries.cs" Link="Common\CoreLib\Interop\Windows\Interop.Libraries.cs" />
+ <Compile Include="$(CommonPath)\CoreLib\Interop\Windows\Kernel32\Interop.EventWaitHandle.cs" Link="Common\CoreLib\Interop\Windows\Kernel32\Interop.EventWaitHandle.cs" />
<Compile Include="$(CommonPath)\CoreLib\Interop\Windows\Kernel32\Interop.Constants.cs" Link="Common\CoreLib\Interop\Windows\Kernel32\Interop.Constants.cs" />
<Compile Include="$(CommonPath)\CoreLib\Interop\Windows\Kernel32\Interop.FormatMessage.cs" Link="Common\CoreLib\Interop\Windows\Kernel32\Interop.FormatMessage.cs" />
<Compile Include="$(CommonPath)\CoreLib\Interop\Windows\Kernel32\Interop.Mutex.cs" Link="Common\CoreLib\Interop\Windows\Kernel32\Interop.Mutex.cs" />
<Compile Include="System\Security\AccessControl\MutexSecurity.cs" />
<Compile Include="System\Security\AccessControl\EventWaitHandleSecurity.cs" />
<Compile Include="System\Security\AccessControl\SemaphoreSecurity.cs" />
+ <Compile Include="System\Threading\EventWaitHandleAcl.cs" />
<Compile Include="System\Threading\MutexAcl.cs" />
<Compile Include="System\Threading\SemaphoreAcl.cs" />
<Compile Include="System\Threading\ThreadingAclExtensions.cs" />
<ItemGroup Condition="'$(TargetsNetFx)' == 'true'">
<Reference Include="mscorlib" />
<Reference Include="System" />
+ <Compile Include="System\Threading\EventWaitHandleAcl.net46.cs" />
<Compile Include="System\Threading\MutexAcl.net46.cs" />
<Compile Include="System\Threading\SemaphoreAcl.net46.cs" />
<Compile Include="System\Threading\ThreadingAclExtensions.net46.cs" />
--- /dev/null
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.IO;
+using System.Runtime.InteropServices;
+using System.Security.AccessControl;
+using Microsoft.Win32.SafeHandles;
+
+namespace System.Threading
+{
+ public static class EventWaitHandleAcl
+ {
+ /// <summary>Gets or creates an <see cref="EventWaitHandle" /> instance, allowing a <see cref="EventWaitHandleSecurity " /> instance to be optionally specified to set it during the event creation.</summary>
+ /// <param name="initialState"><see langword="true" /> to set the initial state to signaled if the named event is created as a result of this call; <see langword="false" /> to set it to non-signaled.</param>
+ /// <param name="mode">One of the enum values that determines whether the event resets automatically or manually.</param>
+ /// <param name="name">The name, if the event is a system-wide synchronization event; otherwise, <see langword="null" /> or an empty string.</param>
+ /// <param name="createdNew">When this method returns, this argument is always set to <see langword="true" /> if a local event is created; that is, when <paramref name="name" /> is <see langword="null" /> or <see cref="string.Empty" />. If <paramref name="name" /> has a valid, non-empty value, this argument is set to <see langword="true" /> when the system event is created, or it is set to <see langword="false" /> if an existing system event is found with that name. This parameter is passed uninitialized.</param>
+ /// <param name="eventSecurity">The optional Windows access control security to apply.</param>
+ /// <returns>An object that represents a system event wait handle, if named, or a local event wait handle, if nameless.</returns>
+ /// <exception cref="ArgumentNullException">.NET Framework only: The <paramref name="name" /> length is beyond MAX_PATH (260 characters).</exception>
+ /// <exception cref="ArgumentOutOfRangeException">The <paramref name="mode" /> enum value was out of legal range.</exception>
+ /// <exception cref="DirectoryNotFoundException">Could not find a part of the path specified in <paramref name="name" />.</exception>
+ /// <exception cref="WaitHandleCannotBeOpenedException">A system-wide synchronization event with the provided <paramref name="name" /> was not found.
+ /// -or-
+ /// An <see cref="EventWaitHandle" /> with system-wide name <paramref name="name" /> cannot be created. An <see cref="EventWaitHandle" /> of a different type might have the same name.</exception>
+ /// <remarks>If a `name` is passed and the system event already exists, the existing event is returned. If `name` is `null` or <see cref="string.Empty" />, a new local event is always created.</remarks>
+ public static unsafe EventWaitHandle Create(bool initialState, EventResetMode mode, string name, out bool createdNew, EventWaitHandleSecurity eventSecurity)
+ {
+ if (eventSecurity == null)
+ {
+ return new EventWaitHandle(initialState, mode, name, out createdNew);
+ }
+
+ if (mode != EventResetMode.AutoReset && mode != EventResetMode.ManualReset)
+ {
+ throw new ArgumentOutOfRangeException(nameof(mode));
+ }
+
+ uint eventFlags = initialState ? Interop.Kernel32.CREATE_EVENT_INITIAL_SET : 0;
+ if (mode == EventResetMode.ManualReset)
+ {
+ eventFlags |= Interop.Kernel32.CREATE_EVENT_MANUAL_RESET;
+ }
+
+ fixed (byte* pSecurityDescriptor = eventSecurity.GetSecurityDescriptorBinaryForm())
+ {
+ var secAttrs = new Interop.Kernel32.SECURITY_ATTRIBUTES
+ {
+ nLength = (uint)sizeof(Interop.Kernel32.SECURITY_ATTRIBUTES),
+ lpSecurityDescriptor = (IntPtr)pSecurityDescriptor
+ };
+
+ SafeWaitHandle handle = Interop.Kernel32.CreateEventEx(
+ (IntPtr)(&secAttrs),
+ name,
+ eventFlags,
+ (uint)EventWaitHandleRights.FullControl);
+
+ ValidateHandle(handle, name, out createdNew);
+
+ EventWaitHandle ewh = new EventWaitHandle(initialState, mode);
+ SafeWaitHandle old = ewh.SafeWaitHandle;
+ ewh.SafeWaitHandle = handle;
+ old.Dispose();
+
+ return ewh;
+ }
+ }
+
+ private static void ValidateHandle(SafeWaitHandle handle, string name, out bool createdNew)
+ {
+ int errorCode = Marshal.GetLastWin32Error();
+
+ if (handle.IsInvalid)
+ {
+ handle.SetHandleAsInvalid();
+
+ if (!string.IsNullOrEmpty(name) && errorCode == Interop.Errors.ERROR_INVALID_HANDLE)
+ throw new WaitHandleCannotBeOpenedException(SR.Format(SR.WaitHandleCannotBeOpenedException_InvalidHandle, name));
+
+ throw Win32Marshal.GetExceptionForWin32Error(errorCode, name);
+ }
+
+ createdNew = (errorCode != Interop.Errors.ERROR_ALREADY_EXISTS);
+ }
+ }
+}
--- /dev/null
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Security.AccessControl;
+
+namespace System.Threading
+{
+ public static class EventWaitHandleAcl
+ {
+ public static EventWaitHandle Create(
+ bool initialState,
+ EventResetMode mode,
+ string name,
+ out bool createdNew,
+ EventWaitHandleSecurity eventSecurity)
+ {
+ return new EventWaitHandle(initialState, mode, name, out createdNew, eventSecurity);
+ }
+ }
+}
--- /dev/null
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Security.AccessControl;
+using System.Security.Principal;
+using Xunit;
+
+namespace System.Threading.Tests
+{
+ public class EventWaitHandleAclTests : AclTests
+ {
+ [Fact]
+ public void EventWaitHandle_Create_NullSecurity()
+ {
+ CreateAndVerifyEventWaitHandle(
+ initialState: true,
+ mode: EventResetMode.AutoReset,
+ name: GetRandomName(),
+ expectedSecurity: null,
+ expectedCreatedNew: true).Dispose();
+ }
+
+ [Theory]
+ [InlineData(null)]
+ [InlineData("")]
+ public void EventWaitHandle_Create_NameMultipleNew(string name)
+ {
+ EventWaitHandleSecurity security = GetBasicEventWaitHandleSecurity();
+
+ using EventWaitHandle handle1 = CreateAndVerifyEventWaitHandle(
+ initialState: true,
+ mode: EventResetMode.AutoReset,
+ name,
+ security,
+ expectedCreatedNew: true);
+
+ using EventWaitHandle handle2 = CreateAndVerifyEventWaitHandle(
+ initialState: true,
+ mode: EventResetMode.AutoReset,
+ name,
+ security,
+ expectedCreatedNew: true);
+ }
+
+ [Fact]
+ public void EventWaitHandle_Create_CreateNewExisting()
+ {
+ string name = GetRandomName();
+ EventWaitHandleSecurity security = GetBasicEventWaitHandleSecurity();
+
+ using EventWaitHandle handle1 = CreateAndVerifyEventWaitHandle(
+ initialState: true,
+ mode: EventResetMode.AutoReset,
+ name,
+ security,
+ expectedCreatedNew: true);
+
+ using EventWaitHandle handle2 = CreateAndVerifyEventWaitHandle(
+ initialState: true,
+ mode: EventResetMode.AutoReset,
+ name,
+ security,
+ expectedCreatedNew: false);
+ }
+
+ [Fact]
+ public void EventWaitHandle_Create_GlobalPrefixNameNotFound()
+ {
+ string prefixedName = @"GLOBAL\" + GetRandomName();
+
+ Assert.Throws<DirectoryNotFoundException>(() =>
+ {
+ CreateEventWaitHandle(
+ initialState: true,
+ mode: EventResetMode.AutoReset,
+ prefixedName,
+ expectedSecurity: GetBasicEventWaitHandleSecurity(),
+ expectedCreatedNew: true).Dispose();
+ });
+ }
+
+ // The documentation says MAX_PATH is the length limit for name, but it won't throw any errors:
+ // https://docs.microsoft.com/en-us/windows/win32/api/synchapi/nf-synchapi-createeventexw
+ // The .NET Core constructors for EventWaitHandle do not throw on name longer than MAX_LENGTH, so the extension method should match the behavior:
+ // https://source.dot.net/#System.Private.CoreLib/shared/System/Threading/EventWaitHandle.Windows.cs,20
+ // The .NET Framework constructor throws:
+ // https://referencesource.microsoft.com/#mscorlib/system/threading/eventwaithandle.cs,59
+ [Fact]
+ public void EventWaitHandle_Create_BeyondMaxPathLength()
+ {
+ string name = new string('x', Interop.Kernel32.MAX_PATH + 100);
+ EventWaitHandleSecurity security = GetBasicEventWaitHandleSecurity();
+ EventResetMode mode = EventResetMode.AutoReset;
+
+ if (PlatformDetection.IsFullFramework)
+ {
+ Assert.Throws<ArgumentException>(() =>
+ {
+ CreateEventWaitHandle(
+ initialState: true,
+ mode,
+ name,
+ security,
+ expectedCreatedNew: true).Dispose();
+ });
+ }
+ else
+ {
+ using EventWaitHandle created = CreateAndVerifyEventWaitHandle(
+ initialState: true,
+ mode,
+ name,
+ security,
+ expectedCreatedNew: true);
+
+ using EventWaitHandle openedByName = EventWaitHandle.OpenExisting(name);
+ Assert.NotNull(openedByName);
+ }
+ }
+
+ [Theory]
+ [InlineData((EventResetMode)int.MinValue)]
+ [InlineData((EventResetMode)(-1))]
+ [InlineData((EventResetMode)2)]
+ [InlineData((EventResetMode)int.MaxValue)]
+ public void EventWaitHandle_Create_InvalidMode(EventResetMode mode)
+ {
+ if (PlatformDetection.IsFullFramework)
+ {
+ Assert.Throws<ArgumentException>(() =>
+ {
+ CreateEventWaitHandle(
+ initialState: true,
+ mode,
+ GetRandomName(),
+ GetBasicEventWaitHandleSecurity(),
+ expectedCreatedNew: true).Dispose();
+ });
+ }
+ else
+ {
+ Assert.Throws<ArgumentOutOfRangeException>("mode", () =>
+ {
+ CreateEventWaitHandle(
+ initialState: true,
+ mode,
+ GetRandomName(),
+ GetBasicEventWaitHandleSecurity(),
+ expectedCreatedNew: true).Dispose();
+ });
+ }
+ }
+
+ public static IEnumerable<object[]> EventWaitHandle_Create_SpecificParameters_MemberData() =>
+ from initialState in new[] { true, false }
+ from mode in new[] { EventResetMode.AutoReset, EventResetMode.ManualReset }
+ from rights in new[] { EventWaitHandleRights.FullControl, EventWaitHandleRights.Synchronize, EventWaitHandleRights.Modify, EventWaitHandleRights.Modify | EventWaitHandleRights.Synchronize }
+ from accessControl in new[] { AccessControlType.Allow, AccessControlType.Deny }
+ select new object[] { initialState, mode, rights, accessControl };
+
+ [Theory]
+ [MemberData(nameof(EventWaitHandle_Create_SpecificParameters_MemberData))]
+ public void EventWaitHandle_Create_SpecificParameters(bool initialState, EventResetMode mode, EventWaitHandleRights rights, AccessControlType accessControl)
+ {
+ EventWaitHandleSecurity security = GetEventWaitHandleSecurity(WellKnownSidType.BuiltinUsersSid, rights, accessControl);
+ CreateAndVerifyEventWaitHandle(
+ initialState,
+ mode,
+ GetRandomName(),
+ security,
+ expectedCreatedNew: true).Dispose();
+
+ }
+
+ private EventWaitHandleSecurity GetBasicEventWaitHandleSecurity()
+ {
+ return GetEventWaitHandleSecurity(
+ WellKnownSidType.BuiltinUsersSid,
+ EventWaitHandleRights.FullControl,
+ AccessControlType.Allow);
+ }
+
+ private EventWaitHandleSecurity GetEventWaitHandleSecurity(WellKnownSidType sid, EventWaitHandleRights rights, AccessControlType accessControl)
+ {
+ EventWaitHandleSecurity security = new EventWaitHandleSecurity();
+ SecurityIdentifier identity = new SecurityIdentifier(sid, null);
+ EventWaitHandleAccessRule accessRule = new EventWaitHandleAccessRule(identity, rights, accessControl);
+ security.AddAccessRule(accessRule);
+ return security;
+ }
+ private EventWaitHandle CreateEventWaitHandle(bool initialState, EventResetMode mode, string name, EventWaitHandleSecurity expectedSecurity, bool expectedCreatedNew)
+ {
+ EventWaitHandle handle = EventWaitHandleAcl.Create(initialState, mode, name, out bool createdNew, expectedSecurity);
+ Assert.NotNull(handle);
+ Assert.Equal(expectedCreatedNew, createdNew);
+ return handle;
+ }
+
+ private EventWaitHandle CreateAndVerifyEventWaitHandle(bool initialState, EventResetMode mode, string name, EventWaitHandleSecurity expectedSecurity, bool expectedCreatedNew)
+ {
+ EventWaitHandle eventHandle = CreateEventWaitHandle(initialState, mode, name, expectedSecurity, expectedCreatedNew);
+
+ if (expectedSecurity != null)
+ {
+ EventWaitHandleSecurity actualSecurity = eventHandle.GetAccessControl();
+ VerifyEventWaitHandleSecurity(expectedSecurity, actualSecurity);
+ }
+
+ return eventHandle;
+ }
+
+ private void VerifyEventWaitHandleSecurity(EventWaitHandleSecurity expectedSecurity, EventWaitHandleSecurity actualSecurity)
+ {
+ Assert.Equal(typeof(EventWaitHandleRights), expectedSecurity.AccessRightType);
+ Assert.Equal(typeof(EventWaitHandleRights), actualSecurity.AccessRightType);
+
+ List<EventWaitHandleAccessRule> expectedAccessRules = expectedSecurity.GetAccessRules(includeExplicit: true, includeInherited: false, typeof(SecurityIdentifier))
+ .Cast<EventWaitHandleAccessRule>().ToList();
+
+ List<EventWaitHandleAccessRule> actualAccessRules = actualSecurity.GetAccessRules(includeExplicit: true, includeInherited: false, typeof(SecurityIdentifier))
+ .Cast<EventWaitHandleAccessRule>().ToList();
+
+ Assert.Equal(expectedAccessRules.Count, actualAccessRules.Count);
+ if (expectedAccessRules.Count > 0)
+ {
+ Assert.All(expectedAccessRules, actualAccessRule =>
+ {
+ int count = expectedAccessRules.Count(expectedAccessRule => AreAccessRulesEqual(expectedAccessRule, actualAccessRule));
+ Assert.True(count > 0);
+ });
+ }
+ }
+
+ private bool AreAccessRulesEqual(EventWaitHandleAccessRule expectedRule, EventWaitHandleAccessRule actualRule)
+ {
+ return
+ expectedRule.AccessControlType == actualRule.AccessControlType &&
+ expectedRule.EventWaitHandleRights == actualRule.EventWaitHandleRights &&
+ expectedRule.InheritanceFlags == actualRule.InheritanceFlags &&
+ expectedRule.PropagationFlags == actualRule.PropagationFlags;
+ }
+ }
+}
[InlineData("")]
public void Mutex_Create_NameMultipleNew(string name)
{
- var security = GetBasicMutexSecurity();
+ MutexSecurity security = GetBasicMutexSecurity();
using Mutex mutex1 = CreateAndVerifyMutex(initiallyOwned: true, name, security, expectedCreatedNew: true);
using Mutex mutex2 = CreateAndVerifyMutex(initiallyOwned: true, name, security, expectedCreatedNew: true);
public void Mutex_Create_CreateNewExisting()
{
string name = GetRandomName();
- var security = GetBasicMutexSecurity();
+ MutexSecurity security = GetBasicMutexSecurity();
using Mutex mutexNew = CreateAndVerifyMutex(initiallyOwned: true, name, security, expectedCreatedNew: true);
using Mutex mutexExisting = CreateAndVerifyMutex(initiallyOwned: true, name, security, expectedCreatedNew: false);
[InlineData(false, MutexRights.Modify, AccessControlType.Deny)]
public void Mutex_Create_SpecificParameters(bool initiallyOwned, MutexRights rights, AccessControlType accessControl)
{
- var security = GetMutexSecurity(WellKnownSidType.BuiltinUsersSid, rights, accessControl);
+ MutexSecurity security = GetMutexSecurity(WellKnownSidType.BuiltinUsersSid, rights, accessControl);
CreateAndVerifyMutex(initiallyOwned, GetRandomName(), security, expectedCreatedNew: true).Dispose();
}
private MutexSecurity GetMutexSecurity(WellKnownSidType sid, MutexRights rights, AccessControlType accessControl)
{
- var security = new MutexSecurity();
+ MutexSecurity security = new MutexSecurity();
SecurityIdentifier identity = new SecurityIdentifier(sid, null);
- var accessRule = new MutexAccessRule(identity, rights, accessControl);
+ MutexAccessRule accessRule = new MutexAccessRule(identity, rights, accessControl);
security.AddAccessRule(accessRule);
return security;
}
[InlineData("")]
public void Semaphore_Create_NameMultipleNew(string name)
{
- var security = GetBasicSemaphoreSecurity();
+ SemaphoreSecurity security = GetBasicSemaphoreSecurity();
bool expectedCreatedNew = true;
using Semaphore semaphore1 = CreateAndVerifySemaphore(
public void Semaphore_Create_CreateNewExisting()
{
string name = GetRandomName();
- var security = GetBasicSemaphoreSecurity();
+ SemaphoreSecurity security = GetBasicSemaphoreSecurity();
using Semaphore SemaphoreNew = CreateAndVerifySemaphore(
DefaultInitialCount,
[InlineData(SemaphoreRights.Modify | SemaphoreRights.Synchronize, AccessControlType.Deny)]
public void Semaphore_Create_SpecificSecurity(SemaphoreRights rights, AccessControlType accessControl)
{
- var security = GetSemaphoreSecurity(WellKnownSidType.BuiltinUsersSid, rights, accessControl);
+ SemaphoreSecurity security = GetSemaphoreSecurity(WellKnownSidType.BuiltinUsersSid, rights, accessControl);
CreateAndVerifySemaphore(
DefaultInitialCount,
private SemaphoreSecurity GetSemaphoreSecurity(WellKnownSidType sid, SemaphoreRights rights, AccessControlType accessControl)
{
- var security = new SemaphoreSecurity();
+ SemaphoreSecurity security = new SemaphoreSecurity();
SecurityIdentifier identity = new SecurityIdentifier(sid, null);
- var accessRule = new SemaphoreAccessRule(identity, rights, accessControl);
+ SemaphoreAccessRule accessRule = new SemaphoreAccessRule(identity, rights, accessControl);
security.AddAccessRule(accessRule);
return security;
}
<Compile Include="$(CommonPath)\CoreLib\Interop\Windows\Kernel32\Interop.Constants.cs" Link="Common\CoreLib\Interop\Windows\Kernel32\Interop.Constants.cs" />
<Compile Include="$(CommonPath)\CoreLib\Interop\Windows\Kernel32\Interop.MAX_PATH.cs" Link="Common\CoreLib\Interop\Windows\Kernel32\Interop.MAX_PATH.cs" />
<Compile Include="AclTests.cs" />
+ <Compile Include="EventWaitHandleAclTests.cs" />
<Compile Include="MutexAclTests.cs" />
<Compile Include="MutexSecurityTests.cs" />
<Compile Include="SemaphoreAclTests.cs" />