Testing sandboxed code
Two things make Landlock-Sharp tests different from a typical unit test:
- The sandbox is irrevocable. Once a thread is sandboxed, it stays sandboxed for the rest of its life. Tests that enforce a sandbox must run on a thread they can throw away.
- The platform matters. Landlock only works on Linux x86-64 with kernel ≥ 5.13. Tests must be skipped or no-op'd on every other host.
This page covers patterns for both. The library's own test suite follows them and is a good reference.
Pattern 1 — guard on IsSupported()
The cheapest way to make a test portable is to short-circuit when Landlock isn't there:
[TestMethod]
public void CreatesRulesetSuccessfully()
{
if (!Landlock.IsSupported())
{
Assert.Inconclusive("Landlock not supported on this host");
return;
}
var sandbox = Landlock.CreateRuleset(Landlock.FileSystem.CORE);
Assert.IsNotNull(sandbox);
}
Assert.Inconclusive (MSTest) or [SkipOnPlatform] (xUnit) keeps the suite green on Windows, macOS, ARM Linux, and old kernels.
Pattern 2 — enforce on a throw-away thread
Enforcing Landlock on the main test-runner thread would poison every test that runs after it. The fix is to enforce inside a thread you start and join just for that test:
[TestMethod]
public void EnforceBlocksOutsidePaths()
{
if (!Landlock.IsSupported())
{
Assert.Inconclusive("Landlock not supported");
return;
}
bool blocked = false;
var t = new Thread(() =>
{
Landlock.CreateRuleset(Landlock.FileSystem.CORE)
.AddPathBeneathRule("/tmp", Landlock.FileSystem.READ_FILE)
.Enforce();
try { File.ReadAllText("/etc/hostname"); blocked = false; }
catch { blocked = true; }
});
t.Start();
t.Join();
Assert.IsTrue(blocked);
}
When t exits, the kernel cleans up its Landlock domain. The rest of the test run is unaffected. This is exactly the approach used in LandlockTests.LandlockEnforcesFileRestrictions.
Don't use `Task.Run`
The task scheduler picks threads from the .NET thread pool. Sandboxing a pooled thread leaks the restriction to whatever Task runs on that thread next. Use new Thread(...) so you own the thread's lifetime.
Pattern 3 — assert that something should be blocked
Most useful Landlock tests have the shape "this access used to work, sandboxing makes it fail." Run the test twice — once with the sandbox, once without — and assert the symmetric outcomes:
private static bool CanRead(string path)
{
try { File.ReadAllText(path); return true; }
catch { return false; }
}
[TestMethod]
public void SandboxBlocksUnlistedPaths()
{
if (!Landlock.IsSupported()) { Assert.Inconclusive("…"); return; }
bool inside = false;
bool outside = false;
var sandboxed = new Thread(() =>
{
Landlock.CreateRuleset(Landlock.FileSystem.CORE)
.AddPathBeneathRule("/tmp", Landlock.FileSystem.READ_FILE, Landlock.FileSystem.READ_DIR)
.Enforce();
inside = CanRead("/tmp/hello.txt");
outside = CanRead("/etc/hostname");
});
File.WriteAllText("/tmp/hello.txt", "hi");
sandboxed.Start();
sandboxed.Join();
Assert.IsTrue(inside, "Read inside the allow-list should succeed");
Assert.IsFalse(outside, "Read outside the allow-list should be blocked");
}
Pattern 4 — ABI gating
Some tests only make sense above a particular kernel version. Combine Landlock.GetAbiVersion() with Assert.Inconclusive:
[TestMethod]
public void TcpConnectIsBlocked()
{
if (!Landlock.IsSupported() || Landlock.GetAbiVersion() < 4)
{
Assert.Inconclusive("Requires Landlock ABI ≥ 4 (kernel 6.7+)");
return;
}
// … test that uses Landlock.Network.* …
}
The full ABI → kernel table is on the ABI versions page.
Running the suite in CI
Two CI-side things make Landlock tests reliable:
- Pin the runner image. Newer GitHub Actions / Azure Pipelines / GitLab CI Ubuntu images get newer kernels — and therefore higher Landlock ABIs — over time. Pin to a specific image label if you want stable feature coverage. Lookup tables for runner-image kernel versions live in each provider's docs.
- Run inside privileged containers. A few container runtimes (e.g. Docker with seccomp profiles that block
prctl(PR_SET_NO_NEW_PRIVS)) interfere with Landlock. IfIsSupported()returnsfalseinside the container buttrueon the host, check the container's seccomp profile.
The Landlock-Sharp repo runs its own tests on Ubuntu in Azure Pipelines — see .devops/azure-pipelines.yml in the repo for the current setup.
Cross-reference
- Landlock.Test/LandlockTests.cs — the binding's own tests, kept simple deliberately.
- landlock-test-tools — the upstream C testsuite.