summaryrefslogtreecommitdiff
path: root/tests/Agent.Windows.Tests/SecurityPolicyTests.cs
diff options
context:
space:
mode:
Diffstat (limited to 'tests/Agent.Windows.Tests/SecurityPolicyTests.cs')
-rw-r--r--tests/Agent.Windows.Tests/SecurityPolicyTests.cs77
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();