Landlock:非特权访问控制

作者:

Mickaël Salaün

日期:

2024 年 10 月

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 (使用过滤器的安全计算))或任何其他处理任务 credentials(7) 的 LSM。例如,一个进程的线程可以对自己应用 Landlock 规则,但这些规则不会自动应用于其他兄弟线程(与 POSIX 线程凭据更改不同,参见 nptl(7))。

当一个线程对其自身进行沙箱化时,我们可以保证相关的安全策略将持续在该线程的所有后代上强制执行。这允许为每个应用程序创建独立的、模块化的安全策略,这些策略会根据它们的运行时父策略自动组合在一起。

Ptrace 限制

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

IPC 范围

类似于隐式的 Ptrace 限制,我们可能希望进一步限制沙箱之间的交互。每个 Landlock 域都可以通过在规则集中指定它来显式地限定一组操作的范围。例如,如果一个沙箱化的进程不应该能够通过抽象的 unix(7) 套接字 connect(2) 到一个非沙箱化的进程,我们可以使用 LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET 来指定这样的限制。此外,如果一个沙箱化的进程不应该能够向非沙箱化的进程发送信号,我们可以使用 LANDLOCK_SCOPE_SIGNAL 来指定这个限制。

当一个沙箱化进程的域没有被限定范围时,它可以连接到一个非沙箱化的进程。如果一个进程的域被限定了范围,它只能连接到同一范围内进程创建的套接字。此外,如果一个进程被限定为向非限定范围的进程发送信号,它只能向同一范围内的进程发送信号。

当连接的数据报套接字的域被限定范围时,它的行为类似于流套接字,这意味着如果在套接字连接后限定了范围,它仍然可以像流套接字一样 send(2) 数据。然而,在相同的情况下,未连接的数据报套接字不能在其范围之外发送数据(使用 sendto(2))。

一个具有限定范围域的进程可以继承一个由非限定范围进程创建的套接字。该进程无法连接到此套接字,因为它具有限定范围的域。

IPC 范围不支持例外,因此如果一个域被限定了范围,则无法添加任何规则来允许访问范围外的资源或进程。

截断文件

LANDLOCK_ACCESS_FS_WRITE_FILELANDLOCK_ACCESS_FS_TRUNCATE 涵盖的操作都会更改文件的内容,有时以非直观的方式重叠。建议始终将它们一起指定。

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

还应注意,截断文件不需要 LANDLOCK_ACCESS_FS_WRITE_FILE 权限。除了 truncate(2) 系统调用之外,还可以通过带有标志 O_RDONLY | O_TRUNCopen(2) 完成此操作。

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

与文件描述符关联的权限

在打开文件时,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) 或带有 O_TRUNCopen(2) 截断文件。此访问权限自 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 将始终拒绝在不同目录之间重新父文件(reparent files)的尝试。

    除了源目录和目标目录都具有 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 命令 (FIOCLEX, FIONCLEX),

    • 针对文件描述的 IOCTL 命令 (FIONBIO, FIOASYNC),

    • 针对文件系统的 IOCTL 命令 (FIFREEZE, FITHAW, FIGETBSZ, FS_IOC_GETFSUUID, FS_IOC_GETFSSYSFSPATH)

    • 一些与设备一起使用时没有意义,但其实现是安全的并返回正确错误代码的 IOCTL 命令 (FS_IOC_FIEMAP, FICLONE, FICLONERANGE, FIDEDUPERANGE)

    此访问权限自 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_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 规则集,并在成功时返回相关的文件描述符。

如果 flagsLANDLOCK_CREATE_RULESET_VERSIONattr 为 NULL 且 size 为 0,则返回的值是支持的最高 Landlock ABI 版本(从 1 开始)。

可能返回的错误有:

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

  • EINVAL:未知的 flags,或未知的访问权限,或未知的作用域,或过小的 size

  • E2BIGattrsize 不一致;

  • EFAULTattrsize 不一致;

  • ENOMSG:空的 landlock_ruleset_attr.handled_access_fs

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() 的参数。

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

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

出于向后兼容性的原因,需要显式列出处理的访问权限。在大多数用例中,使用 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

必须为 0。

描述

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

可能返回的错误有:

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

  • EINVAL: flags 不为 0。

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

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

  • EPERM: ruleset_fd 没有对底层规则集的读取权限,或者当前线程不是在 no_new_privs 模式下运行,或者在其命名空间中没有 CAP_SYS_ADMIN

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

当前限制

文件系统拓扑修改

使用文件系统限制沙箱化的线程无法通过 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) 的发送。

内核支持

构建时配置

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 操作已经不可能。

问题与解答

用户空间沙箱管理器呢?

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

命名空间和容器呢?

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

其他文档