Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

BPF LSM

Overview

BPF LSM (Linux Security Module) allows attaching eBPF programs to LSM hooks, enabling dynamic, programmable security policies without writing kernel modules. Merged in Linux 5.7 (commit fc61143), BPF LSM lets you implement security checks (file access, task creation, network operations) as BPF programs that run at LSM hook points.

BPF LSM complements SELinux and AppArmor by allowing custom, per-pod or per-container security policies that can be deployed and updated without rebooting.

Introduced: Linux 5.7 (commit fc61143) Source: security/bpf/ Kconfig: CONFIG_BPF_LSM=y


Architecture

flowchart TD
    subgraph UserSpace["Userspace"]
        LIBBPF["libbpf / bpftool"]
        POLICY["Security policy (BPF program)"]
        TETRAGON["Tetragon / Falco"]
    end

    subgraph Kernel["Kernel"]
        LSM_CORE["LSM framework"]
        BPF_LSM["BPF LSM"]
        HOOKS["LSM hooks<br>(file_open, task_alloc, ...)"]
        VERIFIER["BPF Verifier"]
    end

    subgraph BPF["BPF Programs"]
        PROG1["file_open_check<br>(block unauthorized opens)"]
        PROG2["task_create_check<br>(limit fork)"]
        PROG3["socket_connect_check<br>(network policy)"]
        MAPS["BPF maps<br>(policy state)"]
    end

    LIBBPF -->|load BPF program| BPF_LSM
    POLICY --> LIBBPF
    TETRAGON -->|load policies| LIBBPF
    LSM_CORE --> HOOKS
    HOOKS --> BPF_LSM
    VERIFIER -->|verify before load| BPF_LSM
    BPF_LSM --> PROG1
    BPF_LSM --> PROG2
    BPF_LSM --> PROG3
    PROG1 --> MAPS
    PROG2 --> MAPS
    PROG3 --> MAPS

LSM Hooks

BPF LSM programs attach to existing LSM hooks:

HookCalled WhenUse Case
file_openFile openedAccess control
file_permissionPermission checkRead/write control
task_allocProcess createdFork limiting
task_killSignal sentSignal filtering
socket_connectTCP/UDP connectNetwork policy
socket_bindSocket bindPort restriction
bprm_check_securityExecExec policy
inode_createFile createdCreation policy
inode_unlinkFile deletedDeletion policy
capableCapability checkCapability restriction
ptrace_access_checkptrace attachAnti-debugging
bpfBPF syscallBPF self-protection
cred_prepareCredential copyCredential policy

BPF LSM Program Structure

// SPDX-License-Identifier: GPL-2.0
#include <linux/bpf.h>
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_tracing.h>

SEC("lsm/file_open")
int BPF_PROG(restrict_open, struct file *file)
{
    /* Get current task's cgroup */
    u64 cgroup_id = bpf_get_current_cgroup_id();

    /* Check if file path matches blocked pattern */
    char path[256];
    bpf_d_path(&file->f_path, path, sizeof(path));

    /* Block access to /etc/shadow for non-root */
    if (bpf_strncmp(path, 11, "/etc/shadow") == 0) {
        u32 uid = bpf_get_current_uid_gid();
        if (uid != 0)
            return -EACCES;  /* Deny access */
    }

    return 0;  /* Allow */
}

char LICENSE[] SEC("license") = "GPL";

LSM Hook Execution Order

The LSM framework calls hooks in registration order. When multiple LSMs are active (e.g., SELinux + BPF LSM), each gets a chance to deny:

sequenceDiagram
    participant App as Userspace
    participant K as Kernel (file_open)
    participant SEL as SELinux
    participant BPF as BPF LSM
    participant FS as Filesystem

    App->>K: open("/etc/shadow")
    K->>SEL: security_file_open()
    SEL-->>K: 0 (allow)
    K->>BPF: BPF LSM hook
    BPF->>BPF: Check policy (uid != 0 → deny)
    BPF-->>K: -EACCES
    K-->>App: Permission denied

The ret parameter in BPF LSM programs carries the return value from previous LSMs. If a prior LSM already denied, the BPF program should propagate that denial:

SEC("lsm/file_open")
int BPF_PROG(extra_check, struct file *file, int ret)
{
    if (ret != 0)
        return ret;  /* Already denied by prior LSM */
    /* Additional check */
    return 0;
}

BPF LSM Helpers

/* Task information */
u64 bpf_get_current_pid_tgid(void);    /* PID + TID */
u64 bpf_get_current_uid_gid(void);     /* UID + GID */
int bpf_get_current_comm(void *buf, u32 size); /* Process name */

/* Cgroup */
u64 bpf_get_current_cgroup_id(void);   /* Cgroup ID */

/* Path operations */
int bpf_d_path(struct path *path, char *buf, u32 sz); /* Path to string */

/* String operations */
int bpf_strncmp(const char *s1, u32 s1_size, const char *s2); /* Compare */

/* Override return value */
int bpf_override_return(void *regs, unsigned long rc); /* Override */

/* Time */
u64 bpf_ktime_get_ns(void);           /* Monotonic timestamp */

/* BPF map operations */
void *bpf_map_lookup_elem(struct bpf_map *map, const void *key);
int bpf_map_update_elem(struct bpf_map *map, const void *key,
                        const void *value, u64 flags);
int bpf_map_delete_elem(struct bpf_map *map, const void *key);

/* Task credentials */
struct cred *bpf_get_task_cred(struct task_struct *task);

/* CO-RE (Compile Once, Run Everywhere) */
void *bpf_core_read(void *dst, int size, const void *src);

Loading BPF LSM Programs

Using libbpf

#include <bpf/libbpf.h>
#include <bpf/bpf.h>

int main(void)
{
    struct bpf_object *obj;
    struct bpf_link *link;

    /* Open and load BPF object */
    obj = bpf_object__open_file("restrict_open.bpf.o", NULL);
    bpf_object__load(obj);

    /* Attach to LSM hook */
    struct bpf_program *prog = bpf_object__find_program_by_name(obj, "restrict_open");
    link = bpf_program__attach_lsm(prog);

    /* Program is now active */
    pause();
    bpf_link__destroy(link);
    return 0;
}
#include "restrict_open.skel.h"

int main(void)
{
    struct restrict_open *skel;
    int err;

    skel = restrict_open__open_and_load();
    if (!skel) {
        fprintf(stderr, "Failed to open/load\n");
        return 1;
    }

    err = restrict_open__attach(skel);
    if (err) {
        fprintf(stderr, "Failed to attach\n");
        restrict_open__destroy(skel);
        return 1;
    }

    printf("BPF LSM active. Ctrl+C to exit.\n");
    while (1) sleep(1);

    restrict_open__destroy(skel);
    return 0;
}

Using bpftool

# Load BPF LSM program
bpftool prog load restrict_open.bpf.o /sys/fs/bpf/restrict_open type lsm

# Attach to LSM hook
bpftool link create /sys/fs/bpf/restrict_open_link \
    prog /sys/fs/bpf/restrict_open \
    target lsm/file_open

# List loaded BPF LSM programs
bpftool prog list type lsm

# Show attached links
bpftool link list

# Detach
bpftool link detach id <link_id>
bpftool prog detach id <prog_id> type lsm

Use Cases

Container Security Policy

SEC("lsm/socket_connect")
int BPF_PROG(container_network_policy, struct socket *sock,
             struct sockaddr *address, int addrlen)
{
    u64 cgroup_id = bpf_get_current_cgroup_id();

    /* Block containers from connecting to metadata service */
    if (address->sa_family == AF_INET) {
        struct sockaddr_in *addr = (struct sockaddr_in *)address;
        if (addr->sin_addr.s_addr == htonl(0xC0A80001)) /* 192.168.0.1 */
            return -ECONNREFUSED;
    }

    return 0;
}

Exec Restriction

SEC("lsm/bprm_check_security")
int BPF_PROG(restrict_exec, struct linux_binprm *bprm)
{
    char filename[256];
    bpf_probe_read_str(filename, sizeof(filename), bprm->filename);

    /* Block execution of specific binaries */
    if (bpf_strncmp(filename, 15, "/usr/bin/sudo") == 0)
        return -EPERM;

    return 0;
}

Rate-Limiting Syscalls

struct {
    __uint(type, BPF_MAP_TYPE_HASH);
    __uint(max_entries, 1024);
    __type(key, u32);    /* PID */
    __type(value, u64);  /* Last kill timestamp */
} kill_rate SEC(".maps");

SEC("lsm/task_kill")
int BPF_PROG(rate_limit_kill, struct task_struct *p,
             struct kernel_siginfo *info, int sig, int ret)
{
    if (ret != 0) return ret;

    u32 pid = bpf_get_current_pid_tgid() >> 32;
    u64 now = bpf_ktime_get_ns();
    u64 *last = bpf_map_lookup_elem(&kill_rate, &pid);

    if (last) {
        if (now - *last < 100000000ULL)  /* 100ms */
            return -EPERM;
    }

    bpf_map_update_elem(&kill_rate, &pid, &now, BPF_ANY);
    return 0;
}

Capability-Based Restrictions

SEC("lsm/capable")
int BPF_PROG(restrict_cap, const struct cred *cred,
             struct user_namespace *targ_ns,
             int cap, int audit, int ret)
{
    if (ret != 0) return ret;

    /* Deny CAP_SYS_ADMIN for specific processes */
    if (cap == CAP_SYS_ADMIN) {
        char comm[16];
        bpf_get_current_comm(comm, sizeof(comm));
        char target[] = "untrusted_app";
        if (bpf_strncmp(comm, sizeof(target), target) == 0)
            return -EPERM;
    }

    return 0;
}

Threat Model

What BPF LSM Protects Against

ThreatMitigation
Unauthorized file accessPath-based allow/deny lists
Container escapeCgroup-scoped policies on task_alloc, bprm_check_security
Privilege escalationCapability restrictions via capable hook
Network exfiltrationsocket_connect filtering by destination
Malicious binary executionExec allowlists via bprm_check_security
Signal abuseRate-limiting via task_kill

What BPF LSM Does NOT Protect Against

LimitationExplanation
Kernel exploitsBPF LSM runs inside the kernel; a kernel bug can bypass it
BPF verifier bypassIf the BPF verifier has bugs, malicious programs can be loaded
Denial of serviceA buggy BPF LSM program can block legitimate operations
Hardware attacksNo protection against cold-boot, DMA, or physical access
Policy bypass via CAP_BPFUsers with CAP_BPF + CAP_MAC_ADMIN can load/replace policies

Attack Surface of BPF LSM Itself

flowchart TD
    subgraph AttackSurface["BPF LSM Attack Surface"]
        VERIFIER["BPF Verifier bugs"]
        MAP_LEAK["Map data leaks"]
        HOOK_RACE["TOCTOU in hook checks"]
        POLICY_BYPASS["Policy logic errors"]
        PRIV_ESC["CAP_BPF + CAP_MAC_ADMIN abuse"]
        KERNEL_BUG["Kernel pointer leaks via helpers"]
    end

    subgraph Mitigations["Mitigations"]
        UNPRIV["Unprivileged BPF disabled"]
        BPF_RESTRICT["bpf_restrict_unpriv sysctl"]
        LOCKDOWN["Kernel lockdown mode"]
        KASLR["KASLR"]
        CFI["Control-Flow Integrity"]
    end

    VERIFIER --> UNPRIV
    MAP_LEAK --> BPF_RESTRICT
    HOOK_RACE --> POLICY_BYPASS
    PRIV_ESC --> LOCKDOWN
    KERNEL_BUG --> KASLR
    KERNEL_BUG --> CFI

Key mitigations in the kernel:

  • BPF verifier (kernel/bpf/verifier.c): Ensures programs terminate, don’t access arbitrary memory, and don’t leak pointers.
  • Unprivileged BPF restriction: kernel.unprivileged_bpf_disabled=1 prevents unprivileged BPF usage.
  • Kernel lockdown mode: Prevents BPF programs from accessing kernel memory even with CAP_BPF.
  • KASLR: Makes kernel address guessing harder for BPF-based exploits.

Kernel Internals

BPF LSM Hook Registration

When CONFIG_BPF_LSM=y, the kernel registers BPF as an LSM during boot:

/* security/bpf/hooks.c */
static struct security_hook_list bpf_lsm_hooks[] __lsm_ro_after_init = {
    LSM_HOOK_INIT(file_open, bpf_lsm_file_open),
    LSM_HOOK_INIT(file_permission, bpf_lsm_file_permission),
    LSM_HOOK_INIT(task_alloc, bpf_lsm_task_alloc),
    LSM_HOOK_INIT(task_kill, bpf_lsm_task_kill),
    LSM_HOOK_INIT(socket_connect, bpf_lsm_socket_connect),
    /* ... many more hooks ... */
};

static int __init bpf_lsm_init(void)
{
    security_add_hooks(bpf_lsm_hooks, ARRAY_SIZE(bpf_lsm_hooks), "bpf");
    pr_info("LSM: BPF LSM initialized\n");
    return 0;
}

BPF Program Invocation Path

sequenceDiagram
    participant Sys as Syscall (e.g., open)
    participant VFS as VFS Layer
    participant LSM as LSM Framework
    participant SEL as SELinux hook
    participant BL as BPF LSM hook
    participant BPF as BPF program
    participant Map as BPF maps

    Sys->>VFS: do_sys_open()
    VFS->>LSM: security_file_open(file)
    LSM->>SEL: selinux_file_open()
    SEL-->>LSM: 0 (allow)
    LSM->>BL: bpf_lsm_file_open()
    BL->>BL: find attached BPF programs
    BL->>BPF: run BPF_PROG(file)
    BPF->>Map: bpf_map_lookup_elem()
    Map-->>BPF: policy data
    BPF-->>BL: 0 (allow) or -EACCES
    BL-->>LSM: result
    LSM-->>VFS: result
    VFS-->>Sys: fd or error

BPF LSM Internal Structures

/* kernel/bpf/bpf_lsm.c */
struct bpf_lsm_link {
    struct bpf_link link;        /* Base BPF link */
    struct bpf_prog *prog;      /* Attached BPF program */
    struct hlist_node tramp_link;/* Trampoline linkage */
};

/* Programs are attached via BPF trampoline */
/* kernel/bpf/trampoline.c */
struct bpf_trampoline {
    struct hlist_node hlist;
    struct btf_func_model model;
    void *func;
    struct bpf_prog *progs[BPF_TRAMP_MAX];
    /* ... */
};

BPF Verifier Checks for LSM Programs

The BPF verifier performs additional checks for BPF_PROG_TYPE_LSM:

/* kernel/bpf/verifier.c */
static int check_lsm_prog(struct bpf_verifier_env *env,
                          struct bpf_prog *prog)
{
    /* LSM programs must return int */
    if (prog->type == BPF_PROG_TYPE_LSM) {
        /* Verify return type is int */
        /* Verify first argument matches hook signature */
        /* Verify BTF IDs are valid for attachment */
        /* Ensure program doesn't modify hook arguments */
    }
    return 0;
}

Production Use: Tetragon and Falco

Tetragon (Cilium)

Tetragon uses BPF LSM for real-time enforcement in Kubernetes:

# Tetragon TracingPolicy for file access enforcement
apiVersion: cilium.io/v1alpha1
kind: TracingPolicy
metadata:
  name: restrict-sensitive-files
spec:
  kprobes:
  - call: "fd_install"
    syscall: false
    args:
    - index: 0
      type: int
    - index: 1
      type: "file"
  lsmhooks:
  - hook: "file_open"
    args:
    - index: 0
      type: "file"
    selectors:
    - matchBinaries:
      - operator: NotIn
        values:
        - "/usr/bin/kubectl"
        - "/usr/bin/systemctl"
      matchActions:
      - action: Sigkill

Falco with BPF LSM

Falco can use BPF LSM for syscall interception:

# Falco rule using BPF LSM
- rule: Detect Shell in Container
  desc: Detect shell spawning in container
  condition: >
    spawned_process and container and
    proc.name in (bash, sh, zsh)
  output: >
    Shell spawned in container
    (user=%user.name container=%container.name
     shell=%proc.name parent=%proc.pname)
  priority: WARNING
  tags: [container, shell]

Security Considerations

# BPF LSM requires CAP_BPF + CAP_LSM
# Or privileged container

# Check if BPF LSM is available
cat /sys/kernel/security/lsm
# lockdown,capability,landlock,bpf

# BPF LSM ordering (last checked first)
# Programs run in reverse attach order

Hardening BPF LSM Deployments

# 1. Disable unprivileged BPF
sysctl -w kernel.unprivileged_bpf_disabled=1

# 2. Enable kernel lockdown
echo integrity > /sys/kernel/security/lockdown

# 3. Pin BPF programs to prevent removal
bpftool prog load policy.bpf.o /sys/fs/bpf/policy type lsm

# 4. Use BPF token for delegated loading (Linux 6.9+)
# Mount bpffs with delegation
mount -t bpf bpffs /sys/fs/bpf -o delegate_cmds=PROG_LOAD

# 5. Audit BPF program loads
# Enable audit for BPF syscalls
auditctl -a always,exit -F arch=b64 -S bpf -k bpf_load

Common Pitfalls

PitfallDescriptionSolution
Missing LSM entryBPF LSM not in /sys/kernel/security/lsmAdd bpf to lsm= boot parameter
No BTFKernel lacks BTF dataEnable CONFIG_DEBUG_INFO_BTF=y
Program detachedProcess exit removes programsPin to bpffs
Verifier rejectionProgram too complexSimplify logic, use tail calls
TOCTOU racesPath changes between check and useUse inode-based checks instead of path-based

Configuration Examples

Boot Parameter Setup

# Enable BPF LSM via kernel command line
# Edit /etc/default/grub or bootloader config
GRUB_CMDLINE_LINUX="lsm=lockdown,capability,yama,apparmor,bpf"

# Update bootloader
update-grub

# Verify after reboot
cat /sys/kernel/security/lsm
# lockdown,capability,yama,apparmor,bpf

Kconfig Requirements

# Required kernel config
CONFIG_BPF_LSM=y              # BPF LSM support
CONFIG_BPF_SYSCALL=y          # BPF syscall
CONFIG_DEBUG_INFO_BTF=y       # BTF for CO-RE
CONFIG_BPF_JIT=y              # BPF JIT compiler
CONFIG_CGROUP_BPF=y           # BPF cgroup support
CONFIG_NET=y                  # For network hooks
CONFIG_BPF_EVENTS=y           # For tracing integration

Delegated BPF Loading (Linux 6.9+)

# Create delegated BPF filesystem
mkdir -p /sys/fs/bpf/delegated

# Mount with delegation
mount -t bpf bpffs /sys/fs/bpf/delegated \
    -o delegate_cmds=PROG_LOAD,MAP_CREATE

# Non-root users with CAP_BPF can now load programs
# to /sys/fs/bpf/delegated/

Integration with systemd

# /etc/systemd/system/bpf-policy.service
[Unit]
Description=Load BPF LSM security policy
After=network.target

[Service]
Type=oneshot
ExecStart=/usr/local/bin/bpf-lsm-loader /etc/bpf-lsm/policy.bpf.o
RemainAfterExit=yes

[Install]
WantedBy=multi-user.target

BPF Maps for Policy State

BPF maps provide persistent state for BPF LSM programs:

Map Types Used in BPF LSM

Map TypeUse CaseExample
BPF_MAP_TYPE_HASHAllow/deny listsBlocked inodes, UIDs
BPF_MAP_TYPE_RINGBUFEvent loggingAudit trail of denials
BPF_MAP_TYPE_ARRAYCountersDenial counts per UID
BPF_MAP_TYPE_LRU_HASHBounded stateRate-limiting timestamps
BPF_MAP_TYPE_PERCPU_ARRAYPer-CPU countersLock-free statistics

Example: Policy Map with Userspace Update

/* BPF side */
struct {
    __uint(type, BPF_MAP_TYPE_HASH);
    __uint(max_entries, 1024);
    __type(key, u32);      /* UID */
    __type(value, u64);    /* Bitmask of allowed operations */
} policy_map SEC(".maps");

SEC("lsm/file_open")
int BPF_PROG(check_open, struct file *file, int ret)
{
    if (ret != 0) return ret;

    u32 uid = bpf_get_current_uid_gid() & 0xFFFFFFFF;
    u64 *policy = bpf_map_lookup_elem(&policy_map, &uid);
    if (!policy)
        return -EACCES;  /* Default deny */

    if (!(*policy & ALLOW_FILE_OPEN))
        return -EACCES;

    return 0;
}
# Userspace: update policy via bpftool
bpftool map update pinned /sys/fs/bpf/policy_map \
    key 1000 0 0 0 \
    value 0x07 0 0 0 0 0 0 0

Source Files

FileContents
security/bpf/hooks.cBPF LSM hook registration
security/bpf/lsm.cBPF LSM core
kernel/bpf/bpf_lsm.cBPF LSM program type
kernel/bpf/trampoline.cBPF trampoline for hook attachment
kernel/bpf/verifier.cBPF verifier (includes LSM checks)
include/linux/bpf_lsm.hBPF LSM header
include/linux/lsm_hooks.hLSM hook definitions

Further Reading


See Also

  • eBPF — BPF subsystem overview
  • LSM — LSM framework
  • SELinux — SELinux LSM
  • Landlock — Landlock LSM
  • seccomp — seccomp-bpf
  • dm-verity — verified boot