Skip to content

Commit 9adbe89

Browse files
gnoackl0kod
authored andcommitted
selftests/landlock: Add filesystem access benchmark
fs_bench benchmarks the performance of Landlock's path walk by exercising it in a scenario that amplifies Landlock's overhead: * Create a large number of nested directories * Enforce a Landlock policy in which a rule is associated with each of these subdirectories * Benchmark openat() applied to the deepest directory, forcing Landlock to walk the entire path. Signed-off-by: Günther Noack <gnoack3000@gmail.com> Link: https://lore.kernel.org/r/20260206151154.97915-3-gnoack3000@gmail.com [mic: Fix missing mode with O_CREAT, improve text consistency, sort includes] Signed-off-by: Mickaël Salaün <mic@digikod.net>
1 parent de4b09a commit 9adbe89

3 files changed

Lines changed: 216 additions & 0 deletions

File tree

tools/testing/selftests/landlock/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
/*_test
2+
/fs_bench
23
/sandbox-and-launch
34
/true
45
/wait-pipe

tools/testing/selftests/landlock/Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ LOCAL_HDRS += $(wildcard *.h)
99
src_test := $(wildcard *_test.c)
1010

1111
TEST_GEN_PROGS := $(src_test:.c=)
12+
TEST_GEN_PROGS += fs_bench
1213

1314
TEST_GEN_PROGS_EXTENDED := \
1415
true \
Lines changed: 214 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,214 @@
1+
// SPDX-License-Identifier: GPL-2.0
2+
/*
3+
* Landlock filesystem benchmark
4+
*
5+
* This program benchmarks the time required for file access checks. We use a
6+
* large number (-d flag) of nested directories where each directory inode has
7+
* an associated Landlock rule, and we repeatedly (-n flag) exercise a file
8+
* access for which Landlock has to walk the path all the way up to the root.
9+
*
10+
* With an increasing number of nested subdirectories, Landlock's portion of the
11+
* overall system call time increases, which makes the effects of Landlock
12+
* refactorings more measurable.
13+
*
14+
* This benchmark does *not* measure the building of the Landlock ruleset. The
15+
* time required to add all these rules is not large enough to be easily
16+
* measurable. A separate benchmark tool would be better to test that, and that
17+
* tool could then also use a simpler file system layout.
18+
*
19+
* Copyright © 2026 Google LLC
20+
*/
21+
22+
#define _GNU_SOURCE
23+
#include <err.h>
24+
#include <errno.h>
25+
#include <fcntl.h>
26+
#include <linux/landlock.h>
27+
#include <linux/prctl.h>
28+
#include <stdbool.h>
29+
#include <stdio.h>
30+
#include <stdlib.h>
31+
#include <string.h>
32+
#include <sys/prctl.h>
33+
#include <sys/stat.h>
34+
#include <sys/times.h>
35+
#include <time.h>
36+
#include <unistd.h>
37+
38+
#include "wrappers.h"
39+
40+
static void usage(const char *const argv0)
41+
{
42+
printf("Usage:\n");
43+
printf(" %s [OPTIONS]\n", argv0);
44+
printf("\n");
45+
printf(" Benchmark expensive Landlock checks for D nested dirs\n");
46+
printf("\n");
47+
printf("Options:\n");
48+
printf(" -h help\n");
49+
printf(" -L disable Landlock (as a baseline)\n");
50+
printf(" -d D set directory depth to D\n");
51+
printf(" -n N set number of benchmark iterations to N\n");
52+
}
53+
54+
/*
55+
* Build a deep directory, enforce Landlock and return the FD to the
56+
* deepest dir. On any failure, exit the process with an error.
57+
*/
58+
static int build_directory(size_t depth, const bool use_landlock)
59+
{
60+
const char *path = "d"; /* directory name */
61+
int abi, ruleset_fd, curr, prev;
62+
63+
if (use_landlock) {
64+
abi = landlock_create_ruleset(NULL, 0,
65+
LANDLOCK_CREATE_RULESET_VERSION);
66+
if (abi < 7)
67+
err(1, "Landlock ABI too low: got %d, wanted 7+", abi);
68+
}
69+
70+
ruleset_fd = -1;
71+
if (use_landlock) {
72+
struct landlock_ruleset_attr attr = {
73+
.handled_access_fs = LANDLOCK_ACCESS_FS_IOCTL_DEV |
74+
LANDLOCK_ACCESS_FS_WRITE_FILE |
75+
LANDLOCK_ACCESS_FS_MAKE_REG,
76+
};
77+
ruleset_fd = landlock_create_ruleset(&attr, sizeof(attr), 0U);
78+
if (ruleset_fd < 0)
79+
err(1, "landlock_create_ruleset");
80+
}
81+
82+
curr = open(".", O_PATH);
83+
if (curr < 0)
84+
err(1, "open(.)");
85+
86+
while (depth--) {
87+
if (use_landlock) {
88+
struct landlock_path_beneath_attr attr = {
89+
.allowed_access = LANDLOCK_ACCESS_FS_IOCTL_DEV,
90+
.parent_fd = curr,
91+
};
92+
if (landlock_add_rule(ruleset_fd,
93+
LANDLOCK_RULE_PATH_BENEATH, &attr,
94+
0) < 0)
95+
err(1, "landlock_add_rule");
96+
}
97+
98+
if (mkdirat(curr, path, 0700) < 0)
99+
err(1, "mkdirat(%s)", path);
100+
101+
prev = curr;
102+
curr = openat(curr, path, O_PATH);
103+
if (curr < 0)
104+
err(1, "openat(%s)", path);
105+
106+
close(prev);
107+
}
108+
109+
if (use_landlock) {
110+
if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) < 0)
111+
err(1, "prctl");
112+
113+
if (landlock_restrict_self(ruleset_fd, 0) < 0)
114+
err(1, "landlock_restrict_self");
115+
}
116+
117+
close(ruleset_fd);
118+
return curr;
119+
}
120+
121+
static void remove_recursively(const size_t depth)
122+
{
123+
const char *path = "d"; /* directory name */
124+
125+
int fd = openat(AT_FDCWD, ".", O_PATH);
126+
127+
if (fd < 0)
128+
err(1, "openat(.)");
129+
130+
for (size_t i = 0; i < depth - 1; i++) {
131+
int oldfd = fd;
132+
133+
fd = openat(fd, path, O_PATH);
134+
if (fd < 0)
135+
err(1, "openat(%s)", path);
136+
close(oldfd);
137+
}
138+
139+
for (size_t i = 0; i < depth; i++) {
140+
if (unlinkat(fd, path, AT_REMOVEDIR) < 0)
141+
err(1, "unlinkat(%s)", path);
142+
int newfd = openat(fd, "..", O_PATH);
143+
144+
close(fd);
145+
fd = newfd;
146+
}
147+
close(fd);
148+
}
149+
150+
int main(int argc, char *argv[])
151+
{
152+
bool use_landlock = true;
153+
size_t num_iterations = 100000;
154+
size_t num_subdirs = 10000;
155+
int c, curr, fd;
156+
struct tms start_time, end_time;
157+
158+
setbuf(stdout, NULL);
159+
while ((c = getopt(argc, argv, "hLd:n:")) != -1) {
160+
switch (c) {
161+
case 'h':
162+
usage(argv[0]);
163+
return EXIT_SUCCESS;
164+
case 'L':
165+
use_landlock = false;
166+
break;
167+
case 'd':
168+
num_subdirs = atoi(optarg);
169+
break;
170+
case 'n':
171+
num_iterations = atoi(optarg);
172+
break;
173+
default:
174+
usage(argv[0]);
175+
return EXIT_FAILURE;
176+
}
177+
}
178+
179+
printf("*** Benchmark ***\n");
180+
printf("%zu dirs, %zu iterations, %s Landlock\n", num_subdirs,
181+
num_iterations, use_landlock ? "with" : "without");
182+
183+
if (times(&start_time) == -1)
184+
err(1, "times");
185+
186+
curr = build_directory(num_subdirs, use_landlock);
187+
188+
for (int i = 0; i < num_iterations; i++) {
189+
fd = openat(curr, "file.txt", O_CREAT | O_TRUNC | O_WRONLY,
190+
0600);
191+
if (use_landlock) {
192+
if (fd == 0)
193+
errx(1, "openat succeeded, expected EACCES");
194+
if (errno != EACCES)
195+
err(1, "openat expected EACCES, but got");
196+
}
197+
if (fd != -1)
198+
close(fd);
199+
}
200+
201+
if (times(&end_time) == -1)
202+
err(1, "times");
203+
204+
printf("*** Benchmark concluded ***\n");
205+
printf("System: %ld clocks\n",
206+
end_time.tms_stime - start_time.tms_stime);
207+
printf("User : %ld clocks\n",
208+
end_time.tms_utime - start_time.tms_utime);
209+
printf("Clocks per second: %ld\n", CLOCKS_PER_SEC);
210+
211+
close(curr);
212+
213+
remove_recursively(num_subdirs);
214+
}

0 commit comments

Comments
 (0)