英语

Landlock LSM:内核文档

作者:

Mickaël Salaün

日期:

2025 年 3 月

Landlock 的目标是创建作用域访问控制(即沙盒)。为了加强整个系统,此功能应该对任何进程可用,包括非特权进程。因为这样的进程可能被入侵或植入后门(即不可信),所以从内核和其他进程的角度来看,Landlock 的功能必须是安全的。因此,Landlock 的接口必须暴露最小的攻击面。

Landlock 的设计目的是供非特权进程使用,同时遵循由其他访问控制机制(例如 DAC、LSM)强制执行的系统安全策略。 Landlock 规则不得干扰系统上强制执行的其他访问控制,而只能添加更多限制。

任何用户都可以在他们的进程上强制执行 Landlock 规则集。它们被合并并针对继承的规则集进行评估,以确保只能添加更多约束。

用户空间文档可以在这里找到:Landlock:非特权访问控制

安全访问控制的指导原则

  • Landlock 规则应侧重于对内核对象的访问控制,而不是系统调用过滤(即系统调用参数),这是 seccomp-bpf 的目的。

  • 为了避免多种侧信道攻击(例如安全策略泄露、基于 CPU 的攻击),Landlock 规则应无法以编程方式与用户空间通信。

  • 内核访问检查不应减慢来自非沙盒进程的访问请求。

  • 与 Landlock 操作相关的计算(例如,强制执行规则集)应仅影响请求它们的进程。

  • 沙盒进程直接从内核获得的资源(例如,文件描述符)应保留其作用域访问权限(在资源获取时),无论哪个进程使用它们。参见 文件描述符访问权限

  • 访问拒绝应根据系统和 Landlock 域配置进行记录。日志条目必须包含有关拒绝原因和相关安全策略所有者的信息。这种日志生成应该对允许的请求产生可忽略不计的性能和内存影响。

设计选择

Inode 访问权限

所有访问权限都与 inode 以及可以通过它访问的内容相关联。读取目录的内容并不意味着允许读取列出的 inode 的内容。实际上,文件名是其父目录本地的,并且 inode 可以通过(硬)链接被多个文件名引用。能够取消链接文件只会直接影响目录,而不会影响未链接的 inode。这就是为什么 LANDLOCK_ACCESS_FS_REMOVE_FILELANDLOCK_ACCESS_FS_REFER 不允许绑定到文件,而只允许绑定到目录的原因。

文件描述符访问权限

访问权限在打开时被检查并绑定到文件描述符。基本原则是,当等效的操作序列在相同的 Landlock 域下执行时,应导致相同的结果。

LANDLOCK_ACCESS_FS_TRUNCATE 权限为例,如果相关文件层次结构未授予该访问权限,则可能允许打开一个文件进行写入,而不允许 ftruncate 结果文件描述符。以下操作序列具有相同的语义,因此应具有相同的结果

  • truncate(path);

  • int fd = open(path, O_WRONLY); ftruncate(fd); close(fd);

与文件访问模式(例如 O_RDWR)类似,附加到文件描述符的 Landlock 访问权限即使在进程之间传递(例如通过 Unix 域套接字)也会保留。然后,即使接收进程未被 Landlock 沙盒化,也会强制执行此类访问权限。实际上,这是保持整个系统访问控制一致所必需的,并且这避免了通过文件描述符传递(即混乱的副手攻击)的无人值守绕过。

测试

有关向后兼容性、ptrace 限制和文件系统支持的用户空间测试可以在这里找到:tools/testing/selftests/landlock/

内核结构

对象

struct landlock_object_underops

对底层对象的操作

定义:

struct landlock_object_underops {
    void (*release)(struct landlock_object *const object) __releases(object->lock);
};

成员

release

释放底层对象(例如,inode 的 iput())。

struct landlock_object

绑定到内核对象的安全 blob

定义:

struct landlock_object {
    refcount_t usage;
    spinlock_t lock;
    void *underobj;
    union {
        struct rcu_head rcu_free;
        const struct landlock_object_underops *underops;
    };
};

成员

usage

此计数器用于将对象绑定到匹配它的规则或在添加新规则时保持其活动状态。如果此计数器达到零,则不得修改此结构,但仍可以在 RCU 读取端临界区内读取此计数器。当向 usage 计数器为零的对象添加新规则时,我们必须等到指向此对象的指针设置为 NULL(或回收)。

lock

防止并发修改。必须从 usage 降至零时到清理 underobj 对此对象的任何弱引用时持有此锁。

锁顺序:inode->i_lock 嵌套在其中。

underobj

用于清理对象并将对象标记为绑定到其底层内核结构。此指针受 lock 保护。参见 landlock_release_inodes() 和 release_inode()。

{unnamed_union}

anonymous

rcu_free

允许从 RCU 读取端临界区内无锁使用 usagelockunderobjrcu_freeunderops 仅由 landlock_put_object() 使用。

underops

允许 landlock_put_object() 释放底层对象(例如,inode)。

描述

此结构的目标是以安全的方式将一组短暂的访问权限(属于不同域)绑定到内核对象(例如,inode)。这意味着处理并发使用和修改。

struct landlock_object 的生命周期取决于引用它的规则。

文件系统

struct landlock_inode_security

Inode 安全 blob

定义:

struct landlock_inode_security {
    struct landlock_object __rcu *object;
};

成员

object

指向已分配对象的弱指针。新对象的所有分配都受底层 inode->i_lock 保护。但是,从 inode 中原子地分离 object 仅受 object->lock 保护,从 object 的 usage 引用计数降至零到此指针被置空的时间(参见 release_inode() 和 hook_sb_delete())。实际上,由于 get_inode_object() 执行的仔细 rcu_access_pointer() 检查,这种分离不需要 inode->i_lock。

描述

允许引用绑定到 inode 的 struct landlock_object(即底层对象)。

struct landlock_file_security

文件安全 blob

定义:

struct landlock_file_security {
    access_mask_t allowed_access;
#ifdef CONFIG_AUDIT;
    deny_masks_t deny_masks;
    u8 fown_layer;
#endif ;
    struct landlock_cred_security fown_subject;
};

成员

allowed_access

打开文件时可用的访问权限。这不一定是当时可用的全套访问权限,但它是授权以后对已打开文件进行操作所必需的子集。

deny_masks

域层级别,拒绝可选访问(参见 _LANDLOCK_ACCESS_FS_OPTIONAL)。

fown_layer

具有 LANDLOCK_SCOPE_SIGNAL 的 fown_subject->domain 的层级别。

fown_subject

任务的 Landlock 凭据,该任务设置可能接收信号的 PID,例如,将 MSG_OOB 写入相关套接字时的 SIGURG。此指针受相关 file->f_owner->lock 保护,就像 fown_struct 的成员:pid、uid 和 euid 一样。

描述

此信息在 hook_file_open 中打开文件时填充,并跟踪打开文件时可用的相关 Landlock 访问权限。其他 LSM 钩子使用这些权限来授权对已打开文件的操作。

struct landlock_superblock_security

超级块安全 blob

定义:

struct landlock_superblock_security {
    atomic_long_t inode_refs;
};

成员

inode_refs

正在由 release_inode() 释放的(来自此超级块)挂起的 inode 的数量。参见 struct super_block->s_fsnotify_inode_refs 。

描述

允许 hook_sb_delete() 等待对 release_inode() 的并发调用。

规则集和域

域是一个绑定到一组主体(即任务的凭据)的只读规则集。每次在任务上强制执行规则集时,当前域将被复制,并且规则集作为新域中的新规则层导入。实际上,一旦进入域,每个规则都绑定到一个层级别。要授予对对象的访问权限,每个层中至少一个规则必须允许对对象执行请求的操作。然后,任务只能转换为一个新域,该域是当前域的约束与任务提供的规则集的约束的交集。

对于正在沙盒化的任务,主体的定义是隐式的,这使得推理更容易,并有助于避免陷阱。

struct landlock_layer

给定层的访问权限

定义:

struct landlock_layer {
    u16 level;
    access_mask_t access;
};

成员

level

此层在层堆栈中的位置。

access

允许对内核对象执行的操作的位字段。它们与对象类型相关(例如 LANDLOCK_ACTION_FS_READ)。

union landlock_key

规则集的红黑树的键

定义:

union landlock_key {
    struct landlock_object *object;
    uintptr_t data;
};

成员

object

ptr

用于标识内核对象的指针(例如,inode)。

data

用于标识任意 32 位值(例如,TCP 端口)的原始数据。

enum landlock_key_type

union landlock_key 的类型

常量

LANDLOCK_KEY_INODE

landlock_ruleset.root_inode 的节点键的类型。

LANDLOCK_KEY_NET_PORT

landlock_ruleset.root_net_port 的节点键的类型。

struct landlock_id

定义:

struct landlock_id {
    union landlock_key key;
    const enum landlock_key_type type;
};

成员

规则集的唯一规则标识符

key

标识内核对象(例如,inode)或原始值(例如,TCP 端口)。

type

landlock_ruleset 的根树的类型。

struct landlock_rule

定义:

struct landlock_rule {
    struct rb_node node;
    union landlock_key key;
    u32 num_layers;
    struct landlock_layer layers[] ;
};

成员

绑定到对象的访问权限

node

规则集的唯一规则标识符

规则集的红黑树中的节点。

一个用于标识内核对象(例如,inode)或原始数据值(例如,网络套接字端口)的联合体。这用作此规则集元素的键。该指针设置一次,永不修改。它始终指向一个已分配的对象,因为每个规则都会增加其对象的引用计数。

num_layers

layers 中的条目数。

layers

从最新到最新的层堆栈,实现为灵活数组成员 (FAM)。

struct landlock_ruleset

定义:

struct landlock_ruleset {
    struct rb_root root_inode;
#if IS_ENABLED(CONFIG_INET);
    struct rb_root root_net_port;
#endif ;
    struct landlock_hierarchy *hierarchy;
    union {
        struct work_struct work_free;
        struct {
            struct mutex lock;
            refcount_t usage;
            u32 num_rules;
            u32 num_layers;
            struct access_masks access_masks[];
        };
    };
};

成员

Landlock 规则集

root_inode

包含具有 inode 对象的 struct landlock_rule 节点的红黑树的根。一旦规则集绑定到进程(即作为域),此树就是不可变的,直到 usage 达到零。

root_net_port

包含具有网络端口的 struct landlock_rule 节点的红黑树的根。一旦规则集绑定到进程(即作为域),此树就是不可变的,直到 usage 达到零。

hierarchy

{unnamed_union}

anonymous

即使父域消失也允许层次结构识别。这是 ptrace 保护所需要的。

work_free

允许在无锁部分内释放规则集。当 usage 达到零时,这仅由 landlock_put_ruleset_deferred() 使用。然后不使用字段 lockusagenum_rulesnum_layersaccess_masks

anonymous

lock

{unnamed_struct}

usage

保护 root 免受并发修改,如果 usage 大于零。

引用此规则集的进程(即域)或文件描述符的数量。

num_rules

一个用于标识内核对象(例如,inode)或原始数据值(例如,网络套接字端口)的联合体。这用作此规则集元素的键。该指针设置一次,永不修改。它始终指向一个已分配的对象,因为每个规则都会增加其对象的引用计数。

此规则集中非重叠(即不适用于同一对象)的规则的数量。

此规则集中使用的层数。这允许检查所有层是否允许访问请求。值为 0 表示非合并的规则集(即非域)。

access_masks

描述

包含规则集限制的文件系统和网络操作的子集。域将合并规则集的所有层保存在堆栈 (FAM) 中,从第一层到最后一层。这些层用于合并规则集、用户空间向后兼容性(即面向未来)以及正确处理没有重叠访问权限的合并规则集。这些层设置一次,并且在规则集的生命周期内永不更改。

此数据结构必须包含唯一条目、可更新且快速匹配对象。

struct access_masks landlock_union_access_masks(const struct landlock_ruleset *const domain)

返回域中处理的所有访问权限

参数

const struct landlock_ruleset *const domain

Landlock 规则集(用作域)

返回

所有域的访问掩码的 OR 的 access_masks 结果。