Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/DeelerDev/linux/llms.txt

Use this file to discover all available pages before exploring further.

The Linux Security Modules (LSM) framework is the kernel’s infrastructure for pluggable mandatory access control. It was incorporated into the mainline kernel in December 2003, evolving from a joint effort between several security projects — including SELinux, Immunix, SGI, and Janus — in response to a challenge from Linus Torvalds to provide a general-purpose security hook mechanism. By itself LSM adds no restrictions; it provides the substrate on which individual security modules register policy.

How the LSM framework works

LSM inserts hook calls at security-relevant points throughout the kernel: before a file is opened, before a socket is created, when a process’s credentials change, when an IPC object is accessed, and hundreds of other operations. Each hook call passes relevant context to registered modules, which return a decision (allow or deny) based on their own policy.

Hook-based architecture

Hooks are maintained in lists — one list per hook type. When the kernel reaches a hook point, it calls every registered handler for that hook in the order specified by CONFIG_LSM. The operation is denied if any handler returns a non-zero error code.
/* From security/security.c — simplified illustration of a hook call */
int security_inode_permission(struct inode *inode, int mask)
{
    /* Calls each registered LSM hook for inode permission checks */
    return call_int_hook(inode_permission, 0, inode, mask);
}
Security data is attached to kernel objects as opaque blobs via void * fields in structures like struct task_struct, struct cred, struct inode, struct file, struct super_block, struct sk_buff, and struct kern_ipc_perm. Each LSM manages its own blob layout.

Security hook list registration

An LSM registers its hooks at init time by calling security_add_hooks() with a struct security_hook_list array:
static struct security_hook_list my_lsm_hooks[] __ro_after_init = {
    LSM_HOOK_INIT(inode_permission, my_lsm_inode_permission),
    LSM_HOOK_INIT(file_open,        my_lsm_file_open),
    LSM_HOOK_INIT(task_kill,        my_lsm_task_kill),
};

static int __init my_lsm_init(void)
{
    security_add_hooks(my_lsm_hooks, ARRAY_SIZE(my_lsm_hooks), &my_lsm_id);
    return 0;
}
The struct lsm_id struct identifies the module by name and a numeric ID from uapi/linux/lsm.h. Hook registration is permanent — once registered, hooks cannot be removed (with the limited deprecated exception of SELinux’s self-removal path, which is no longer supported).

Hook stacking

When multiple LSMs are active, each hook is called in sequence. The capabilities module always runs first. Minor LSMs (Yama, Lockdown) run next, followed by the major MAC module (SELinux, AppArmor, Smack, or TOMOYO). Landlock and BPF LSM can stack alongside a major LSM. The active LSM stack is readable at runtime:
cat /sys/kernel/security/lsm
# example output: capability,landlock,lockdown,yama,selinux
The LSM framework requires CONFIG_SECURITY=y. The capabilities module (security/commoncap.c) is always compiled in and always runs first regardless of other configuration.

Enabling LSMs at boot

LSMs are selected at build time and optionally overridden at boot with the lsm= kernel command line parameter. The parameter accepts a comma-separated ordered list:
# Example: enable SELinux and Yama, in that order
lsm=capability,yama,selinux
The CONFIG_DEFAULT_SECURITY and CONFIG_LSM build options set the compiled-in default order. To check what the running kernel was built with:
cat /boot/config-$(uname -r) | grep CONFIG_LSM

Available LSMs

SELinux was originally developed by the NSA and is the reference implementation of mandatory access control in Linux. Every subject (process) and object (file, socket, IPC object) receives a security label called a security context, for example system_u:system_r:httpd_t:s0. A policy database — compiled from human-readable policy source — defines which context pairs are allowed to interact for each operation class.SELinux operates in two modes:
  • Enforcing — policy violations are denied and logged.
  • Permissive — policy violations are logged but allowed. Useful for developing or debugging policy.
Check and change the current mode:
getenforce          # prints Enforcing, Permissive, or Disabled
setenforce 0        # switch to permissive (runtime, not persistent)
setenforce 1        # switch back to enforcing
The SELinux policy is typically stored in /etc/selinux/ and loaded by the init system. On RHEL-family systems, the semanage, audit2allow, and restorecon tools are the primary policy management interfaces.
AppArmor confines programs using per-application profiles that specify which filesystem paths, capabilities, and network operations a program may use. Profiles are simpler to write than SELinux policy because they operate on file paths rather than labels.Check loaded profiles and their modes:
aa-status
A profile can be in one of three modes:
  • enforce — violations are denied and logged.
  • complain — violations are logged but allowed. Used for developing profiles.
  • disabled — the profile is loaded but not enforced.
Profiles live in /etc/apparmor.d/. Load or reload a profile:
apparmor_parser -r /etc/apparmor.d/usr.sbin.nginx
AppArmor is the default MAC module on Ubuntu and Debian. The aa-genprof and aa-logprof tools help generate profiles from audit logs.
Smack (Simplified Mandatory Access Control Kernel) assigns short ASCII labels to processes and filesystem objects. A rule table controls which subject labels may read, write, or execute objects with a given label. Smack is used in embedded Linux environments, including Tizen.Labels are stored as extended attributes (security.SMACK64) on filesystem objects. The active label for a process can be read from /proc/self/attr/smack/current.
TOMOYO is a pathname-based MAC system with a distinctive learning mode: it can operate in a training mode that records all access attempts, then enforce only the recorded set. This makes it practical for environments where writing policy by hand is not feasible.Domains in TOMOYO correspond to program execution paths (the sequence of execve() calls that led to a process). Policy is stored in /etc/tomoyo/.
Landlock allows any process — including unprivileged ones — to voluntarily restrict its own access to filesystem paths and TCP ports by constructing a ruleset and enforcing it. Unlike other LSMs, Landlock policy is expressed entirely in user space without requiring any kernel policy files.Landlock stacks on top of other LSMs — a Landlock ruleset can only add restrictions, never remove restrictions imposed by DAC or other LSMs.See Landlock usage below.
BPF LSM (CONFIG_BPF_LSM) allows eBPF programs to be attached to LSM hook points. This makes it possible to implement and update security policy at runtime without a kernel recompile or reboot. The bpf() syscall with BPF_PROG_TYPE_LSM is used to load hook programs.BPF LSM programs must be loaded by a process with CAP_BPF and CAP_PERFMON or CAP_SYS_ADMIN.

Using SELinux

Enable SELinux by adding it to the lsm= boot parameter and ensuring CONFIG_SECURITY_SELINUX=y is set. On most distributions, the init system loads the compiled policy automatically. The /sys/fs/selinux/ pseudo-filesystem exposes the SELinux policy interface. Process labels are readable from /proc/<pid>/attr/current.
# Show the SELinux context of a running process
ps -eZ | grep nginx

# Show the SELinux context of a file
ls -Z /var/www/html/index.html

# Restore default file contexts from policy
restorecon -v /var/www/html/index.html

Using AppArmor

AppArmor profiles are plain text files. A minimal profile that confines a hypothetical daemon to read from /etc/myapp/ and write to /var/log/myapp/:
#include <tunables/global>

/usr/sbin/myapp {
  #include <abstractions/base>

  /etc/myapp/**  r,
  /var/log/myapp/** w,
  /run/myapp.pid rw,
}
Load the profile and set it to enforcing:
apparmor_parser -a /etc/apparmor.d/usr.sbin.myapp
aa-enforce /usr/sbin/myapp

Landlock user-space sandboxing

Landlock lets a process sandbox itself before handling untrusted input. The API involves three steps: create a ruleset, add rules to it, then enforce it with landlock_restrict_self().
#include <linux/landlock.h>
#include <sys/prctl.h>
#include <sys/syscall.h>

/* Step 1: create a ruleset declaring which access rights to handle */
struct landlock_ruleset_attr ruleset_attr = {
    .handled_access_fs =
        LANDLOCK_ACCESS_FS_READ_FILE |
        LANDLOCK_ACCESS_FS_READ_DIR,
};

int ruleset_fd = syscall(SYS_landlock_create_ruleset,
                         &ruleset_attr, sizeof(ruleset_attr), 0);

/* Step 2: add a rule allowing read access to /usr */
struct landlock_path_beneath_attr path_attr = {
    .allowed_access = LANDLOCK_ACCESS_FS_READ_FILE |
                      LANDLOCK_ACCESS_FS_READ_DIR,
    .parent_fd = open("/usr", O_PATH | O_CLOEXEC),
};
syscall(SYS_landlock_add_rule, ruleset_fd,
        LANDLOCK_RULE_PATH_BENEATH, &path_attr, 0);

/* Step 3: enforce — no new privileges, then restrict self */
prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0);
syscall(SYS_landlock_restrict_self, ruleset_fd, 0);
After landlock_restrict_self(), the calling thread and all its descendants are subject to the ruleset. The restriction is additive: subsequent landlock_restrict_self() calls can only tighten the policy, never relax it.
Check for Landlock support by calling landlock_create_ruleset() with the LANDLOCK_CREATE_RULESET_VERSION flag before attempting to build a ruleset. Older kernels will return ENOSYS.

Writing a custom LSM

New LSMs are accepted into the kernel when their intent — what they protect against and when a user would choose them — is documented in Documentation/admin-guide/LSM/. This allows LSM code to be audited against its stated goals. To write a custom LSM:
1

Define an lsm_id

Declare a struct lsm_id with a unique name and an ID from uapi/linux/lsm.h. The name must be approved by LSM maintainers for in-tree modules.
static const struct lsm_id my_lsm_id = {
    .name = "my_lsm",
    .id   = LSM_ID_UNDEF,  /* use a real ID for upstream submission */
};
2

Implement hook functions

Write functions matching the hook signatures defined in security/security.c. Return 0 to allow an operation or a negative errno to deny it.
static int my_lsm_inode_permission(struct inode *inode, int mask)
{
    /* inspect inode and mask; return 0 or -EACCES */
    return 0;
}
3

Register hooks

Use LSM_HOOK_INIT() to build the hook list and call security_add_hooks() from your init function.
DEFINE_LSM(my_lsm) = {
    .name  = "my_lsm",
    .init  = my_lsm_init,
    .order = LSM_ORDER_MUTABLE,
};
4

Add Kconfig and documentation

Add a CONFIG_SECURITY_MY_LSM Kconfig entry and write documentation under Documentation/admin-guide/LSM/my_lsm.rst describing what your LSM protects against and in which scenarios it should be used.
Once hooks are registered via security_add_hooks(), they cannot be removed. Do not attempt to unregister LSM hooks from a module that could be unloaded — LSM modules are expected to persist for the lifetime of the kernel.

Security architecture overview

DAC, capabilities, namespaces, and the full security layer stack.

Kernel hardening

KASLR, stack canaries, SMEP/SMAP, and Kconfig hardening options.

Build docs developers (and LLMs) love