Landlock LSM:内核文档

作者:

Mickaël Salaün

日期:

2022 年 12 月

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

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

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

用户空间文档可在此处找到:Landlock:非特权访问控制

安全访问控制的指导原则

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

  • 为了避免多种类型的旁路通道攻击(例如安全策略泄露、基于 CPU 的攻击),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 读取端临界区内读取此计数器。 当向使用计数器为零的对象添加新规则时,我们必须等到指向此对象的指针设置为 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 保护。 但是,将 object 与 inode 原子分离仅受 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;
    struct landlock_ruleset *fown_domain;
};

成员

allowed_access

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

fown_domain

设置 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

用于标识内核对象的指针(例如,一个 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

规则集红黑树中的节点。

key

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

num_layers

layers 中的条目数。

layers

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

struct landlock_hierarchy

规则集层次结构中的节点

定义:

struct landlock_hierarchy {
    struct landlock_hierarchy *parent;
    refcount_t usage;
};

成员

parent

指向父节点的指针,如果它是根 Landlock 域,则为 NULL。

usage

潜在子域的数量加上其父域。

struct landlock_ruleset

Landlock 规则集

定义:

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[];
        };
    };
};

成员

root_inode

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

root_net_port

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

hierarchy

即使父域消失,也可以进行层次结构标识。这是 ptrac保护所必需的。

{unnamed_union}

anonymous

work_free

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

{unnamed_struct}

anonymous

lock

防止在 usage 大于零时并发修改 root

usage

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

num_rules

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

num_layers

此规则集中使用的层数。这允许检查所有层是否允许访问请求。值为 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 结果的访问掩码。

const struct landlock_ruleset *landlock_get_applicable_domain(const struct landlock_ruleset *const domain, const struct access_masks masks)

如果 domain 至少适用于(处理)masks 中指定的访问权限之一,则返回 domain

参数

const struct landlock_ruleset *const domain

Landlock 规则集(用作域)

const struct access_masks masks

访问掩码

返回

如果处理了 masks 中指定的任何访问权限,则返回 domain,否则返回 NULL。