summaryrefslogtreecommitdiff
path: root/src/Agent.Admin/Agent.Admin.Host/LoopbackExemption.cs
blob: 34494912d7efc0958b6fedc7bac7aab8775129de (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
using System.ComponentModel;
using System.Runtime.InteropServices;
using System.Security.Principal;
using Windows.Win32;
using Windows.Win32.Foundation;
using Windows.Win32.Security;
using Windows.Win32.System.Memory;

namespace Agent.Admin.Host;

internal static unsafe class LoopbackExemption
{
    private const string MutexName = "Local\\AgentSandboxMcpLoopbackExemption";

    public static bool Contains(SecurityIdentifier sid)
    {
        using MutexLease mutex = AcquireMutex();
        return Read().Any(item => item.Sid.Equals(sid));
    }

    public static bool Ensure(SecurityIdentifier sid)
    {
        using MutexLease mutex = AcquireMutex();
        List<Entry> entries = Read();
        if (entries.Any(item => item.Sid.Equals(sid))) return false;
        entries.Add(new(sid, 0));
        Write(entries);
        return true;
    }

    public static void Remove(SecurityIdentifier sid)
    {
        using MutexLease mutex = AcquireMutex();
        List<Entry> entries = Read();
        if (entries.RemoveAll(item => item.Sid.Equals(sid)) == 0) return;
        Write(entries);
    }

    private static MutexLease AcquireMutex()
    {
        var mutex = new Mutex(false, MutexName);
        try
        {
            try
            {
                if (!mutex.WaitOne(TimeSpan.FromSeconds(30))) throw new TimeoutException("Timed out waiting for the loopback exemption lock.");
            }
            catch (AbandonedMutexException)
            {
                // Ownership is granted to this thread when the prior owner died.
            }
            return new(mutex);
        }
        catch
        {
            mutex.Dispose();
            throw;
        }
    }

    private static List<Entry> Read()
    {
        uint result = PInvoke.NetworkIsolationGetAppContainerConfig(out uint count, out SID_AND_ATTRIBUTES* native);
        ThrowIfError(result, "NetworkIsolationGetAppContainerConfig");
        try
        {
            var entries = new List<Entry>(checked((int)count));
            for (uint index = 0; index < count; index++)
            {
                SID_AND_ATTRIBUTES item = native[index];
                entries.Add(new(new SecurityIdentifier((nint)item.Sid.Value), item.Attributes));
            }
            return entries;
        }
        finally
        {
            HANDLE heap = PInvoke.GetProcessHeap();
            for (uint index = 0; index < count; index++)
                if (native[index].Sid.Value is not null) _ = PInvoke.HeapFree(heap, HEAP_FLAGS.HEAP_NONE, native[index].Sid.Value);
            if (native is not null) _ = PInvoke.HeapFree(heap, HEAP_FLAGS.HEAP_NONE, native);
        }
    }

    private static void Write(IReadOnlyList<Entry> entries)
    {
        var native = new SID_AND_ATTRIBUTES[entries.Count];
        var allocations = new List<nint>(entries.Count);
        try
        {
            for (int index = 0; index < entries.Count; index++)
            {
                byte[] bytes = new byte[entries[index].Sid.BinaryLength];
                entries[index].Sid.GetBinaryForm(bytes, 0);
                void* allocation = NativeMemory.Alloc((nuint)bytes.Length);
                Marshal.Copy(bytes, 0, (nint)allocation, bytes.Length);
                allocations.Add((nint)allocation);
                native[index].Sid = new PSID(allocation);
                native[index].Attributes = entries[index].Attributes;
            }
            ThrowIfError(PInvoke.NetworkIsolationSetAppContainerConfig(native), "NetworkIsolationSetAppContainerConfig");
        }
        finally
        {
            foreach (nint allocation in allocations) NativeMemory.Free((void*)allocation);
        }
    }

    private static void ThrowIfError(uint result, string operation)
    {
        if (result != 0) throw new Win32Exception(unchecked((int)result), $"{operation} failed with Win32 error 0x{result:X8}.");
    }

    private sealed record Entry(SecurityIdentifier Sid, uint Attributes);

    private sealed class MutexLease(Mutex mutex) : IDisposable
    {
        public void Dispose()
        {
            mutex.ReleaseMutex();
            mutex.Dispose();
        }
    }
}