-
Notifications
You must be signed in to change notification settings - Fork 49
Add admin-mode enablement for disabled event log channels #662
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
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
Some comments aren't visible on the classic Files Changed page.
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
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
158 changes: 158 additions & 0 deletions
158
src/EventLogExpert.Eventing/Writers/ChannelConfigWriter.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,158 @@ | ||
| // // Copyright (c) Microsoft Corporation. | ||
| // // Licensed under the MIT License. | ||
|
|
||
| using EventLogExpert.Eventing.Interop; | ||
| using EventLogExpert.Eventing.Readers; | ||
| using EventLogExpert.Logging.Abstractions; | ||
| using System.Runtime.InteropServices; | ||
|
|
||
| namespace EventLogExpert.Eventing.Writers; | ||
|
|
||
| public sealed class ChannelConfigWriter(ITraceLogger? logger = null) : IChannelConfigWriter | ||
| { | ||
| private readonly ITraceLogger? _logger = logger; | ||
|
|
||
| public ChannelEnableResult EnableChannel(string channelName) | ||
| { | ||
| ArgumentException.ThrowIfNullOrWhiteSpace(channelName); | ||
|
|
||
| using var channelConfig = NativeMethods.EvtOpenChannelConfig( | ||
| EventLogSession.GlobalSession.Handle, | ||
| channelName, | ||
| 0); | ||
|
|
||
| if (channelConfig.IsInvalid) | ||
| { | ||
| return FromNativeError(Marshal.GetLastWin32Error(), channelName, nameof(NativeMethods.EvtOpenChannelConfig)); | ||
| } | ||
|
|
||
| // Read-first, fail-closed: a failed or non-Boolean read must never be treated as "disabled" (that would drive a | ||
| // machine write on an unreadable state). Only a successful Boolean read that is already true short-circuits. | ||
| if (!TryReadEnabled(channelConfig, channelName, out bool alreadyEnabled, out ChannelEnableResult readFailure)) | ||
| { | ||
| return readFailure; | ||
| } | ||
|
|
||
| if (alreadyEnabled) | ||
| { | ||
| return new ChannelEnableResult(ChannelEnableOutcome.AlreadyEnabled, Win32ErrorCodes.ERROR_SUCCESS); | ||
| } | ||
|
|
||
| var enabledValue = new EvtVariant(true); | ||
|
|
||
| if (!NativeMethods.EvtSetChannelConfigProperty( | ||
| channelConfig, | ||
| EvtChannelConfigPropertyId.EvtChannelConfigEnabled, | ||
| 0, | ||
| in enabledValue)) | ||
| { | ||
| return FromNativeError( | ||
| Marshal.GetLastWin32Error(), | ||
| channelName, | ||
| nameof(NativeMethods.EvtSetChannelConfigProperty)); | ||
| } | ||
|
|
||
| return !NativeMethods.EvtSaveChannelConfig(channelConfig, 0) ? | ||
| FromNativeError(Marshal.GetLastWin32Error(), | ||
| channelName, | ||
| nameof(NativeMethods.EvtSaveChannelConfig)) : | ||
| // EvtSaveChannelConfig returning true is the persistence guarantee, so the change is committed here. | ||
| new ChannelEnableResult(ChannelEnableOutcome.Enabled, Win32ErrorCodes.ERROR_SUCCESS); | ||
| } | ||
|
|
||
| private ChannelEnableResult FromNativeError(int win32Error, string channelName, string stage) | ||
| { | ||
| _logger?.Debug($"{nameof(EnableChannel)}: {stage} failed for {channelName}. Error: {win32Error}"); | ||
|
|
||
| var outcome = win32Error switch | ||
| { | ||
| Win32ErrorCodes.ERROR_ACCESS_DENIED => ChannelEnableOutcome.AccessDenied, | ||
| Win32ErrorCodes.ERROR_EVT_CHANNEL_NOT_FOUND => ChannelEnableOutcome.NotFound, | ||
| _ => ChannelEnableOutcome.Failed | ||
| }; | ||
|
|
||
| return new ChannelEnableResult(outcome, win32Error); | ||
| } | ||
|
|
||
| private unsafe bool TryReadEnabled( | ||
| EvtHandle channelConfig, | ||
| string channelName, | ||
| out bool enabled, | ||
| out ChannelEnableResult failure) | ||
| { | ||
| enabled = false; | ||
| failure = null!; | ||
| IntPtr buffer = IntPtr.Zero; | ||
|
|
||
| try | ||
| { | ||
| bool success = NativeMethods.EvtGetChannelConfigProperty( | ||
| channelConfig, | ||
| EvtChannelConfigPropertyId.EvtChannelConfigEnabled, | ||
| 0, | ||
| 0, | ||
| IntPtr.Zero, | ||
| out int bufferSize); | ||
|
|
||
| int sizeError = Marshal.GetLastWin32Error(); | ||
|
|
||
| if (!success && sizeError != Win32ErrorCodes.ERROR_INSUFFICIENT_BUFFER) | ||
| { | ||
| failure = FromNativeError(sizeError, channelName, nameof(NativeMethods.EvtGetChannelConfigProperty)); | ||
|
|
||
| return false; | ||
| } | ||
|
|
||
| buffer = Marshal.AllocHGlobal(bufferSize); | ||
|
|
||
| if (!NativeMethods.EvtGetChannelConfigProperty( | ||
| channelConfig, | ||
| EvtChannelConfigPropertyId.EvtChannelConfigEnabled, | ||
| 0, | ||
| bufferSize, | ||
| buffer, | ||
| out _)) | ||
| { | ||
| failure = FromNativeError( | ||
| Marshal.GetLastWin32Error(), | ||
| channelName, | ||
| nameof(NativeMethods.EvtGetChannelConfigProperty)); | ||
|
|
||
| return false; | ||
| } | ||
|
|
||
| var variant = *(EvtVariant*)buffer; | ||
|
|
||
| if (variant.Type != (uint)EvtVariantType.Boolean) | ||
| { | ||
| // The read succeeded but the value is not Boolean, so the last Win32 error is not meaningful; surface a | ||
| // non-zero sentinel (ERROR_INVALID_DATA) rather than 0 so downstream diagnostics stay actionable. | ||
| _logger?.Debug( | ||
| $"{nameof(EnableChannel)}: {channelName} enabled property was not Boolean (type {variant.Type})."); | ||
|
|
||
| failure = new ChannelEnableResult(ChannelEnableOutcome.Failed, Win32ErrorCodes.ERROR_INVALID_DATA); | ||
|
|
||
| return false; | ||
| } | ||
|
|
||
| enabled = variant.BooleanVal != 0; | ||
| return true; | ||
| } | ||
| catch (Exception ex) when (ex is not OutOfMemoryException | ||
| and not StackOverflowException | ||
| and not AccessViolationException) | ||
| { | ||
| _logger?.Debug($"{nameof(EnableChannel)}: unexpected failure reading enabled state for {channelName}.\n{ex}"); | ||
| failure = new ChannelEnableResult(ChannelEnableOutcome.Failed, Win32ErrorCodes.ERROR_INVALID_DATA); | ||
|
|
||
| return false; | ||
| } | ||
|
jschick04 marked this conversation as resolved.
|
||
| finally | ||
| { | ||
| if (buffer != IntPtr.Zero) | ||
| { | ||
| Marshal.FreeHGlobal(buffer); | ||
| } | ||
| } | ||
| } | ||
| } | ||
25 changes: 25 additions & 0 deletions
25
src/EventLogExpert.Eventing/Writers/ChannelEnableOutcome.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,25 @@ | ||
| // // Copyright (c) Microsoft Corporation. | ||
| // // Licensed under the MIT License. | ||
|
|
||
| namespace EventLogExpert.Eventing.Writers; | ||
|
|
||
| public enum ChannelEnableOutcome | ||
| { | ||
| /// <summary>The channel was disabled and has now been enabled (the save committed).</summary> | ||
| Enabled, | ||
|
|
||
| /// <summary>The channel was already enabled, so no write was performed.</summary> | ||
| AlreadyEnabled, | ||
|
|
||
| /// <summary>The caller is not elevated; the enable was not attempted (set by the runtime service).</summary> | ||
| NotElevated, | ||
|
|
||
| /// <summary>The channel configuration could not be opened or saved because access was denied.</summary> | ||
| AccessDenied, | ||
|
|
||
| /// <summary>No channel with the requested name is registered on the computer.</summary> | ||
| NotFound, | ||
|
|
||
| /// <summary>The enable failed for any other reason; see the Win32 error code.</summary> | ||
| Failed | ||
| } |
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,6 @@ | ||
| // // Copyright (c) Microsoft Corporation. | ||
| // // Licensed under the MIT License. | ||
|
|
||
| namespace EventLogExpert.Eventing.Writers; | ||
|
|
||
| public sealed record ChannelEnableResult(ChannelEnableOutcome Outcome, int Win32Error); |
13 changes: 13 additions & 0 deletions
13
src/EventLogExpert.Eventing/Writers/IChannelConfigWriter.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,13 @@ | ||
| // // Copyright (c) Microsoft Corporation. | ||
| // // Licensed under the MIT License. | ||
|
|
||
| namespace EventLogExpert.Eventing.Writers; | ||
|
|
||
| public interface IChannelConfigWriter | ||
| { | ||
| /// <summary> | ||
| /// Enables a disabled event log channel by persisting <c>EvtChannelConfigEnabled = true</c>. Requires an elevated | ||
| /// process; without elevation a native call fails and the result is <see cref="ChannelEnableOutcome.AccessDenied" />. | ||
| /// </summary> | ||
| ChannelEnableResult EnableChannel(string channelName); | ||
| } |
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
44 changes: 44 additions & 0 deletions
44
src/EventLogExpert.Runtime/Scenarios/ChannelEnableService.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,44 @@ | ||
| // // Copyright (c) Microsoft Corporation. | ||
| // // Licensed under the MIT License. | ||
|
|
||
| using EventLogExpert.Eventing.Writers; | ||
| using EventLogExpert.Runtime.Common.Identity; | ||
|
|
||
| namespace EventLogExpert.Runtime.Scenarios; | ||
|
|
||
| internal sealed class ChannelEnableService( | ||
| IChannelConfigWriter channelConfigWriter, | ||
| IChannelReadinessService readinessService, | ||
| IWindowsIdentityProvider identityProvider) : IChannelEnableService | ||
| { | ||
| private readonly Lazy<bool> _canEnable = new(identityProvider.IsUserInAdministratorRole); | ||
|
|
||
| public bool CanEnable => _canEnable.Value; | ||
|
|
||
| public async Task<ChannelEnableResult> EnableAsync(string channel, CancellationToken cancellationToken = default) | ||
| { | ||
| ArgumentException.ThrowIfNullOrWhiteSpace(channel); | ||
|
|
||
| // Do not attempt a write we know will be denied; a non-elevated caller never reaches the native layer. | ||
| if (!CanEnable) | ||
| { | ||
| return new ChannelEnableResult(ChannelEnableOutcome.NotElevated, 0); | ||
| } | ||
|
|
||
| // Cancellation is honored only up to the moment the native write begins: the token guards both the | ||
| // synchronous entry and the Task.Run scheduling boundary, so a cancel that arrives before the writer runs | ||
| // prevents the write. Once EnableChannel starts it runs to completion (Task.Run stops consulting the token | ||
| // once the delegate is executing), so a committed machine change is never reported as cancelled. | ||
| cancellationToken.ThrowIfCancellationRequested(); | ||
|
|
||
| var result = await Task.Run(() => channelConfigWriter.EnableChannel(channel), cancellationToken) | ||
| .ConfigureAwait(false); | ||
|
|
||
| if (result.Outcome is ChannelEnableOutcome.Enabled or ChannelEnableOutcome.AlreadyEnabled) | ||
| { | ||
| readinessService.Invalidate(); | ||
| } | ||
|
|
||
| return result; | ||
| } | ||
| } |
17 changes: 17 additions & 0 deletions
17
src/EventLogExpert.Runtime/Scenarios/IChannelEnableService.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,17 @@ | ||
| // // Copyright (c) Microsoft Corporation. | ||
| // // Licensed under the MIT License. | ||
|
|
||
| using EventLogExpert.Eventing.Writers; | ||
|
|
||
| namespace EventLogExpert.Runtime.Scenarios; | ||
|
|
||
| public interface IChannelEnableService | ||
| { | ||
| /// <summary> | ||
| /// Whether an enable can be ATTEMPTED (the process is elevated). A true value is not a guarantee that the write | ||
| /// will succeed - a channel-specific ACL can still deny an elevated caller. | ||
| /// </summary> | ||
| bool CanEnable { get; } | ||
|
|
||
| Task<ChannelEnableResult> EnableAsync(string channel, CancellationToken cancellationToken = default); | ||
| } |
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,26 @@ | ||
| // // Copyright (c) Microsoft Corporation. | ||
| // // Licensed under the MIT License. | ||
|
|
||
| using EventLogExpert.Runtime.Alerts; | ||
|
|
||
| namespace EventLogExpert.UI.Common; | ||
|
|
||
| internal static class EnableChannelConfirmation | ||
| { | ||
| public static Task<bool> ConfirmAsync(IAlertDialogService dialog, string channelName, bool isAnalyticOrDebug) | ||
| { | ||
| var message = | ||
| $"Enable the \"{channelName}\" log?\n\n" + | ||
| "This changes Windows event logging for the whole computer and stays in effect until it is disabled again. " + | ||
| "Only events generated after enabling are collected - events that occurred while the log was disabled cannot be recovered."; | ||
|
|
||
| if (isAnalyticOrDebug) | ||
| { | ||
| message += | ||
| "\n\nThis is an analytic or debug log: enabling it can clear the records it already holds, and Windows may " + | ||
| "keep collecting to it while refusing to open it for live viewing until it is disabled again."; | ||
| } | ||
|
|
||
| return dialog.ShowAlert("Enable log", message, "Enable", "Cancel"); | ||
| } | ||
| } |
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.