Proof of concept. This plugin explores in-kernel LSM enforcement for Falco. See the Limitations section for what it can't do yet and possible ways to address it.
Falco observes the stream of system calls, matches it against a set of rules, and raises an alert when a rule fires, it is fundamentally a detection tool.
This plugin extends Falco from detection to prevention: it can stop a threat in the kernel, before the offending operation completes, instead of only reporting it after the fact.
The user defines Falco-like policies, written in the same condition language as ordinary Falco rules. At plugin initialization each policy's condition is transpiled into a small eBPF program and compiled; when Falco opens a capture, those programs are attached to the relevant LSM hooks. From then on, whenever a controlled operation occurs, such as a file open or a program execution, the hook evaluates the condition in-kernel and applies the policy's action:
notify— emit an event to Falco (surfaced as a normal alert) and let the operation proceed.deny— block the operation by returning-EPERM: the syscall fails cleanly, the process keeps running (it is not killed), and an event is still emitted.
flowchart LR
subgraph USERSPACE
direction TB
P["Policies<br/>(Falco conditions)"]
C["C program"]
P -->|static analysis| C
end
subgraph KERNEL
direction TB
SE["LSM hook fires<br/>(file open / program exec)"]
E["eBPF program<br/>(compiled policy)"]
D{"Condition<br/>matches?"}
A["ALLOW<br/>(return 0)"]
B["DENY<br/>(return -EPERM)"]
SE --> E
E -->|"in-kernel condition evaluation ⚡"| D
D -->|no| A
D -->|yes| B
end
C -->|"compiled at init & attached to LSM hook"| E
- Linux kernel with BTF (
/sys/kernel/btf/vmlinux) and BPF LSM enabled:CONFIG_BPF_LSM=yandbpfin the active LSM list (boot withlsm=...,bpf; verifycat /sys/kernel/security/lsmcontainsbpf). clangandbpftoolavailable at runtime — the plugin generatesvmlinux.hand compiles each policy to eBPF at init.
cargo build --releaseProduces the loadable plugin at target/release/libenforcement.so.
Point a Falco config at the built plugin and your policy ruleset (see
policies.example.yaml):
# falco.yaml
plugins:
- name: enforcement
library_path: libenforcement.so
# path to the policy ruleset to compile
init_config: policies.example.yaml
load_plugins:
- enforcementThen run Falco with that config and a rules file (see
rules.example.yaml):
sudo falco -c falco.yaml -r rules.example.yamlA policy condition must be anchored on evt.type, and may combine the fields
below with and / or / not and the operators =, !=, <, <=, >,
>=, in, contains, startswith, endswith, exists.
| Field | Type | Description |
|---|---|---|
evt.type |
string | The operation being evaluated; required to anchor the condition. Supported values map to an LSM hook (see below). |
fd.name |
string | The full, kernel-resolved path the operation targets — the file being opened, or the program being executed. |
evt.arg.flags |
integer | Raw open flags (file->f_flags). open family only. Kernel-processed bitmask |
proc.name |
string | Process name — the kernel comm. |
proc.pid |
integer | Process ID (thread-group ID). |
thread.tid |
integer | Thread ID. |
user.uid |
integer | User ID of the process. |
group.gid |
integer | Group ID of the process. |
Each value is governed by the LSM hook it maps to:
evt.type |
LSM hook |
|---|---|
open, openat, openat2, creat |
file_open |
execve, execveat |
bprm_check_security |
The supported set is limited by which operations have a matching LSM hook — this is not fundamental. See Limitations.
Available on the emitted event, for use in rule output and further filtering:
| Field | Type | Description |
|---|---|---|
enforcement.policy |
string | Name of the policy that matched. |
enforcement.reason |
string | The matched policy's reason. |
enforcement.action |
string | The matched policy's action (notify or deny). |
enforcement.condition |
string | The matched policy's condition. |
enforcement.syscall |
string | The LSM hook that fired (e.g. file_open). |
enforcement.pid |
integer | PID of the process that triggered the match. |
enforcement.tid |
integer | TID of the triggering thread. |
enforcement.comm |
string | comm of the triggering process. |
Only operations with a matching LSM hook are supported. The usable
evt.type values are limited to operations that have a corresponding LSM
hook — currently the open and exec families.
Possible way to address it: the same transpile-to-eBPF approach could
instead target raw syscall interception (a kprobe/tracepoint on the
syscall itself), which would potentially cover every evt.type.
Raw interception can't return a clean -EPERM from the security layer,
so blocking would rely on bpf_override_return (kernel error-injection).
Only statelessly-extractable fields are supported. A condition can only reference fields that can be resolved on the spot at the hook — from the current task and the hook's arguments (see Supported fields). Fields that Falco derives from accumulated cross-event state (process lineage, container/k8s metadata, resolved fd tables, …) are not available.
Possible way to address it: consume Falco's syscall event stream to build and maintain in-kernel state (BPF maps) that the probes can read, enriching them with fields that cannot be computed from a single hook invocation.