root/tools/testing/selftests/landlock/base_test.c
// SPDX-License-Identifier: GPL-2.0
/*
 * Landlock tests - Common user space base
 *
 * Copyright © 2017-2020 Mickaël Salaün <mic@digikod.net>
 * Copyright © 2019-2020 ANSSI
 */

#define _GNU_SOURCE
#include <errno.h>
#include <fcntl.h>
#include <linux/keyctl.h>
#include <linux/landlock.h>
#include <string.h>
#include <sys/prctl.h>
#include <sys/socket.h>
#include <sys/types.h>

#include "common.h"

#ifndef O_PATH
#define O_PATH 010000000
#endif

TEST(inconsistent_attr)
{
        const long page_size = sysconf(_SC_PAGESIZE);
        char *const buf = malloc(page_size + 1);
        struct landlock_ruleset_attr *const ruleset_attr = (void *)buf;

        ASSERT_NE(NULL, buf);

        /* Checks copy_from_user(). */
        ASSERT_EQ(-1, landlock_create_ruleset(ruleset_attr, 0, 0));
        /* The size if less than sizeof(struct landlock_attr_enforce). */
        ASSERT_EQ(EINVAL, errno);
        ASSERT_EQ(-1, landlock_create_ruleset(ruleset_attr, 1, 0));
        ASSERT_EQ(EINVAL, errno);
        ASSERT_EQ(-1, landlock_create_ruleset(ruleset_attr, 7, 0));
        ASSERT_EQ(EINVAL, errno);

        ASSERT_EQ(-1, landlock_create_ruleset(NULL, 1, 0));
        /* The size if less than sizeof(struct landlock_attr_enforce). */
        ASSERT_EQ(EFAULT, errno);

        ASSERT_EQ(-1, landlock_create_ruleset(
                              NULL, sizeof(struct landlock_ruleset_attr), 0));
        ASSERT_EQ(EFAULT, errno);

        ASSERT_EQ(-1, landlock_create_ruleset(ruleset_attr, page_size + 1, 0));
        ASSERT_EQ(E2BIG, errno);

        /* Checks minimal valid attribute size. */
        ASSERT_EQ(-1, landlock_create_ruleset(ruleset_attr, 8, 0));
        ASSERT_EQ(ENOMSG, errno);
        ASSERT_EQ(-1, landlock_create_ruleset(
                              ruleset_attr,
                              sizeof(struct landlock_ruleset_attr), 0));
        ASSERT_EQ(ENOMSG, errno);
        ASSERT_EQ(-1, landlock_create_ruleset(ruleset_attr, page_size, 0));
        ASSERT_EQ(ENOMSG, errno);

        /* Checks non-zero value. */
        buf[page_size - 2] = '.';
        ASSERT_EQ(-1, landlock_create_ruleset(ruleset_attr, page_size, 0));
        ASSERT_EQ(E2BIG, errno);

        ASSERT_EQ(-1, landlock_create_ruleset(ruleset_attr, page_size + 1, 0));
        ASSERT_EQ(E2BIG, errno);

        free(buf);
}

TEST(abi_version)
{
        const struct landlock_ruleset_attr ruleset_attr = {
                .handled_access_fs = LANDLOCK_ACCESS_FS_READ_FILE,
        };
        ASSERT_EQ(8, landlock_create_ruleset(NULL, 0,
                                             LANDLOCK_CREATE_RULESET_VERSION));

        ASSERT_EQ(-1, landlock_create_ruleset(&ruleset_attr, 0,
                                              LANDLOCK_CREATE_RULESET_VERSION));
        ASSERT_EQ(EINVAL, errno);

        ASSERT_EQ(-1, landlock_create_ruleset(NULL, sizeof(ruleset_attr),
                                              LANDLOCK_CREATE_RULESET_VERSION));
        ASSERT_EQ(EINVAL, errno);

        ASSERT_EQ(-1,
                  landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr),
                                          LANDLOCK_CREATE_RULESET_VERSION));
        ASSERT_EQ(EINVAL, errno);

        ASSERT_EQ(-1, landlock_create_ruleset(NULL, 0,
                                              LANDLOCK_CREATE_RULESET_VERSION |
                                                      1 << 31));
        ASSERT_EQ(EINVAL, errno);
}

/*
 * Old source trees might not have the set of Kselftest fixes related to kernel
 * UAPI headers.
 */
#ifndef LANDLOCK_CREATE_RULESET_ERRATA
#define LANDLOCK_CREATE_RULESET_ERRATA (1U << 1)
#endif

TEST(errata)
{
        const struct landlock_ruleset_attr ruleset_attr = {
                .handled_access_fs = LANDLOCK_ACCESS_FS_READ_FILE,
        };
        int errata;

        errata = landlock_create_ruleset(NULL, 0,
                                         LANDLOCK_CREATE_RULESET_ERRATA);
        /* The errata bitmask will not be backported to tests. */
        ASSERT_LE(0, errata);
        TH_LOG("errata: 0x%x", errata);

        ASSERT_EQ(-1, landlock_create_ruleset(&ruleset_attr, 0,
                                              LANDLOCK_CREATE_RULESET_ERRATA));
        ASSERT_EQ(EINVAL, errno);

        ASSERT_EQ(-1, landlock_create_ruleset(NULL, sizeof(ruleset_attr),
                                              LANDLOCK_CREATE_RULESET_ERRATA));
        ASSERT_EQ(EINVAL, errno);

        ASSERT_EQ(-1,
                  landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr),
                                          LANDLOCK_CREATE_RULESET_ERRATA));
        ASSERT_EQ(EINVAL, errno);

        ASSERT_EQ(-1, landlock_create_ruleset(
                              NULL, 0,
                              LANDLOCK_CREATE_RULESET_VERSION |
                                      LANDLOCK_CREATE_RULESET_ERRATA));
        ASSERT_EQ(-1, landlock_create_ruleset(NULL, 0,
                                              LANDLOCK_CREATE_RULESET_ERRATA |
                                                      1 << 31));
        ASSERT_EQ(EINVAL, errno);
}

/* Tests ordering of syscall argument checks. */
TEST(create_ruleset_checks_ordering)
{
        const int last_flag = LANDLOCK_CREATE_RULESET_ERRATA;
        const int invalid_flag = last_flag << 1;
        int ruleset_fd;
        const struct landlock_ruleset_attr ruleset_attr = {
                .handled_access_fs = LANDLOCK_ACCESS_FS_READ_FILE,
        };

        /* Checks priority for invalid flags. */
        ASSERT_EQ(-1, landlock_create_ruleset(NULL, 0, invalid_flag));
        ASSERT_EQ(EINVAL, errno);

        ASSERT_EQ(-1, landlock_create_ruleset(&ruleset_attr, 0, invalid_flag));
        ASSERT_EQ(EINVAL, errno);

        ASSERT_EQ(-1, landlock_create_ruleset(NULL, sizeof(ruleset_attr),
                                              invalid_flag));
        ASSERT_EQ(EINVAL, errno);

        ASSERT_EQ(-1,
                  landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr),
                                          invalid_flag));
        ASSERT_EQ(EINVAL, errno);

        /* Checks too big ruleset_attr size. */
        ASSERT_EQ(-1, landlock_create_ruleset(&ruleset_attr, -1, 0));
        ASSERT_EQ(E2BIG, errno);

        /* Checks too small ruleset_attr size. */
        ASSERT_EQ(-1, landlock_create_ruleset(&ruleset_attr, 0, 0));
        ASSERT_EQ(EINVAL, errno);
        ASSERT_EQ(-1, landlock_create_ruleset(&ruleset_attr, 1, 0));
        ASSERT_EQ(EINVAL, errno);

        /* Checks valid call. */
        ruleset_fd =
                landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0);
        ASSERT_LE(0, ruleset_fd);
        ASSERT_EQ(0, close(ruleset_fd));
}

/* Tests ordering of syscall argument checks. */
TEST(add_rule_checks_ordering)
{
        const struct landlock_ruleset_attr ruleset_attr = {
                .handled_access_fs = LANDLOCK_ACCESS_FS_EXECUTE,
        };
        struct landlock_path_beneath_attr path_beneath_attr = {
                .allowed_access = LANDLOCK_ACCESS_FS_EXECUTE,
                .parent_fd = -1,
        };
        const int ruleset_fd =
                landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0);

        ASSERT_LE(0, ruleset_fd);

        /* Checks invalid flags. */
        ASSERT_EQ(-1, landlock_add_rule(-1, 0, NULL, 1));
        ASSERT_EQ(EINVAL, errno);

        /* Checks invalid ruleset FD. */
        ASSERT_EQ(-1, landlock_add_rule(-1, 0, NULL, 0));
        ASSERT_EQ(EBADF, errno);

        /* Checks invalid rule type. */
        ASSERT_EQ(-1, landlock_add_rule(ruleset_fd, 0, NULL, 0));
        ASSERT_EQ(EINVAL, errno);

        /* Checks invalid rule attr. */
        ASSERT_EQ(-1, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_PATH_BENEATH,
                                        NULL, 0));
        ASSERT_EQ(EFAULT, errno);

        /* Checks invalid path_beneath.parent_fd. */
        ASSERT_EQ(-1, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_PATH_BENEATH,
                                        &path_beneath_attr, 0));
        ASSERT_EQ(EBADF, errno);

        /* Checks valid call. */
        path_beneath_attr.parent_fd =
                open("/tmp", O_PATH | O_NOFOLLOW | O_DIRECTORY | O_CLOEXEC);
        ASSERT_LE(0, path_beneath_attr.parent_fd);
        ASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_PATH_BENEATH,
                                       &path_beneath_attr, 0));
        ASSERT_EQ(0, close(path_beneath_attr.parent_fd));
        ASSERT_EQ(0, close(ruleset_fd));
}

/* Tests ordering of syscall argument and permission checks. */
TEST(restrict_self_checks_ordering)
{
        const struct landlock_ruleset_attr ruleset_attr = {
                .handled_access_fs = LANDLOCK_ACCESS_FS_EXECUTE,
        };
        struct landlock_path_beneath_attr path_beneath_attr = {
                .allowed_access = LANDLOCK_ACCESS_FS_EXECUTE,
                .parent_fd = -1,
        };
        const int ruleset_fd =
                landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0);

        ASSERT_LE(0, ruleset_fd);
        path_beneath_attr.parent_fd =
                open("/tmp", O_PATH | O_NOFOLLOW | O_DIRECTORY | O_CLOEXEC);
        ASSERT_LE(0, path_beneath_attr.parent_fd);
        ASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_PATH_BENEATH,
                                       &path_beneath_attr, 0));
        ASSERT_EQ(0, close(path_beneath_attr.parent_fd));

        /* Checks unprivileged enforcement without no_new_privs. */
        drop_caps(_metadata);
        ASSERT_EQ(-1, landlock_restrict_self(-1, -1));
        ASSERT_EQ(EPERM, errno);
        ASSERT_EQ(-1, landlock_restrict_self(-1, 0));
        ASSERT_EQ(EPERM, errno);
        ASSERT_EQ(-1, landlock_restrict_self(ruleset_fd, 0));
        ASSERT_EQ(EPERM, errno);

        ASSERT_EQ(0, prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0));

        /* Checks invalid flags. */
        ASSERT_EQ(-1, landlock_restrict_self(-1, -1));
        ASSERT_EQ(EINVAL, errno);

        /* Checks invalid ruleset FD. */
        ASSERT_EQ(-1, landlock_restrict_self(-1, 0));
        ASSERT_EQ(EBADF, errno);

        /* Checks valid call. */
        ASSERT_EQ(0, landlock_restrict_self(ruleset_fd, 0));
        ASSERT_EQ(0, close(ruleset_fd));
}

TEST(restrict_self_fd)
{
        int fd;

        fd = open("/dev/null", O_RDONLY | O_CLOEXEC);
        ASSERT_LE(0, fd);

        EXPECT_EQ(-1, landlock_restrict_self(fd, 0));
        EXPECT_EQ(EBADFD, errno);
}

TEST(restrict_self_fd_logging_flags)
{
        int fd;

        fd = open("/dev/null", O_RDONLY | O_CLOEXEC);
        ASSERT_LE(0, fd);

        /*
         * LANDLOCK_RESTRICT_SELF_LOG_SUBDOMAINS_OFF accepts -1 but not any file
         * descriptor.
         */
        EXPECT_EQ(-1, landlock_restrict_self(
                              fd, LANDLOCK_RESTRICT_SELF_LOG_SUBDOMAINS_OFF));
        EXPECT_EQ(EBADFD, errno);
}

TEST(restrict_self_logging_flags)
{
        const __u32 last_flag = LANDLOCK_RESTRICT_SELF_TSYNC;

        /* Tests invalid flag combinations. */

        EXPECT_EQ(-1, landlock_restrict_self(-1, last_flag << 1));
        EXPECT_EQ(EINVAL, errno);

        EXPECT_EQ(-1, landlock_restrict_self(-1, -1));
        EXPECT_EQ(EINVAL, errno);

        /* Tests valid flag combinations. */

        EXPECT_EQ(-1, landlock_restrict_self(-1, 0));
        EXPECT_EQ(EBADF, errno);

        EXPECT_EQ(-1, landlock_restrict_self(
                              -1, LANDLOCK_RESTRICT_SELF_LOG_SAME_EXEC_OFF));
        EXPECT_EQ(EBADF, errno);

        EXPECT_EQ(-1,
                  landlock_restrict_self(
                          -1,
                          LANDLOCK_RESTRICT_SELF_LOG_SAME_EXEC_OFF |
                                  LANDLOCK_RESTRICT_SELF_LOG_SUBDOMAINS_OFF));
        EXPECT_EQ(EBADF, errno);

        EXPECT_EQ(-1,
                  landlock_restrict_self(
                          -1,
                          LANDLOCK_RESTRICT_SELF_LOG_NEW_EXEC_ON |
                                  LANDLOCK_RESTRICT_SELF_LOG_SUBDOMAINS_OFF));
        EXPECT_EQ(EBADF, errno);

        EXPECT_EQ(-1, landlock_restrict_self(
                              -1, LANDLOCK_RESTRICT_SELF_LOG_NEW_EXEC_ON));
        EXPECT_EQ(EBADF, errno);

        EXPECT_EQ(-1,
                  landlock_restrict_self(
                          -1, LANDLOCK_RESTRICT_SELF_LOG_SAME_EXEC_OFF |
                                      LANDLOCK_RESTRICT_SELF_LOG_NEW_EXEC_ON));
        EXPECT_EQ(EBADF, errno);

        /* Tests with an invalid ruleset_fd. */

        EXPECT_EQ(-1, landlock_restrict_self(
                              -2, LANDLOCK_RESTRICT_SELF_LOG_SUBDOMAINS_OFF));
        EXPECT_EQ(EBADF, errno);

        EXPECT_EQ(0, landlock_restrict_self(
                             -1, LANDLOCK_RESTRICT_SELF_LOG_SUBDOMAINS_OFF));
}

TEST(ruleset_fd_io)
{
        struct landlock_ruleset_attr ruleset_attr = {
                .handled_access_fs = LANDLOCK_ACCESS_FS_READ_FILE,
        };
        int ruleset_fd;
        char buf;

        drop_caps(_metadata);
        ruleset_fd =
                landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0);
        ASSERT_LE(0, ruleset_fd);

        ASSERT_EQ(-1, write(ruleset_fd, ".", 1));
        ASSERT_EQ(EINVAL, errno);
        ASSERT_EQ(-1, read(ruleset_fd, &buf, 1));
        ASSERT_EQ(EINVAL, errno);

        ASSERT_EQ(0, close(ruleset_fd));
}

/* Tests enforcement of a ruleset FD transferred through a UNIX socket. */
TEST(ruleset_fd_transfer)
{
        struct landlock_ruleset_attr ruleset_attr = {
                .handled_access_fs = LANDLOCK_ACCESS_FS_READ_DIR,
        };
        struct landlock_path_beneath_attr path_beneath_attr = {
                .allowed_access = LANDLOCK_ACCESS_FS_READ_DIR,
        };
        int ruleset_fd_tx, dir_fd;
        int socket_fds[2];
        pid_t child;
        int status;

        drop_caps(_metadata);

        /* Creates a test ruleset with a simple rule. */
        ruleset_fd_tx =
                landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0);
        ASSERT_LE(0, ruleset_fd_tx);
        path_beneath_attr.parent_fd =
                open("/tmp", O_PATH | O_NOFOLLOW | O_DIRECTORY | O_CLOEXEC);
        ASSERT_LE(0, path_beneath_attr.parent_fd);
        ASSERT_EQ(0,
                  landlock_add_rule(ruleset_fd_tx, LANDLOCK_RULE_PATH_BENEATH,
                                    &path_beneath_attr, 0));
        ASSERT_EQ(0, close(path_beneath_attr.parent_fd));

        /* Sends the ruleset FD over a socketpair and then close it. */
        ASSERT_EQ(0, socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0,
                                socket_fds));
        ASSERT_EQ(0, send_fd(socket_fds[0], ruleset_fd_tx));
        ASSERT_EQ(0, close(socket_fds[0]));
        ASSERT_EQ(0, close(ruleset_fd_tx));

        child = fork();
        ASSERT_LE(0, child);
        if (child == 0) {
                const int ruleset_fd_rx = recv_fd(socket_fds[1]);

                ASSERT_LE(0, ruleset_fd_rx);
                ASSERT_EQ(0, close(socket_fds[1]));

                /* Enforces the received ruleset on the child. */
                ASSERT_EQ(0, prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0));
                ASSERT_EQ(0, landlock_restrict_self(ruleset_fd_rx, 0));
                ASSERT_EQ(0, close(ruleset_fd_rx));

                /* Checks that the ruleset enforcement. */
                ASSERT_EQ(-1, open("/", O_RDONLY | O_DIRECTORY | O_CLOEXEC));
                ASSERT_EQ(EACCES, errno);
                dir_fd = open("/tmp", O_RDONLY | O_DIRECTORY | O_CLOEXEC);
                ASSERT_LE(0, dir_fd);
                ASSERT_EQ(0, close(dir_fd));
                _exit(_metadata->exit_code);
                return;
        }

        ASSERT_EQ(0, close(socket_fds[1]));

        /* Checks that the parent is unrestricted. */
        dir_fd = open("/", O_RDONLY | O_DIRECTORY | O_CLOEXEC);
        ASSERT_LE(0, dir_fd);
        ASSERT_EQ(0, close(dir_fd));
        dir_fd = open("/tmp", O_RDONLY | O_DIRECTORY | O_CLOEXEC);
        ASSERT_LE(0, dir_fd);
        ASSERT_EQ(0, close(dir_fd));

        ASSERT_EQ(child, waitpid(child, &status, 0));
        ASSERT_EQ(1, WIFEXITED(status));
        ASSERT_EQ(EXIT_SUCCESS, WEXITSTATUS(status));
}

TEST(cred_transfer)
{
        struct landlock_ruleset_attr ruleset_attr = {
                .handled_access_fs = LANDLOCK_ACCESS_FS_READ_DIR,
        };
        int ruleset_fd, dir_fd;
        pid_t child;
        int status;

        drop_caps(_metadata);

        dir_fd = open("/", O_RDONLY | O_DIRECTORY | O_CLOEXEC);
        EXPECT_LE(0, dir_fd);
        EXPECT_EQ(0, close(dir_fd));

        /* Denies opening directories. */
        ruleset_fd =
                landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0);
        ASSERT_LE(0, ruleset_fd);
        EXPECT_EQ(0, prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0));
        ASSERT_EQ(0, landlock_restrict_self(ruleset_fd, 0));
        EXPECT_EQ(0, close(ruleset_fd));

        /* Checks ruleset enforcement. */
        EXPECT_EQ(-1, open("/", O_RDONLY | O_DIRECTORY | O_CLOEXEC));
        EXPECT_EQ(EACCES, errno);

        /* Needed for KEYCTL_SESSION_TO_PARENT permission checks */
        EXPECT_NE(-1, syscall(__NR_keyctl, KEYCTL_JOIN_SESSION_KEYRING, NULL, 0,
                              0, 0))
        {
                TH_LOG("Failed to join session keyring: %s", strerror(errno));
        }

        child = fork();
        ASSERT_LE(0, child);
        if (child == 0) {
                /* Checks ruleset enforcement. */
                EXPECT_EQ(-1, open("/", O_RDONLY | O_DIRECTORY | O_CLOEXEC));
                EXPECT_EQ(EACCES, errno);

                /*
                 * KEYCTL_SESSION_TO_PARENT is a no-op unless we have a
                 * different session keyring in the child, so make that happen.
                 */
                EXPECT_NE(-1, syscall(__NR_keyctl, KEYCTL_JOIN_SESSION_KEYRING,
                                      NULL, 0, 0, 0));

                /*
                 * KEYCTL_SESSION_TO_PARENT installs credentials on the parent
                 * that never go through the cred_prepare hook, this path uses
                 * cred_transfer instead.
                 */
                EXPECT_EQ(0, syscall(__NR_keyctl, KEYCTL_SESSION_TO_PARENT, 0,
                                     0, 0, 0));

                /* Re-checks ruleset enforcement. */
                EXPECT_EQ(-1, open("/", O_RDONLY | O_DIRECTORY | O_CLOEXEC));
                EXPECT_EQ(EACCES, errno);

                _exit(_metadata->exit_code);
                return;
        }

        EXPECT_EQ(child, waitpid(child, &status, 0));
        EXPECT_EQ(1, WIFEXITED(status));
        EXPECT_EQ(EXIT_SUCCESS, WEXITSTATUS(status));

        /* Re-checks ruleset enforcement. */
        EXPECT_EQ(-1, open("/", O_RDONLY | O_DIRECTORY | O_CLOEXEC));
        EXPECT_EQ(EACCES, errno);
}

TEST_HARNESS_MAIN