Forum

Notifications
Clear all

Has anyone tried using seccomp with landlock for a defense-in-depth agent sandbox?

8 Posts
8 Users
0 Reactions
12 Views
(@supply_chain_scout_em)
Eminent Member
Joined: 2 months ago
Posts: 21
Topic starter   [#1725]

I've been reviewing the isolation requirements for our new telemetry agent, which will run with elevated privileges on customer hosts. While we currently employ a seccomp-bpf filter to block unnecessary syscalls, I'm concerned it's insufficient for a modern defense-in-depth approach. The agent only needs to read from specific procfs paths, write to its own log socket, and communicate via a local UNIX socket.

Seccomp handles the syscall layer, but doesn't restrict filesystem operations beyond coarse `read`/`write` controls. This is where Landlock seems promising. My hypothesis is that layering Landlock on top of seccomp could provide a more granular, filesystem-centric sandbox.

Has anyone in the forum experimented with this combination for a similar workload? I'm particularly interested in practical insights around:
* Order of operations: enabling Landlock before or after seccomp rules are applied?
* Handling of required accesses, like the agent's need to read `/proc//stat` for specific PIDs, but not arbitrary files.
* Interaction with namespacing. Does Landlock add meaningful protection if we're already using a mount namespace?
* Any gotchas with common runtime dependencies (e.g., glibc) when paths are restricted.

The goal is to move from a simple "allow list" of syscalls to a true "allow list" of accessible filesystem hierarchies, which should mitigate risks from potential dependency exploits. I'm tracking CVE-2022-0185 and similar kernel vulnerabilities, which remind us that syscall filtering alone isn't a complete barrier.

- Em


Know your dependencies, or they will know you.


   
Quote
(@selfhost_sec_architect_lee)
Eminent Member
Joined: 2 months ago
Posts: 25
 

Great question. I've been down this exact path with an internal monitoring agent, and the combo works well once you sort out the order.

> Order of operations
You need to apply Landlock *after* seccomp, but before your agent starts its main logic. The trick is that Landlock requires `prctl(PR_SET_SECCOMP, ...)` to have already run. If you do it the other way around, some Landlock operations might get blocked by your own seccomp policy. I've got a snippet somewhere showing the sequence.

On your point about mount namespaces: Landlock adds a lot, even with one. A mount namespace restricts *what* you can see, but Landlock defines *how* you can interact with what's visible. An attacker who breaks into your agent process could `truncate` or `chmod` files it shouldn't, if only a mount namespace is in play. Landlock prevents that.

Biggest gotcha I hit: Landlock ABI v1 couldn't handle some `procfs` accesses gracefully. You have to be careful with your rule set if you're reading from `/proc//`. I ended up locking it down to specific enumerated paths. It's more verbose, but safer.

Happy to share my test harness if you're interested.


Isolation is freedom.


   
ReplyQuote
(@network_seg)
Eminent Member
Joined: 2 months ago
Posts: 21
 

Good question, and you're right to look at Landlock for finer-grained control. The order user493 mentioned is crucial. I'd also stress that Landlock's path-based restrictions really shine for your `/proc/[pid]/stat` requirement. You can allow read access to `/proc/self/stat` and a directory like `/proc//` without opening up the entire `/proc`.

One nuance with namespaces: a mount namespace changes the filesystem view, but Landlock rules are applied to the VFS layer *after* that translation. So even if you hide `/etc` via namespace, Landlock can still enforce rules on whatever paths are visible, like blocking write access to a visible log directory. They address different layers of the problem.

Be careful with runtime dependencies, especially if your agent uses a dynamic linker or needs to load libraries after sandboxing. Landlock rules are inherited, so any child process or library call will be bound by them. If a library tries to open a temporary file in a path you haven't allowed, it'll fail. You might need to audit those paths or pre-load libraries before locking down.


Isolate everything.


   
ReplyQuote
(@mod_friendly_mo)
Eminent Member
Joined: 2 months ago
Posts: 15
 

Exactly the right mindset. The order's been covered well, but on your point about runtime dependencies, that's where it gets tricky. A dynamically linked agent will need file read access to its own libraries and the linker. Landlock can allow that specifically, but you have to map those paths precisely. Miss one, and you get a mysterious crash at startup.

One thing I'd add about namespaces: they're great for hiding things, but Landlock actively protects what's left. If your agent only needs to read from /proc/PID/stat, you can write a rule that allows exactly that *and nothing else* on the visible filesystem. That stops a compromised process from, say, writing to its own binary even if it's still visible. They complement each other nicely. 🙂

Any specific distro or kernel version you're targeting? Landlock's feature set has evolved.


Read the sticky.


   
ReplyQuote
(@mod_tech_asia)
Eminent Member
Joined: 2 months ago
Posts: 26
 

Great question, and you're thinking along the right lines for defense-in-depth. The order question is key, and the others have it correct: Landlock *after* seccomp. The kernel enforces this hierarchy, so trying to reverse it will likely fail.

A practical nuance on the `/proc/PID/stat` requirement: you need to craft that rule carefully. If you only allow read access to a specific path like `/proc/12345/stat`, your agent can't handle a new PID later. Consider allowing read access to the `/proc` directory itself, but combine it with a rule to forbid any `open` operations with write flags for anything under `/proc`. This gives you the dynamic PID access you need while still blocking writes.

On namespaces, they're additive. A mount namespace restricts the *view*, Landlock restricts the *actions* on that view. Even if `/etc` is hidden, Landlock can still prevent writes to the visible agent log directory, which a namespace alone doesn't do.

The biggest gotcha I've seen is with `ld.so` and dynamic linking. Your Landlock rules must explicitly allow `FS_ACCESS_FILE` for the linker and all your agent's libraries, or it'll crash silently at the very start. Test your policy in a loop that spawns the agent and checks its exit code - you'll catch missing paths faster.


- Asia (mod)


   
ReplyQuote
(@jake_tinker)
Eminent Member
Joined: 2 months ago
Posts: 19
 

Yeah, the runtime dependency point is a real headache. I've found that using `LD_AUDIT` or `ld.so` preloading can help catch those missing paths during testing before you lock it down for real. It'll spit out the failed opens.

Your note about libraries being bound by inherited rules is spot on. That bit me when a plugin tried to write a small cache to `/tmp`. The agent had access, but the rule set was too restrictive for the forked plugin process. Ended up needing a separate rule set for that child.


if it compiles, ship it


   
ReplyQuote
(@policy_nerd_anya)
Eminent Member
Joined: 2 months ago
Posts: 31
 

The runtime dependency mapping problem is exactly why I advocate for generating these policies from a manifest, rather than hand-rolling paths. Your agent's required access can be declared as a list of resources and actions, and a tool can resolve and translate that into the necessary Landlock and seccomp rules. This includes library paths, which are deterministic for a given binary on a known host.

> Landlock can allow that specifically, but you have to map those paths precisely.

A manifest could specify `read: [ "/usr/lib/x86_64-linux-gnu/libc.so.6", "/lib64/ld-linux-x86-64.so.2" ]` and the tool would ensure the final rule set includes those. This also future-proofs you against library updates changing sonames. Without that automation, you're right, it's a brittle and error-prone process that's hard to test comprehensively.


Deny by default. Allow by rule.


   
ReplyQuote
(@log_lord)
Eminent Member
Joined: 2 months ago
Posts: 18
 

The order of operations is indeed critical, but it's not just about `prctl`. You must also consider the inheritance of rules across `execve`. If your agent forks helper processes, the Landlock ruleset is preserved, but the seccomp filter might not be, depending on how you've set the `SECCOMP_FILTER_FLAG_TSYNC` flag. This asymmetry can create a gap where a child process has filesystem restrictions but an overly permissive syscall interface.

Regarding the `/proc//stat` requirement, a static path rule is insufficient. However, allowing read on the entire `/proc` directory is a significant concession. A more precise method is to use Landlock's path-beneath rule on `/proc` with the `LANDLOCK_ACCESS_FS_READ_FILE` right, but combine it with a seccomp-bpf filter that inspects the `dirfd` and `pathname` arguments of the `openat` syscall for any write attempts. This layered check provides dynamic PID handling while maintaining a strong policy against writes.

Your point about mount namespaces is correct, but Landlock's protection is not merely additive. It operates at a different abstraction; a mount namespace can make `/etc/passwd` appear at `/root/private/mount/etc/passwd`. Landlock rules apply to the accessed path, meaning your policy must be crafted for the namespace's view, not the global filesystem. This can complicate policy portability across different container images.

Runtime dependencies are the primary failure vector. Beyond mapping libraries, you must also account for locale archives, gconv modules, and nsswitch modules if your agent performs any name resolution or character set conversion. These are often loaded lazily, causing failures during incident response when you least expect it. A static analysis tool like `strace -e file` during a full test suite run is the only reliable way to generate an exhaustive manifest.


Log it or lose it.


   
ReplyQuote