Skip to content

Commit e119981

Browse files
l0kodJames Morris
authored andcommitted
selftests/landlock: Add user space tests
Test all Landlock system calls, ptrace hooks semantic and filesystem access-control with multiple layouts. Test coverage for security/landlock/ is 93.6% of lines. The code not covered only deals with internal kernel errors (e.g. memory allocation) and race conditions. Cc: James Morris <[email protected]> Cc: Jann Horn <[email protected]> Cc: Serge E. Hallyn <[email protected]> Cc: Shuah Khan <[email protected]> Signed-off-by: Mickaël Salaün <[email protected]> Reviewed-by: Vincent Dagonneau <[email protected]> Reviewed-by: Kees Cook <[email protected]> Link: https://lore.kernel.org/r/[email protected] Signed-off-by: James Morris <[email protected]>
1 parent 265885d commit e119981

File tree

10 files changed

+3570
-0
lines changed

10 files changed

+3570
-0
lines changed

MAINTAINERS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10005,6 +10005,7 @@ W: https://landlock.io
1000510005
T: git https://github.com/landlock-lsm/linux.git
1000610006
F: include/uapi/linux/landlock.h
1000710007
F: security/landlock/
10008+
F: tools/testing/selftests/landlock/
1000810009
K: landlock
1000910010
K: LANDLOCK
1001010011

tools/testing/selftests/Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ TARGETS += ir
2525
TARGETS += kcmp
2626
TARGETS += kexec
2727
TARGETS += kvm
28+
TARGETS += landlock
2829
TARGETS += lib
2930
TARGETS += livepatch
3031
TARGETS += lkdtm
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
/*_test
2+
/true
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
# SPDX-License-Identifier: GPL-2.0
2+
3+
CFLAGS += -Wall -O2
4+
5+
src_test := $(wildcard *_test.c)
6+
7+
TEST_GEN_PROGS := $(src_test:.c=)
8+
9+
TEST_GEN_PROGS_EXTENDED := true
10+
11+
KSFT_KHDR_INSTALL := 1
12+
OVERRIDE_TARGETS := 1
13+
include ../lib.mk
14+
15+
khdr_dir = $(top_srcdir)/usr/include
16+
17+
$(khdr_dir)/linux/landlock.h: khdr
18+
@:
19+
20+
$(OUTPUT)/true: true.c
21+
$(LINK.c) $< $(LDLIBS) -o $@ -static
22+
23+
$(OUTPUT)/%_test: %_test.c $(khdr_dir)/linux/landlock.h ../kselftest_harness.h common.h
24+
$(LINK.c) $< $(LDLIBS) -o $@ -lcap -I$(khdr_dir)
Lines changed: 219 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,219 @@
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

Comments
 (0)