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

Rewrite convertfrom sddlstring in csharp #3936

Conversation

zacateras
Copy link

@@ -25,6 +25,7 @@
<PackageReference Include="System.Security.AccessControl" Version="4.4.0-preview1-25302-01" />
<PackageReference Include="System.Security.Cryptography.Pkcs" Version="4.4.0-preview1-25302-01" />
<PackageReference Include="System.Text.Encoding.CodePages" Version="4.4.0-preview1-25302-01" />
<PackageReference Include="System.Threading.AccessControl" Version="4.4.0-preview1-25302-01" />
Copy link
Author

Choose a reason for hiding this comment

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

this is required by types moved from System.Security.AccessControl to System.Threading.AccessControl (System.Security.AccessControl.MutexRights), am I allowed to add new references here ?

Copy link
Member

Choose a reason for hiding this comment

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

It looks to me the package System.Threading.AccessControl is required by Microsoft.PowerShell.Commands.Utility instead of System.Management.Automation. If that's the case, this PackageReference entry should be added to Microosft.PowerShell.Commands.Utility instead. And please remove the same entry from Microsoft.PowerShell.SDK.csproj.

@daxian-dbw
Copy link
Member

@iSazonov if you have time, could you please review this PR?

Copy link
Collaborator

@iSazonov iSazonov left a comment

Choose a reason for hiding this comment

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

Leave a comment

[ValidateSet(
"FileSystemRights", "RegistryRights", "ActiveDirectoryRights",
"MutexRights", "SemaphoreRights", "CryptoKeyRights",
"EventWaitHandleRights")]
Copy link
Collaborator

Choose a reason for hiding this comment

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

Please create C# constants for this strings because they're using in other places.

Copy link
Author

Choose a reason for hiding this comment

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

used custom enum instead

protected override void BeginProcessing()
{
#if CORECLR
if (Type == "CryptoKeyRights" || Type == "ActiveDirectoryRights")
Copy link
Collaborator

Choose a reason for hiding this comment

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

Copy link
Author

Choose a reason for hiding this comment

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

thanks, done

{
return sid.Translate(typeof(NTAccount)).ToString();
}
catch {}
Copy link
Collaborator

Choose a reason for hiding this comment

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

I believe we should write an error for user.

{ "FileSystemRights", typeof(System.Security.AccessControl.FileSystemRights) },
{ "RegistryRights", typeof(System.Security.AccessControl.RegistryRights) },
#if !CORECLR
{ "ActiveDirectoryRights", typeof(System.DirectoryServices.ActiveDirectoryRights) },
Copy link
Collaborator

Choose a reason for hiding this comment

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

See my comment above about "ActiveDirectoryRights".

Copy link
Author

Choose a reason for hiding this comment

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

done

/// </summary>
private List<string> GetAccessRights(long accessMask, string type)
{
IDictionary<string, Type> rightTypes = new Dictionary<string, Type>
Copy link
Collaborator

Choose a reason for hiding this comment

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

We should use OrderedDictionary.
rightTypes is good candidate for static cache.

Copy link
Author

Choose a reason for hiding this comment

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

added static cache field

{
foreach (var accessFlag in Enum.GetNames(rightType))
{
long longKeyValue = (long)Enum.Parse(rightType, accessFlag);
Copy link
Collaborator

Choose a reason for hiding this comment

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

Oh, we re-evaluate Enum.GetNames and Enum.Parse again and again. We should refactor "GetAccessRights" to use cache(s).

Copy link
Member

Choose a reason for hiding this comment

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

We could write a script to generate C# and avoid reflection entirely.

Copy link
Author

Choose a reason for hiding this comment

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

@lzybkr what kind of code generation script do you mean? t4 template?

Copy link
Member

Choose a reason for hiding this comment

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

No, I just meant a PowerShell script that generates switch statements or something like that.

Copy link
Author

Choose a reason for hiding this comment

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

added ps1 generator

Copy link
Collaborator

@iSazonov iSazonov Jun 16, 2017

Choose a reason for hiding this comment

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

@lzybkr Will this require recompilation if there will are changes in CoreFX? We could initilize this in static cache on Begin step.
It seems so rare that the use of generation seems to be an unnecessary complication.


if (ace is QualifiedAce qualifiedAce)
{
aceString += qualifiedAce.AceQualifier;
Copy link
Collaborator

Choose a reason for hiding this comment

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

We should use StringBuilder because aceString is changed multiple times.

Copy link
Author

Choose a reason for hiding this comment

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

done

return aceStrings;
}
}
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

Please add Newline.

Copy link
Author

Choose a reason for hiding this comment

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

ok

/// <summary>
/// '{0}' is not supported in this system.
/// </summary>
public static string TypeNotSupported { get { return UtilityCommonStrings.TypeNotSupported; } }
Copy link
Collaborator

Choose a reason for hiding this comment

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

It seems we can directly use UtilityCommonStrings.TypeNotSupported

Copy link
Author

Choose a reason for hiding this comment

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

removed obsolete property


// Go through the entries in the different right types, and see if they apply to the
// provided access mask. If they do, then add that to the result.
foreach (var rightType in typesToExamine)
Copy link
Collaborator

Choose a reason for hiding this comment

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

@SteveL-MSFT @daxian-dbw We should make a conclusion about the algorithm. SDDL is always "object-specific" but we go through all "types". I believe that's a mistake. An user always know the object type (where he did get the SDDL), should set it explicit and the cmdlet must decode only against this type.

Copy link
Member

Choose a reason for hiding this comment

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

I don't understand this domain enough to be confident here - but in principle I fully agree with @iSazonov - the current algorithm is strange.

Copy link
Collaborator

Choose a reason for hiding this comment

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

@lzybkr Could you please ping an expert in this domain?

Copy link
Member

Choose a reason for hiding this comment

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

The documentation for SDDL looks pretty good, I think if we have concerns about this algorithm, we can answer them ourselves, but I don't have time for that now.

Copy link
Collaborator

Choose a reason for hiding this comment

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

I re-read the document and made any tests - My conclusion is full discard the algorithm:

  1. From the docs:

Each type of securable object defines its own set of specific access rights and its own mapping of generic access rights. For information about the specific and generic access rights for each type of securable object, see the overview for that type of object.

  • the conclusion is an SDDL has no meaning without object entity - the cmdlet should get the object type as argument.
  1. The cmdlet calls [Security.AccessControl.CommonSecurityDescriptor]::new($false,$false,$Sddl)
    The constructor in Core FX

I tested the cmdlet with (1) [Security.AccessControl.CommonSecurityDescriptor]::new($false,$false,$Sddl) and (2) [Security.AccessControl.CommonSecurityDescriptor]::new($true,$false,$Sddl) for path "c:\test" (it is container) and got different results.

  • the conclusion is the cmdlet should take into account isContainer (folders/containers) and isDS (directory objects and containers).
  1. CoreFX can throw exceptions (ArgumentException) - maybe the cmdlet should catch them and re-throw (put CoreFX exception in InnerException)

  2. The cmdlet should have strongly typed output. In current PSCustomObject we should add "Type".

  3. "Type" - we can define it as Enum ( remove unsupported member by #if !Unix ) and exclude ValidateSet at all.

  4. We should add tests for all object types.

Copy link
Author

Choose a reason for hiding this comment

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

@iSazonov I also have doubts about current method of converting ACE AccessMask into access flag enum based strings. There are situations when flag cannot be converted without additional logic i.e. FileSystemRights contains entries with duplicate flag values - some applies only for files, other only for containers. I guess that this won't be solved by requiring Type parameter.

Copy link
Collaborator

Choose a reason for hiding this comment

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

@zacateras Please see (2) point from my conclusion.

•the conclusion is the cmdlet should take into account isContainer (folders/containers) and isDS (directory objects and containers).

Copy link
Author

Choose a reason for hiding this comment

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

@iSazonov Apart from fixing CommonSecurityDescriptor constructor call (by requiring isContainer and isDS flags) we should also take care about correct parsing of AccessMasks to enum names. FileSystemRights and ActiveDirectoryRights contain several names that use the same value, eg. FileSystemRights contain AppendData & CreateDirectories which have the same value (4). In consequence Directories with access flag (4) can get AppendData right, what is incorrect.

As a solution we can add custom enums (or at least custom mapping dictionaries) containing distinct subsets of allowed values - one for containers (ContainerSystemRights) and one for other objects (FileSystemRights). Alternatively we can discard CommonSecurityDescriptor/Right-Enum based parsing algorithm entirely and try to implement text parser using sddl docs. What do you think ?

@iSazonov
Copy link
Collaborator

iSazonov commented Jun 6, 2017

@zacateras Thanks for your work! I have question about the algorithm that I addressed to PowerShell team - please wait the conclusion before continue.

Also we haven't tests for the cmdlet - you should add its and pass CI before we can merge.

Copy link
Member

@lzybkr lzybkr left a comment

Choose a reason for hiding this comment

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

Thanks for the contribution - I'll be very happy to see this one merged.

@@ -1,174 +0,0 @@

Copy link
Member

Choose a reason for hiding this comment

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

I think you can git rm this file now.

Copy link
Author

@zacateras zacateras Jun 8, 2017

Choose a reason for hiding this comment

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

this file name is referenced 3 times:

  • /src/Modules/map.json (remove entry?)
  • /src/System.Management.Automation/engine/InitialSessionState.cs (replace with some other build result file?)
  • /src/System.Management.Automation/security/Authenticode.cs (replace with some other build result file?)

Copy link
Member

Choose a reason for hiding this comment

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

Thanks for catching that.
Remove the entry in map.json - that's perfectly safe.
I think you can change the other two references to Microsoft.PowerShell.Utility.psd1.

Copy link
Author

Choose a reason for hiding this comment

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

done

protected override void BeginProcessing()
{
#if CORECLR
if (Type == "CryptoKeyRights" || Type == "ActiveDirectoryRights")
Copy link
Member

Choose a reason for hiding this comment

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

ValidateSet is case insensitive, so we would need a case insensitive comparison here too.

That said, maybe it's better to change the parameter type to an enum.

Copy link
Member

Choose a reason for hiding this comment

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

I should add - we don't need this code at all - the ValidateSet could simply be different under CORECLR.

The PowerShell version of this command necessarily had runtime code to cover this case, but when we are building different binaries, we can do better.

Copy link
Author

Choose a reason for hiding this comment

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

removed ValidateSet, used custom enum instead


PSObject result = new PSObject();

result.Properties.Add(new PSNoteProperty("Owner", owner));
Copy link
Member

Choose a reason for hiding this comment

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

Do we want strongly typed output instead?

Copy link
Member

Choose a reason for hiding this comment

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

Following up on my own question and after playing around with this cmdlet a little, we definitely want strongly typed output.

Copy link
Author

@zacateras zacateras Jun 16, 2017

Choose a reason for hiding this comment

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

ok, done

{
foreach (var accessFlag in Enum.GetNames(rightType))
{
long longKeyValue = (long)Enum.Parse(rightType, accessFlag);
Copy link
Member

Choose a reason for hiding this comment

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

We could write a script to generate C# and avoid reflection entirely.


// Go through the entries in the different right types, and see if they apply to the
// provided access mask. If they do, then add that to the result.
foreach (var rightType in typesToExamine)
Copy link
Member

Choose a reason for hiding this comment

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

I don't understand this domain enough to be confident here - but in principle I fully agree with @iSazonov - the current algorithm is strange.

Copy link
Member

@SteveL-MSFT SteveL-MSFT left a comment

Choose a reason for hiding this comment

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

Can you add some tests? I just cloned this branch and a basic attempt to use this cmdlet failed:

PS C:\users\slee\repos\zacateras> $sddl = "O:BAG:BAD:(A;CI;CCDCLCSWRPWPRCWD;;;BA)(A;CI;CCDCRP;;;NS)(A;CI;CCDCRP;;;LS)(A;
CI;CCDCRP;;;AU)"
PS C:\users\slee\repos\zacateras> ConvertFrom-SddlString -Sddl $sddl
ConvertFrom-SddlString : Unable to cast object of type 'System.Security.AccessControl.FileSystemRights' to type
'System.Int64'.
At line:1 char:1
+ ConvertFrom-SddlString -Sddl $sddl
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : NotSpecified: (:) [ConvertFrom-SddlString], InvalidCastException
    + FullyQualifiedErrorId : System.InvalidCastException,Microsoft.PowerShell.Commands.ConvertFromSddlStringCommand

Which worked with the old script cmdlet:

PS C:\users\slee\repos\PowerShell> $sddl = "O:BAG:BAD:(A;CI;CCDCLCSWRPWPRCWD;;;BA)(A;CI;CCDCRP;;;NS)(A;CI;CCDCRP;;;LS)(A
;CI;CCDCRP;;;AU)"
PS C:\users\slee\repos\PowerShell> ConvertFrom-SddlString -Sddl $sddl


Owner            : BUILTIN\Administrators
Group            : BUILTIN\Administrators
DiscretionaryAcl : {NT AUTHORITY\Authenticated Users: AccessAllowed (ListDirectory, WriteData,
                   WriteExtendedAttributes), NT AUTHORITY\LOCAL SERVICE: AccessAllowed (ListDirectory, WriteData,
                   WriteExtendedAttributes), NT AUTHORITY\NETWORK SERVICE: AccessAllowed (ListDirectory, WriteData,
                   WriteExtendedAttributes), BUILTIN\Administrators: AccessAllowed (ChangePermissions,
                   CreateDirectories, ExecuteKey, ListDirectory, ReadExtendedAttributes, ReadPermissions, Traverse,
                   WriteData, WriteExtendedAttributes, WriteKey)}
SystemAcl        : {}
RawDescriptor    : System.Security.AccessControl.CommonSecurityDescriptor

@zacateras zacateras force-pushed the rewrite-convertfrom-sddlstring-in-csharp branch 4 times, most recently from 76608a2 to 51f3466 Compare June 16, 2017 10:03
@iSazonov
Copy link
Collaborator

@SteveL-MSFT The algorithm is broken! Please see my comment above #3936 (comment) I believe we cannot merge the PR.

Copy link
Member

@SteveL-MSFT SteveL-MSFT left a comment

Choose a reason for hiding this comment

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

Reviewing algorithm

@iSazonov
Copy link
Collaborator

@daxian-dbw Can we use System.DirectoryService or before we should move to 4.5.0 version ?

@iSazonov
Copy link
Collaborator

iSazonov commented Aug 3, 2017

@zacateras Do you plan to continue?

@zacateras zacateras force-pushed the rewrite-convertfrom-sddlstring-in-csharp branch 2 times, most recently from cbb1c14 to 0fb99ce Compare August 3, 2017 23:07
@zacateras zacateras force-pushed the rewrite-convertfrom-sddlstring-in-csharp branch from 0fb99ce to 3a8d3de Compare August 3, 2017 23:23
@zacateras
Copy link
Author

@iSazonov yep. I thought that we are waiting for some external review.

@SteveL-MSFT
Copy link
Member

Now that Beta.5 is out, I'll spend time on this tomorrow

@iSazonov
Copy link
Collaborator

iSazonov commented Aug 4, 2017

We should follow MSDN docs - see my comment above.

@iSazonov
Copy link
Collaborator

@SteveL-MSFT Could you please continue?

@@ -0,0 +1,119 @@
$RightTypes = @(
Copy link
Member

Choose a reason for hiding this comment

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

Was this file meant to be checked in? It's not part of the build to run and the resulting source doesn't indicate it shouldn't be edited.

/// </summary>
SemaphoreRights,

#if !CORECLR
Copy link
Member

Choose a reason for hiding this comment

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

since we're officially removing fullclr support, we should not include this

{ 2031619, "FullControl" },
}
},
#if !CORECLR
Copy link
Member

Choose a reason for hiding this comment

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

since we're officially removing fullclr support, we should not include this

@@ -0,0 +1,21 @@
Describe "ConvertFrom-SddlString" -Tags "CI" {
It "Should convert without type" -Skip:($IsLinux -Or $IsOSX) {
Copy link
Member

Choose a reason for hiding this comment

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

Since this cmdlet is not supported on non-Windows, you should skip them in bulk using this pattern https://github.com/PowerShell/PowerShell/blob/master/docs/testing-guidelines/WritingPesterTests.md#skipping-tests-in-bulk (that way if additional tests get added they get auto-skipped)

$SddlStringInfo.Group | Should Be "BUILTIN\Administrators"
$SddlStringInfo.DiscretionaryAcl.Length | Should BeGreaterThan 0
$SddlStringInfo.SystemAcl.Length | Should Be $null
$SddlStringInfo.RawDescriptor | Should BeOfType [System.Security.AccessControl.CommonSecurityDescriptor]
Copy link
Member

Choose a reason for hiding this comment

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

Should be some tests for -Type parameter

@@ -80,6 +80,8 @@

<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="2.2.0" />
<PackageReference Include="System.DirectoryServices" Version="4.0.0-preview3-25423-02" />
Copy link
Member

Choose a reason for hiding this comment

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

This doesn't appear to be a supported package, perhaps we should not support AD rights for now.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Official package is System.DirectoryService. It is 4.5.0 version. @daxian-dbw said that we should review the API before use.

@SteveL-MSFT
Copy link
Member

SteveL-MSFT commented Aug 29, 2017

Sorry this took so long, got randomized by other things. I also compared the result with

ConvertFrom-SddlString "D:(A;;FA;;;SY)(A;;FA;;;BA)"
# old script version
DiscretionaryAcl : {NT AUTHORITY\SYSTEM: AccessAllowed (ChangePermissions, CreateDirectories, Delete, DeleteSubdirectoriesAndFiles, ExecuteKey, FullControl,
                   FullControl, FullControl, FullControl, ListDirectory, Modify, Read, ReadAndExecute, ReadAttributes, ReadExtendedAttributes,
                   ReadPermissions, Synchronize, TakeOwnership, Traverse, Write, WriteAttributes, WriteData, WriteExtendedAttributes, WriteKey),
                   BUILTIN\Administrators: AccessAllowed (ChangePermissions, CreateDirectories, Delete, DeleteSubdirectoriesAndFiles, ExecuteKey, FullControl,
                   FullControl, FullControl, FullControl, ListDirectory, Modify, Read, ReadAndExecute, ReadAttributes, ReadExtendedAttributes,
                   ReadPermissions, Synchronize, TakeOwnership, Traverse, Write, WriteAttributes, WriteData, WriteExtendedAttributes, WriteKey)}
# c# version
DiscretionaryAcl : {NT AUTHORITY\SYSTEM: AccessAllowed (AppendData, ChangePermissions, CreateFiles, Delete, DeleteSubdirectoriesAndFiles, ExecuteFile,
                   FullControl, FullControl, FullControl, FullControl, Modify, Read, ReadAndExecute, ReadAttributes, ReadData, ReadExtendedAttributes,
                   ReadKey, ReadPermissions, Synchronize, TakeOwnership, Write, WriteAttributes, WriteExtendedAttributes, WriteKey), BUILTIN\Administrators:
                   AccessAllowed (AppendData, ChangePermissions, CreateFiles, Delete, DeleteSubdirectoriesAndFiles, ExecuteFile, FullControl, FullControl,
                   FullControl, FullControl, Modify, Read, ReadAndExecute, ReadAttributes, ReadData, ReadExtendedAttributes, ReadKey, ReadPermissions,
                   Synchronize, TakeOwnership, Write, WriteAttributes, WriteExtendedAttributes, WriteKey)}

And the newer version has more permissions (although not sure if the old one was wrong).

@iSazonov
Copy link
Collaborator

@SteveL-MSFT Did you look my comment above? Main is "SDDL has no meaning without object entity - the cmdlet should get the object type as argument."

@SteveL-MSFT
Copy link
Member

@iSazonov Seems like if you have the object, you would be using Get-Acl. ConvertFrom-SDDLString is more likely used when you have just the SDDL and just want to understand (perhaps roughly) what it represents. Not sure how useful this is if you require the object.

@iSazonov
Copy link
Collaborator

@SteveL-MSFT My idea is that we should make -Type mandatory.

@zacateras
Copy link
Author

zacateras commented Aug 29, 2017

@iSazonov If AccessMask is presented using well known aliases then it can be parsed entirely without the knowledge of securable object type (Type parameter).

In case of numerical AccessMasks (eg. 0x02142512) we can translate only higher 16-32bits if the Type is not provided (lower bits can remain unparsed as a reminder). If the Type parameter is present then specific rights scheme could be used - according to constant definitions in winnt.h. Also more securable object types can be considered - according to the list in docs.

I started an implementation of SDDL parser using the documentation. The code can be found here (with examples here). If we want to use parts of the implemented approach then I can integrate its simplified form into the command.

What do you think about such solution?

@iSazonov
Copy link
Collaborator

iSazonov commented Sep 4, 2017

@zacateras I still don't understand the need for this heuristic. For which scenarios is it addressed? As for me it looks like a trasformation enum1 -> int -> enum2 - what's confusing.

@PowerShell PowerShell deleted a comment from msftclas Sep 26, 2017
@PowerShell PowerShell deleted a comment from msftclas Sep 26, 2017
@lzybkr lzybkr removed their assignment Nov 13, 2017
@daxian-dbw
Copy link
Member

This PR has been left unattended for a long time. I will close this PR for now.
@zacateras Please feel free to reopen it if you decide to continue on it at a later time.

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

Successfully merging this pull request may close these issues.

None yet

7 participants