@@ -10,7 +10,7 @@ src_test := $(wildcard *_test.c)
TEST_GEN_PROGS := $(src_test:.c=)
-TEST_GEN_PROGS_EXTENDED := true bench/sandboxer
+TEST_GEN_PROGS_EXTENDED := true bench/sandboxer bench/microbench
TEST_PROGS_EXTENDED := bench/run.sh
@@ -20,6 +20,11 @@ $(TEST_GEN_PROGS_EXTENDED): LDFLAGS += -static
include ../lib.mk
+$(OUTPUT)/bench/microbench: bench/microbench.c bench/common.c
+$(OUTPUT)/bench/sandboxer: bench/sandboxer.c bench/common.c
+
# Targets with $(OUTPUT)/ prefix:
$(TEST_GEN_PROGS): LDLIBS += -lcap
$(TEST_GEN_PROGS_EXTENDED): LDFLAGS += -static
+
+EXTRA_CLEAN = $(OUTPUT)/common.o
new file mode 100644
@@ -0,0 +1,283 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Methods to configure and apply Landlock ruleset from file.
+ *
+ * Copyright © 2024 Huawei Tech. Co., Ltd.
+ */
+#define _GNU_SOURCE
+
+#include <linux/landlock.h>
+#include <stdio.h>
+#include <sys/prctl.h>
+#include <fcntl.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/syscall.h>
+#include <errno.h>
+#include <string.h>
+#include <unistd.h>
+#include <assert.h>
+#include <err.h>
+
+#include "common.h"
+
+#ifndef landlock_create_ruleset
+static inline int
+landlock_create_ruleset(const struct landlock_ruleset_attr *const attr,
+ const size_t size, const __u32 flags)
+{
+ return syscall(__NR_landlock_create_ruleset, attr, size, flags);
+}
+#endif
+
+#ifndef landlock_add_rule
+static inline int landlock_add_rule(const int ruleset_fd,
+ const enum landlock_rule_type rule_type,
+ const void *const rule_attr,
+ const __u32 flags)
+{
+ return syscall(__NR_landlock_add_rule, ruleset_fd, rule_type, rule_attr,
+ flags);
+}
+#endif
+
+#ifndef landlock_restrict_self
+static inline int landlock_restrict_self(const int ruleset_fd,
+ const __u32 flags)
+{
+ return syscall(__NR_landlock_restrict_self, ruleset_fd, flags);
+}
+#endif
+
+#define ACCESS_FILE \
+ (LANDLOCK_ACCESS_FS_EXECUTE | LANDLOCK_ACCESS_FS_WRITE_FILE | \
+ LANDLOCK_ACCESS_FS_READ_FILE | LANDLOCK_ACCESS_FS_TRUNCATE | \
+ LANDLOCK_ACCESS_FS_IOCTL_DEV)
+
+/* Cf. security/landlock/limits.h */
+#define LANDLOCK_MAX_NUM_LAYERS 16
+#define LANDLOCK_MAX_RULE (LANDLOCK_RULE_NET_PORT + 1)
+
+static struct {
+ char path[STRBUF_MAXLEN];
+ unsigned long long handled_access;
+} landlock_topologies[LANDLOCK_MAX_RULE] = {};
+
+void set_ruleset_config(enum landlock_rule_type rule_type,
+ const char *topology_file,
+ unsigned long long access_right)
+{
+ strncpy(landlock_topologies[rule_type].path, topology_file,
+ sizeof(landlock_topologies[rule_type].path));
+ landlock_topologies[rule_type].handled_access = access_right;
+}
+
+static int add_rule_from_str_fs(const char *strkey, const int ruleset_fd)
+{
+ int err = 1;
+ struct stat statbuf;
+ struct landlock_path_beneath_attr path_beneath;
+
+ path_beneath.parent_fd = open(strkey, O_PATH | O_CLOEXEC);
+
+ if (path_beneath.parent_fd < 0) {
+ pr_warn("Failed to open \"%s\": %s\n", strkey, strerror(errno));
+ goto cleanup;
+ }
+ if (fstat(path_beneath.parent_fd, &statbuf)) {
+ pr_warn("Failed to stat \"%s\": %s\n", strkey, strerror(errno));
+ goto cleanup;
+ }
+ path_beneath.allowed_access =
+ landlock_topologies[LANDLOCK_RULE_PATH_BENEATH].handled_access;
+
+ if (!S_ISDIR(statbuf.st_mode))
+ path_beneath.allowed_access &= ACCESS_FILE;
+
+ if (landlock_add_rule(ruleset_fd, LANDLOCK_RULE_PATH_BENEATH,
+ &path_beneath, 0)) {
+ pr_warn("Failed to update the ruleset with \"%s\": %s\n",
+ strkey, strerror(errno));
+ goto cleanup;
+ }
+ err = 0;
+cleanup:
+ close(path_beneath.parent_fd);
+ return err;
+}
+
+static int add_rule_from_str_net(const char *strkey, const int ruleset_fd)
+{
+ struct landlock_net_port_attr net_port;
+ int port;
+
+ /* errno is set in atoi() on error. */
+ errno = 0;
+ port = atoi(strkey);
+ if (errno) {
+ pr_warn("atoi() failed on %s\n", strkey);
+ goto out;
+ }
+
+ net_port.port = port;
+ net_port.allowed_access =
+ landlock_topologies[LANDLOCK_RULE_NET_PORT].handled_access;
+
+ if (landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_PORT, &net_port,
+ 0)) {
+ pr_warn("Failed to update the ruleset with \"%s\": %s\n",
+ strkey, strerror(errno));
+ goto out;
+ }
+ return 0;
+out:
+ return 1;
+}
+
+static int landlock_init_topology_layer(const int ruleset_fd,
+ enum landlock_rule_type rule_type,
+ FILE *topology_fp, unsigned int n_layer,
+ bool *changed)
+{
+ int err = 1;
+ char *strrule = NULL, *strrule_parsed, *strrule_next;
+ char *newline;
+ size_t file_pos = 0;
+ int n_rule_layer;
+ int (*add_rule_from_str)(const char *strkey, const int ruleset_fd);
+
+ switch (rule_type) {
+ case LANDLOCK_RULE_PATH_BENEATH:
+ add_rule_from_str = add_rule_from_str_fs;
+ break;
+ case LANDLOCK_RULE_NET_PORT:
+ add_rule_from_str = add_rule_from_str_net;
+ break;
+ default:
+ assert(0 && "Incorrect rule_type");
+ }
+
+ fseek(topology_fp, 0, SEEK_SET);
+
+ while (getline(&strrule, &file_pos, topology_fp) != -1) {
+ strrule_parsed = strrule;
+
+ newline = strchr(strrule_parsed, '\n');
+ if (newline)
+ *newline = 0;
+
+ strrule_next = strsep(&strrule_parsed, " ");
+ if (!strrule_next) {
+ pr_warn("Failed to parse rule: \"%s\"\n", strrule);
+ goto cleanup;
+ }
+
+ /* errno is set in atoi() on error. */
+ errno = 0;
+ n_rule_layer = atoi(strrule_next);
+ if (errno) {
+ pr_warn("atoi() failed on %s\n", strrule_next);
+ goto cleanup;
+ }
+
+ if (n_rule_layer != n_layer)
+ continue;
+ if (n_rule_layer >= LANDLOCK_MAX_NUM_LAYERS) {
+ pr_warn("Layer number exceeds the allowed value for the key: %s\n",
+ strrule_next);
+ goto cleanup;
+ }
+
+ if (add_rule_from_str(strrule_parsed, ruleset_fd))
+ goto cleanup;
+
+ *changed = true;
+ }
+
+ if (!feof(topology_fp)) {
+ pr_warn("Failed to read lines from \"%s\"\n",
+ landlock_topologies[rule_type].path);
+ goto cleanup;
+ }
+
+ err = 0;
+cleanup:
+ free(strrule);
+ return err;
+}
+
+int landlock_do_sandboxing(void)
+{
+ int err = 1;
+ int ruleset_fd;
+ FILE *fp[LANDLOCK_MAX_RULE] = {};
+ const char *path;
+ unsigned int n_layer = 1;
+ bool changed = true;
+
+ struct landlock_ruleset_attr ruleset_attr = {
+ .handled_access_fs =
+ landlock_topologies[LANDLOCK_RULE_PATH_BENEATH]
+ .handled_access,
+ .handled_access_net =
+ landlock_topologies[LANDLOCK_RULE_NET_PORT]
+ .handled_access,
+ };
+
+ ruleset_fd =
+ landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0);
+ if (ruleset_fd < 0) {
+ pr_warn("Failed to create a ruleset: %s\n", strerror(errno));
+ return 1;
+ }
+
+ for (int rule_type = 0; rule_type < LANDLOCK_MAX_RULE; rule_type++) {
+ if (!landlock_topologies[rule_type].handled_access)
+ continue;
+ path = landlock_topologies[rule_type].path;
+ fp[rule_type] = fopen(path, "r");
+ if (!fp[rule_type]) {
+ fprintf(stderr,
+ "Failed to open topology fp \"%s\": %s\n", path,
+ strerror(errno));
+ goto cleanup;
+ }
+ }
+
+ while (changed) {
+ changed = false;
+
+ for (int rule_type = 0; rule_type < LANDLOCK_MAX_RULE;
+ rule_type++) {
+ if (!landlock_topologies[rule_type].handled_access)
+ continue;
+ if (landlock_init_topology_layer(ruleset_fd, rule_type,
+ fp[rule_type], n_layer,
+ &changed))
+ goto cleanup;
+ }
+
+ if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)) {
+ pr_warn("Failed to restrict privileges: %s\n",
+ strerror(errno));
+ goto cleanup;
+ }
+ if (landlock_restrict_self(ruleset_fd, 0)) {
+ pr_warn("Failed to enforce ruleset\n");
+ goto cleanup;
+ }
+ n_layer++;
+ }
+
+ err = 0;
+cleanup:
+ for (int rule_type = 0; rule_type < LANDLOCK_MAX_RULE; rule_type++) {
+ if (fp[rule_type])
+ fclose(fp[rule_type]);
+ }
+
+ close(ruleset_fd);
+ return err;
+}
new file mode 100644
@@ -0,0 +1,18 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef LANDLOCK_BENCH_COMMON_H
+#define LANDLOCK_BENCH_COMMON_H
+
+#include <stdio.h>
+#include <linux/landlock.h>
+
+#define STRBUF_MAXLEN 128
+
+extern void set_ruleset_config(enum landlock_rule_type rule_type,
+ const char *topology_file,
+ unsigned long long access_right);
+
+extern int landlock_do_sandboxing(void);
+
+#define pr_warn(fmt, ...) fprintf(stderr, fmt, ##__VA_ARGS__)
+
+#endif
new file mode 100644
@@ -0,0 +1,192 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Microbenchmark syscall workload.
+ *
+ * Copyright © 2024 Huawei Tech. Co., Ltd.
+ */
+
+#include <time.h>
+#define _GNU_SOURCE
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <getopt.h>
+#include <string.h>
+#include <unistd.h>
+#include <time.h>
+#include <linux/landlock.h>
+#include <assert.h>
+
+#include "common.h"
+
+#define SYS_DELIM ","
+
+static const char topology_file[] = ".topology";
+
+static void run_openat(int samples)
+{
+ int fd;
+
+ while (samples--) {
+ fd = open("/dev/zero", O_RDONLY);
+ close(fd);
+ }
+}
+
+static const struct {
+ const char name[STRBUF_MAXLEN];
+ const char descr[STRBUF_MAXLEN];
+ enum landlock_rule_type rule_type;
+ unsigned long long access;
+ const char landlock_topology[10 * STRBUF_MAXLEN];
+ void (*run)(int samples);
+} workload_protos[] = {
+ {
+ .name = "openat",
+ .descr =
+ "open /dev/zero file with O_RDONLY and immediately close it",
+ .rule_type = LANDLOCK_RULE_PATH_BENEATH,
+ .access = LANDLOCK_ACCESS_FS_READ_FILE,
+ .landlock_topology = "1 /dev/zero\n",
+ .run = run_openat,
+ },
+};
+
+static const int n_workload_protos =
+ sizeof(workload_protos) / sizeof(*workload_protos);
+
+static int dump_topology(int n_proto)
+{
+ int err = 0;
+ FILE *fp = NULL;
+ const char *topology;
+ size_t bytes_to_write;
+
+ fp = fopen(topology_file, "w");
+
+ if (!fp) {
+ pr_warn("Unable to open file %s\n", topology_file);
+ goto out;
+ }
+
+ topology = workload_protos[n_proto].landlock_topology;
+ bytes_to_write = strlen(topology);
+
+ if (bytes_to_write !=
+ fwrite(topology, sizeof(char), bytes_to_write, fp)) {
+ pr_warn("Failed to dump topology into %s\n", topology_file);
+ goto out;
+ }
+
+ err = 0;
+out:
+ fclose(fp);
+ return err;
+}
+
+static int sandbox_proto(int n_proto)
+{
+ int err = 1;
+
+ err = dump_topology(n_proto);
+ if (err)
+ goto out;
+
+ set_ruleset_config(workload_protos[n_proto].rule_type, topology_file,
+ workload_protos[n_proto].access);
+
+ err = landlock_do_sandboxing();
+ if (err)
+ goto out;
+
+ err = 0;
+out:
+ return err;
+}
+
+int main(const int argc, char *const argv[])
+{
+ int c, samples = -1, err = 1;
+ int is_sandbox = 0;
+ const char *strsys = NULL;
+ struct timespec start, finish;
+ unsigned long long duration, sample_duration;
+
+ while ((c = getopt(argc, argv, "n:e:sh")) != -1) {
+ switch (c) {
+ case 'n':
+ /* errno is set in atoi() on error. */
+ errno = 0;
+ samples = atoi(optarg);
+ if (errno) {
+ pr_warn("atoi() failed on %s\n", optarg);
+ goto out;
+ }
+ break;
+ case 'e':
+ strsys = optarg;
+ break;
+ case 's':
+ is_sandbox = 1;
+ break;
+ case 'h':
+ pr_warn("Usage: %s [OPTIONS]\n"
+ "\n"
+ "Options:\n"
+ " -n SAMPLES number of syscall samples to execute\n"
+ " -e SYSCALL_LIST specify syscalls which would be used as workload\n"
+ "\n"
+ "Following cases are implemented:\n",
+ argv[0]);
+
+ for (int i = 0; i < n_workload_protos; i++) {
+ pr_warn("* %s\t%s\n", workload_protos[i].name,
+ workload_protos[i].descr);
+ }
+ pr_warn("\n");
+ goto out;
+ }
+ }
+
+ if (!strsys) {
+ pr_warn("Syscalls are not specified\n");
+ goto out;
+ }
+
+ if (samples < 1) {
+ pr_warn("Samples are not specified\n");
+ goto out;
+ }
+
+ for (int i = 0; i < n_workload_protos; i++) {
+ if (strcmp(strsys, workload_protos[i].name) == 0) {
+
+ if (is_sandbox) {
+ if (sandbox_proto(i))
+ goto out;
+ }
+
+ assert(clock_gettime(CLOCK_PROCESS_CPUTIME_ID,
+ &start) == 0);
+ workload_protos[i].run(samples);
+ assert(clock_gettime(CLOCK_PROCESS_CPUTIME_ID,
+ &finish) == 0);
+
+ duration = finish.tv_sec - start.tv_sec;
+ duration *= 1000000000ULL;
+ duration += finish.tv_nsec - start.tv_nsec;
+
+ sample_duration = duration / samples;
+
+ pr_warn("%llu ns/sample\n", sample_duration);
+ break;
+ }
+ }
+
+ err = 0;
+out:
+ return err;
+}
@@ -15,6 +15,8 @@ KSFT_SKIP=4
REL_DIR=$(dirname $(realpath $0))
PERF_BIN=/usr/bin/perf
SANDBOXER_BIN=$REL_DIR/sandboxer
+MICROBENCH_BIN=$REL_DIR/microbench
+CUSTOM_TRACER_BIN=$REL_DIR/tracer
TASKSET=/usr/bin/taskset
NICE=/usr/bin/nice
@@ -27,6 +29,7 @@ OUTPUT=
SANDBOXER_ARGS=
ACCESS=
TRACED_SYSCALLS=
+MICROBENCH=false
WORKLOAD=
SILENCE=false
TRACE_CMD=
@@ -34,6 +37,8 @@ TRACE_CMD=
CPU_AFFINITY=0
SANDBOX_DELAY=300 # msecs
REPEAT=5
+MICROBENCH_INITIAL_ITERATIONS=10000000
+STDDEV_STABLE=0.1 # %
err()
{
@@ -44,10 +49,12 @@ err()
help()
{
echo "Usage: $0 [OPTIONS] [WORKLOAD_CMD]"
+ echo " or: $0 [OPTIONS] -m"
echo "Measure overhead of Landlock hooks for the specified workload."
echo
echo "Options:"
echo " -e TRACED_SYSCALLS specify syscalls which would be traced while benchmarking"
+ echo " -m run microbenchmark workload for TRACED_SYSCALLS"
echo " -p PERF_BINARY use PERF_BINARY instead of /usr/bin/perf"
echo " -D MSECS wait MSECS msecs before tracing sandboxed workload"
echo " (default: $SANDBOX_DELAY)"
@@ -83,10 +90,11 @@ add_sandboxer_args()
parse_and_check_arguments()
{
- while getopts smp:e:r:o:t:D:c:h arg
+ while getopts smbp:e:r:o:t:D:c:h arg
do
case $arg in
e) TRACED_SYSCALLS=$OPTARG ;;
+ m) MICROBENCH=true ;;
t) add_sandboxer_args $OPTARG ;;
p) PERF_BIN=`realpath $OPTARG` ;;
D) SANDBOX_DELAY=$OPTARG ;;
@@ -105,8 +113,13 @@ parse_and_check_arguments()
err Sandboxer binary does not exist
fi
- if [ -z "$WORKLOAD" ]; then
- err Specify workload cmd
+ # At least one must be present.
+ if [ -z "$WORKLOAD" ] && ! $MICROBENCH ; then
+ err Specify workload cmd or -m flag
+ fi
+
+ if [ $MICROBENCH ] && [ ! -f $SANDBOXER_BIN ]; then
+ err Binary of microbenchmarking script does not exist
fi
if [ -z "$TRACED_SYSCALLS" ]; then
@@ -198,14 +211,6 @@ print_overhead()
dump_avg_durations $BASE_TRACE_DUMP > $TMP_BUF && mv $TMP_BUF $BASE_TRACE_DUMP
dump_avg_durations $LL_TRACE_DUMP > $TMP_BUF && mv $TMP_BUF $LL_TRACE_DUMP
- print "\nTracing results\n"
- print "===============\n"
- print "cmd: "
- print "%s " $WORKLOAD
- print "\n"
- print "syscalls: %s\n" $TRACED_SYSCALLS
- print "access: %s\n" $ACCESS
-
print "overhead:\n"
print " %-20s %10s %10s %23s\n" "syscall" "bcalls" "scalls" "duration+overhead(us)"
print " %-20s %10s %10s %23s\n" "=======" "======" "======" "====================="
@@ -237,14 +242,48 @@ print_overhead()
done < $BASE_TRACE_DUMP
}
+print_overhead_workload()
+{
+ print "\nTracing results\n"
+ print "===============\n"
+ print "cmd: "
+ print "%s " $WORKLOAD
+ print "\n"
+ print "syscalls: %s\n" $TRACED_SYSCALLS
+ print "access: %s\n" $ACCESS
+
+ print_overhead
+}
+
+print_overhead_microbench()
+{
+ print "\nTracing results\n"
+ print "===============\n"
+ print "cmd: Microbenchmarks\n"
+ print "syscalls: %s\n" $TRACED_SYSCALLS
+
+ print_overhead
+}
+
+form_trace_cmd()
+{
+ trace_cmd=$TRACE_CMD
+ trace_cmd+=" -e $1 -D $SANDBOX_DELAY -o $TMP_BUF"
+ trace_cmd+=" $TASKSET -c $CPU_AFFINITY"
+ trace_cmd+=" $NICE -n -19"
+
+ echo $trace_cmd
+}
+
run_traced_workload()
{
+ trace_cmd=$(form_trace_cmd $TRACED_SYSCALLS)
+
if [ $1 == 0 ]; then
output=$BASE_TRACE_DUMP
- sandbox_cmd=
else
output=$LL_TRACE_DUMP
- sandbox_cmd="$SANDBOXER_BIN $SANDBOXER_ARGS"
+ trace_cmd+="$SANDBOXER_BIN $SANDBOXER_ARGS"
fi
echo '' > $output
@@ -254,9 +293,9 @@ run_traced_workload()
for i in $(seq 1 $REPEAT);
do
if $SILENCE; then
- $TRACE_CMD $sandbox_cmd $WORKLOAD > /dev/null
+ $trace_cmd $WORKLOAD > /dev/null
else
- $TRACE_CMD $sandbox_cmd $WORKLOAD
+ $trace_cmd $WORKLOAD
fi
res=$?
@@ -278,6 +317,47 @@ run_traced_workload()
echo "${sec}.${msec}s elapsed"
}
+run_traced_microbench()
+{
+ if [ $1 == 0 ]; then
+ output=$BASE_TRACE_DUMP
+ sandbox_opt=
+ else
+ output=$LL_TRACE_DUMP
+ sandbox_opt=-s
+ fi
+
+ echo '' > $output
+
+ syscalls_to_parse=$(echo $TRACED_SYSCALLS | sed 's/,/\n/g')
+
+ for syscall in $syscalls_to_parse; do
+ n_iters=$MICROBENCH_INITIAL_ITERATIONS
+ trace_cmd=$(form_trace_cmd $syscall)
+
+ while
+ $trace_cmd $MICROBENCH_BIN -e $syscall -n $n_iters $sandbox_opt
+ res=$?
+ if [ $res != 0 ]; then
+ exit $KSFT_FAIL
+ fi
+
+ rm_headers $TMP_BUF
+ output_avg="$(dump_avg_durations_epoch $TMP_BUF)"
+ echo "$output_avg" > $TMP_BUF2
+
+ stddev=$(cat $TMP_BUF | sed 's/ \+ / /g' | cut -d " " -f $stddev_col)
+ stddev=${stddev::-1}
+
+ echo syscall: $syscall, stddev: $stddev%, iterations: $n_iters
+
+ n_iters=$(bc -l <<< "$n_iters*2")
+ [ $(bc -l <<< "$stddev < $STDDEV_STABLE") == 0 ]
+ do true; done
+ cat $TMP_BUF2 >> $output
+ done
+}
+
trap "exit $KSFT_SKIP" INT
parse_and_check_arguments $@
@@ -286,9 +366,11 @@ if [ ! -z "$OUTPUT" ]; then
echo '' > $OUTPUT
fi
-TRACE_CMD="$PERF_BIN trace -s -e $TRACED_SYSCALLS -D $SANDBOX_DELAY -o $TMP_BUF"
-TRACE_CMD+=" $TASKSET -c $CPU_AFFINITY"
-TRACE_CMD+=" $NICE -n -19"
+if $CUSTOM_TRACER; then
+ TRACE_CMD=$CUSTOM_TRACER_BIN
+else
+ TRACE_CMD="$PERF_BIN trace -s"
+fi
if [ ! -z "$WORKLOAD" ]; then
echo "Tracing baseline workload..."
@@ -297,5 +379,15 @@ if [ ! -z "$WORKLOAD" ]; then
echo "Tracing sandboxed workload..."
run_traced_workload 1
- print_overhead
+ print_overhead_workload
+fi
+
+if $MICROBENCH; then
+ echo "Tracing baseline microbenchmarks..."
+ run_traced_microbench 0
+
+ echo "Tracing sandboxed microbenchmarks..."
+ run_traced_microbench 1
+
+ print_overhead_microbench
fi
@@ -9,69 +9,17 @@
#define _GNU_SOURCE
#include <linux/landlock.h>
-#include <stdio.h>
-#include <sys/prctl.h>
-#include <fcntl.h>
-#include <stdbool.h>
-#include <stdlib.h>
-#include <sys/stat.h>
-#include <sys/types.h>
-#include <sys/syscall.h>
-#include <errno.h>
-#include <string.h>
#include <unistd.h>
-#include <assert.h>
-#include <err.h>
#include <getopt.h>
+#include <string.h>
+#include <errno.h>
+#include <assert.h>
+#include <stdlib.h>
-#ifndef landlock_create_ruleset
-static inline int
-landlock_create_ruleset(const struct landlock_ruleset_attr *const attr,
- const size_t size, const __u32 flags)
-{
- return syscall(__NR_landlock_create_ruleset, attr, size, flags);
-}
-#endif
-
-#ifndef landlock_add_rule
-static inline int landlock_add_rule(const int ruleset_fd,
- const enum landlock_rule_type rule_type,
- const void *const rule_attr,
- const __u32 flags)
-{
- return syscall(__NR_landlock_add_rule, ruleset_fd, rule_type, rule_attr,
- flags);
-}
-#endif
-
-#ifndef landlock_restrict_self
-static inline int landlock_restrict_self(const int ruleset_fd,
- const __u32 flags)
-{
- return syscall(__NR_landlock_restrict_self, ruleset_fd, flags);
-}
-#endif
-
-#define ACCESS_FILE \
- (LANDLOCK_ACCESS_FS_EXECUTE | LANDLOCK_ACCESS_FS_WRITE_FILE | \
- LANDLOCK_ACCESS_FS_READ_FILE | LANDLOCK_ACCESS_FS_TRUNCATE | \
- LANDLOCK_ACCESS_FS_IOCTL_DEV)
-
-/* Cf. security/landlock/limits.h */
-#define LANDLOCK_MAX_NUM_LAYERS 16
-#define LANDLOCK_MAX_RULE (LANDLOCK_RULE_NET_PORT + 1)
+#include "common.h"
#define STRTOPOLOGY_DELIM ":"
-#define pr_warn(fmt, ...) fprintf(stderr, fmt, ##__VA_ARGS__)
-
-#define STRBUF_MAXLEN 128
-
-static struct {
- char path[STRBUF_MAXLEN];
- unsigned long long handled_access;
-} landlock_topologies[LANDLOCK_MAX_RULE] = {};
-
static int get_ruleset_data_source(enum landlock_rule_type rule_type,
const char *strtopology)
{
@@ -89,9 +37,6 @@ static int get_ruleset_data_source(enum landlock_rule_type rule_type,
goto out;
}
- strncpy(landlock_topologies[rule_type].path, str_file,
- sizeof(landlock_topologies[rule_type].path));
-
/* errno is set in strtol() on error. */
errno = 0;
handled_access = (unsigned long long)strtol(str_access, NULL, 16);
@@ -100,221 +45,13 @@ static int get_ruleset_data_source(enum landlock_rule_type rule_type,
str_access);
goto out;
}
- landlock_topologies[rule_type].handled_access = handled_access;
-
- return 0;
-out:
- return 1;
-}
-
-static int add_rule_from_str_fs(const char *strkey, const int ruleset_fd)
-{
- int err = 1;
- struct stat statbuf;
- struct landlock_path_beneath_attr path_beneath;
-
- path_beneath.parent_fd = open(strkey, O_PATH | O_CLOEXEC);
-
- if (path_beneath.parent_fd < 0) {
- pr_warn("Failed to open \"%s\": %s\n", strkey, strerror(errno));
- goto cleanup;
- }
- if (fstat(path_beneath.parent_fd, &statbuf)) {
- pr_warn("Failed to stat \"%s\": %s\n", strkey, strerror(errno));
- goto cleanup;
- }
- path_beneath.allowed_access =
- landlock_topologies[LANDLOCK_RULE_PATH_BENEATH].handled_access;
-
- if (!S_ISDIR(statbuf.st_mode))
- path_beneath.allowed_access &= ACCESS_FILE;
-
- if (landlock_add_rule(ruleset_fd, LANDLOCK_RULE_PATH_BENEATH,
- &path_beneath, 0)) {
- pr_warn("Failed to update the ruleset with \"%s\": %s\n",
- strkey, strerror(errno));
- goto cleanup;
- }
- err = 0;
-cleanup:
- close(path_beneath.parent_fd);
- return err;
-}
-
-static int add_rule_from_str_net(const char *strkey, const int ruleset_fd)
-{
- struct landlock_net_port_attr net_port;
- int port;
-
- /* errno is set in atoi() on error. */
- errno = 0;
- port = atoi(strkey);
- if (errno) {
- pr_warn("atoi() failed on %s\n", strkey);
- goto out;
- }
-
- net_port.port = port;
- net_port.allowed_access =
- landlock_topologies[LANDLOCK_RULE_NET_PORT].handled_access;
- if (landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_PORT, &net_port,
- 0)) {
- pr_warn("Failed to update the ruleset with \"%s\": %s\n",
- strkey, strerror(errno));
- goto out;
- }
+ set_ruleset_config(rule_type, str_file, handled_access);
return 0;
out:
return 1;
}
-static int landlock_init_topology_layer(const int ruleset_fd,
- enum landlock_rule_type rule_type,
- FILE *topology_fp, unsigned int n_layer,
- bool *changed)
-{
- int err = 1;
- char *strrule = NULL, *strrule_parsed, *strrule_next;
- char *newline;
- size_t file_pos = 0;
- int n_rule_layer;
- int (*add_rule_from_str)(const char *strkey, const int ruleset_fd);
-
- switch (rule_type) {
- case LANDLOCK_RULE_PATH_BENEATH:
- add_rule_from_str = add_rule_from_str_fs;
- break;
- case LANDLOCK_RULE_NET_PORT:
- add_rule_from_str = add_rule_from_str_net;
- break;
- default:
- assert(0 && "Incorrect rule_type");
- }
-
- fseek(topology_fp, 0, SEEK_SET);
-
- while (getline(&strrule, &file_pos, topology_fp) != -1) {
- strrule_parsed = strrule;
-
- newline = strchr(strrule_parsed, '\n');
- if (newline)
- *newline = 0;
-
- strrule_next = strsep(&strrule_parsed, " ");
- if (!strrule_next) {
- pr_warn("Failed to parse rule: \"%s\"\n", strrule);
- goto cleanup;
- }
-
- /* errno is set in atoi() on error. */
- errno = 0;
- n_rule_layer = atoi(strrule_next);
- if (errno) {
- pr_warn("atoi() failed on %s\n", strrule_next);
- goto cleanup;
- }
-
- if (n_rule_layer != n_layer)
- continue;
- if (n_rule_layer >= LANDLOCK_MAX_NUM_LAYERS) {
- pr_warn("Layer number exceeds the allowed value for the key: %s\n",
- strrule_next);
- goto cleanup;
- }
-
- if (add_rule_from_str(strrule_parsed, ruleset_fd))
- goto cleanup;
-
- *changed = true;
- }
-
- if (!feof(topology_fp)) {
- pr_warn("Failed to read lines from \"%s\"\n",
- landlock_topologies[rule_type].path);
- goto cleanup;
- }
-
- err = 0;
-cleanup:
- free(strrule);
- return err;
-}
-
-static int landlock_do_sandboxing(void)
-{
- int err = 1;
- int ruleset_fd;
- FILE *fp[LANDLOCK_MAX_RULE] = {};
- const char *path;
- unsigned int n_layer = 1;
- bool changed = true;
-
- struct landlock_ruleset_attr ruleset_attr = {
- .handled_access_fs =
- landlock_topologies[LANDLOCK_RULE_PATH_BENEATH]
- .handled_access,
- .handled_access_net =
- landlock_topologies[LANDLOCK_RULE_NET_PORT]
- .handled_access,
- };
-
- ruleset_fd =
- landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0);
- if (ruleset_fd < 0) {
- pr_warn("Failed to create a ruleset: %s\n", strerror(errno));
- return 1;
- }
-
- for (int rule_type = 0; rule_type < LANDLOCK_MAX_RULE; rule_type++) {
- if (!landlock_topologies[rule_type].handled_access)
- continue;
- path = landlock_topologies[rule_type].path;
- fp[rule_type] = fopen(path, "r");
- if (!fp[rule_type]) {
- fprintf(stderr,
- "Failed to open topology fp \"%s\": %s\n", path,
- strerror(errno));
- goto cleanup;
- }
- }
-
- while (changed) {
- changed = false;
-
- for (int rule_type = 0; rule_type < LANDLOCK_MAX_RULE;
- rule_type++) {
- if (!landlock_topologies[rule_type].handled_access)
- continue;
- if (landlock_init_topology_layer(ruleset_fd, rule_type,
- fp[rule_type], n_layer,
- &changed))
- goto cleanup;
- }
-
- if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)) {
- pr_warn("Failed to restrict privileges: %s\n",
- strerror(errno));
- goto cleanup;
- }
- if (landlock_restrict_self(ruleset_fd, 0)) {
- pr_warn("Failed to enforce ruleset\n");
- goto cleanup;
- }
- n_layer++;
- }
-
- err = 0;
-cleanup:
- for (int rule_type = 0; rule_type < LANDLOCK_MAX_RULE; rule_type++) {
- if (fp[rule_type])
- fclose(fp[rule_type]);
- }
-
- close(ruleset_fd);
- return err;
-}
-
int main(const int argc, char *const argv[])
{
int c, longind = -1;
Microbenchmarking is a simple and most common way to obtain stable measurement results on operations such as syscalls. * Add bench/microbench.c script. Each microbenchmark in this script is configured with Landlock ruleset and access right required by sandboxer and with a simple function that executes syscall in a loop. Currently, only openat(2) is supported. * Add bench/common.c and move there sandboxing-related functions from bench/sandboxer.c. This is necessary so that bench/microbench.c can directly use sandboxing methods without spawning sandboxer process. * Support microbenchmarking option in bench/run.sh. In order to provide stable metrics, number of syscall samples is dynamically increased after run until standard deviation of samples divided by mean is less than 0.1%. Signed-off-by: Mikhail Ivanov <ivanov.mikhail1@huawei-partners.com> --- tools/testing/selftests/landlock/Makefile | 7 +- .../testing/selftests/landlock/bench/common.c | 283 ++++++++++++++++++ .../testing/selftests/landlock/bench/common.h | 18 ++ .../selftests/landlock/bench/microbench.c | 192 ++++++++++++ tools/testing/selftests/landlock/bench/run.sh | 130 ++++++-- .../selftests/landlock/bench/sandboxer.c | 275 +---------------- 6 files changed, 616 insertions(+), 289 deletions(-) create mode 100644 tools/testing/selftests/landlock/bench/common.c create mode 100644 tools/testing/selftests/landlock/bench/common.h create mode 100644 tools/testing/selftests/landlock/bench/microbench.c