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_FILE
或 LANDLOCK_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 读取端临界区内无锁使用 usage、lock 和 underobj。 rcu_free 和 underops 仅由 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¶
常量
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() 使用。然后,lock、usage、num_rules、num_layers 和 access_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。