-
Notifications
You must be signed in to change notification settings - Fork 53
Add channel readiness with dynamic per-channel access detection #660
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
Merged
Merged
Changes from all commits
Commits
Show all changes
6 commits
Select commit
Hold shift + click to select a range
6791ba4
Add channel readiness with dynamic per-channel access detection
jschick04 a657a50
Make live presence async and drop the unused log-names helper
jschick04 a9bd301
Defer channel config enrichment out of presence enumeration
jschick04 0237562
Derive dashboard live presence from the single readiness fetch
jschick04 477313c
Honor cancellation while enriching channel readiness
jschick04 0cf7a50
Pool the access-check retry buffer and show optional channel readiness
jschick04 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
117 changes: 117 additions & 0 deletions
117
src/EventLogExpert.Eventing/Interop/NativeMethods.Access.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,117 @@ | ||
| // // Copyright (c) Microsoft Corporation. | ||
| // // Licensed under the MIT License. | ||
|
|
||
| using Microsoft.Win32.SafeHandles; | ||
| using System.Runtime.InteropServices; | ||
|
|
||
| namespace EventLogExpert.Eventing.Interop; | ||
|
|
||
| internal static partial class NativeMethods | ||
| { | ||
| private const string Advapi32Api = "advapi32.dll"; | ||
|
|
||
| [LibraryImport(Advapi32Api, SetLastError = true)] | ||
| [return: MarshalAs(UnmanagedType.Bool)] | ||
| internal static partial bool AccessCheck( | ||
| IntPtr securityDescriptor, | ||
| SafeAccessTokenHandle clientToken, | ||
| uint desiredAccess, | ||
| ref GenericMapping genericMapping, | ||
| IntPtr privilegeSet, | ||
| ref uint privilegeSetLength, | ||
| out uint grantedAccess, | ||
| [MarshalAs(UnmanagedType.Bool)] out bool accessStatus); | ||
|
|
||
| [LibraryImport(Advapi32Api, EntryPoint = "ConvertStringSecurityDescriptorToSecurityDescriptorW", SetLastError = true, StringMarshalling = StringMarshalling.Utf16)] | ||
| [return: MarshalAs(UnmanagedType.Bool)] | ||
| internal static partial bool ConvertStringSecurityDescriptorToSecurityDescriptor( | ||
| string stringSecurityDescriptor, | ||
| uint stringSecurityDescriptorRevision, | ||
| out IntPtr securityDescriptor, | ||
| out uint securityDescriptorSize); | ||
|
|
||
| [LibraryImport(Advapi32Api, SetLastError = true)] | ||
| [return: MarshalAs(UnmanagedType.Bool)] | ||
| internal static partial bool DuplicateTokenEx( | ||
| SafeAccessTokenHandle existingToken, | ||
| uint desiredAccess, | ||
| IntPtr tokenAttributes, | ||
| SecurityImpersonationLevel impersonationLevel, | ||
| TokenType tokenType, | ||
| out SafeAccessTokenHandle newToken); | ||
|
|
||
| [LibraryImport(Kernel32Api, SetLastError = true)] | ||
| internal static partial IntPtr GetCurrentProcess(); | ||
|
|
||
| [LibraryImport(Kernel32Api, SetLastError = true)] | ||
| internal static partial IntPtr LocalFree(IntPtr memory); | ||
|
|
||
| [LibraryImport(Advapi32Api, EntryPoint = "LookupPrivilegeValueW", SetLastError = true, StringMarshalling = StringMarshalling.Utf16)] | ||
| [return: MarshalAs(UnmanagedType.Bool)] | ||
| internal static partial bool LookupPrivilegeValue( | ||
| string? systemName, | ||
| string name, | ||
| out Luid luid); | ||
|
|
||
| [LibraryImport(Advapi32Api)] | ||
| internal static partial void MapGenericMask(ref uint accessMask, ref GenericMapping genericMapping); | ||
|
|
||
| [LibraryImport(Advapi32Api, SetLastError = true)] | ||
| [return: MarshalAs(UnmanagedType.Bool)] | ||
| internal static partial bool OpenProcessToken( | ||
| IntPtr processHandle, | ||
| uint desiredAccess, | ||
| out SafeAccessTokenHandle tokenHandle); | ||
|
|
||
| [LibraryImport(Advapi32Api, SetLastError = true)] | ||
| [return: MarshalAs(UnmanagedType.Bool)] | ||
| internal static partial bool PrivilegeCheck( | ||
| SafeAccessTokenHandle clientToken, | ||
| ref PrivilegeSet requiredPrivileges, | ||
| [MarshalAs(UnmanagedType.Bool)] out bool result); | ||
| } | ||
|
|
||
| [StructLayout(LayoutKind.Sequential)] | ||
| internal struct GenericMapping | ||
| { | ||
| internal uint GenericRead; | ||
| internal uint GenericWrite; | ||
| internal uint GenericExecute; | ||
| internal uint GenericAll; | ||
| } | ||
|
|
||
| internal enum SecurityImpersonationLevel | ||
| { | ||
| SecurityAnonymous = 0, | ||
| SecurityIdentification = 1, | ||
| SecurityImpersonation = 2, | ||
| SecurityDelegation = 3 | ||
| } | ||
|
|
||
| internal enum TokenType | ||
| { | ||
| TokenPrimary = 1, | ||
| TokenImpersonation = 2 | ||
| } | ||
|
|
||
| [StructLayout(LayoutKind.Sequential)] | ||
| internal struct Luid | ||
| { | ||
| internal uint LowPart; | ||
| internal int HighPart; | ||
| } | ||
|
|
||
| [StructLayout(LayoutKind.Sequential)] | ||
| internal struct LuidAndAttributes | ||
| { | ||
| internal Luid Luid; | ||
| internal uint Attributes; | ||
| } | ||
|
|
||
| [StructLayout(LayoutKind.Sequential)] | ||
| internal struct PrivilegeSet | ||
| { | ||
| internal uint PrivilegeCount; | ||
| internal uint Control; | ||
| internal LuidAndAttributes Privilege; | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,12 @@ | ||
| // // Copyright (c) Microsoft Corporation. | ||
| // // Licensed under the MIT License. | ||
|
|
||
| namespace EventLogExpert.Eventing.Readers; | ||
|
|
||
| public enum ChannelAccess | ||
| { | ||
| Accessible, | ||
| RequiresElevation, | ||
| Unknown, | ||
| NotEvaluated | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| // // Copyright (c) Microsoft Corporation. | ||
| // // Licensed under the MIT License. | ||
|
|
||
| using EventLogExpert.Eventing.Interop; | ||
|
|
||
| namespace EventLogExpert.Eventing.Readers; | ||
|
|
||
| public sealed record ChannelConfig(bool? Enabled, ChannelAccess Access, EvtChannelType? Type) | ||
| { | ||
| public static ChannelConfig Unknown { get; } = new(null, ChannelAccess.Unknown, null); | ||
| } |
8 changes: 8 additions & 0 deletions
8
src/EventLogExpert.Eventing/Readers/ChannelConfigPropertySnapshot.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| // // Copyright (c) Microsoft Corporation. | ||
| // // Licensed under the MIT License. | ||
|
|
||
| using EventLogExpert.Eventing.Interop; | ||
|
|
||
| namespace EventLogExpert.Eventing.Readers; | ||
|
|
||
| internal sealed record ChannelConfigPropertySnapshot(bool? Enabled, string? AccessSddl, EvtChannelType? Type); |
127 changes: 127 additions & 0 deletions
127
src/EventLogExpert.Eventing/Readers/EventLogChannelConfigPropertyReader.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,127 @@ | ||
| // // Copyright (c) Microsoft Corporation. | ||
| // // Licensed under the MIT License. | ||
|
|
||
| using EventLogExpert.Eventing.Interop; | ||
| using EventLogExpert.Logging.Abstractions; | ||
| using System.Runtime.InteropServices; | ||
|
|
||
| namespace EventLogExpert.Eventing.Readers; | ||
|
|
||
| internal sealed class EventLogChannelConfigPropertyReader(ITraceLogger? logger = null) : IChannelConfigPropertyReader | ||
| { | ||
| private readonly ITraceLogger? _logger = logger; | ||
|
|
||
| public ChannelConfigPropertySnapshot ReadProperties(string channelName) | ||
| { | ||
| ArgumentException.ThrowIfNullOrWhiteSpace(channelName); | ||
|
|
||
| using var channelConfig = NativeMethods.EvtOpenChannelConfig( | ||
| EventLogSession.GlobalSession.Handle, | ||
| channelName, | ||
| 0); | ||
|
|
||
| if (channelConfig.IsInvalid) | ||
| { | ||
| int openError = Marshal.GetLastWin32Error(); | ||
|
|
||
| _logger?.Debug( | ||
| $"{nameof(ReadProperties)}: EvtOpenChannelConfig failed for {channelName}. Error: {openError} ({NativeMethods.FormatSystemMessage((uint)openError) ?? "unknown"})"); | ||
|
|
||
| return new ChannelConfigPropertySnapshot(null, null, null); | ||
| } | ||
|
|
||
| var type = ReadChannelType(channelConfig, channelName); | ||
| var enabled = ReadEnabled(channelConfig, channelName); | ||
| var accessSddl = type is EvtChannelType.Analytic or EvtChannelType.Debug | ||
| ? null | ||
| : ReadAccessSddl(channelConfig, channelName); | ||
|
|
||
| return new ChannelConfigPropertySnapshot(enabled, accessSddl, type); | ||
| } | ||
|
|
||
| // ConvertVariant boxes the channel type as uint; Enum.IsDefined(typeof(...), boxedUint) throws on this int-backed enum, so match the enum value via the generic overload. | ||
| internal static EvtChannelType? ConvertChannelType(object? value) => | ||
| value switch | ||
| { | ||
| uint raw when Enum.IsDefined((EvtChannelType)raw) => (EvtChannelType)raw, | ||
| int raw when Enum.IsDefined((EvtChannelType)raw) => (EvtChannelType)raw, | ||
| _ => null | ||
| }; | ||
|
|
||
| private string? ReadAccessSddl(EvtHandle channelConfig, string channelName) => | ||
| ReadProperty(channelConfig, EvtChannelConfigPropertyId.EvtChannelConfigAccess, channelName) as string; | ||
|
|
||
| private EvtChannelType? ReadChannelType(EvtHandle channelConfig, string channelName) => | ||
| ConvertChannelType(ReadProperty(channelConfig, EvtChannelConfigPropertyId.EvtChannelConfigType, channelName)); | ||
|
|
||
| private bool? ReadEnabled(EvtHandle channelConfig, string channelName) => | ||
| ReadProperty(channelConfig, EvtChannelConfigPropertyId.EvtChannelConfigEnabled, channelName) as bool?; | ||
|
|
||
| private unsafe object? ReadProperty( | ||
| EvtHandle channelConfig, | ||
| EvtChannelConfigPropertyId propertyId, | ||
| string channelName) | ||
| { | ||
| IntPtr buffer = IntPtr.Zero; | ||
|
|
||
| try | ||
| { | ||
| bool success = NativeMethods.EvtGetChannelConfigProperty( | ||
| channelConfig, | ||
| propertyId, | ||
| 0, | ||
| 0, | ||
| IntPtr.Zero, | ||
| out int bufferSize); | ||
|
|
||
| int sizeError = Marshal.GetLastWin32Error(); | ||
|
|
||
| if (!success && sizeError != Win32ErrorCodes.ERROR_INSUFFICIENT_BUFFER) | ||
| { | ||
| _logger?.Debug( | ||
| $"{nameof(ReadProperty)}: size probe failed for {channelName} {propertyId}. Error: {sizeError}"); | ||
|
|
||
| return null; | ||
| } | ||
|
|
||
| buffer = Marshal.AllocHGlobal(bufferSize); | ||
|
|
||
| success = NativeMethods.EvtGetChannelConfigProperty( | ||
| channelConfig, | ||
| propertyId, | ||
| 0, | ||
| bufferSize, | ||
| buffer, | ||
| out _); | ||
|
|
||
| if (!success) | ||
| { | ||
| int readError = Marshal.GetLastWin32Error(); | ||
|
|
||
| _logger?.Debug( | ||
| $"{nameof(ReadProperty)}: read failed for {channelName} {propertyId}. Error: {readError}"); | ||
|
|
||
| return null; | ||
| } | ||
|
|
||
| var variant = *(EvtVariant*)buffer; | ||
| return NativeMethods.ConvertVariant(variant); | ||
| } | ||
| catch (Exception ex) when (ex is not OutOfMemoryException | ||
| and not StackOverflowException | ||
| and not AccessViolationException) | ||
| { | ||
| _logger?.Debug( | ||
| $"{nameof(ReadProperty)}: unexpected failure for {channelName} {propertyId}.\n{ex}"); | ||
|
|
||
| return null; | ||
| } | ||
| finally | ||
| { | ||
| if (buffer != IntPtr.Zero) | ||
| { | ||
| Marshal.FreeHGlobal(buffer); | ||
| } | ||
| } | ||
| } | ||
| } | ||
55 changes: 55 additions & 0 deletions
55
src/EventLogExpert.Eventing/Readers/EventLogChannelConfigReader.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,55 @@ | ||
| // // Copyright (c) Microsoft Corporation. | ||
| // // Licensed under the MIT License. | ||
|
|
||
| using EventLogExpert.Eventing.Common.Channels; | ||
| using EventLogExpert.Eventing.Interop; | ||
| using EventLogExpert.Logging.Abstractions; | ||
|
|
||
| namespace EventLogExpert.Eventing.Readers; | ||
|
|
||
| public sealed class EventLogChannelConfigReader : IChannelConfigReader, IDisposable | ||
| { | ||
| private readonly IChannelAccessEvaluator _accessEvaluator; | ||
| private readonly IChannelAccessNative? _ownedAccessNative; | ||
| private readonly IChannelConfigPropertyReader _propertyReader; | ||
|
|
||
| public EventLogChannelConfigReader(ITraceLogger? logger = null) | ||
| { | ||
| _ownedAccessNative = new Win32ChannelAccessNative(); | ||
| _accessEvaluator = new NativeChannelAccessEvaluator(_ownedAccessNative); | ||
| _propertyReader = new EventLogChannelConfigPropertyReader(logger); | ||
| } | ||
|
|
||
| internal EventLogChannelConfigReader( | ||
| IChannelConfigPropertyReader propertyReader, | ||
| IChannelAccessEvaluator accessEvaluator) | ||
| { | ||
| _propertyReader = propertyReader; | ||
| _accessEvaluator = accessEvaluator; | ||
| } | ||
|
|
||
| public void Dispose() => _ownedAccessNative?.Dispose(); | ||
|
|
||
| public ChannelConfig ReadConfig(string channelName) | ||
| { | ||
| ArgumentException.ThrowIfNullOrWhiteSpace(channelName); | ||
|
|
||
| var properties = _propertyReader.ReadProperties(channelName); | ||
|
|
||
| if (properties.Type is not { } type) | ||
| { | ||
| return new ChannelConfig(properties.Enabled, ChannelAccess.Unknown, null); | ||
| } | ||
|
|
||
| if (type is EvtChannelType.Analytic or EvtChannelType.Debug) | ||
| { | ||
| return new ChannelConfig(properties.Enabled, ChannelAccess.NotEvaluated, type); | ||
| } | ||
|
|
||
| var access = _accessEvaluator.EvaluateAccess( | ||
| properties.AccessSddl, | ||
| string.Equals(channelName, LogChannelNames.SecurityLog, StringComparison.OrdinalIgnoreCase)); | ||
|
|
||
| return new ChannelConfig(properties.Enabled, access, type); | ||
| } | ||
| } |
9 changes: 9 additions & 0 deletions
9
src/EventLogExpert.Eventing/Readers/IChannelAccessEvaluator.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| // // Copyright (c) Microsoft Corporation. | ||
| // // Licensed under the MIT License. | ||
|
|
||
| namespace EventLogExpert.Eventing.Readers; | ||
|
|
||
| internal interface IChannelAccessEvaluator | ||
| { | ||
| ChannelAccess EvaluateAccess(string? sddl, bool isSecurityChannel); | ||
| } |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.