[PATCH v2 1/3] selftests/landlock: Add filesystem access benchmark

Mickaël Salaün mic at digikod.net
Wed Jan 28 21:31:23 UTC 2026


On Sun, Jan 25, 2026 at 08:58:51PM +0100, Günther Noack wrote:
> 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 at gmail.com>
> ---
>  tools/testing/selftests/landlock/.gitignore |   1 +
>  tools/testing/selftests/landlock/Makefile   |   1 +
>  tools/testing/selftests/landlock/fs_bench.c | 161 ++++++++++++++++++++
>  3 files changed, 163 insertions(+)
>  create mode 100644 tools/testing/selftests/landlock/fs_bench.c
> 
> diff --git a/tools/testing/selftests/landlock/.gitignore b/tools/testing/selftests/landlock/.gitignore
> index a820329cae0d..1974e17a2611 100644
> --- a/tools/testing/selftests/landlock/.gitignore
> +++ b/tools/testing/selftests/landlock/.gitignore
> @@ -1,4 +1,5 @@
>  /*_test
> +/fs_bench
>  /sandbox-and-launch
>  /true
>  /wait-pipe
> diff --git a/tools/testing/selftests/landlock/Makefile b/tools/testing/selftests/landlock/Makefile
> index 044b83bde16e..fc43225d319a 100644
> --- a/tools/testing/selftests/landlock/Makefile
> +++ b/tools/testing/selftests/landlock/Makefile
> @@ -9,6 +9,7 @@ LOCAL_HDRS += $(wildcard *.h)
>  src_test := $(wildcard *_test.c)
>  
>  TEST_GEN_PROGS := $(src_test:.c=)
> +TEST_GEN_PROGS += fs_bench
>  
>  TEST_GEN_PROGS_EXTENDED := \
>  	true \
> diff --git a/tools/testing/selftests/landlock/fs_bench.c b/tools/testing/selftests/landlock/fs_bench.c
> new file mode 100644
> index 000000000000..a3b686418bc5
> --- /dev/null
> +++ b/tools/testing/selftests/landlock/fs_bench.c
> @@ -0,0 +1,161 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Landlock filesystem benchmark

You might want to add some copyright.

> + */
> +
> +#define _GNU_SOURCE
> +#include <err.h>
> +#include <fcntl.h>
> +#include <linux/landlock.h>
> +#include <stdbool.h>
> +#include <stdio.h>
> +#include <stdio.h>
> +#include <stdlib.h>
> +#include <string.h>
> +#include <sys/prctl.h>
> +#include <sys/stat.h>
> +#include <sys/syscall.h>
> +#include <sys/times.h>
> +#include <time.h>
> +#include <unistd.h>
> +
> +void usage(const char *argv0)

const

> +{
> +	printf("Usage:\n");
> +	printf("  %s [OPTIONS]\n", argv0);
> +	printf("\n");
> +	printf("  Benchmark expensive Landlock checks for D nested dirs\n");
> +	printf("\n");
> +	printf("Options:\n");
> +	printf("  -h	help\n");
> +	printf("  -L	disable Landlock (as a baseline)\n");
> +	printf("  -d D	set directory depth to D\n");
> +	printf("  -n N	set number of benchmark iterations to N\n");
> +}
> +
> +/*
> + * Build a deep directory, enforce Landlock and return the FD to the
> + * deepest dir.  On any failure, exit the process with an error.
> + */
> +int build_directory(size_t depth, bool use_landlock)

const

> +{
> +	const char *path = "d"; /* directory name */
> +	int abi, ruleset_fd, current, previous;
> +
> +	if (use_landlock) {
> +		abi = syscall(SYS_landlock_create_ruleset, NULL, 0,
> +			      LANDLOCK_CREATE_RULESET_VERSION);

Please include wrappers.h and use the related syscall helpers.  One of
the benefit is to use __NR_* constants defined by the installed kernel
headers.

> +		if (abi < 7)
> +			err(1, "Landlock ABI too low: got %d, wanted 7+", abi);
> +	}
> +
> +	ruleset_fd = -1;
> +	if (use_landlock) {
> +		struct landlock_ruleset_attr attr = {
> +			.handled_access_fs =
> +				0xffff, /* All FS access rights as of 2026-01 */
> +		};
> +		ruleset_fd = syscall(SYS_landlock_create_ruleset, &attr,
> +				     sizeof(attr), 0U);
> +		if (ruleset_fd < 0)
> +			err(1, "landlock_create_ruleset");
> +	}
> +
> +	current = open(".", O_PATH);
> +	if (current < 0)
> +		err(1, "open(.)");
> +
> +	while (depth--) {
> +		if (use_landlock) {
> +			struct landlock_path_beneath_attr attr = {
> +				.allowed_access = LANDLOCK_ACCESS_FS_IOCTL_DEV,
> +				.parent_fd = current,
> +			};
> +			if (syscall(SYS_landlock_add_rule, ruleset_fd,
> +				    LANDLOCK_RULE_PATH_BENEATH, &attr, 0) < 0)
> +				err(1, "landlock_add_rule");
> +		}
> +
> +		if (mkdirat(current, path, 0700) < 0)
> +			err(1, "mkdirat(%s)", path);

We should have a loop to build the directories, then start the timer and
have another loop to add Landlock rules.

> +
> +		previous = current;
> +		current = openat(current, path, O_PATH);
> +		if (current < 0)
> +			err(1, "open(%s)", path);
> +
> +		close(previous);
> +	}
> +
> +	if (use_landlock) {
> +		if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) < 0)
> +			err(1, "prctl");
> +
> +		if (syscall(SYS_landlock_restrict_self, ruleset_fd, 0) < 0)
> +			err(1, "landlock_restrict_self");
> +	}
> +
> +	close(ruleset_fd);
> +	return current;
> +}
> +
> +int main(int argc, char *argv[])
> +{
> +	bool use_landlock = true;
> +	size_t num_iterations = 100000;
> +	size_t num_subdirs = 10000;
> +	int c, current, fd;
> +	struct tms start_time, end_time;
> +
> +	setbuf(stdout, NULL);
> +	while ((c = getopt(argc, argv, "hLd:n:")) != -1) {
> +		switch (c) {
> +		case 'h':
> +			usage(argv[0]);
> +			return EXIT_SUCCESS;
> +		case 'L':
> +			use_landlock = false;
> +			break;
> +		case 'd':
> +			num_subdirs = atoi(optarg);
> +			break;
> +		case 'n':
> +			num_iterations = atoi(optarg);
> +			break;
> +		default:
> +			usage(argv[0]);
> +			return EXIT_FAILURE;
> +		}
> +	}
> +
> +	printf("*** Benchmark ***\n");

We should probably use ksft_*() helpers in main (see
seccomp_benchmark.c).

> +	printf("%zu dirs, %zu iterations, %s landlock\n", num_subdirs,
> +	       num_iterations, use_landlock ? "with" : "without");
> +
> +	if (times(&start_time) == -1)
> +		err(1, "times");
> +
> +	current = build_directory(num_subdirs, use_landlock);
> +
> +	for (int i = 0; i < num_iterations; i++) {
> +		fd = openat(current, ".", O_DIRECTORY);

We can use AT_EMPTY_PATH (with an empty path) instead of "."
I guess the benchmark should not change, but better to check again.

> +		if (fd != -1) {
> +			if (use_landlock)
> +				errx(1, "openat succeeded, expected error");
> +
> +			close(fd);
> +		}
> +	}
> +
> +	if (times(&end_time) == -1)
> +		err(1, "times");

The created directories should be removed here (setup and teardown).

> +
> +	printf("*** Benchmark concluded ***\n");
> +	printf("System: %ld clocks\n",
> +	       end_time.tms_stime - start_time.tms_stime);
> +	printf("User  : %ld clocks\n",
> +	       end_time.tms_utime - start_time.tms_utime);
> +	printf("Clocks per second: %ld\n", CLOCKS_PER_SEC);
> +
> +	close(current);
> +}
> -- 
> 2.52.0
> 
> 



More information about the Linux-security-module-archive mailing list