diff options
Diffstat (limited to 'tests/Agent.Windows.Tests/SecurityPolicyTests.cs')
| -rw-r--r-- | tests/Agent.Windows.Tests/SecurityPolicyTests.cs | 77 |
1 files changed, 72 insertions, 5 deletions
diff --git a/tests/Agent.Windows.Tests/SecurityPolicyTests.cs b/tests/Agent.Windows.Tests/SecurityPolicyTests.cs index 09ed61d..d0c32a8 100644 --- a/tests/Agent.Windows.Tests/SecurityPolicyTests.cs +++ b/tests/Agent.Windows.Tests/SecurityPolicyTests.cs @@ -431,8 +431,10 @@ public sealed class SecurityPolicyTests } } - [Fact] - public void Lpac_baseline_allows_registry_read_but_denies_registry_write_and_com_activation() + [Theory] + [InlineData(false)] + [InlineData(true)] + public void Lpac_capability_matrix_preserves_registry_and_com_denials(bool allowNetwork) { AppContainerProfile profile = AppContainerProfile.EnsureFixed(); using TestDirectory directory = new(); @@ -448,7 +450,7 @@ public sealed class SecurityPolicyTests { var launcher = new RestrictedProcessLauncher( new EntryExecutablePolicy([new ApprovedEntryExecutable("powershell", powershell, EntryExecutablePolicy.ComputeSha256(powershell), true, null)], [], []), - audit, profile.Sid, new SandboxJobLimits(2, 256UL * 1024 * 1024)); + audit, profile.Sid, new SandboxJobLimits(2, 256UL * 1024 * 1024), allowNetwork); string script = $"$ErrorActionPreference='Stop'; $p=(Get-ItemProperty 'HKLM:\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion').ProductName; " + $"$writeDenied=$false; try {{ New-Item -Path 'HKCU:\\Software\\{registryName}' -Force | Out-Null }} catch {{ $writeDenied=$true }}; " + "$comDenied=$false; try { $null=New-Object -ComObject Scripting.FileSystemObject } catch { $comDenied=$true }; " + @@ -464,6 +466,9 @@ public sealed class SecurityPolicyTests Assert.Contains("REGISTRY_WRITE_DENIED=True", result.StandardOutput, StringComparison.Ordinal); Assert.Contains("COM_DENIED=True", result.StandardOutput, StringComparison.Ordinal); Assert.Contains("BROKER_SECRET_INHERITED=False", result.StandardOutput, StringComparison.Ordinal); + Assert.Contains(audit.Events, item => item.Event == "process.start" && + item.Data["baseline_capabilities"] == "registryRead,lpacInstrumentation" && + item.Data["network_capabilities"] == (allowNetwork ? "internetClient,privateNetworkClientServer" : "none")); } finally { @@ -507,6 +512,31 @@ public sealed class SecurityPolicyTests } [Fact] + public void Approved_cmd_starts_unapproved_descendant_by_unqualified_name() + { + AppContainerProfile profile = AppContainerProfile.EnsureFixed(); + using TestDirectory directory = new(); + string cmd = Environment.GetEnvironmentVariable("ComSpec")!; + var audit = new MemorySecurityAuditSink(); + TaskAclManager acl = new(profile.Sid, audit); + Guid taskId = Guid.NewGuid(); + AclSnapshot snapshot = acl.Grant(taskId, directory.Path, FileCapability.Read | FileCapability.Write); + try + { + var launcher = new RestrictedProcessLauncher( + new EntryExecutablePolicy([new ApprovedEntryExecutable("cmd", cmd, EntryExecutablePolicy.ComputeSha256(cmd), true, null)], [], []), + audit, profile.Sid, new SandboxJobLimits(4, 512UL * 1024 * 1024)); + RestrictedProcessResult result = launcher.Run(taskId, "cmd", ["/d", "/c", "cmd.exe /d /c exit 0"], + directory.Path, TimeSpan.FromSeconds(30), null, null, TestContext.Current.CancellationToken); + Assert.Equal(0u, result.ExitCode); + Assert.Contains(audit.Events, item => item.Event == "process.start" && + item.Data["token_is_app_container"] == bool.TrueString && item.Data["process_in_job"] == bool.TrueString && + item.Data["descendant_policy"] == "lpac_inherited"); + } + finally { acl.Restore(taskId, snapshot); } + } + + [Fact] public void Approved_python314_starts_python_subprocess_inside_lpac_job() { AppContainerProfile profile = AppContainerProfile.EnsureFixed(); @@ -656,8 +686,8 @@ public sealed class SecurityPolicyTests snapshots.Add(acl.Grant(taskId, directory.Path, FileCapability.Read | FileCapability.Write | FileCapability.Delete | FileCapability.Execute)); snapshots.Add(acl.Grant(taskId, runtimeRoot, FileCapability.Read | FileCapability.Execute)); - // System32 owns a protected DACL. Do not rewrite it; successful cmd descendants below - // are the enforcement check for its existing LPAC read/execute access. + // System32 owns an OS-managed protected DACL. Do not rewrite it; descendant + // execution relies on its existing LPAC read/execute access. var launcher = new RestrictedProcessLauncher( new EntryExecutablePolicy([new ApprovedEntryExecutable("dotnet", dotnet, EntryExecutablePolicy.ComputeSha256(dotnet), false, runtimeRoot)], [runtimeRoot], [directory.Path]), audit, profile.Sid, new SandboxJobLimits(16, 1024UL * 1024 * 1024)); @@ -975,6 +1005,43 @@ public sealed class SecurityPolicyTests } [Fact] + public async Task Os_runtime_root_protected_dacl_is_never_modified() + { + using TestDirectory directory = new(); + string system32 = ResolvedPath.Resolve(Environment.SystemDirectory); + string cmd = ResolvedPath.Resolve(Environment.GetEnvironmentVariable("ComSpec")!); + DirectorySecurity security = FileSystemAclExtensions.GetAccessControl(new DirectoryInfo(system32), AccessControlSections.Access); + var raw = new RawSecurityDescriptor(security.GetSecurityDescriptorBinaryForm(), 0); + Assert.True(raw.ControlFlags.HasFlag(ControlFlags.DiscretionaryAclProtected)); + string before = Convert.ToHexString(security.GetSecurityDescriptorBinaryForm()); + string cmdBefore = Convert.ToHexString(FileSystemAclExtensions.GetAccessControl( + new FileInfo(cmd), AccessControlSections.Access).GetSecurityDescriptorBinaryForm()); + var audit = new MemorySecurityAuditSink(); + var settings = new EffectiveSandboxPolicy( + [new FilePolicyEntry(cmd, FileCapability.Read | FileCapability.Execute, "approved_shell_executable"), + new FilePolicyEntry(system32, FileCapability.Read | FileCapability.Execute, "os_runtime_root")], + [new ApprovedEntryExecutable("cmd", cmd, EntryExecutablePolicy.ComputeSha256(cmd), true, null)], [], [], + new SandboxJobLimits(), "none", [], Path.Combine(directory.Path, "journals"), Path.Combine(directory.Path, "backups"), + 16 * 1024 * 1024, 1024 * 1024, 1000, 10000, BaselineCapabilities: ["registryRead", "lpacInstrumentation"]); + var backend = new WindowsToolBackend(settings, audit); + Guid taskId = Guid.NewGuid(); + + await backend.BeginTaskAsync(taskId, Guid.NewGuid(), TestContext.Current.CancellationToken); + await backend.EndTaskAsync(taskId, true, TestContext.Current.CancellationToken); + + string after = Convert.ToHexString(FileSystemAclExtensions.GetAccessControl( + new DirectoryInfo(system32), AccessControlSections.Access).GetSecurityDescriptorBinaryForm()); + string cmdAfter = Convert.ToHexString(FileSystemAclExtensions.GetAccessControl( + new FileInfo(cmd), AccessControlSections.Access).GetSecurityDescriptorBinaryForm()); + Assert.Equal(before, after); + Assert.Equal(cmdBefore, cmdAfter); + Assert.DoesNotContain(audit.Events, item => item.Event is ("acl.apply" or "acl.restore") && + item.Data.TryGetValue("path", out string? path) && + (string.Equals(path, system32, StringComparison.OrdinalIgnoreCase) || + string.Equals(path, cmd, StringComparison.OrdinalIgnoreCase))); + } + + [Fact] public async Task Dir_mkdir_creates_directory_within_approved_root_and_cleans_up() { using TestDirectory directory = new(); |
