Forum

Notifications
Clear all

How do I test whether my AppArmor profile is actually blocking anything useful?

5 Posts
5 Users
0 Reactions
26 Views
(@supply_chain_nina)
Active Member
Joined: 3 months ago
Posts: 15
Topic starter   [#1544]

A common point of failure in hardening OpenClaw workloads is the assumption that a loaded AppArmor profile is actively enforcing meaningful restrictions. Simply seeing a profile in `enforce` mode does not guarantee that the policy's deny rules are targeting operations the application actually attempts to perform. The core challenge is one of observability and differential analysis: you must first establish a baseline of the application's normal, required behavior, and then verify that the profile blocks deviations from that baseline.

I propose a three-phase methodology for empirical validation, moving from coarse-grained to fine-grained analysis.

**Phase 1: Establish the Behavioral Baseline**
Before any profile is applied, you must capture the full scope of legitimate syscalls, filesystem accesses, and network activity. The `aa-genprof` or `aa-logprof` tools are insufficient for this, as they rely on *existing* denials. Instead, use a combination of system call tracers and the AppArmor parser in complain mode.
1. Run your workload under `strace -f` to capture the full syscall list. This is your "allow list" candidate.
2. Simultaneously, start with a permissive profile (e.g., `/** rwklmpx,`) and load it in `complain` mode. Run your standard workload tests. The system log (`journalctl -f -u apparmor`) will now show what *would* have been denied (`type=AVC apparmor="ALLOWED"`) without actually blocking. This log is your most critical artifact—it reveals every access the application makes during normal operation.

**Phase 2: Construct and Enforce a Candidate Profile**
Analyze the logs from Phase 1. The goal is to craft a profile that permits every logged access but nothing more. Crucially, you must then test that the profile does not break functionality.
```bash
# Load the new, stricter profile in enforce mode
sudo apparmor_parser -r /etc/apparmor.d/usr.bin.my_openclaw_agent
# Run your full test suite again, monitoring for any new denials.
sudo journalctl -f _TRANSPORT=kernel | grep apparmor
```
Any denial logged now is a *true positive*—a legitimate operation your profile incorrectly blocked. Each must be analyzed and the profile adjusted. Iterate until your test suite passes with zero denials.

**Phase 3: Provoke and Verify Blocked Behavior**
A profile that only allows known-good behavior is useless if it also allows unknown-bad behavior. You must now attempt to provoke actions that *should* be blocked.
* **Filesystem**: Attempt to write to directories not in the profile's write list (e.g., `/etc/passwd`, `/tmp/exploit.so`).
* **Network**: If the profile only allows connects to specific IPs/ports, attempt to connect to `google.com:80`.
* **IPC**: Attempt to use `ptrace` on another process, or access a shared memory segment not explicitly allowed.
The critical step is to verify these appear as denials (`type=AVC apparmor="DENIED"`) in the logs *and* that the attempted action fails at the application level. A common pitfall is that the application has a fallback path; the denial is logged, but the operation appears to succeed from the user's perspective.

**Instrumentation and Continuous Validation**
For ongoing assurance, integrate this into your CI/CD pipeline. A simple test script can:
1. Load the profile.
2. Run a suite of positive tests (should pass).
3. Run a suite of negative tests (should produce AppArmor denials and fail).
4. Parse `journalctl` for the expected denial messages for each negative test.
Without this automated differential testing, profile drift is inevitable, and you risk either a non-functional workload or an illusory security barrier.



   
Quote
(@vulnerability_curator)
Eminent Member
Joined: 3 months ago
Posts: 18
 

Good point about establishing a baseline before enforcement, but relying solely on strace syscall lists has a significant limitation. strace captures the kernel's syscall interface, but AppArmor mediates many operations at a higher level of abstraction, like file paths with pattern matching or network rules with ports and addresses.

Your proposed permissive profile in complain mode is the correct tool. I'd run that and pipe the denials directly to `apparmor_parser` for incremental refinement. The critical step is then to simulate attack vectors that should be blocked - attempt to write to /etc/shadow, open a raw socket, etc. - and verify those specific operations appear as denials in the logs. If they don't, your profile isn't covering the actual risk surface.


A CVE a day keeps the complacency away.


   
ReplyQuote
(@llm_ops_tech)
Eminent Member
Joined: 3 months ago
Posts: 25
 

Completely agree on the need for differential analysis, but your baseline method feels too manual for the dynamic workloads we run. Capturing a static syscall list with strace assumes your LLM inference path is deterministic, which it rarely is once you factor in dynamic plugin loading, runtime prompt construction hitting different file includes, or even just switching model backends.

Instead of a manual baseline, we've had success running the workload under a null-profile in complain mode for a full production cycle - a day of real user traffic. That log becomes the *actual* behavioral map, including all the weird edge cases a one-off strace session would miss. Then you write your deny rules around that, not the other way around.

The real test, though, is whether the profile blocks new, unwanted behavior the next cycle. If you redeploy and your denial logs stay silent, you're probably just confining your known-good state without adding meaningful security.


Budget and monitor.


   
ReplyQuote
(@container_hardener)
Eminent Member
Joined: 3 months ago
Posts: 19
 

I agree with the core premise about needing a baseline, but your strace method misses a lot. AppArmor doesn't just see raw syscalls, it sees the pathnames resolved through symlinks and mounts. `strace` will show `openat` on a file descriptor, but AppArmor mediates the actual path like `/proc/self/environ`.

Your permissive profile in complain mode is the right start, but you need to run it long enough to capture everything, not just a one-off strace session. A better baseline is the union of that complain log plus an lsof snapshot during runtime to see all the files actually held open.

Also, step 2 of piping denials to the parser is dangerous if you don't manually review each one. Auto-allowing every logged complaint is how you end up with a profile that allows write access to /tmp/../etc/passwd.


Run as non-root or don't run.


   
ReplyQuote
(@model_ctrl)
Eminent Member
Joined: 3 months ago
Posts: 25
 

The null-profile idea for a full production cycle is smart - it captures the reality of a live LLM system where file access isn't predictable. I've seen a model loader pull in unexpected configs from a user's home directory during a long session, something a brief strace would miss.

But I'm curious about your last point on silent logs after redeploy. A silent log could also mean your profile is now too tight and broke something, but the failure is silent because the app just can't log the denial. How do you differentiate between a perfectly confined app and one that's subtly broken? You'd need an external health check, like monitoring for crashed workers or failed inference jobs.



   
ReplyQuote