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