Kernel, eBPF and LSM
Three angles on the same subject: what separates userspace from the kernel and where the kernel decides whether a program may execute; what eBPF is and why it is safe to load into the kernel; and how BPF-LSM provides the exact point to say "no".
In 30 seconds
When a program is about to execute, the kernel passes through a checkpoint called bprm_check_security. An eBPF program hangs there: it looks at the binary name, checks a table of blocked commands and, on a match, returns -EPERM, so the exec never happens. eBPF is code that runs inside the kernel but first passes a verifier that proves it cannot hang or touch invalid memory. LSM (Linux Security Modules) is the kernel's frame of security hooks where that decision is anchored.
bprm_check_security point in the kernel; eBPF checks the table and denies with -EPERM.From scratch: userspace, kernel and the syscall
The system runs in two worlds. Userspace is where ordinary programs live; the kernel is the privileged core controlling CPU, memory, disk and network. A program does not touch hardware directly: it makes a syscall, a formal request to the kernel ("execute this binary", "open this socket"). Executing a program goes through a syscall of the execve family, and inside the kernel that path crosses the bprm_check_security hook (from binary parameters): the last point where security policy can approve or veto the exec before the new program image takes over.
Why it matters
The userspace hook (the exit 2) depends on the IDE calling the hook. If the pretool is disconnected, or the agent finds a way to execute something around it, userspace does not see it. The kernel sees everything: no process executes without passing through bprm_check_security. Hence the need for a decision down there: a safety net that does not depend on the IDE's cooperation.
How it correlates
eBPF, verifier and LSM fit together like this: LSM provides the hooks (where to decide); eBPF is the technology that allows loading logic into those hooks without recompiling the kernel; and the verifier is what makes that acceptable, proving, before loading, that the program terminates and corrupts nothing. That is why the loops are fixed-size with #pragma unroll and the paths are bounded: it is what the verifier demands. The data structures attached to that program are BPF maps, of several types as needed.
How Nemesis applies it
The program declares the maps it uses: a hash table with the blocked commands, a ring buffer to emit events to userspace, and LPM tries for the egress allowlist (detailed in group 3).
📄 .nemesis/ebpf-kernel/ebpf/nemesis-block.bpf.c:20-67 · mapas hash, ringbuf, array e LPM trie
struct {
__uint(type, BPF_MAP_TYPE_HASH);
__uint(max_entries, 256);
__type(key, struct command_key);
__type(value, __u8);
} blocked_commands SEC(".maps");
struct {
__uint(type, BPF_MAP_TYPE_RINGBUF);
__uint(max_entries, 1 << 24);
} events SEC(".maps");And the exec hook: it honors a decision made earlier in the LSM chain (never overriding a ret already taken), checks whether the process belongs to the agent, reads the binary basename and, if it is in the table, returns -EPERM.
📄 .nemesis/ebpf-kernel/ebpf/nemesis-block.bpf.c:68-138 · hook lsm/bprm_check_security
SEC("lsm/bprm_check_security")
int BPF_PROG(nemesis_check_exec, struct linux_binprm *bprm, int ret)
{
if (ret != 0)
return ret; // respeita decisão anterior da cadeia LSM
// ... escopo por cgroup do agente, lê basename ...
blocked = bpf_map_lookup_elem(&blocked_commands, &key);
if (!blocked)
return 0;
return -EPERM; // nega o exec no kernel
}Decisions and limits
The program never overrides an earlier denial in the LSM chain: if ret != 0, it returns that same ret. Overriding it would punch a hole in another security policy of the system. And the scope is declared: this is Linux. On macOS there is no eBPF (the analog would be the Endpoint Security Framework, not yet built); there, without the pretool, there is no kernel backstop.
BPF-LSM also needs to be enabled in the kernel (bpf in /sys/kernel/security/lsm), and the eBPF daemon is only started when the pretool confirms that support.
Self-check
▸Why does kernel control cover cases the userspace hook does not?
Because every binary execution passes through bprm_check_security in the kernel, regardless of the IDE calling the pretool. It is the backstop when the hook is disconnected.
▸What is the eBPF verifier for?
It proves, before loading, that the program terminates (no infinite loops) and touches no invalid memory. That is what makes it safe to run custom code inside the kernel; hence fixed-size loops.
▸What happens if another LSM already denied the action?
The program returns the received ret unchanged. The decision only happens when the chain has not decided yet (ret == 0).
Further reading
Citações verificadas contra o repositório. Voltar ao índice.