提交 66decde8 编写于 作者: C Carlos Sanchez Lopez 提交者: GitHub

Add Mutex creation extension methods that take an ACL (dotnet/corefx#42281)

Approved API Proposal: dotnet/corefx#41662

Description
We don't currently have a way to create a Mutex with a given ACL in .NET Core. We can modify the ACL, but it would be more secure to have the proper ACL on the object from the start.

Customer impact
Before this change, customers had to create a Mutex, then set its ACLs. This presents a few problems:

Potential security hole as mutexes can be accessed between creation and modification.
Porting difficulties as there isn't a 1-1 API replacement
This change addresses those problems by adding a new extension method that allows creating a Mutex and ensuring the provided ACLs are set during creation.

Commit migrated from https://github.com/dotnet/corefx/commit/46edc58620f29557e23fea29ed8392a0f2c9e31c
上级 01fcd653
Microsoft Visual Studio Solution File, Format Version 12.00 Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15 # Visual Studio Version 16
VisualStudioVersion = 15.0.27213.1 VisualStudioVersion = 16.0.29411.138
MinimumVisualStudioVersion = 10.0.40219.1 MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "System.Threading.AccessControl.Tests", "tests\System.Threading.AccessControl.Tests.csproj", "{458E445C-DF3C-4E4D-8E1D-F2FAC365BB40}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "System.Threading.AccessControl.Tests", "tests\System.Threading.AccessControl.Tests.csproj", "{458E445C-DF3C-4E4D-8E1D-F2FAC365BB40}"
ProjectSection(ProjectDependencies) = postProject ProjectSection(ProjectDependencies) = postProject
......
...@@ -147,4 +147,9 @@ public static partial class ThreadingAclExtensions ...@@ -147,4 +147,9 @@ public static partial class ThreadingAclExtensions
public static void SetAccessControl(this System.Threading.Mutex mutex, System.Security.AccessControl.MutexSecurity mutexSecurity) { } 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 void SetAccessControl(this System.Threading.Semaphore semaphore, System.Security.AccessControl.SemaphoreSecurity semaphoreSecurity) { }
} }
public static class MutexAcl
{
public static System.Threading.Mutex Create(bool initiallyOwned, string name, out bool createdNew, System.Security.AccessControl.MutexSecurity mutexSecurity) { throw null; }
}
} }
<root> <?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata"> <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" /> <xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true"> <xsd:element name="root" msdata:IsDataSet="true">
...@@ -63,4 +123,52 @@ ...@@ -63,4 +123,52 @@
<data name="PlatformNotSupported_AccessControl" xml:space="preserve"> <data name="PlatformNotSupported_AccessControl" xml:space="preserve">
<value>Access Control List (ACL) APIs are part of resource management on Windows and are not supported on this platform.</value> <value>Access Control List (ACL) APIs are part of resource management on Windows and are not supported on this platform.</value>
</data> </data>
<data name="Argument_WaitHandleNameTooLong" xml:space="preserve">
<value>The length of the name exceeds the maximum limit.</value>
</data>
<data name="ArgumentOutOfRange_Enum" xml:space="preserve">
<value>Enum value was out of legal range.</value>
</data>
<data name="IO_AlreadyExists_Name" xml:space="preserve">
<value>Cannot create '{0}' because a file or directory with the same name already exists.</value>
</data>
<data name="IO_FileExists_Name" xml:space="preserve">
<value>The file '{0}' already exists.</value>
</data>
<data name="IO_FileNotFound" xml:space="preserve">
<value>Unable to find the specified file.</value>
</data>
<data name="IO_FileNotFound_FileName" xml:space="preserve">
<value>Could not find file '{0}'.</value>
</data>
<data name="IO_PathNotFound_NoPathName" xml:space="preserve">
<value>Could not find a part of the path.</value>
</data>
<data name="IO_PathNotFound_Path" xml:space="preserve">
<value>Could not find a part of the path '{0}'.</value>
</data>
<data name="IO_PathTooLong" xml:space="preserve">
<value>The specified file name or path is too long, or a component of the specified path is too long.</value>
</data>
<data name="IO_PathTooLong_Path" xml:space="preserve">
<value>The path '{0}' is too long, or a component of the specified path is too long.</value>
</data>
<data name="IO_SharingViolation_File" xml:space="preserve">
<value>The process cannot access the file '{0}' because it is being used by another process.</value>
</data>
<data name="IO_SharingViolation_NoFileName" xml:space="preserve">
<value>The process cannot access the file because it is being used by another process.</value>
</data>
<data name="UnauthorizedAccess_IODenied_NoPathName" xml:space="preserve">
<value>Access to the path is denied.</value>
</data>
<data name="UnauthorizedAccess_IODenied_Path" xml:space="preserve">
<value>Access to the path '{0}' is denied.</value>
</data>
<data name="Argument_CannotBeNullOrEmpty" xml:space="preserve">
<value>Argument cannot be null or empty.</value>
</data>
<data name="Threading_WaitHandleCannotBeOpenedException_InvalidHandle" xml:space="preserve">
<value>A WaitHandle with system-wide name '{0}' cannot be created. A WaitHandle of a different type might have the same name.</value>
</data>
</root> </root>
\ No newline at end of file
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<OSGroup Condition="'$(OSGroup)' == ''">Windows_NT</OSGroup> <OSGroup Condition="'$(OSGroup)' == ''">Windows_NT</OSGroup>
<GeneratePlatformNotSupportedAssemblyMessage Condition="'$(TargetsNetStandard)' == 'true' and '$(TargetsWindows)' != 'true'">SR.PlatformNotSupported_AccessControl</GeneratePlatformNotSupportedAssemblyMessage> <GeneratePlatformNotSupportedAssemblyMessage Condition="'$(TargetsNetStandard)' == 'true' and '$(TargetsWindows)' != 'true'">SR.PlatformNotSupported_AccessControl</GeneratePlatformNotSupportedAssemblyMessage>
<IsPartialFacadeAssembly Condition="'$(TargetsNetFx)' == 'true'">true</IsPartialFacadeAssembly> <IsPartialFacadeAssembly Condition="'$(TargetsNetFx)' == 'true'">true</IsPartialFacadeAssembly>
<Configurations>net461-Windows_NT-Debug;net461-Windows_NT-Release;netfx-Windows_NT-Debug;netfx-Windows_NT-Release;netstandard2.0-Debug;netstandard2.0-Release;netstandard2.0-Windows_NT-Debug;netstandard2.0-Windows_NT-Release</Configurations> <Configurations>net461-Windows_NT-Debug;net461-Windows_NT-Release;netfx-Windows_NT-Debug;netfx-Windows_NT-Release;netstandard2.0-Debug;netstandard2.0-Release;netstandard2.0-Windows_NT-Debug;netstandard2.0-Windows_NT-Release</Configurations>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup> </PropertyGroup>
<ItemGroup Condition="'$(TargetsWindows)' == 'true'">
<Compile Include="$(CommonPath)\CoreLib\Interop\Windows\Kernel32\Interop.MAX_PATH.cs" Link="Common\CoreLib\Interop\Windows\Kernel32\Interop.MAX_PATH.cs" />
</ItemGroup>
<ItemGroup Condition="'$(TargetsNetStandard)' == 'true' and '$(TargetsWindows)' == 'true'"> <ItemGroup Condition="'$(TargetsNetStandard)' == 'true' and '$(TargetsWindows)' == 'true'">
<Compile Include="$(CommonPath)\Interop\Windows\Interop.Errors.cs"> <Compile Include="$(CommonPath)\Interop\Windows\Interop.Errors.cs" Link="Common\Interop\Windows\Interop.Errors.cs" />
<Link>Common\Interop\Windows\Interop.Errors.cs</Link> <Compile Include="$(CommonPath)\CoreLib\Interop\Windows\Interop.BOOL.cs" Link="Common\CoreLib\Interop\Windows\Interop.BOOL.cs" />
</Compile> <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.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="$(CommonPath)\CoreLib\Interop\Windows\Kernel32\Interop.SECURITY_ATTRIBUTES.cs" Link="Common\CoreLib\Interop\Windows\Kernel32\Interop.SECURITY_ATTRIBUTES.cs" />
<Compile Include="$(CommonPath)\CoreLib\System\IO\Win32Marshal.cs" Link="Common\CoreLib\System\IO\Win32Marshal.cs" />
<Compile Include="System\Security\AccessControl\MutexSecurity.cs" /> <Compile Include="System\Security\AccessControl\MutexSecurity.cs" />
<Compile Include="System\Security\AccessControl\EventWaitHandleSecurity.cs" /> <Compile Include="System\Security\AccessControl\EventWaitHandleSecurity.cs" />
<Compile Include="System\Security\AccessControl\SemaphoreSecurity.cs" /> <Compile Include="System\Security\AccessControl\SemaphoreSecurity.cs" />
<Compile Include="System\Threading\MutexAcl.cs" />
<Compile Include="System\Threading\ThreadingAclExtensions.cs" /> <Compile Include="System\Threading\ThreadingAclExtensions.cs" />
</ItemGroup> </ItemGroup>
<ItemGroup Condition="'$(TargetsNetFx)' == 'true'"> <ItemGroup Condition="'$(TargetsNetFx)' == 'true'">
<Reference Include="mscorlib" /> <Reference Include="mscorlib" />
<Reference Include="System" /> <Reference Include="System" />
<Compile Include="System\Threading\MutexAcl.net46.cs" />
<Compile Include="System\Threading\ThreadingAclExtensions.net46.cs" /> <Compile Include="System\Threading\ThreadingAclExtensions.net46.cs" />
</ItemGroup> </ItemGroup>
<ItemGroup Condition="'$(TargetsNetFx)' != 'true'"> <ItemGroup Condition="'$(TargetsNetFx)' != 'true'">
<Reference Include="System.Memory" />
<Reference Include="System.Security.AccessControl" /> <Reference Include="System.Security.AccessControl" />
<Reference Include="System.Security.Principal.Windows" /> <Reference Include="System.Security.Principal.Windows" />
</ItemGroup> </ItemGroup>
......
// 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 MutexAcl
{
/// <summary>Gets or creates <see cref="Mutex" /> instance, allowing a <see cref="MutexSecurity" /> to be optionally specified to set it during the mutex creation.</summary>
/// <param name="initiallyOwned"><see langword="true" /> to give the calling thread initial ownership of the named system mutex if the named system mutex is created as a result of this call; otherwise, <see langword="false" />.</param>
/// <param name="name">The optional name of the system mutex. If this argument is set to <see langword="null" /> or <see cref="string.Empty" />, a local mutex is created.</param>
/// <param name="createdNew">When this method returns, this argument is always set to <see langword="true" /> if a local mutex 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 mutex is created, or it is set to <see langword="false" /> if an existing system mutex is found with that name. This parameter is passed uninitialized.</param>
/// <param name="mutexSecurity">The optional mutex access control security to apply.</param>
/// <returns>An object that represents a system mutex, if named, or a local mutex, if nameless.</returns>
/// <exception cref="ArgumentException">.NET Framework only: The length of the name exceeds the maximum limit.</exception>
/// <exception cref="WaitHandleCannotBeOpenedException">A mutex handle with system-wide <paramref name="name" /> cannot be created. A mutex handle of a different type might have the same name.</exception>
public static unsafe Mutex Create(bool initiallyOwned, string name, out bool createdNew, MutexSecurity mutexSecurity)
{
if (mutexSecurity == null)
{
return new Mutex(initiallyOwned, name, out createdNew);
}
uint mutexFlags = initiallyOwned ? Interop.Kernel32.CREATE_MUTEX_INITIAL_OWNER : 0;
fixed (byte* pSecurityDescriptor = mutexSecurity.GetSecurityDescriptorBinaryForm())
{
var secAttrs = new Interop.Kernel32.SECURITY_ATTRIBUTES
{
nLength = (uint)sizeof(Interop.Kernel32.SECURITY_ATTRIBUTES),
lpSecurityDescriptor = (IntPtr)pSecurityDescriptor
};
SafeWaitHandle handle = Interop.Kernel32.CreateMutexEx(
(IntPtr)(&secAttrs),
name,
mutexFlags,
(uint)MutexRights.FullControl // Equivalent to MUTEX_ALL_ACCESS
);
ValidateMutexHandle(handle, name, out createdNew);
Mutex mutex = new Mutex(initiallyOwned);
SafeWaitHandle old = mutex.SafeWaitHandle;
mutex.SafeWaitHandle = handle;
old.Dispose();
return mutex;
}
}
private static void ValidateMutexHandle(SafeWaitHandle mutexHandle, string name, out bool createdNew)
{
int errorCode = Marshal.GetLastWin32Error();
if (mutexHandle.IsInvalid)
{
mutexHandle.SetHandleAsInvalid();
if (errorCode == Interop.Errors.ERROR_FILENAME_EXCED_RANGE)
{
// On Unix, length validation is done by CoreCLR's PAL after converting to utf-8
throw new ArgumentException(SR.Argument_WaitHandleNameTooLong, nameof(name));
}
if (errorCode == Interop.Errors.ERROR_INVALID_HANDLE)
{
throw new WaitHandleCannotBeOpenedException(SR.Format(SR.Threading_WaitHandleCannotBeOpenedException_InvalidHandle, name));
}
throw Win32Marshal.GetExceptionForWin32Error(errorCode, name);
}
createdNew = (errorCode != Interop.Errors.ERROR_ALREADY_EXISTS);
}
}
}
// 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 MutexAcl
{
public static Mutex Create(bool initiallyOwned, string name, out bool createdNew, MutexSecurity mutexSecurity)
{
return new Mutex(initiallyOwned, name, out createdNew, mutexSecurity);
}
}
}
// 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.
namespace System.Threading.Tests
{
public class AclTests
{
protected string GetRandomName()
{
return Guid.NewGuid().ToString("N");
}
}
}
// 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.Linq;
using System.Security.AccessControl;
using System.Security.Principal;
using Xunit;
namespace System.Threading.Tests
{
public class MutexAclTests : AclTests
{
[Fact]
public void Mutex_Create_NullSecurity()
{
CreateAndVerifyMutex(initiallyOwned: true, GetRandomName(), expectedSecurity: null, expectedCreatedNew: true).Dispose();
}
[Theory]
[InlineData(null)]
[InlineData("")]
public void Mutex_Create_NameMultipleNew(string name)
{
var security = GetBasicMutexSecurity();
using Mutex mutex1 = CreateAndVerifyMutex(initiallyOwned: true, name, security, expectedCreatedNew: true);
using Mutex mutex2 = CreateAndVerifyMutex(initiallyOwned: true, name, security, expectedCreatedNew: true);
}
[Fact]
public void Mutex_Create_CreateNewExisting()
{
string name = GetRandomName();
var security = GetBasicMutexSecurity();
using Mutex mutexNew = CreateAndVerifyMutex(initiallyOwned: true, name, security, expectedCreatedNew: true);
using Mutex mutexExisting = CreateAndVerifyMutex(initiallyOwned: true, name, security, expectedCreatedNew: false);
}
[Fact]
public void Mutex_Create_BeyondMaxPathLength()
{
string name = new string('x', Interop.Kernel32.MAX_PATH + 100);
if (PlatformDetection.IsFullFramework)
{
Assert.Throws<ArgumentException>(() =>
{
CreateAndVerifyMutex(initiallyOwned: true, name, GetBasicMutexSecurity(), expectedCreatedNew: true).Dispose();
});
}
else
{
using Mutex created = CreateAndVerifyMutex(initiallyOwned: true, name, GetBasicMutexSecurity(), expectedCreatedNew: true);
using Mutex openedByName = Mutex.OpenExisting(name);
Assert.NotNull(openedByName);
}
}
[Theory]
[InlineData(true, MutexRights.FullControl, AccessControlType.Allow)]
[InlineData(true, MutexRights.FullControl, AccessControlType.Deny)]
[InlineData(true, MutexRights.Synchronize, AccessControlType.Allow)]
[InlineData(true, MutexRights.Synchronize, AccessControlType.Deny)]
[InlineData(true, MutexRights.Modify, AccessControlType.Allow)]
[InlineData(true, MutexRights.Modify, AccessControlType.Deny)]
[InlineData(true, MutexRights.Modify | MutexRights.Synchronize, AccessControlType.Allow)]
[InlineData(true, MutexRights.Modify | MutexRights.Synchronize, AccessControlType.Deny)]
[InlineData(false, MutexRights.FullControl, AccessControlType.Allow)]
[InlineData(false, MutexRights.FullControl, AccessControlType.Deny)]
[InlineData(false, MutexRights.Synchronize, AccessControlType.Allow)]
[InlineData(false, MutexRights.Synchronize, AccessControlType.Deny)]
[InlineData(false, MutexRights.Modify, AccessControlType.Allow)]
[InlineData(false, MutexRights.Modify, AccessControlType.Deny)]
public void Mutex_Create_SpecificParameters(bool initiallyOwned, MutexRights rights, AccessControlType accessControl)
{
var security = GetMutexSecurity(WellKnownSidType.BuiltinUsersSid, rights, accessControl);
CreateAndVerifyMutex(initiallyOwned, GetRandomName(), security, expectedCreatedNew: true).Dispose();
}
private MutexSecurity GetBasicMutexSecurity()
{
return GetMutexSecurity(
WellKnownSidType.BuiltinUsersSid,
MutexRights.FullControl,
AccessControlType.Allow);
}
private MutexSecurity GetMutexSecurity(WellKnownSidType sid, MutexRights rights, AccessControlType accessControl)
{
var security = new MutexSecurity();
SecurityIdentifier identity = new SecurityIdentifier(sid, null);
var accessRule = new MutexAccessRule(identity, rights, accessControl);
security.AddAccessRule(accessRule);
return security;
}
private Mutex CreateAndVerifyMutex(bool initiallyOwned, string name, MutexSecurity expectedSecurity, bool expectedCreatedNew)
{
Mutex mutex = MutexAcl.Create(initiallyOwned, name, out bool createdNew, expectedSecurity);
Assert.NotNull(mutex);
Assert.Equal(createdNew, expectedCreatedNew);
if (expectedSecurity != null)
{
MutexSecurity actualSecurity = mutex.GetAccessControl();
VerifyMutexSecurity(expectedSecurity, actualSecurity);
}
return mutex;
}
private void VerifyMutexSecurity(MutexSecurity expectedSecurity, MutexSecurity actualSecurity)
{
Assert.Equal(typeof(MutexRights), expectedSecurity.AccessRightType);
Assert.Equal(typeof(MutexRights), actualSecurity.AccessRightType);
List<MutexAccessRule> expectedAccessRules = expectedSecurity.GetAccessRules(includeExplicit: true, includeInherited: false, typeof(SecurityIdentifier))
.Cast<MutexAccessRule>().ToList();
List<MutexAccessRule> actualAccessRules = actualSecurity.GetAccessRules(includeExplicit: true, includeInherited: false, typeof(SecurityIdentifier))
.Cast<MutexAccessRule>().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(MutexAccessRule expectedRule, MutexAccessRule actualRule)
{
return
expectedRule.AccessControlType == actualRule.AccessControlType &&
expectedRule.MutexRights == actualRule.MutexRights &&
expectedRule.InheritanceFlags == actualRule.InheritanceFlags &&
expectedRule.PropagationFlags == actualRule.PropagationFlags;
}
}
}
...@@ -3,6 +3,10 @@ ...@@ -3,6 +3,10 @@
<Configurations>netcoreapp-Windows_NT-Debug;netcoreapp-Windows_NT-Release;netfx-Windows_NT-Debug;netfx-Windows_NT-Release</Configurations> <Configurations>netcoreapp-Windows_NT-Debug;netcoreapp-Windows_NT-Release;netfx-Windows_NT-Debug;netfx-Windows_NT-Release</Configurations>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<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="MutexAclTests.cs" />
<Compile Include="MutexSecurityTests.cs" /> <Compile Include="MutexSecurityTests.cs" />
<Compile Include="ThreadingAclExtensionsTests.cs" /> <Compile Include="ThreadingAclExtensionsTests.cs" />
</ItemGroup> </ItemGroup>
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册