Landlock: 非特权访问控制

作者:

Mickaël Salaün

日期:

2025 年 3 月

Landlock 的目标是允许限制一组进程的环境权限(例如,全局文件系统或网络访问)。 由于 Landlock 是一个可堆叠的 LSM,它使得除了现有的系统范围的访问控制之外,还可以创建安全的安全沙箱作为新的安全层。 这种沙箱有望帮助缓解用户空间应用程序中错误或意外/恶意行为的安全影响。 Landlock 使任何进程(包括非特权进程)都能够安全地限制自身。

我们可以通过在内核日志中查找“landlock: Up and running”(以 root 身份)来快速确保 Landlock 在运行系统中启用:dmesg | grep landlock || journalctl -kb -g landlock 。 开发人员还可以通过相关的系统调用轻松检查 Landlock 支持。 如果当前不支持 Landlock,我们需要适当地配置内核

Landlock 规则

Landlock 规则描述了进程打算对对象执行的操作。 一组规则聚合在一个规则集中,然后可以限制强制执行它的线程及其未来的子线程。

两种现有的规则类型是

文件系统规则

对于这些规则,对象是一个文件层次结构,相关的文件系统操作通过文件系统访问权限定义。

网络规则(自 ABI v4 起)

对于这些规则,对象是一个 TCP 端口,相关的操作通过网络访问权限定义。

定义和强制执行安全策略

我们首先需要定义将包含我们的规则的规则集。

对于此示例,规则集将包含仅允许文件系统读取操作并建立特定 TCP 连接的规则。 文件系统写入操作和其他 TCP 操作将被拒绝。

然后,规则集需要处理这两种操作。 这是向后和向前兼容所必需的(即,内核和用户空间可能不知道彼此支持的限制),因此需要明确关于默认拒绝的访问权限。

struct landlock_ruleset_attr ruleset_attr = {
    .handled_access_fs =
        LANDLOCK_ACCESS_FS_EXECUTE |
        LANDLOCK_ACCESS_FS_WRITE_FILE |
        LANDLOCK_ACCESS_FS_READ_FILE |
        LANDLOCK_ACCESS_FS_READ_DIR |
        LANDLOCK_ACCESS_FS_REMOVE_DIR |
        LANDLOCK_ACCESS_FS_REMOVE_FILE |
        LANDLOCK_ACCESS_FS_MAKE_CHAR |
        LANDLOCK_ACCESS_FS_MAKE_DIR |
        LANDLOCK_ACCESS_FS_MAKE_REG |
        LANDLOCK_ACCESS_FS_MAKE_SOCK |
        LANDLOCK_ACCESS_FS_MAKE_FIFO |
        LANDLOCK_ACCESS_FS_MAKE_BLOCK |
        LANDLOCK_ACCESS_FS_MAKE_SYM |
        LANDLOCK_ACCESS_FS_REFER |
        LANDLOCK_ACCESS_FS_TRUNCATE |
        LANDLOCK_ACCESS_FS_IOCTL_DEV,
    .handled_access_net =
        LANDLOCK_ACCESS_NET_BIND_TCP |
        LANDLOCK_ACCESS_NET_CONNECT_TCP,
    .scoped =
        LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET |
        LANDLOCK_SCOPE_SIGNAL,
};

因为我们可能不知道应用程序将在哪个内核版本上执行,所以遵循尽力而为的安全方法更安全。 实际上,无论用户使用哪个内核,我们都应该尽可能地保护用户。

为了与旧的 Linux 版本兼容,我们检测可用的 Landlock ABI 版本,并且仅使用可用的访问权限子集

int abi;

abi = landlock_create_ruleset(NULL, 0, LANDLOCK_CREATE_RULESET_VERSION);
if (abi < 0) {
    /* Degrades gracefully if Landlock is not handled. */
    perror("The running kernel does not enable to use Landlock");
    return 0;
}
switch (abi) {
case 1:
    /* Removes LANDLOCK_ACCESS_FS_REFER for ABI < 2 */
    ruleset_attr.handled_access_fs &= ~LANDLOCK_ACCESS_FS_REFER;
    __attribute__((fallthrough));
case 2:
    /* Removes LANDLOCK_ACCESS_FS_TRUNCATE for ABI < 3 */
    ruleset_attr.handled_access_fs &= ~LANDLOCK_ACCESS_FS_TRUNCATE;
    __attribute__((fallthrough));
case 3:
    /* Removes network support for ABI < 4 */
    ruleset_attr.handled_access_net &=
        ~(LANDLOCK_ACCESS_NET_BIND_TCP |
          LANDLOCK_ACCESS_NET_CONNECT_TCP);
    __attribute__((fallthrough));
case 4:
    /* Removes LANDLOCK_ACCESS_FS_IOCTL_DEV for ABI < 5 */
    ruleset_attr.handled_access_fs &= ~LANDLOCK_ACCESS_FS_IOCTL_DEV;
    __attribute__((fallthrough));
case 5:
    /* Removes LANDLOCK_SCOPE_* for ABI < 6 */
    ruleset_attr.scoped &= ~(LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET |
                             LANDLOCK_SCOPE_SIGNAL);
}

这使得可以创建包含规则的包含性规则集。

int ruleset_fd;

ruleset_fd = landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0);
if (ruleset_fd < 0) {
    perror("Failed to create a ruleset");
    return 1;
}

现在,我们可以通过返回的引用此规则集的文件描述符向此规则集添加新规则。 该规则将仅允许读取文件层次结构/usr 。 如果没有其他规则,则写入操作将被规则集拒绝。 要将/usr添加到规则集中,我们使用O_PATH标志打开它,并使用此文件描述符填充&struct landlock_path_beneath_attr

int err;
struct landlock_path_beneath_attr path_beneath = {
    .allowed_access =
        LANDLOCK_ACCESS_FS_EXECUTE |
        LANDLOCK_ACCESS_FS_READ_FILE |
        LANDLOCK_ACCESS_FS_READ_DIR,
};

path_beneath.parent_fd = open("/usr", O_PATH | O_CLOEXEC);
if (path_beneath.parent_fd < 0) {
    perror("Failed to open file");
    close(ruleset_fd);
    return 1;
}
err = landlock_add_rule(ruleset_fd, LANDLOCK_RULE_PATH_BENEATH,
                        &path_beneath, 0);
close(path_beneath.parent_fd);
if (err) {
    perror("Failed to update ruleset");
    close(ruleset_fd);
    return 1;
}

还可能需要按照与规则集创建相同的逻辑创建规则,方法是根据 Landlock ABI 版本过滤访问权限。 在此示例中,不需要这样做,因为所有请求的allowed_access权限已在 ABI 1 中可用。

对于网络访问控制,我们可以添加一组允许将端口号用于特定操作的规则:HTTPS 连接。

struct landlock_net_port_attr net_port = {
    .allowed_access = LANDLOCK_ACCESS_NET_CONNECT_TCP,
    .port = 443,
};

err = landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_PORT,
                        &net_port, 0);

下一步是限制当前线程获得更多权限(例如,通过 SUID 二进制文件)。 我们现在有一个规则集,第一个规则允许读取对/usr的访问,同时拒绝文件系统的所有其他处理的访问,第二个规则允许 HTTPS 连接。

if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)) {
    perror("Failed to restrict privileges");
    close(ruleset_fd);
    return 1;
}

当前线程现在已准备好使用规则集对自己进行沙箱化。

if (landlock_restrict_self(ruleset_fd, 0)) {
    perror("Failed to enforce ruleset");
    close(ruleset_fd);
    return 1;
}
close(ruleset_fd);

如果landlock_restrict_self系统调用成功,则当前线程现在受到限制,并且此策略将在其随后创建的所有子线程上强制执行。 一旦线程被 Landlock 锁定,就无法删除其安全策略; 只允许添加更多限制。 这些线程现在位于一个新的 Landlock 域中,该域是其父域(如果有)与新规则集的合并。

完整的有效代码可以在samples/landlock/sandboxer.c中找到。

良好实践

建议尽可能将访问权限设置为文件层次结构的叶子。 例如,最好将~/doc/作为只读层次结构,并将~/tmp/作为读写层次结构,而不是将~/作为只读层次结构,并将~/tmp/作为读写层次结构。 遵循这种良好实践会导致不依赖于其位置(即父目录)的自给自足的层次结构。 当我们想要允许链接或重命名时,这一点尤其重要。 实际上,对每个目录使用一致的访问权限可以更改此类目录的位置,而无需依赖目标目录的访问权限(除了此操作所需的访问权限,请参阅LANDLOCK_ACCESS_FS_REFER文档)。

具有自给自足的层次结构也有助于将所需的访问权限收紧到最小的数据集。 这也有助于避免沉淀目录,即可以链接到但不能从其中链接的数据的目录。 但是,这取决于数据组织,这可能不受开发人员的控制。 在这种情况下,授予对~/tmp/的读写访问权限,而不是只写访问权限,可能会允许将~/tmp/移动到不可读目录,并且仍然能够列出~/tmp/的内容。

文件路径访问权限的层

每次线程强制执行规则集时,它都会使用新的策略层更新其 Landlock 域。 此补充策略与可能已经限制此线程的任何其他规则集堆叠在一起。 然后,沙箱线程可以使用新的强制执行的规则集安全地向自身添加更多约束。

如果路径上遇到的至少一个规则授予访问权限,则一个策略层授予对文件路径的访问权限。 只有当所有强制执行的策略层都授予访问权限以及所有其他系统访问控制(例如,文件系统 DAC、其他 LSM 策略等)时,沙箱线程才能访问文件路径。

绑定挂载和 OverlayFS

Landlock 允许限制对文件层次结构的访问,这意味着这些访问权限可以通过绑定挂载传播(请参阅共享子树),但不能通过Overlay 文件系统传播。

绑定挂载将源文件层次结构镜像到目标。 然后,目标层次结构由完全相同的文件组成,Landlock 规则可以通过源或目标路径绑定到这些文件。 这些规则限制了在路径上遇到它们时的访问,这意味着它们可以同时限制对多个文件层次结构的访问,无论这些层次结构是否是绑定挂载的结果。

OverlayFS 挂载点由上层和下层组成。 这些层组合在一个合并目录中,并且该合并目录在挂载点处可用。 此合并层次结构可能包括来自上层和下层的文件,但在合并层次结构上执行的修改仅反映在上层。 从 Landlock 策略的角度来看,所有 OverlayFS 层和合并层次结构都是独立的,并且每个都包含它们自己的文件和目录集,这与绑定挂载不同。 限制 OverlayFS 层的策略不会限制生成的合并层次结构,反之亦然。 然后,Landlock 用户应该只考虑他们想要允许访问的文件层次结构,而不管底层文件系统如何。

继承

每个由clone(2)产生的新线程都从其父线程继承 Landlock 域限制。 这类似于 seccomp 继承(请参阅Seccomp BPF (使用过滤器进行安全计算))或处理任务的凭据(7)的任何其他 LSM。 例如,一个进程的线程可以将其 Landlock 规则应用于自身,但它们不会自动应用于其他兄弟线程(与 POSIX 线程凭据更改不同,请参阅nptl(7))。

当线程对自己进行沙箱化时,我们保证相关的安全策略将始终在此线程的所有后代上强制执行。 这允许为每个应用程序创建独立和模块化的安全策略,这些策略将根据其运行时父策略自动相互组合。

Ptrace 限制

与非沙箱进程相比,沙箱进程的权限较少,因此在操作另一个进程时必须受到额外的限制。 要允许在目标进程上使用ptrace(2)和相关系统调用,沙箱进程应具有目标进程访问权限的超集,这意味着被追踪者必须是追踪者的子域。

IPC 范围限定

与隐式Ptrace 限制类似,我们可能希望进一步限制沙箱之间的交互。 因此,在规则集创建时,每个 Landlock 域都可以限制某些操作的范围,以便这些操作只能到达同一 Landlock 域或嵌套 Landlock 域(“范围”)中的进程。

可以限定范围的操作包括

LANDLOCK_SCOPE_SIGNAL

这限制了将信号发送到在同一或嵌套 Landlock 域中运行的目标进程。

LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET

这限制了我们可以connect(2)到的抽象unix(7)套接字集,这些套接字地址是由同一或嵌套 Landlock 域中的进程创建的。

在非连接数据报套接字上进行sendto(2)的处理方式就像它在进行隐式connect(2)一样,如果远程端不是来自同一或嵌套 Landlock 域,则会被阻止。

先前连接的套接字上的sendto(2)将不受限制。 这适用于数据报套接字和流套接字。

IPC 范围限定不支持通过landlock_add_rule(2)进行异常。 如果操作限定在域内,则无法添加规则以允许访问范围外的资源或进程。

截断文件

LANDLOCK_ACCESS_FS_WRITE_FILELANDLOCK_ACCESS_FS_TRUNCATE涵盖的操作都更改了文件的内容,并且有时以非直观的方式重叠。 建议始终将这两者一起指定。

一个特别令人惊讶的例子是creat(2)。 名称表明此系统调用需要创建和写入文件的权限。 但是,如果同名下的现有文件已经存在,它也需要截断权限。

还应注意的是,截断文件不需要LANDLOCK_ACCESS_FS_WRITE_FILE权限。 除了truncate(2)系统调用外,还可以通过open(2)与标志O_RDONLY | O_TRUNC来完成。

截断权限与打开的文件关联(见下文)。

与文件描述符关联的权限

打开文件时,LANDLOCK_ACCESS_FS_TRUNCATELANDLOCK_ACCESS_FS_IOCTL_DEV权限的可用性与新创建的文件描述符相关联,并将用于后续的截断和使用ftruncate(2)ioctl(2)进行的 ioctl 尝试。 此行为类似于打开文件进行读取或写入,权限在open(2)期间检查,但在后续的read(2)write(2)调用期间不检查。

因此,一个进程可能具有多个引用同一文件的打开的文件描述符,但 Landlock 在使用这些文件描述符进行操作时会强制执行不同的操作。 当强制执行 Landlock 规则集并且进程保留在强制执行之前和之后打开的文件描述符时,可能会发生这种情况。 也可以在进程之间传递此类文件描述符,保留其 Landlock 属性,即使某些涉及的进程没有强制执行 Landlock 规则集。

兼容性

向后和向前兼容性

Landlock 旨在与内核的过去和未来版本兼容。 这要归功于系统调用属性和关联的位标志,特别是规则集的handled_access_fs 。 使处理的访问权限明确可以使内核和用户空间彼此之间有一个清晰的合同。 这是确保沙箱化不会随着系统更新而变得更严格(这可能会破坏应用程序)所必需的。

开发人员可以订阅Landlock 邮件列表,以便有意使用最新的可用功能更新和测试其应用程序。 为了用户的利益,并且因为他们可能使用不同的内核版本,强烈建议遵循尽力而为的安全方法,方法是在运行时检查 Landlock ABI 版本并且仅强制执行支持的功能。

Landlock ABI 版本

可以使用sys_landlock_create_ruleset()系统调用读取 Landlock ABI 版本

int abi;

abi = landlock_create_ruleset(NULL, 0, LANDLOCK_CREATE_RULESET_VERSION);
if (abi < 0) {
    switch (errno) {
    case ENOSYS:
        printf("Landlock is not supported by the current kernel.\n");
        break;
    case EOPNOTSUPP:
        printf("Landlock is currently disabled.\n");
        break;
    }
    return 0;
}
if (abi >= 2) {
    printf("Landlock supports LANDLOCK_ACCESS_FS_REFER.\n");
}

第一个 ABI 版本隐式支持以下内核接口。 仅从特定版本支持的功能会明确标记为这样。

内核接口

访问权限

可以通过属性(例如,struct landlock_path_beneath_attr)定义内核对象上的一组操作,包括访问的位掩码。

文件系统标志

这些标志允许将沙箱进程限制为对文件和目录执行的一组操作。 在沙箱化之前打开的文件或目录不受这些限制的约束。

以下访问权限仅适用于文件

  • LANDLOCK_ACCESS_FS_EXECUTE:执行文件。

  • LANDLOCK_ACCESS_FS_WRITE_FILE:以写入访问权限打开文件。 打开文件进行写入时,通常还需要LANDLOCK_ACCESS_FS_TRUNCATE权限。 在许多情况下,这些系统调用会在覆盖现有文件时截断它们(例如,creat(2))。

  • LANDLOCK_ACCESS_FS_READ_FILE:以读取访问权限打开文件。

  • LANDLOCK_ACCESS_FS_TRUNCATE:使用truncate(2)ftruncate(2)creat(2)open(2)O_TRUNC截断文件。 自 Landlock ABI 的第三个版本以来,此访问权限可用。

是否可以使用ftruncate(2)截断打开的文件或与ioctl(2)一起使用,是在open(2)期间确定的,其方式与使用LANDLOCK_ACCESS_FS_READ_FILELANDLOCK_ACCESS_FS_WRITE_FILEopen(2)期间检查读取和写入权限的方式相同。

目录可以接收与文件或目录相关的访问权限。 以下访问权限适用于目录本身及其下方的目录

  • LANDLOCK_ACCESS_FS_READ_DIR:打开目录或列出其内容。

但是,以下访问权限仅适用于目录的内容,而不适用于目录本身

  • LANDLOCK_ACCESS_FS_REMOVE_DIR:删除空目录或重命名目录。

  • LANDLOCK_ACCESS_FS_REMOVE_FILE:取消链接(或重命名)文件。

  • LANDLOCK_ACCESS_FS_MAKE_CHAR:创建(或重命名或链接)字符设备。

  • LANDLOCK_ACCESS_FS_MAKE_DIR:创建(或重命名)目录。

  • LANDLOCK_ACCESS_FS_MAKE_REG:创建(或重命名或链接)常规文件。

  • LANDLOCK_ACCESS_FS_MAKE_SOCK:创建(或重命名或链接)UNIX 域套接字。

  • LANDLOCK_ACCESS_FS_MAKE_FIFO:创建(或重命名或链接)命名管道。

  • LANDLOCK_ACCESS_FS_MAKE_BLOCK:创建(或重命名或链接)块设备。

  • LANDLOCK_ACCESS_FS_MAKE_SYM:创建(或重命名或链接)符号链接。

  • LANDLOCK_ACCESS_FS_REFER:将文件从一个不同的目录链接或重命名到另一个目录(即,重新确定文件层次结构的父目录)。

    自 Landlock ABI 的第二个版本以来,此访问权限可用。

    这是任何规则集默认拒绝的唯一访问权限,即使未在规则集创建时将其指定为处理。 要使规则集授予此权限的唯一方法是明确允许它用于特定目录,方法是向规则集添加匹配的规则。

    特别是,当使用第一个 Landlock ABI 版本时,Landlock 将始终拒绝在不同目录之间重新确定文件父目录的尝试。

    除了源目录和目标目录具有LANDLOCK_ACCESS_FS_REFER访问权限外,尝试的链接或重命名操作还必须满足以下约束

    • 重新确定的文件的访问权限在目标目录中不得超过其先前在源目录中拥有的访问权限。 如果尝试这样做,则操作将导致EXDEV错误。

    • 链接或重命名时,必须为目标目录授予相应文件类型的LANDLOCK_ACCESS_FS_MAKE_*权限。 否则,操作将导致EACCES错误。

    • 重命名时,必须为源目录授予相应文件类型的LANDLOCK_ACCESS_FS_REMOVE_*权限。 否则,操作将导致EACCES错误。

    如果未满足多个要求,则EACCES错误代码优先于EXDEV

以下访问权限同时适用于文件和目录

  • LANDLOCK_ACCESS_FS_IOCTL_DEV:在打开的字符或块设备上调用ioctl(2)命令。

    此访问权限适用于设备驱动程序实现的所有ioctl(2)命令。 但是,以下常见 IOCTL 命令继续可以调用,而与LANDLOCK_ACCESS_FS_IOCTL_DEV权限无关

    • 面向文件描述符的 IOCTL 命令(FIOCLEXFIONCLEX),

    • 面向文件描述的 IOCTL 命令(FIONBIOFIOASYNC),

    • 面向文件系统的 IOCTL 命令(FIFREEZEFITHAWFIGETBSZFS_IOC_GETFSUUIDFS_IOC_GETFSSYSFSPATH

    • 某些 IOCTL 命令在与设备一起使用时没有意义,但它们的实现是安全的并返回正确的错误代码(FS_IOC_FIEMAPFICLONEFICLONERANGEFIDEDUPERANGE

    自 Landlock ABI 的第五个版本以来,此访问权限可用。

警告

目前无法限制可通过这些 syscall 系列访问的某些文件相关操作:chdir(2)stat(2)flock(2)chmod(2)chown(2)setxattr(2)utime(2)fcntl(2)access(2)。 未来的 Landlock 演变将能够限制它们。

网络标志

这些标志允许将沙箱进程限制为一组网络操作。

自 Landlock ABI 版本 4 起,支持此功能。

以下访问权限适用于 TCP 端口号

  • LANDLOCK_ACCESS_NET_BIND_TCP:将 TCP 套接字绑定到本地端口。

  • LANDLOCK_ACCESS_NET_CONNECT_TCP:将活动 TCP 套接字连接到远程端口。

范围标志

这些标志允许将沙箱进程与一组 IPC 操作隔离。 为规则集设置标志会将 Landlock 域隔离,以禁止连接到域外的资源。

自 Landlock ABI 版本 6 起,支持此功能。

范围

  • LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET:限制沙箱进程连接到由相关 Landlock 域外的进程(例如,父域或非沙箱进程)创建的抽象 UNIX 套接字。

  • LANDLOCK_SCOPE_SIGNAL: 限制沙盒进程向域外的另一个进程发送信号。

创建新的规则集

long sys_landlock_create_ruleset(const struct landlock_ruleset_attr __user *const attr, const size_t size, const __u32 flags)

创建一个新的规则集

参数

const struct landlock_ruleset_attr __user *const attr

指向 struct landlock_ruleset_attr 的指针,用于标识新规则集的范围。

const size_t size

指向的 struct landlock_ruleset_attr 的大小(用于向后和向前兼容)。

const __u32 flags

支持的值

  • LANDLOCK_CREATE_RULESET_VERSION

  • LANDLOCK_CREATE_RULESET_ERRATA

描述

此系统调用允许创建一个新的 Landlock 规则集,并在成功时返回相关的文件描述符。

如果设置了 LANDLOCK_CREATE_RULESET_VERSIONLANDLOCK_CREATE_RULESET_ERRATA,则 attr 必须为 NULL,且 size 必须为 0。

可能返回的错误有

  • EOPNOTSUPP: 内核支持 Landlock,但在启动时被禁用;

  • EINVAL: 未知的 flags,或未知的访问权限,或未知的范围,或太小的 size;

  • E2BIG: attrsize 不一致;

  • EFAULT: attrsize 不一致;

  • ENOMSG: 空的 landlock_ruleset_attr.handled_access_fs

标志

LANDLOCK_CREATE_RULESET_VERSION

获取最高支持的 Landlock ABI 版本 (从 1 开始)。

LANDLOCK_CREATE_RULESET_ERRATA

获取当前 Landlock ABI 版本中已修复问题的位掩码。

struct landlock_ruleset_attr

规则集定义。

定义:

struct landlock_ruleset_attr {
    __u64 handled_access_fs;
    __u64 handled_access_net;
    __u64 scoped;
};

成员

handled_access_fs

处理的文件系统操作的位掩码 (参见 文件系统标志)。

handled_access_net

处理的网络操作的位掩码 (参见 网络标志)。

scoped

作用域的位掩码 (参见 作用域标志),限制 Landlock 域访问外部资源 (例如,IPC)。

描述

sys_landlock_create_ruleset() 的参数。

此结构定义了一组处理的访问权限,即对不同对象类型的一组操作,当规则集生效时,这些操作应默认被拒绝。 反之,此处未明确列出的访问权限,当规则集生效时,不会被该规则集拒绝。

由于历史原因,即使 handled_access_fs 中未设置其位,LANDLOCK_ACCESS_FS_REFER 权限始终默认被拒绝。 为了添加具有此访问权限的新规则,仍然必须显式设置该位 (参见 文件系统标志)。

显式列出处理的访问权限是出于向后兼容性考虑。 在大多数用例中,使用 Landlock 的进程将处理它们在构建时了解的(并且已经在支持所有这些权限的内核上测试过的)各种或所有访问权限。

此结构在未来的 Landlock 版本中可能会增长。

扩展规则集

long sys_landlock_add_rule(const int ruleset_fd, const enum landlock_rule_type rule_type, const void __user *const rule_attr, const __u32 flags)

向规则集添加新规则

参数

const int ruleset_fd

与应使用新规则扩展的规则集绑定的文件描述符。

const enum landlock_rule_type rule_type

标识 rule_attr 指向的结构类型:LANDLOCK_RULE_PATH_BENEATHLANDLOCK_RULE_NET_PORT

const void __user *const rule_attr

指向规则的指针 (与 rule_type 匹配)。

const __u32 flags

必须为 0。

描述

此系统调用允许定义新规则并将其添加到现有规则集。

可能返回的错误有

  • EOPNOTSUPP: 内核支持 Landlock,但在启动时被禁用;

  • EAFNOSUPPORT: rule_typeLANDLOCK_RULE_NET_PORT,但运行中的内核不支持 TCP/IP;

  • EINVAL: flags 不为 0;

  • EINVAL: 规则访问不一致 (即,landlock_path_beneath_attr.allowed_accesslandlock_net_port_attr.allowed_access 不是规则集处理的访问权限的子集)

  • EINVAL: landlock_net_port_attr.port 大于 65535;

  • ENOMSG: 空访问 (例如,landlock_path_beneath_attr.allowed_access 为 0);

  • EBADF: ruleset_fd 不是当前线程的文件描述符,或者 rule_attr 的成员不是预期的文件描述符;

  • EBADFD: ruleset_fd 不是规则集文件描述符,或者 rule_attr 的成员不是预期的文件描述符类型;

  • EPERM: ruleset_fd 没有对底层规则集的写入访问权限;

  • EFAULT: rule_attr 不是有效的地址。

enum landlock_rule_type

Landlock 规则类型

常量

LANDLOCK_RULE_PATH_BENEATH

struct landlock_path_beneath_attr 的类型。

LANDLOCK_RULE_NET_PORT

struct landlock_net_port_attr 的类型。

描述

sys_landlock_add_rule() 的参数。

struct landlock_path_beneath_attr

路径层次结构定义

定义:

struct landlock_path_beneath_attr {
    __u64 allowed_access;
    __s32 parent_fd;
};

成员

allowed_access

此文件层次结构允许的操作的位掩码 (参见 文件系统标志)。

parent_fd

文件描述符,最好使用 O_PATH 打开,用于标识文件层次结构的父目录,或仅标识一个文件。

描述

sys_landlock_add_rule() 的参数。

struct landlock_net_port_attr

网络端口定义

定义:

struct landlock_net_port_attr {
    __u64 allowed_access;
    __u64 port;
};

成员

allowed_access

端口允许的网络操作的位掩码 (参见 网络标志)。

port

主机字节序中的网络端口。

应该注意的是,传递给 bind(2) 的端口 0 将绑定到临时端口范围内的可用端口。 这可以通过 /proc/sys/net/ipv4/ip_local_port_range sysctl 进行配置(也用于 IPv6)。

具有端口 0 和 LANDLOCK_ACCESS_NET_BIND_TCP 权限的 Landlock 规则表示允许请求绑定到端口 0,它将自动转换为绑定到相关的端口范围。

描述

sys_landlock_add_rule() 的参数。

强制执行规则集

long sys_landlock_restrict_self(const int ruleset_fd, const __u32 flags)

在调用线程上强制执行规则集

参数

const int ruleset_fd

与要与目标合并的规则集绑定的文件描述符。

const __u32 flags

支持的值

  • LANDLOCK_RESTRICT_SELF_LOG_SAME_EXEC_OFF

  • LANDLOCK_RESTRICT_SELF_LOG_NEW_EXEC_ON

  • LANDLOCK_RESTRICT_SELF_LOG_SUBDOMAINS_OFF

描述

此系统调用允许在当前线程上强制执行 Landlock 规则集。 强制执行规则集要求任务在其命名空间中具有 CAP_SYS_ADMIN 或以 no_new_privs 运行。 这避免了非特权任务会影响特权子进程的行为的情况。

可能返回的错误有

  • EOPNOTSUPP: 内核支持 Landlock,但在启动时被禁用;

  • EINVAL: flags 包含未知位。

  • EBADF: ruleset_fd 不是当前线程的文件描述符;

  • EBADFD: ruleset_fd 不是规则集文件描述符;

  • EPERM: ruleset_fd 没有对底层规则集的读取访问权限,或者当前线程未以 no_new_privs 运行,或者在其命名空间中没有 CAP_SYS_ADMIN

  • E2BIG: 当前线程已达到堆叠规则集的最大数量。

标志

默认情况下,源自沙盒自身的程序的拒绝访问将通过审计子系统记录。 此类事件通常指示意外行为,例如错误或利用尝试。 但是,为了避免过度记录,默认情况下,不由发起程序创建的域拒绝的访问请求不会被记录。 理由是程序应该知道自己的行为,但不一定知道其他程序的行为。 此默认配置适用于大多数沙盒自身的程序。 对于特定用例,以下标志允许程序修改此默认日志记录行为。

LANDLOCK_RESTRICT_SELF_LOG_SAME_EXEC_OFFLANDLOCK_RESTRICT_SELF_LOG_NEW_EXEC_ON 标志应用于新创建的 Landlock 域。

LANDLOCK_RESTRICT_SELF_LOG_SAME_EXEC_OFF

禁用源自创建 Landlock 域的线程及其子进程的拒绝访问的日志记录,只要它们继续运行相同的可执行代码(即,没有中间的 execve(2) 调用)。 这适用于在不调用 execve(2) 的情况下执行未知代码的程序,例如脚本解释器。 仅沙盒自身的程序不应设置此标志,以便可以通过系统日志通知用户未经授权的访问尝试。

LANDLOCK_RESTRICT_SELF_LOG_NEW_EXEC_ON

execve(2) 调用后启用拒绝访问的日志记录,从而提供对创建的 Landlock 域内新执行程序未经授权的访问尝试的可见性。 仅当域中所有潜在的可执行文件都应符合访问限制时,才建议使用此标志,因为过多的审计日志条目可能会使识别关键事件变得更加困难。

LANDLOCK_RESTRICT_SELF_LOG_SUBDOMAINS_OFF

禁用源自调用方或其后代创建的嵌套 Landlock 域的拒绝访问的日志记录。 应根据运行时配置设置此标志,而不是硬编码,以避免抑制重要的安全事件。 它对于容器运行时或沙盒工具非常有用,这些工具可能会启动本身创建 Landlock 域的程序,否则可能会生成过多的日志。 与 LANDLOCK_RESTRICT_SELF_LOG_SAME_EXEC_OFF 不同,此标志仅影响未来的嵌套域,而不影响正在创建的域。 它也可以与值为 -1 的 ruleset_fd 一起使用,以使子域日志静音而不创建域。

当前限制

文件系统拓扑修改

使用文件系统限制进行沙盒化的线程无法修改文件系统拓扑,无论是通过 mount(2) 还是 pivot_root(2)。 但是,不拒绝 chroot(2) 调用。

特殊文件系统

可以通过 Landlock 限制对常规文件和目录的访问,具体取决于规则集的处理的访问权限。 但是,不来自用户可见的文件系统(例如管道,套接字)的文件,但仍然可以通过 /proc/<pid>/fd/* 访问,目前无法明确限制。 同样,某些特殊的内核文件系统(例如 nsfs),可以通过 /proc/<pid>/ns/* 访问,目前也无法明确限制。 但是,由于 ptrace 限制,对这些敏感 /proc 文件的访问会根据域层次结构自动受到限制。 未来的 Landlock 演变仍然可以通过专用的规则集标志来明确限制此类路径。

规则集层

堆叠规则集限制为 16 层。 对于希望在其 16 个继承的规则集之外强制执行新规则集的任务,这可能是一个问题。 一旦达到此限制,sys_landlock_restrict_self() 将返回 E2BIG。 然后强烈建议在线程的生命周期中仔细构建规则集一次,特别是对于能够启动其他可能也想沙盒化自身的应用程序的应用程序(例如,shell,容器管理器等)。

内存使用情况

分配用于创建规则集的内核内存会被记录,并且可以通过 内存资源控制器 来限制。

IOCTL 支持

LANDLOCK_ACCESS_FS_IOCTL_DEV 权限限制了 ioctl(2) 的使用,但它仅适用于新打开的设备文件。 这明确意味着预先存在的文件描述符(如 stdin,stdout 和 stderr)不受影响。

用户应该意识到,传统上,TTY 设备允许通过 TIOCSTITIOCLINUX IOCTL 命令来控制同一 TTY 上的其他进程。 这两者都需要现代 Linux 系统上的 CAP_SYS_ADMIN,但是 TIOCSTI 的行为是可配置的。

因此,在较旧的系统上,建议关闭继承的 TTY 文件描述符,或者从 /proc/self/fd/* 重新打开它们,而无需 LANDLOCK_ACCESS_FS_IOCTL_DEV 权限(如果可能)。

目前,Landlock 的 IOCTL 支持是粗粒度的,但将来可能会变得更细粒度。 在那之前,建议用户通过文件层次结构建立他们需要的保证,仅在真正需要的地方允许 LANDLOCK_ACCESS_FS_IOCTL_DEV 权限。

以前的限制

文件重命名和链接 (ABI < 2)

由于 Landlock 的目标是无特权访问控制,因此它需要正确处理规则的组合。 这种属性也意味着规则嵌套。 正确处理多个规则集层,每个规则集都能够限制对文件的访问,也意味着从父级到其层次结构继承规则集限制。 由于文件是通过其层次结构来识别和限制的,因此将文件从一个目录移动或链接到另一个目录意味着层次结构约束的传播,或者根据可能丢失的约束来限制这些操作。 为了防止通过重命名或链接进行特权提升,并且为了简单起见,Landlock 以前将链接和重命名限制为同一目录。 从 Landlock ABI 版本 2 开始,由于有了新的 LANDLOCK_ACCESS_FS_REFER 访问权限,现在可以安全地控制重命名和链接。

文件截断 (ABI < 3)

在第三个 Landlock ABI 之前,文件截断无法被拒绝,因此在使用仅支持第一个或第二个 ABI 的内核时,始终允许文件截断。

从 Landlock ABI 版本 3 开始,由于有了新的 LANDLOCK_ACCESS_FS_TRUNCATE 访问权限,现在可以安全地控制截断。

TCP 绑定和连接 (ABI < 4)

从 Landlock ABI 版本 4 开始,由于有了新的 LANDLOCK_ACCESS_NET_BIND_TCPLANDLOCK_ACCESS_NET_CONNECT_TCP 访问权限,现在可以将 TCP 绑定和连接操作限制为仅一组允许的端口。

设备 IOCTL (ABI < 5)

在第五个 Landlock ABI 之前,无法拒绝 IOCTL 操作,因此在使用仅支持早期 ABI 的内核时,始终允许 ioctl(2)

从 Landlock ABI 版本 5 开始,可以使用新的 LANDLOCK_ACCESS_FS_IOCTL_DEV 权限来限制在字符和块设备上使用 ioctl(2)

抽象 UNIX 套接字 (ABI < 6)

从 Landlock ABI 版本 6 开始,可以通过将 LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET 设置为 scoped 规则集属性来限制与抽象 unix(7) 套接字的连接。

信号 (ABI < 6)

从 Landlock ABI 版本 6 开始,可以通过将 LANDLOCK_SCOPE_SIGNAL 设置为 scoped 规则集属性来限制 signal(7) 发送。

日志记录 (ABI < 7)

从 Landlock ABI 版本 7 开始,可以通过传递给 sys_landlock_restrict_self()LANDLOCK_RESTRICT_SELF_LOG_SAME_EXEC_OFFLANDLOCK_RESTRICT_SELF_LOG_NEW_EXEC_ONLANDLOCK_RESTRICT_SELF_LOG_SUBDOMAINS_OFF 标志来控制 Landlock 审计事件的日志记录。有关审计的更多详细信息,请参见 Landlock: 系统范围管理

内核支持

构建时配置

Landlock 最初是在 Linux 5.13 中引入的,但必须在构建时使用 CONFIG_SECURITY_LANDLOCK=y 进行配置。Landlock 还必须像其他安全模块一样在启动时启用。默认情况下启用的安全模块列表由 CONFIG_LSM 设置。然后,内核配置应包含 CONFIG_LSM=landlock,[...],其中 [...] 是运行系统可能需要的其他安全模块的列表(请参阅 CONFIG_LSM 帮助)。

启动时配置

如果运行的内核在 CONFIG_LSM 中没有 landlock,那么我们可以通过在启动加载器配置中的 内核命令行参数 中添加 lsm=landlock,[...] 来启用 Landlock。

例如,如果当前的内置配置是

$ zgrep -h "^CONFIG_LSM=" "/boot/config-$(uname -r)" /proc/config.gz 2>/dev/null
CONFIG_LSM="lockdown,yama,integrity,apparmor"

...并且如果 cmdline 也不包含 landlock

$ sed -n 's/.*\(\<lsm=\S\+\).*/\1/p' /proc/cmdline
lsm=lockdown,yama,integrity,apparmor

...我们应该配置启动加载器以设置一个 cmdline,用 landlock, 前缀扩展 lsm 列表

lsm=landlock,lockdown,yama,integrity,apparmor

重启后,我们可以通过查看内核日志来检查 Landlock 是否已启动并正在运行

# dmesg | grep landlock || journalctl -kb -g landlock
[    0.000000] Command line: [...] lsm=landlock,lockdown,yama,integrity,apparmor
[    0.000000] Kernel command line: [...] lsm=landlock,lockdown,yama,integrity,apparmor
[    0.000000] LSM: initializing lsm=lockdown,capability,landlock,yama,integrity,apparmor
[    0.000000] landlock: Up and running.

内核可能在构建时配置为始终加载 lockdowncapability LSM。 在这种情况下,即使这些 LSM 没有在启动加载器中配置,它们也会出现在 LSM: initializing 日志行的开头。

网络支持

为了能够显式地允许 TCP 操作(例如,添加具有 LANDLOCK_ACCESS_NET_BIND_TCP 的网络规则),内核必须支持 TCP (CONFIG_INET=y)。否则,sys_landlock_add_rule() 会返回 EAFNOSUPPORT 错误,可以安全地忽略它,因为这种 TCP 操作已经不可能了。

问题与解答

用户空间沙箱管理器怎么样?

使用用户空间进程来强制执行对内核资源的限制可能会导致竞争条件或不一致的评估(即 操作系统代码和状态的不正确镜像)。

命名空间和容器怎么样?

命名空间可以帮助创建沙箱,但它们并非为访问控制而设计,因此缺少此类用例的有用功能(例如,没有细粒度的限制)。此外,它们的复杂性可能导致安全问题,尤其是在不受信任的进程可以操纵它们时(参见 控制对用户命名空间的访问)。

如何禁用 Landlock 审计记录?

您可能需要按照此处的说明设置过滤器:Landlock: 系统范围管理

附加文档