Skip to content

Commit

Permalink
♻️ Detect SeCreateGlobalPrivilege and move the shared memory stuff to…
Browse files Browse the repository at this point in the history
… a separate assembly.

This is mostly necessary so that the service can know which rights it has later on.
Hopefully, ACL security stuff that is unimplemented in .NET Core won't be a problem…
  • Loading branch information
hexawyz committed Jan 19, 2025
1 parent 3581928 commit 55b6b33
Show file tree
Hide file tree
Showing 15 changed files with 275 additions and 58 deletions.
19 changes: 19 additions & 0 deletions Exo.sln
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DeviceTools.WinUsb", "src\D
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DeviceTools.Usb", "src\DeviceTools\DeviceTools.Usb\DeviceTools.Usb.csproj", "{53087D24-A134-4744-9136-1E24E14C2A2A}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Exo.Memory", "src\Exo\Core\Exo.Memory\Exo.Memory.csproj", "{6866CC56-DDAC-4511-AA90-FD77E05DB15F}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -1281,6 +1283,22 @@ Global
{53087D24-A134-4744-9136-1E24E14C2A2A}.Release|x64.Build.0 = Release|Any CPU
{53087D24-A134-4744-9136-1E24E14C2A2A}.Release|x86.ActiveCfg = Release|Any CPU
{53087D24-A134-4744-9136-1E24E14C2A2A}.Release|x86.Build.0 = Release|Any CPU
{6866CC56-DDAC-4511-AA90-FD77E05DB15F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{6866CC56-DDAC-4511-AA90-FD77E05DB15F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{6866CC56-DDAC-4511-AA90-FD77E05DB15F}.Debug|arm64.ActiveCfg = Debug|Any CPU
{6866CC56-DDAC-4511-AA90-FD77E05DB15F}.Debug|arm64.Build.0 = Debug|Any CPU
{6866CC56-DDAC-4511-AA90-FD77E05DB15F}.Debug|x64.ActiveCfg = Debug|Any CPU
{6866CC56-DDAC-4511-AA90-FD77E05DB15F}.Debug|x64.Build.0 = Debug|Any CPU
{6866CC56-DDAC-4511-AA90-FD77E05DB15F}.Debug|x86.ActiveCfg = Debug|Any CPU
{6866CC56-DDAC-4511-AA90-FD77E05DB15F}.Debug|x86.Build.0 = Debug|Any CPU
{6866CC56-DDAC-4511-AA90-FD77E05DB15F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{6866CC56-DDAC-4511-AA90-FD77E05DB15F}.Release|Any CPU.Build.0 = Release|Any CPU
{6866CC56-DDAC-4511-AA90-FD77E05DB15F}.Release|arm64.ActiveCfg = Release|Any CPU
{6866CC56-DDAC-4511-AA90-FD77E05DB15F}.Release|arm64.Build.0 = Release|Any CPU
{6866CC56-DDAC-4511-AA90-FD77E05DB15F}.Release|x64.ActiveCfg = Release|Any CPU
{6866CC56-DDAC-4511-AA90-FD77E05DB15F}.Release|x64.Build.0 = Release|Any CPU
{6866CC56-DDAC-4511-AA90-FD77E05DB15F}.Release|x86.ActiveCfg = Release|Any CPU
{6866CC56-DDAC-4511-AA90-FD77E05DB15F}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down Expand Up @@ -1358,6 +1376,7 @@ Global
{A00F0142-5832-4873-8944-874B918419ED} = {B93F3F85-DA80-48CF-A549-9217DD34DD11}
{9D1822E8-F2BF-473B-84ED-B16E206B429F} = {59B8EF45-EA19-477D-B57C-5783B7B1C5CF}
{53087D24-A134-4744-9136-1E24E14C2A2A} = {59B8EF45-EA19-477D-B57C-5783B7B1C5CF}
{6866CC56-DDAC-4511-AA90-FD77E05DB15F} = {062C0665-616A-4FB4-B0DA-2EEBE7FDA7B9}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {E9FEED7F-27EC-4720-9CAB-C2E6FDD8556D}
Expand Down
4 changes: 2 additions & 2 deletions src/DeviceTools/DeviceTools.Firmware/NativeMethods.cs
Original file line number Diff line number Diff line change
Expand Up @@ -62,10 +62,10 @@ public enum VariableEnumerationInformationClass : uint
public static extern uint NtEnumerateSystemEnvironmentValuesEx(VariableEnumerationInformationClass informationClass, void* buffer, ref uint bufferLength);

[DllImport("advapi32", CharSet = CharSet.Unicode, EntryPoint = "LookupPrivilegeValueW", ExactSpelling = true, PreserveSig = true, SetLastError = true)]
public static extern bool LookupPrivilegeValue(string? systemName, string name, out int privilege);
public static extern bool LookupPrivilegeValue(string? systemName, string name, out ulong privilege);

[DllImport("ntdll", ExactSpelling = true, PreserveSig = true, SetLastError = false)]
public static extern uint RtlAdjustPrivilege(int privilege, bool shouldEnablePrivilege, bool isThreadPrivilege, out bool previousValue);
public static extern uint RtlAdjustPrivilege(ulong privilege, bool shouldEnablePrivilege, bool isThreadPrivilege, out bool previousValue);

[DllImport("ntdll", ExactSpelling = true, PreserveSig = true, SetLastError = false)]
public static extern uint RtlNtStatusToDosError(uint status);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ internal static class SystemEnvironmentPrivilege
{
static SystemEnvironmentPrivilege()
{
if (!NativeMethods.LookupPrivilegeValue(null, NativeMethods.SeSystemEnvironmentPrivilege, out int privilege))
if (!NativeMethods.LookupPrivilegeValue(null, NativeMethods.SeSystemEnvironmentPrivilege, out ulong privilege))
{
throw new Win32Exception(Marshal.GetLastWin32Error());
}
Expand Down
10 changes: 10 additions & 0 deletions src/Exo/Core/Exo.Memory/Exo.Memory.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>

</Project>
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
using System.Buffers;
using System.Buffers;
using System.IO.MemoryMappedFiles;
using Microsoft.Win32.SafeHandles;

namespace Exo.Service;
namespace Exo.Memory;

internal sealed unsafe class MemoryMappedFileMemoryManager : MemoryManager<byte>
public sealed unsafe class MemoryMappedFileMemoryManager : MemoryManager<byte>
{
private readonly SafeMemoryMappedViewHandle _viewHandle;
private readonly nint _offset;
Expand All @@ -16,7 +16,7 @@ public MemoryMappedFileMemoryManager(MemoryMappedFile memoryMappedFile, nint off
ArgumentNullException.ThrowIfNull(memoryMappedFile);
ArgumentOutOfRangeException.ThrowIfNegative(offset);
ArgumentOutOfRangeException.ThrowIfNegative(length);
_viewAccessor = memoryMappedFile.CreateViewAccessor((long)offset, length, access);
_viewAccessor = memoryMappedFile.CreateViewAccessor(offset, length, access);
_viewHandle = _viewAccessor.SafeMemoryMappedViewHandle;
_offset = offset;
_length = length;
Expand All @@ -27,9 +27,7 @@ public MemoryMappedFileMemoryManager(MemoryMappedFile memoryMappedFile, nint off
protected override void Dispose(bool disposing)
{
if (Interlocked.Exchange(ref _viewAccessor, null) is { } accessor)
{
accessor.Dispose();
}
}

public override Span<byte> GetSpan() => new((byte*)_viewHandle.DangerousGetHandle(), _length);
Expand Down
166 changes: 166 additions & 0 deletions src/Exo/Core/Exo.Memory/NativeMethods.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
using System.ComponentModel;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Security;

namespace Exo.Memory;

[SuppressUnmanagedCodeSecurity]
internal static unsafe class NativeMethods
{
public const uint NtStatusBufferTooSmall = 0xC0000023;

private const uint ReadControl = 0x00020000U;
private const uint StandardRightsRead = ReadControl;
private const uint TokenQuery = 0x0008;

public const string SeCreateGlobalPrivilege = "SeCreateGlobalPrivilege";

public enum TokenInformationClass : uint
{
TokenUser = 1,
TokenGroups,
TokenPrivileges,
TokenOwner,
TokenPrimaryGroup,
TokenDefaultDacl,
TokenSource,
TokenType,
TokenImpersonationLevel,
TokenStatistics,
TokenRestrictedSids,
TokenSessionId,
TokenGroupsAndPrivileges,
TokenSessionReference,
TokenSandBoxInert,
TokenAuditPolicy,
TokenOrigin,
TokenElevationType,
TokenLinkedToken,
TokenElevation,
TokenHasRestrictions,
TokenAccessInformation,
TokenVirtualizationAllowed,
TokenVirtualizationEnabled,
TokenIntegrityLevel,
TokenUIAccess,
TokenMandatoryPolicy,
TokenLogonSid,
TokenIsAppContainer,
TokenCapabilities,
TokenAppContainerSid,
TokenAppContainerNumber,
TokenUserClaimAttributes,
TokenDeviceClaimAttributes,
TokenRestrictedUserClaimAttributes,
TokenRestrictedDeviceClaimAttributes,
TokenDeviceGroups,
TokenRestrictedDeviceGroups,
TokenSecurityAttributes,
TokenIsRestricted,
TokenProcessTrustLevel,
TokenPrivateNameSpace,
TokenSingletonAttributes,
TokenBnoIsolation,
TokenChildProcessFlags,
TokenIsLessPrivilegedAppContainer,
TokenIsSandboxed,
TokenIsAppSilo,
TokenLoggingInformation,
MaxTokenInfoClass,
}

public readonly struct LuidAndAttributes
{
public readonly Luid Luid;
public readonly PrivilegeAttributes Attributes;
}

public readonly struct Luid : IEquatable<Luid>
{
public readonly uint LowPart;
public readonly uint HighPart;

public override bool Equals(object? obj) => obj is Luid luid && Equals(luid);
public bool Equals(Luid other) => LowPart == other.LowPart && HighPart == other.HighPart;
public override int GetHashCode() => HashCode.Combine(LowPart, HighPart);

public static bool operator ==(Luid left, Luid right) => left.Equals(right);
public static bool operator !=(Luid left, Luid right) => !(left == right);
}

[Flags]
public enum PrivilegeAttributes : uint
{
Disabled = 0x00000000,
EnabledByDefault = 0x00000001,
Enabled = 0x00000002,
Removed = 0x00000004,
UsedForAccess = 0x80000000,
}

[DllImport("advapi32", CharSet = CharSet.Unicode, EntryPoint = "LookupPrivilegeValueW", ExactSpelling = true, PreserveSig = true, SetLastError = true)]
private static extern uint LookupPrivilegeValue(string? systemName, string name, Luid* privilege);

[DllImport("advapi32", ExactSpelling = true, PreserveSig = true, SetLastError = true)]
private static extern uint OpenProcessToken(nint processHandle, uint desiredAccess, out nint tokenHandle);

[DllImport("ntdll", ExactSpelling = true, PreserveSig = true, SetLastError = false)]
private static extern uint NtQueryInformationToken(nint tokenHandle, TokenInformationClass tokenInformationClass, void* tokenInformation, uint tokenInformationLength, uint* returnLength);

[DllImport("ntdll", ExactSpelling = true, PreserveSig = true, SetLastError = false)]
private static extern uint RtlNtStatusToDosError(uint status);

[DllImport("kernel32", ExactSpelling = true, PreserveSig = true, SetLastError = true)]
private static extern uint CloseHandle(nint handle);

[DebuggerHidden]
[StackTraceHidden]
public static void ValidateNtStatus(uint status)
{
if (status != 0)
{
throw new Win32Exception((int)RtlNtStatusToDosError(status));
}
}

public static Luid GetPrivilegeValue(string privilegeName)
{
ArgumentNullException.ThrowIfNull(privilegeName);

Luid value = default;
if (LookupPrivilegeValue(null, privilegeName, &value) == 0)
{
Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error());
}

return value;
}

public static unsafe LuidAndAttributes[] GetProcessPrivileges()
{
if (OpenProcessToken(Process.GetCurrentProcess().Handle, StandardRightsRead | TokenQuery, out nint tokenHandle) == 0)
{
Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error());
}
try
{
Span<byte> data = stackalloc byte[2048];
uint writtenLength = 0;
uint result = NtQueryInformationToken(tokenHandle, TokenInformationClass.TokenPrivileges, Unsafe.AsPointer(ref data[0]), (uint)data.Length, &writtenLength);
if (result != 0)
{
Marshal.ThrowExceptionForHR((int)RtlNtStatusToDosError(result));
}
return MemoryMarshal.Cast<byte, LuidAndAttributes>(data[..(int)writtenLength].Slice(4, (int)(Unsafe.As<byte, uint>(ref data[0]) * sizeof(LuidAndAttributes)))).ToArray();
}
finally
{
if (CloseHandle(tokenHandle) == 0)
{
Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error());
}
}
}
}
67 changes: 67 additions & 0 deletions src/Exo/Core/Exo.Memory/SharedMemory.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
using System.IO.MemoryMappedFiles;
using System.Security.Cryptography;

namespace Exo.Memory;

public sealed class SharedMemory : IDisposable
{
private static readonly string DefaultPrefix = GetAcceptablePrefix();

private static string GetAcceptablePrefix()
{
var seCreateGlobalPrivilege = NativeMethods.GetPrivilegeValue(NativeMethods.SeCreateGlobalPrivilege);
foreach (var privilege in NativeMethods.GetProcessPrivileges())
{
if (privilege.Luid == seCreateGlobalPrivilege) return @"Global\";
}
return @"Local\";
}

public static SharedMemory Create(string prefix, ulong length)
{
ArgumentNullException.ThrowIfNull(prefix);
ArgumentOutOfRangeException.ThrowIfGreaterThan(length, (ulong)long.MaxValue);

string name = string.Create
(
DefaultPrefix.Length + 32 + prefix.Length,
prefix,
static (span, prefix) =>
{
DefaultPrefix.CopyTo(span[..DefaultPrefix.Length]);
prefix.CopyTo(span[DefaultPrefix.Length..]);
RandomNumberGenerator.GetHexString(span[(DefaultPrefix.Length + prefix.Length)..], true);
}
);
return new(name, MemoryMappedFile.CreateNew(name, (long)length, MemoryMappedFileAccess.ReadWrite, MemoryMappedFileOptions.None, HandleInheritability.None), length);
}

public static SharedMemory Open(string name, ulong length, MemoryMappedFileAccess access)
{
ArgumentNullException.ThrowIfNull(name);
ArgumentOutOfRangeException.ThrowIfGreaterThan(length, (ulong)long.MaxValue);

return new(name, MemoryMappedFile.CreateOrOpen(name, (long)length, access), length);
}

private readonly string _name;
private readonly MemoryMappedFile _file;
private readonly ulong _length;

private SharedMemory(string name, MemoryMappedFile file, ulong length)
{
_name = name;
_file = file;
_length = length;
}

public void Dispose() => _file.Dispose();

public string Name => _name;
public ulong Length => _length;

public Stream CreateStream(MemoryMappedFileAccess access) => _file.CreateViewStream(0, (long)_length, access);
public Stream CreateReadStream() => CreateStream(MemoryMappedFileAccess.Read);
public Stream CreateWriteStream() => CreateStream(MemoryMappedFileAccess.Write);
public MemoryMappedFileMemoryManager CreateMemoryManager(MemoryMappedFileAccess access) => new MemoryMappedFileMemoryManager(_file, 0, checked((int)Length), access);
}
1 change: 0 additions & 1 deletion src/Exo/Service/Exo.Service.Core/Exo.Service.Core.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
<ImplicitUsings>enable</ImplicitUsings>
<RootNamespace>Exo.Service</RootNamespace>
<WarningsAsErrors>Nullable</WarningsAsErrors>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>

<ItemGroup>
Expand Down
1 change: 1 addition & 0 deletions src/Exo/Service/Exo.Service.Grpc/Exo.Service.Grpc.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\..\Core\Exo.Memory\Exo.Memory.csproj" />
<ProjectReference Include="..\Exo.Service.Core\Exo.Service.Core.csproj" />
<ProjectReference Include="..\..\Ui\Exo.Contracts.Ui.Overlay\Exo.Contracts.Ui.Overlay.csproj" />
<ProjectReference Include="..\..\Ui\Exo.Contracts.Ui.Settings\Exo.Contracts.Ui.Settings.csproj" />
Expand Down
1 change: 1 addition & 0 deletions src/Exo/Service/Exo.Service.Grpc/GrpcImageService.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System.IO.MemoryMappedFiles;
using System.Runtime.CompilerServices;
using Exo.Contracts.Ui.Settings;
using Exo.Memory;

namespace Exo.Service.Grpc;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using Exo.Settings.Ui.ViewModels;
using Exo.Memory;
using Microsoft.UI.Xaml.Data;
using Microsoft.UI.Xaml.Media.Imaging;

Expand Down
1 change: 1 addition & 0 deletions src/Exo/Ui/Exo.Settings.Ui/Exo.Settings.Ui.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\..\Core\Exo.Memory\Exo.Memory.csproj" />
<ProjectReference Include="..\..\Core\Exo.Metadata\Exo.Metadata.csproj" />
<ProjectReference Include="..\..\Core\Exo.Programming.Contracts\Exo.Programming.Contracts.csproj" />
<ProjectReference Include="..\Exo.Contracts.Ui.Settings\Exo.Contracts.Ui.Settings.csproj" />
Expand Down
1 change: 1 addition & 0 deletions src/Exo/Ui/Exo.Settings.Ui/ImagesPage.xaml.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using Exo.Memory;
using Exo.Settings.Ui.ViewModels;
using Microsoft.UI.Xaml.Controls;
using Windows.Storage.Pickers;
Expand Down
3 changes: 1 addition & 2 deletions src/Exo/Ui/Exo.Settings.Ui/ViewModels/ImagesViewModel.cs
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
using System.Buffers;
using System.Collections.ObjectModel;
using System.IO.MemoryMappedFiles;
using System.Windows.Input;
using Exo.Contracts.Ui.Settings;
using Exo.Memory;
using Exo.Settings.Ui.Services;
using Exo.Ui;
using Microsoft.UI.Xaml.Media;

namespace Exo.Settings.Ui.ViewModels;

Expand Down
Loading

0 comments on commit 55b6b33

Please sign in to comment.