-
Notifications
You must be signed in to change notification settings - Fork 4.9k
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
Spanify and cleanup System.Security.Principal.Windows #1637
Conversation
I'm not sure what build config is failing. building for frameworks netcoreapp and netfx both work locally but allconfigurations fails and I'm not sure how to track down what flags the cause of the failure are under. It looks like whatever build it is just doesn't have span so I'd add it if I knew. There's also no specific code owner for this library so as a shot in the dark and knowing that span usage will need a security perspective review I'll @ you @bartonjs . |
{ | ||
if (subAuthorities == null) | ||
if (subAuthorities.IsEmpty) | ||
{ | ||
throw new ArgumentNullException(nameof(subAuthorities)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this reachable? Wondering if it should be an assert instead.
If it is reachable, is it possible this is now throwing for an empty input whereas previously it would only throw for a null input?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This does seem like it would be better as Debug.Assert(subAuthorities.Length > 0)
. All current callers are passing values downstream of new int[]
. The only one that's possibly a concern is CreateFromBinaryForm, if it gets written to hybridize stack and array. But that just means that CreateFromBinaryForm needs to handle the zero case itself. (And probably needs a test that passes 0 sub-authorities, to make sure we don't regress behavior).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The check is invalid, 0 is a valid and used length. The null check has been hoist out to the caller for the ctor and no other case can pass in null.
{ | ||
throw new ArgumentNullException(nameof(subAuthorities)); | ||
} | ||
|
||
int subAuthoritiesLength = subAuthorities.Length; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why did you create a local for this? Seems unnecessary, and on top of that it can actually defeat JIT optimizations.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I thought I was being helpful. If they JIT wants to do the work of identifying multiple uses of the value I'll let it. Changed.
_subAuthorities = new int[subAuthorities.Length]; | ||
subAuthorities.CopyTo(_subAuthorities, 0); | ||
_subAuthorities = new int[subAuthoritiesLength]; | ||
subAuthorities.CopyTo(new Span<int>(_subAuthorities)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These two lines can just be:
_subAuthorities = subAuthorities.ToArray();
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed.
} | ||
|
||
IdentifierAuthority Authority; | ||
int[] SubAuthorities; | ||
IdentifierAuthority authority; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does this need to be defined up here, or can it instead be moved down to where it's assigned about 30 lines later?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Moved.
|
||
// | ||
// Subauthorities are represented in big-endian format | ||
// | ||
|
||
for (byte i = 0; i < binaryForm[offset + 1]; i++) | ||
for (int i = 0; i < binaryForm[offset + 1]; i++) | ||
{ | ||
unchecked |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The unchecked
can be removed. Nothing in dotnet/runtime has checked enabled.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
@@ -479,17 +465,17 @@ private void CreateFromBinaryForm(byte[] binaryForm, int offset) | |||
(((long)binaryForm[offset + 6]) << 8) + | |||
(((long)binaryForm[offset + 7]))); | |||
|
|||
SubAuthorities = new int[binaryForm[offset + 1]]; | |||
int subAuthoritiesLength = binaryForm[offset + 1]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is possible this is 0? If yes, then the change made in CreateFromParts is going to throw an exception because the span will be empty, whereas previously it would have accepted a non-null but empty array.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unless something already would have caught it, it's entirely possible... because this is interpreting data from a public ctor (SecurityIdentifier..ctor(byte[], int)
).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It can be 0 and valid. I've added a max length check and test.
src/libraries/System.Security.Principal.Windows/src/System/Security/Principal/SID.cs
Show resolved
Hide resolved
@@ -439,8 +432,8 @@ public override bool IsAuthenticated | |||
{ | |||
// This approach will not work correctly for domain guests (will return false | |||
// instead of true). This is a corner-case that is not very interesting. | |||
_isAuthenticated = CheckNtTokenForSid(new SecurityIdentifier(IdentifierAuthority.NTAuthority, | |||
new int[] { Interop.SecurityIdentifier.SECURITY_AUTHENTICATED_USER_RID })) ? 1 : 0; | |||
ReadOnlySpan<int> subAuthorities = stackalloc int[1] { Interop.SecurityIdentifier.SECURITY_AUTHENTICATED_USER_RID }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: you didn't need to separate this out into a local... you can just change the new int[]
to stackalloc int[]
in the argument.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It feels like a better change is to make internal singletons for all of these comparison SIDs. If they happen to use stackalloc, fine, but it's way less important if they're only ever built once.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've made the statically known ones static lazy initialized singletons. I don't see a point making everyone pay for creating them just because they touch the class i'd prefer that only people who call the methods that need them pay the cost.
...libraries/System.Security.Principal.Windows/src/System/Security/Principal/WindowsIdentity.cs
Outdated
Show resolved
Hide resolved
...libraries/System.Security.Principal.Windows/src/System/Security/Principal/WindowsIdentity.cs
Outdated
Show resolved
Hide resolved
...libraries/System.Security.Principal.Windows/src/System/Security/Principal/WindowsIdentity.cs
Outdated
Show resolved
Hide resolved
...ibraries/System.Security.Principal.Windows/src/System/Security/Principal/WindowsPrincipal.cs
Outdated
Show resolved
Hide resolved
} | ||
|
||
_Identities[index] = value; | ||
_identities[index] = value ?? throw new ArgumentNullException(nameof(value)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Personally, I hate this syntax, especially for argument validation. (Mainly because it doesn't compose well, because it leaks half-done work when used for field assignments).
Since there's not a second statement here, and there's unlikely to ever be one, I don't have a technical argument. So I'll just leave it at babbling.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It was an autofix suggestion so I have no attachment to it. Informed babbling is reason enough for me, reverted.
src/libraries/System.Security.Principal.Windows/src/System/Security/Principal/IRCollection.cs
Outdated
Show resolved
Hide resolved
nameof(identifierAuthority), | ||
identifierAuthority, | ||
SR.IdentityReference_IdentifierAuthorityTooLarge); | ||
throw new ArgumentOutOfRangeException(nameof(identifierAuthority), identifierAuthority, SR.IdentityReference_IdentifierAuthorityTooLarge); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
While the nameof was previously indented wrong, I liked the previous, chopped, style better. This line now causes horizontal scrolling on the default github window width.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've reformatted it. In general i'm not a fan of limiting things because github has a silly limitation though because it feels like limiting line lengths to 72 chars because of terminals.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
GitHub's limit is 130, and I usually chop around 120 . I think my code window is about 138 characters wide, which generally avoids the need for a horizontal scrollbar.
We don't actually have a repository-wide rule, but in general it's nice to passers-by to avoid h-scroll on GitHub.
{ | ||
unchecked | ||
{ | ||
SubAuthorities[i] = | ||
subAuthorities[i] = |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't see anything that complains if binaryForm[offset+1]
exceeds MaxSubAuthorities. Please write a test that does so.
I think this is going to give a non-friendly out-of-bounds exception instead of an appropriate "your data is malformed"-type exception.
src/libraries/System.Security.Principal.Windows/src/System/Security/Principal/SID.cs
Outdated
Show resolved
Hide resolved
@@ -172,7 +171,7 @@ internal static byte[] ConvertIntPtrSidToByteArraySid(IntPtr binaryForm) | |||
uint length = (uint)SecurityIdentifier.MaxBinaryLength; | |||
resultSid = new byte[length]; | |||
|
|||
if (FALSE != Interop.Advapi32.CreateWellKnownSid((int)sidType, domainSid == null ? null : domainSid.BinaryForm, resultSid, ref length)) | |||
if (FALSE != Interop.Advapi32.CreateWellKnownSid((int)sidType, domainSid?.BinaryForm, resultSid, ref length)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
FWIW: Of the changes in this file, I like this one (reduced the line length to not scroll) and dislike the other two (added horizontal scrolling).
...libraries/System.Security.Principal.Windows/src/System/Security/Principal/WindowsIdentity.cs
Outdated
Show resolved
Hide resolved
I'd definitely like to see tests (including just pointing out that they exist) for zero and too-many subidentifiers, to make sure that the exception model didn't change (or to codify that we intentionally change it, if we end up needing to). |
As for the flavor that's failing to build, I'm guessing either netstandard2.0 or net461: runtime/src/libraries/System.Security.Principal.Windows/src/Configurations.props Lines 3 to 10 in adc91a7
|
src/libraries/System.Security.Principal.Windows/tests/SecurityIdentifierTests.cs
Outdated
Show resolved
Hide resolved
src/libraries/System.Security.Principal.Windows/src/System/Security/Principal/SID.cs
Outdated
Show resolved
Hide resolved
(((uint)binaryForm[offset + 8 + 4 * i + 1]) << 8) + | ||
(((uint)binaryForm[offset + 8 + 4 * i + 2]) << 16) + | ||
(((uint)binaryForm[offset + 8 + 4 * i + 3]) << 24) | ||
); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Typically the chop style on this would be what it was. I understand the symmetry here with the bracing rules, so I'm more pointing it out than suggesting a change.
I'd like to get rid of Line 51 in 0812d49
because it's rarely used. It's used in one place: Lines 968 to 973 in 0812d49
Would it be safe to drop the ctor initialization and add in if (_claimsIntiailizedLock is null)
{
Interlocked.CompareExchange(ref _claimsIntiailizedLock, new object(), null);
} before the lock is taken? |
If it's really only locked in one place, and also using a boolean as a guard, then perhaps the method can/should be rewritten in terms of LazyInitializer. private void InitializeClaims()
{
bool discard = false;
LazyInitializer.EnsureInitialized(
ref discard,
ref _claimsInitialized,
() =>
{
// current lock body
return true;
});
} |
Evrything addressed and updated including the netcoreapp2.0 build issues. |
src/libraries/System.Security.Principal.Windows/tests/SecurityIdentifierTests.cs
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Overall LGTM - thanks! One notable suggestion re: refactoring involving the subAuthorities local. I realize this suggestion will likely regress a single bounds check optimization, but IMO that's an acceptable tradeoff given the reasoning I listed in the comment.
src/libraries/System.Security.Principal.Windows/src/System/Security/Principal/SID.cs
Outdated
Show resolved
Hide resolved
@@ -361,21 +364,20 @@ private void CreateFromParts(IdentifierAuthority identifierAuthority, int[] subA | |||
// } SID, *PISID; | |||
// | |||
|
|||
byte i; | |||
_binaryForm = new byte[1 + 1 + 6 + 4 * this.SubAuthorityCount]; | |||
_binaryForm = new byte[1 + 1 + 6 + 4 * subAuthorities.Length]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please revert all changes involving the subAuthorities local from this point onward. Essentially, pretend that once the _subAuthorities field is set, the subAuthorities local is set to nullptr. This explicit cut helps lessen the chances of introducing TOCTOU attacks in this code, since we don't want to risk performing operations on the [potentially mutated] subAuthorities local when the _subAuthorities field is instead the source of truth.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've changed the to use the field not the local variable. Would it have been viable to build everything in locals and only assign to fields at the end of the function once everything was known to be ready to move into the constructed state?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't necessarily care about when the field is set. But in particular, the recommendation is that very early on in the method we should make a copy of the data being passed in, and then from that point onward we should only be looking at the copy - not the original source of the data. Whether the copy is immediately assigned to a field or whether the field assignment is deferred to the end of the method isn't really that important IMO.
Test failures seem unrelated. |
That one seems like not a flaky test. |
src/libraries/System.Security.Principal.Windows/src/System/Security/Principal/SID.cs
Outdated
Show resolved
Hide resolved
@David-Engel @cheenamalhotra this will help perf on .net 5 by reducing pool fetch allocations . |
The code for and around
SecurityIdentifier
has a number of places where arrays are created only to be passed to a function which will copy their contents. A temporary array of known and small max length is also used when creating one. These can all be made cheaper using stackalloc and spans. This work is in the first commit.The second commit is a general cleanup and formatting of some code which looks like it's quite old. There are no functional changes just renames and reformat to be within current guidelines and auto code fix applications.