Skip to content

Commit 4a35dd0

Browse files
committed
Updated access checking to return privileges and check status.
1 parent f64cad4 commit 4a35dd0

6 files changed

Lines changed: 163 additions & 38 deletions

File tree

NtApiDotNet/AccessCheckResult.cs

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
// Copyright 2020 Google Inc. All Rights Reserved.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
using System.Collections.Generic;
16+
17+
namespace NtApiDotNet
18+
{
19+
/// <summary>
20+
/// Result of an access check.
21+
/// </summary>
22+
public class AccessCheckResult
23+
{
24+
/// <summary>
25+
/// The NT status code from the access check.
26+
/// </summary>
27+
public NtStatus Status { get; }
28+
/// <summary>
29+
/// The granted access from the check.
30+
/// </summary>
31+
public AccessMask GrantedAccess { get; }
32+
/// <summary>
33+
/// The required privileges for this access.
34+
/// </summary>
35+
public IEnumerable<TokenPrivilege> PrivilegesRequired { get; }
36+
37+
internal AccessCheckResult(NtStatus status, AccessMask granted_access, SafePrivilegeSetBuffer privilege_set)
38+
{
39+
Status = status;
40+
GrantedAccess = granted_access;
41+
PrivilegesRequired = privilege_set?.GetPrivileges() ?? new TokenPrivilege[0];
42+
}
43+
}
44+
}

NtApiDotNet/NtApiDotNet.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646
<Reference Include="System.XML" />
4747
</ItemGroup>
4848
<ItemGroup>
49+
<Compile Include="AccessCheckResult.cs" />
4950
<Compile Include="AccessMask.cs" />
5051
<Compile Include="AccessMaskEntry.cs" />
5152
<Compile Include="Ace.cs" />

NtApiDotNet/NtObjectWithDuplicate.cs

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -194,11 +194,6 @@ internal O ShallowClone()
194194
return ShallowClone(new SafeKernelObjectHandle(Handle.DangerousGetHandle(), false), false);
195195
}
196196

197-
private A UIntToAccess(GenericAccessRights access)
198-
{
199-
return (A)Enum.ToObject(typeof(A), (uint)access);
200-
}
201-
202197
/// <summary>
203198
/// Get granted access for handle.
204199
/// </summary>

NtApiDotNet/NtSecurity.cs

Lines changed: 66 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -494,26 +494,27 @@ public static Sid SidFromSddl(string sddl)
494494
return SidFromSddl(sddl, true).Result;
495495
}
496496

497-
private static NtToken DuplicateForAccessCheck(NtToken token)
497+
private static NtResult<NtToken> DuplicateForAccessCheck(NtToken token, bool throw_on_error)
498498
{
499499
if (token.IsPseudoToken)
500500
{
501501
// This is a pseudo token, pass along as no need to duplicate.
502-
return token;
502+
return token.CreateResult();
503503
}
504504

505505
if (token.TokenType == TokenType.Primary)
506506
{
507-
return token.DuplicateToken(TokenType.Impersonation, SecurityImpersonationLevel.Identification, TokenAccessRights.Query);
507+
return token.DuplicateToken(TokenType.Impersonation,
508+
SecurityImpersonationLevel.Identification, TokenAccessRights.Query, throw_on_error);
508509
}
509510
else if (!token.IsAccessGranted(TokenAccessRights.Query))
510511
{
511-
return token.Duplicate(TokenAccessRights.Query);
512+
return token.Duplicate(TokenAccessRights.Query, throw_on_error);
512513
}
513514
else
514515
{
515516
// If we've got query access rights already just create a shallow clone.
516-
return token.ShallowClone();
517+
return token.ShallowClone().CreateResult();
517518
}
518519
}
519520

@@ -525,10 +526,12 @@ private static NtToken DuplicateForAccessCheck(NtToken token)
525526
/// <param name="access_rights">The set of access rights to check against</param>
526527
/// <param name="principal">An optional principal SID used to replace the SELF SID in a security descriptor.</param>
527528
/// <param name="generic_mapping">The type specific generic mapping (get from corresponding NtType entry).</param>
528-
/// <returns>The allowed access mask as a unsigned integer.</returns>
529+
/// <param name="throw_on_error">True to throw on error.</param>
530+
/// <returns>The result of the access check.</returns>
529531
/// <exception cref="NtException">Thrown if an error occurred in the access check.</exception>
530-
public static AccessMask GetAllowedAccess(SecurityDescriptor sd, NtToken token,
531-
AccessMask access_rights, Sid principal, GenericMapping generic_mapping)
532+
public static NtResult<AccessCheckResult> AccessCheck(SecurityDescriptor sd, NtToken token,
533+
AccessMask access_rights, Sid principal, GenericMapping generic_mapping,
534+
bool throw_on_error)
532535
{
533536
if (sd == null)
534537
{
@@ -542,33 +545,71 @@ public static AccessMask GetAllowedAccess(SecurityDescriptor sd, NtToken token,
542545

543546
if (access_rights.IsEmpty)
544547
{
545-
return AccessMask.Empty;
548+
return new AccessCheckResult(NtStatus.STATUS_ACCESS_DENIED, 0, null).CreateResult();
546549
}
547550

548-
using (SafeBuffer sd_buffer = sd.ToSafeBuffer())
551+
using (var list = new DisposableList())
549552
{
550-
using (NtToken imp_token = DuplicateForAccessCheck(token))
553+
var sd_buffer = list.AddResource(sd.ToSafeBuffer());
554+
var imp_token = list.AddResource(DuplicateForAccessCheck(token, throw_on_error));
555+
if (!imp_token.IsSuccess)
551556
{
552-
using (var privs = new SafePrivilegeSetBuffer())
553-
{
554-
int buffer_length = privs.Length;
557+
return imp_token.Cast<AccessCheckResult>();
558+
}
559+
var self_sid = list.AddResource(principal?.ToSafeBuffer() ?? SafeSidBufferHandle.Null);
560+
var privs = list.AddResource(new SafePrivilegeSetBuffer());
561+
int repeat_count = 1;
555562

556-
using (var self_sid = principal != null ? principal.ToSafeBuffer() : SafeSidBufferHandle.Null)
557-
{
558-
NtSystemCalls.NtAccessCheckByType(sd_buffer, self_sid, imp_token.Handle, access_rights,
559-
SafeHGlobalBuffer.Null, 0, ref generic_mapping, privs,
560-
ref buffer_length, out AccessMask granted_access, out NtStatus result_status).ToNtException();
561-
if (result_status.IsSuccess())
562-
{
563-
return granted_access;
564-
}
565-
return AccessMask.Empty;
566-
}
563+
while (true)
564+
{
565+
int buffer_length = privs.Length;
566+
NtStatus status = NtSystemCalls.NtAccessCheckByType(sd_buffer, self_sid, imp_token.Result.Handle, access_rights,
567+
SafeHGlobalBuffer.Null, 0, ref generic_mapping, privs,
568+
ref buffer_length, out AccessMask granted_access, out NtStatus result_status);
569+
if (repeat_count == 0 || status != NtStatus.STATUS_BUFFER_TOO_SMALL)
570+
{
571+
return status.CreateResult(throw_on_error, ()
572+
=> new AccessCheckResult(result_status, granted_access, privs));
567573
}
574+
575+
repeat_count--;
576+
privs = list.AddResource(new SafePrivilegeSetBuffer(buffer_length));
568577
}
569578
}
570579
}
571580

581+
/// <summary>
582+
/// Do an access check between a security descriptor and a token to determine the allowed access.
583+
/// </summary>
584+
/// <param name="sd">The security descriptor</param>
585+
/// <param name="token">The access token.</param>
586+
/// <param name="access_rights">The set of access rights to check against</param>
587+
/// <param name="principal">An optional principal SID used to replace the SELF SID in a security descriptor.</param>
588+
/// <param name="generic_mapping">The type specific generic mapping (get from corresponding NtType entry).</param>
589+
/// <returns>The result of the access check.</returns>
590+
/// <exception cref="NtException">Thrown if an error occurred in the access check.</exception>
591+
public static AccessCheckResult AccessCheck(SecurityDescriptor sd, NtToken token,
592+
AccessMask access_rights, Sid principal, GenericMapping generic_mapping)
593+
{
594+
return AccessCheck(sd, token, access_rights, principal, generic_mapping, true).Result;
595+
}
596+
597+
/// <summary>
598+
/// Do an access check between a security descriptor and a token to determine the allowed access.
599+
/// </summary>
600+
/// <param name="sd">The security descriptor</param>
601+
/// <param name="token">The access token.</param>
602+
/// <param name="access_rights">The set of access rights to check against</param>
603+
/// <param name="principal">An optional principal SID used to replace the SELF SID in a security descriptor.</param>
604+
/// <param name="generic_mapping">The type specific generic mapping (get from corresponding NtType entry).</param>
605+
/// <returns>The allowed access mask as a unsigned integer.</returns>
606+
/// <exception cref="NtException">Thrown if an error occurred in the access check.</exception>
607+
public static AccessMask GetAllowedAccess(SecurityDescriptor sd, NtToken token,
608+
AccessMask access_rights, Sid principal, GenericMapping generic_mapping)
609+
{
610+
return AccessCheck(sd, token, access_rights, principal, generic_mapping, true).Result.GrantedAccess;
611+
}
612+
572613
/// <summary>
573614
/// Do an access check between a security descriptor and a token to determine the allowed access.
574615
/// </summary>

NtApiDotNet/NtSecurityNative.cs

Lines changed: 49 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
using System;
1616
using System.Collections.Generic;
1717
using System.IO;
18+
using System.Linq;
1819
using System.Runtime.InteropServices;
1920

2021
namespace NtApiDotNet
@@ -254,11 +255,18 @@ internal CachedSigningLevelEaBufferV3(int version2, int flags, SigningLevel sign
254255
}
255256
}
256257

258+
[Flags]
259+
public enum PrivilegeSetControlFlags
260+
{
261+
None = 0,
262+
AllNecessary = 1,
263+
}
264+
257265
[StructLayout(LayoutKind.Sequential), DataStart("Privilege")]
258266
public struct PrivilegeSet
259267
{
260268
public int PrivilegeCount;
261-
public int Control;
269+
public PrivilegeSetControlFlags Control;
262270
public LuidAndAttributes Privilege;
263271
}
264272

@@ -297,15 +305,51 @@ public enum SigningLevel
297305

298306
public class SafePrivilegeSetBuffer : SafeStructureInOutBuffer<PrivilegeSet>
299307
{
300-
public SafePrivilegeSetBuffer(int count)
301-
: base(new PrivilegeSet(),
302-
count * Marshal.SizeOf(typeof(LuidAndAttributes)),
308+
private SafePrivilegeSetBuffer(bool owns_handle)
309+
: base(IntPtr.Zero, 0, owns_handle)
310+
{
311+
}
312+
313+
private SafePrivilegeSetBuffer(PrivilegeSet privilege_set, int count)
314+
: base(privilege_set, count * Marshal.SizeOf(typeof(LuidAndAttributes)),
303315
true)
304316
{
305317
}
306318

307-
public SafePrivilegeSetBuffer() : this(1)
319+
private SafePrivilegeSetBuffer(IEnumerable<TokenPrivilege> privileges,
320+
PrivilegeSetControlFlags control, int count) : this(new PrivilegeSet() { Control = control, PrivilegeCount = count },
321+
count)
322+
{
323+
if (count <= 0)
324+
{
325+
throw new ArgumentException("Privilege count must be greater than 0", nameof(count));
326+
}
327+
var luids = privileges.Select(p => new LuidAndAttributes() { Luid = p.Luid, Attributes = p.Attributes }).ToArray();
328+
Data.WriteArray(0, luids, 0, luids.Length);
329+
}
330+
331+
public SafePrivilegeSetBuffer(int total_size)
332+
: base(total_size, false)
333+
{
334+
}
335+
336+
public SafePrivilegeSetBuffer(IEnumerable<TokenPrivilege> privileges,
337+
PrivilegeSetControlFlags control) : this(privileges, control, privileges.Count())
338+
{
339+
}
340+
341+
public SafePrivilegeSetBuffer() : this(new PrivilegeSet(), 1)
342+
{
343+
}
344+
345+
public static new SafePrivilegeSetBuffer Null => new SafePrivilegeSetBuffer(false);
346+
347+
public IEnumerable<TokenPrivilege> GetPrivileges()
308348
{
349+
var result = Result;
350+
LuidAndAttributes[] luids = new LuidAndAttributes[result.PrivilegeCount];
351+
Data.ReadArray(0, luids, 0, luids.Length);
352+
return luids.Select(l => new TokenPrivilege(l.Luid, l.Attributes)).ToArray();
309353
}
310354
}
311355

NtApiDotNet/NtTokenNative.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -366,7 +366,7 @@ public long ToInt64()
366366
public struct LuidAndAttributes
367367
{
368368
public Luid Luid;
369-
public uint Attributes;
369+
public PrivilegeAttributes Attributes;
370370
}
371371

372372
[StructLayout(LayoutKind.Sequential), DataStart("Privileges")]
@@ -674,7 +674,7 @@ public void AddPrivilege(Luid luid, PrivilegeAttributes attributes)
674674
LuidAndAttributes priv = new LuidAndAttributes
675675
{
676676
Luid = luid,
677-
Attributes = (uint)attributes
677+
Attributes = attributes
678678
};
679679
_privs.Add(priv);
680680
}
@@ -697,7 +697,7 @@ public void AddPrivilege(TokenPrivilege privilege)
697697

698698
public void AddPrivilegeRange(IEnumerable<TokenPrivilege> privileges)
699699
{
700-
_privs.AddRange(privileges.Select(p => new LuidAndAttributes() { Luid = p.Luid, Attributes = (uint)p.Attributes }));
700+
_privs.AddRange(privileges.Select(p => new LuidAndAttributes() { Luid = p.Luid, Attributes = p.Attributes }));
701701
}
702702

703703
public SafeTokenPrivilegesBuffer ToBuffer()

0 commit comments

Comments
 (0)