diff --git a/src/libraries/System.DirectoryServices.AccountManagement/src/System/DirectoryServices/AccountManagement/Utils.cs b/src/libraries/System.DirectoryServices.AccountManagement/src/System/DirectoryServices/AccountManagement/Utils.cs index c4995b0a2c53e..c6bf8714675b6 100644 --- a/src/libraries/System.DirectoryServices.AccountManagement/src/System/DirectoryServices/AccountManagement/Utils.cs +++ b/src/libraries/System.DirectoryServices.AccountManagement/src/System/DirectoryServices/AccountManagement/Utils.cs @@ -227,6 +227,12 @@ internal static unsafe SidType ClassifySID(IntPtr pSid) return SidType.FakeObject; } + // Is the SID S-1-5-0-0-0-RID (sentinel SID)? + if (IsSentinelSID(pSid)) + { + return SidType.FakeObject; + } + return rid switch { 21 => SidType.RealObject, // Account SID @@ -811,5 +817,58 @@ internal static bool IsMachineDC(string computerName) Interop.Dsrole.DsRoleFreeMemory(dsRoleInfoPtr); } } + + // + // The sentinel SID were placed in the domain SID range S-1-5-21-X-Y-Z-R with R < 512 because the existing domain controllers would always filter those SIDs out at boundaries. + // That way, the sentinel SID which says the claims or compound data is safe to consume would be removed should the claims or compound PAC ever pass through a domain controller + // that did not know how to apply security checks. S-1-5-21-X-Y-Z-R means that the SID belongs to a domain(including the local account domain) unless X=Y=Z=0 in which + // case it's a sentinel SID, a special type of pseudo-object that can't be interpreted in isolation. + // + internal static bool IsSentinelSID(IntPtr pSid) + { + Debug.Assert(Interop.Advapi32.IsValidSid(pSid)); + + IntPtr psubAuthorityCount = Interop.Advapi32.GetSidSubAuthorityCount(pSid); + int subAuthorityCount = Marshal.ReadByte(psubAuthorityCount); + + // + // Sentinel SIDs are of format S-1-5-21-X-Y-Z-R, so if the subauthority count is not equal to 5 + // (21-X-Y-Z-R), then it is not a sentinel SID. + // + if (subAuthorityCount != 5) + { + return false; + } + + // + // If the rid is greater than equal to 512 then it is not a sentinel sid + // + int rid = GetLastRidFromSid(pSid); + if (rid >= 512) + { + return false; + } + + // We are going to check for X, Y and Z only hence starting the for loop + // with i = 1, and not reading sunAuthority-1 which is the RID + for (int i = 1; i < subAuthorityCount - 1; i++) + { + IntPtr pcurrentSubauthority = Interop.Advapi32.GetSidSubAuthority(pSid, i); + int currentSubauthority = Marshal.ReadInt32(pcurrentSubauthority); + + // + // We return false as soon as we know the first subauthority is not 0 + // + if (currentSubauthority != 0) + { + return false; + } + } + + // + // This means X=Y=Z=0 + // + return true; + } } } diff --git a/src/libraries/System.DirectoryServices/src/System/DirectoryServices/ActiveDirectory/Utils.cs b/src/libraries/System.DirectoryServices/src/System/DirectoryServices/ActiveDirectory/Utils.cs index 19a6879f635a2..c26c7b3af38b1 100644 --- a/src/libraries/System.DirectoryServices/src/System/DirectoryServices/ActiveDirectory/Utils.cs +++ b/src/libraries/System.DirectoryServices/src/System/DirectoryServices/ActiveDirectory/Utils.cs @@ -2266,6 +2266,12 @@ internal static unsafe SidType ClassifySID(IntPtr pSid) return SidType.FakeObject; } + // Is the SID S-1-5-0-0-0-RID(sentinel SID)? + if (IsSentinelSID(pSid)) + { + return SidType.FakeObject; + } + return rid switch { 21 => SidType.RealObject, // Account SID @@ -2324,5 +2330,58 @@ internal static IntPtr ConvertByteArrayToIntPtr(byte[] bytes) Debug.Assert(pBytes != IntPtr.Zero); return pBytes; } + + // + // The sentinel SID were placed in the domain SID range S-1-5-21-X-Y-Z-R with R < 512 because the existing domain controllers would always filter those SIDs out at boundaries. + // That way, the sentinel SID which says the claims or compound data is safe to consume would be removed should the claims or compound PAC ever pass through a domain controller + // that did not know how to apply security checks. S-1-5-21-X-Y-Z-R means that the SID belongs to a domain(including the local account domain) unless X=Y=Z=0 in which + // case it's a sentinel SID, a special type of pseudo-object that can't be interpreted in isolation. + // + internal static bool IsSentinelSID(IntPtr pSid) + { + Debug.Assert(global::Interop.Advapi32.IsValidSid(pSid)); + + IntPtr psubAuthorityCount = global::Interop.Advapi32.GetSidSubAuthorityCount(pSid); + int subAuthorityCount = Marshal.ReadByte(psubAuthorityCount); + + // + // Sentinel SIDs are of format S-1-5-21-X-Y-Z-R, so if the subauthority count is not equal to 5 + // (21-X-Y-Z-R), then it is not a sentinel SID. + // + if (subAuthorityCount != 5) + { + return false; + } + + // + // If the rid is greater than equal to 512 then it is not a sentinel sid + // + int rid = GetLastRidFromSid(pSid); + if (rid >= 512) + { + return false; + } + + // We are going to check for X, Y and Z only hence starting the for loop + // with i = 1, and not reading sunAuthority-1 which is the RID + for (int i = 1; i < subAuthorityCount - 1; i++) + { + IntPtr pcurrentSubauthority = global::Interop.Advapi32.GetSidSubAuthority(pSid, i); + int currentSubauthority = Marshal.ReadInt32(pcurrentSubauthority); + + // + // We return false as soon as we know the first subauthority is not 0 + // + if (currentSubauthority != 0) + { + return false; + } + } + + // + // This means X=Y=Z=0 + // + return true; + } } }