Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

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>

authored by

Günther Noack and committed by
Mickaël Salaün
9adbe893 de4b09ab

+216
+1
tools/testing/selftests/landlock/.gitignore
··· 1 1 /*_test 2 + /fs_bench 2 3 /sandbox-and-launch 3 4 /true 4 5 /wait-pipe
+1
tools/testing/selftests/landlock/Makefile
··· 9 9 src_test := $(wildcard *_test.c) 10 10 11 11 TEST_GEN_PROGS := $(src_test:.c=) 12 + TEST_GEN_PROGS += fs_bench 12 13 13 14 TEST_GEN_PROGS_EXTENDED := \ 14 15 true \
+214
tools/testing/selftests/landlock/fs_bench.c
··· 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 + }