|
| 1 | +// SPDX-License-Identifier: GPL-2.0 |
| 2 | +/* |
| 3 | + * Landlock tests - Common user space base |
| 4 | + * |
| 5 | + * Copyright © 2017-2020 Mickaël Salaün <[email protected]> |
| 6 | + * Copyright © 2019-2020 ANSSI |
| 7 | + */ |
| 8 | + |
| 9 | +#define _GNU_SOURCE |
| 10 | +#include <errno.h> |
| 11 | +#include <fcntl.h> |
| 12 | +#include <linux/landlock.h> |
| 13 | +#include <string.h> |
| 14 | +#include <sys/prctl.h> |
| 15 | +#include <sys/socket.h> |
| 16 | +#include <sys/types.h> |
| 17 | + |
| 18 | +#include "common.h" |
| 19 | + |
| 20 | +#ifndef O_PATH |
| 21 | +#define O_PATH 010000000 |
| 22 | +#endif |
| 23 | + |
| 24 | +TEST(inconsistent_attr) { |
| 25 | + const long page_size = sysconf(_SC_PAGESIZE); |
| 26 | + char *const buf = malloc(page_size + 1); |
| 27 | + struct landlock_ruleset_attr *const ruleset_attr = (void *)buf; |
| 28 | + |
| 29 | + ASSERT_NE(NULL, buf); |
| 30 | + |
| 31 | + /* Checks copy_from_user(). */ |
| 32 | + ASSERT_EQ(-1, landlock_create_ruleset(ruleset_attr, 0, 0)); |
| 33 | + /* The size if less than sizeof(struct landlock_attr_enforce). */ |
| 34 | + ASSERT_EQ(EINVAL, errno); |
| 35 | + ASSERT_EQ(-1, landlock_create_ruleset(ruleset_attr, 1, 0)); |
| 36 | + ASSERT_EQ(EINVAL, errno); |
| 37 | + |
| 38 | + ASSERT_EQ(-1, landlock_create_ruleset(NULL, 1, 0)); |
| 39 | + /* The size if less than sizeof(struct landlock_attr_enforce). */ |
| 40 | + ASSERT_EQ(EFAULT, errno); |
| 41 | + |
| 42 | + ASSERT_EQ(-1, landlock_create_ruleset(NULL, |
| 43 | + sizeof(struct landlock_ruleset_attr), 0)); |
| 44 | + ASSERT_EQ(EFAULT, errno); |
| 45 | + |
| 46 | + ASSERT_EQ(-1, landlock_create_ruleset(ruleset_attr, page_size + 1, 0)); |
| 47 | + ASSERT_EQ(E2BIG, errno); |
| 48 | + |
| 49 | + ASSERT_EQ(-1, landlock_create_ruleset(ruleset_attr, |
| 50 | + sizeof(struct landlock_ruleset_attr), 0)); |
| 51 | + ASSERT_EQ(ENOMSG, errno); |
| 52 | + ASSERT_EQ(-1, landlock_create_ruleset(ruleset_attr, page_size, 0)); |
| 53 | + ASSERT_EQ(ENOMSG, errno); |
| 54 | + |
| 55 | + /* Checks non-zero value. */ |
| 56 | + buf[page_size - 2] = '.'; |
| 57 | + ASSERT_EQ(-1, landlock_create_ruleset(ruleset_attr, page_size, 0)); |
| 58 | + ASSERT_EQ(E2BIG, errno); |
| 59 | + |
| 60 | + ASSERT_EQ(-1, landlock_create_ruleset(ruleset_attr, page_size + 1, 0)); |
| 61 | + ASSERT_EQ(E2BIG, errno); |
| 62 | + |
| 63 | + free(buf); |
| 64 | +} |
| 65 | + |
| 66 | +TEST(empty_path_beneath_attr) { |
| 67 | + const struct landlock_ruleset_attr ruleset_attr = { |
| 68 | + .handled_access_fs = LANDLOCK_ACCESS_FS_EXECUTE, |
| 69 | + }; |
| 70 | + const int ruleset_fd = landlock_create_ruleset(&ruleset_attr, |
| 71 | + sizeof(ruleset_attr), 0); |
| 72 | + |
| 73 | + ASSERT_LE(0, ruleset_fd); |
| 74 | + |
| 75 | + /* Similar to struct landlock_path_beneath_attr.parent_fd = 0 */ |
| 76 | + ASSERT_EQ(-1, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_PATH_BENEATH, |
| 77 | + NULL, 0)); |
| 78 | + ASSERT_EQ(EFAULT, errno); |
| 79 | + ASSERT_EQ(0, close(ruleset_fd)); |
| 80 | +} |
| 81 | + |
| 82 | +TEST(inval_fd_enforce) { |
| 83 | + ASSERT_EQ(0, prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)); |
| 84 | + |
| 85 | + ASSERT_EQ(-1, landlock_restrict_self(-1, 0)); |
| 86 | + ASSERT_EQ(EBADF, errno); |
| 87 | +} |
| 88 | + |
| 89 | +TEST(unpriv_enforce_without_no_new_privs) { |
| 90 | + int err; |
| 91 | + |
| 92 | + drop_caps(_metadata); |
| 93 | + err = landlock_restrict_self(-1, 0); |
| 94 | + ASSERT_EQ(EPERM, errno); |
| 95 | + ASSERT_EQ(err, -1); |
| 96 | +} |
| 97 | + |
| 98 | +TEST(ruleset_fd_io) |
| 99 | +{ |
| 100 | + struct landlock_ruleset_attr ruleset_attr = { |
| 101 | + .handled_access_fs = LANDLOCK_ACCESS_FS_READ_FILE, |
| 102 | + }; |
| 103 | + int ruleset_fd; |
| 104 | + char buf; |
| 105 | + |
| 106 | + drop_caps(_metadata); |
| 107 | + ruleset_fd = landlock_create_ruleset(&ruleset_attr, |
| 108 | + sizeof(ruleset_attr), 0); |
| 109 | + ASSERT_LE(0, ruleset_fd); |
| 110 | + |
| 111 | + ASSERT_EQ(-1, write(ruleset_fd, ".", 1)); |
| 112 | + ASSERT_EQ(EINVAL, errno); |
| 113 | + ASSERT_EQ(-1, read(ruleset_fd, &buf, 1)); |
| 114 | + ASSERT_EQ(EINVAL, errno); |
| 115 | + |
| 116 | + ASSERT_EQ(0, close(ruleset_fd)); |
| 117 | +} |
| 118 | + |
| 119 | +/* Tests enforcement of a ruleset FD transferred through a UNIX socket. */ |
| 120 | +TEST(ruleset_fd_transfer) |
| 121 | +{ |
| 122 | + struct landlock_ruleset_attr ruleset_attr = { |
| 123 | + .handled_access_fs = LANDLOCK_ACCESS_FS_READ_DIR, |
| 124 | + }; |
| 125 | + struct landlock_path_beneath_attr path_beneath_attr = { |
| 126 | + .allowed_access = LANDLOCK_ACCESS_FS_READ_DIR, |
| 127 | + }; |
| 128 | + int ruleset_fd_tx, dir_fd; |
| 129 | + union { |
| 130 | + /* Aligned ancillary data buffer. */ |
| 131 | + char buf[CMSG_SPACE(sizeof(ruleset_fd_tx))]; |
| 132 | + struct cmsghdr _align; |
| 133 | + } cmsg_tx = {}; |
| 134 | + char data_tx = '.'; |
| 135 | + struct iovec io = { |
| 136 | + .iov_base = &data_tx, |
| 137 | + .iov_len = sizeof(data_tx), |
| 138 | + }; |
| 139 | + struct msghdr msg = { |
| 140 | + .msg_iov = &io, |
| 141 | + .msg_iovlen = 1, |
| 142 | + .msg_control = &cmsg_tx.buf, |
| 143 | + .msg_controllen = sizeof(cmsg_tx.buf), |
| 144 | + }; |
| 145 | + struct cmsghdr *cmsg; |
| 146 | + int socket_fds[2]; |
| 147 | + pid_t child; |
| 148 | + int status; |
| 149 | + |
| 150 | + drop_caps(_metadata); |
| 151 | + |
| 152 | + /* Creates a test ruleset with a simple rule. */ |
| 153 | + ruleset_fd_tx = landlock_create_ruleset(&ruleset_attr, |
| 154 | + sizeof(ruleset_attr), 0); |
| 155 | + ASSERT_LE(0, ruleset_fd_tx); |
| 156 | + path_beneath_attr.parent_fd = open("/tmp", O_PATH | O_NOFOLLOW | |
| 157 | + O_DIRECTORY | O_CLOEXEC); |
| 158 | + ASSERT_LE(0, path_beneath_attr.parent_fd); |
| 159 | + ASSERT_EQ(0, landlock_add_rule(ruleset_fd_tx, LANDLOCK_RULE_PATH_BENEATH, |
| 160 | + &path_beneath_attr, 0)); |
| 161 | + ASSERT_EQ(0, close(path_beneath_attr.parent_fd)); |
| 162 | + |
| 163 | + cmsg = CMSG_FIRSTHDR(&msg); |
| 164 | + ASSERT_NE(NULL, cmsg); |
| 165 | + cmsg->cmsg_len = CMSG_LEN(sizeof(ruleset_fd_tx)); |
| 166 | + cmsg->cmsg_level = SOL_SOCKET; |
| 167 | + cmsg->cmsg_type = SCM_RIGHTS; |
| 168 | + memcpy(CMSG_DATA(cmsg), &ruleset_fd_tx, sizeof(ruleset_fd_tx)); |
| 169 | + |
| 170 | + /* Sends the ruleset FD over a socketpair and then close it. */ |
| 171 | + ASSERT_EQ(0, socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, socket_fds)); |
| 172 | + ASSERT_EQ(sizeof(data_tx), sendmsg(socket_fds[0], &msg, 0)); |
| 173 | + ASSERT_EQ(0, close(socket_fds[0])); |
| 174 | + ASSERT_EQ(0, close(ruleset_fd_tx)); |
| 175 | + |
| 176 | + child = fork(); |
| 177 | + ASSERT_LE(0, child); |
| 178 | + if (child == 0) { |
| 179 | + int ruleset_fd_rx; |
| 180 | + |
| 181 | + *(char *)msg.msg_iov->iov_base = '\0'; |
| 182 | + ASSERT_EQ(sizeof(data_tx), recvmsg(socket_fds[1], &msg, MSG_CMSG_CLOEXEC)); |
| 183 | + ASSERT_EQ('.', *(char *)msg.msg_iov->iov_base); |
| 184 | + ASSERT_EQ(0, close(socket_fds[1])); |
| 185 | + cmsg = CMSG_FIRSTHDR(&msg); |
| 186 | + ASSERT_EQ(cmsg->cmsg_len, CMSG_LEN(sizeof(ruleset_fd_tx))); |
| 187 | + memcpy(&ruleset_fd_rx, CMSG_DATA(cmsg), sizeof(ruleset_fd_tx)); |
| 188 | + |
| 189 | + /* Enforces the received ruleset on the child. */ |
| 190 | + ASSERT_EQ(0, prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)); |
| 191 | + ASSERT_EQ(0, landlock_restrict_self(ruleset_fd_rx, 0)); |
| 192 | + ASSERT_EQ(0, close(ruleset_fd_rx)); |
| 193 | + |
| 194 | + /* Checks that the ruleset enforcement. */ |
| 195 | + ASSERT_EQ(-1, open("/", O_RDONLY | O_DIRECTORY | O_CLOEXEC)); |
| 196 | + ASSERT_EQ(EACCES, errno); |
| 197 | + dir_fd = open("/tmp", O_RDONLY | O_DIRECTORY | O_CLOEXEC); |
| 198 | + ASSERT_LE(0, dir_fd); |
| 199 | + ASSERT_EQ(0, close(dir_fd)); |
| 200 | + _exit(_metadata->passed ? EXIT_SUCCESS : EXIT_FAILURE); |
| 201 | + return; |
| 202 | + } |
| 203 | + |
| 204 | + ASSERT_EQ(0, close(socket_fds[1])); |
| 205 | + |
| 206 | + /* Checks that the parent is unrestricted. */ |
| 207 | + dir_fd = open("/", O_RDONLY | O_DIRECTORY | O_CLOEXEC); |
| 208 | + ASSERT_LE(0, dir_fd); |
| 209 | + ASSERT_EQ(0, close(dir_fd)); |
| 210 | + dir_fd = open("/tmp", O_RDONLY | O_DIRECTORY | O_CLOEXEC); |
| 211 | + ASSERT_LE(0, dir_fd); |
| 212 | + ASSERT_EQ(0, close(dir_fd)); |
| 213 | + |
| 214 | + ASSERT_EQ(child, waitpid(child, &status, 0)); |
| 215 | + ASSERT_EQ(1, WIFEXITED(status)); |
| 216 | + ASSERT_EQ(EXIT_SUCCESS, WEXITSTATUS(status)); |
| 217 | +} |
| 218 | + |
| 219 | +TEST_HARNESS_MAIN |
0 commit comments