From patchwork Fri Aug 16 00:59:40 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: Mikhail Ivanov X-Patchwork-Id: 13765314 Received: from szxga05-in.huawei.com (szxga05-in.huawei.com [45.249.212.191]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id D8873ECC; Fri, 16 Aug 2024 01:00:18 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=45.249.212.191 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1723770022; cv=none; b=YeDkwKWfnR5i6c4+GfdoC5gMFeUSZ1ytzPgWXkcWGwKRS3MeuDIAsJRP8ygDgLxBDyYaCSffMHgE1a/+uzKp4JXx4YloUyoN9FS55PkQ/SuLALm5WC8PaCtZcvE3kowIOW1Rud4zJx2ielF5fYbOrUGK2S7+Hztt8ALd2TCl0Rg= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1723770022; c=relaxed/simple; bh=2CECiWeAA90jrbNdHJdmbnWtyf7ajzRswu+VOL4S2EA=; h=From:To:CC:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=eobj42IjF/Gr2xW0wZC4+uTZTXyo0zShNxOmmS5gyCWJyCIFNhJupisOwogKkq7AuMkEBDBsaxzYaNlqijnaSC9L5GofoiTZDSzcRPOqj8NfaK2vFLHNIr7aGyHdZgaeqnOPCGqy5CUP1ztY5wU+cwmC2u9yB8NaUBNtW5xpsTo= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=huawei-partners.com; spf=pass smtp.mailfrom=huawei-partners.com; arc=none smtp.client-ip=45.249.212.191 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=huawei-partners.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=huawei-partners.com Received: from mail.maildlp.com (unknown [172.19.162.112]) by szxga05-in.huawei.com (SkyGuard) with ESMTP id 4WlNqp1SQcz1HGYc; Fri, 16 Aug 2024 08:57:10 +0800 (CST) Received: from dggpemm500020.china.huawei.com (unknown [7.185.36.49]) by mail.maildlp.com (Postfix) with ESMTPS id 5129B14010C; Fri, 16 Aug 2024 09:00:16 +0800 (CST) Received: from mscphis02103.huawei.com (10.123.65.215) by dggpemm500020.china.huawei.com (7.185.36.49) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.1.2507.39; Fri, 16 Aug 2024 09:00:14 +0800 From: Mikhail Ivanov To: CC: , , , , , , , Subject: [RFC PATCH v1 1/4] selftests/landlock: Implement performance impact measurement tool Date: Fri, 16 Aug 2024 08:59:40 +0800 Message-ID: <20240816005943.1832694-2-ivanov.mikhail1@huawei-partners.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20240816005943.1832694-1-ivanov.mikhail1@huawei-partners.com> References: <20240816005943.1832694-1-ivanov.mikhail1@huawei-partners.com> Precedence: bulk X-Mailing-List: linux-security-module@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 X-ClientProxiedBy: mscpeml500003.china.huawei.com (7.188.49.51) To dggpemm500020.china.huawei.com (7.185.36.49) Motivation =========== Landlock LSM hooks are executed with many operations on Linux internal objects (files, sockets). This hooks can noticeably affect performance of such operations as it was demonstrated in the filesystem caching patchset [1]. Having ability to calculate Landlock performance overhead allows to compare kernel changes and estimate the acceptability of new features (e.g. [2], [3], [4]). Implementation design ===================== Calling LSM hooks in userspace mostly expected while using syscalls. Therefore, syscall duration can be used as a metric. Calculation of this is performed by gathering timestamps in per-syscall enter/exit tracepoints while executing some workload command. Landlock overhead for syscall X is defined as the difference between duration of X in sandboxed environment and non-sandboxed duration of X. Syscall duration measurement ============================ perf trace is used to measure syscall duration as more efficient version of strace. This tool uses per-syscall raw tracepoints as perf events for gathering syscalls time statistics for specified workload. In order to reduce noise during measurement following mechanisms are used: * CPU affinity * CPU isolation * Tickless mode (nohz_full) * SMT disable * cpufreq disable CPU 0 is isolated and used for benchmarking. To provide stability of measurement results and to be safe from anomaly ones workload is executed in N iterations (N is set by the user of the tool). Average value from this iterations is taken to represent final result. Sandboxing workload =================== Proposed mechanism allows to test different sandbox scenarios. Ruleset of sandboxed workload can be configured with following entities: * List of rule keys and ruleset layers corresponding to the keys. This entity is useful to benchmark different ruleset configurations which affects performance of the hooks (e.g. big number of layers or rules). This list should be stored in a text file. * Access right common for each key. Configuring access right is useful to control what hooks should be triggered during measurement. handled_access of ruleset is also set with this number. First entitity is called a Landlock topology. These 2 entities are passed to sandboxer with a command arguments. For example following command will generate a topology in which keys are linux source files and layer of each key is assigned with the depth of the file: $FIND $LINUX_PATH -maxdepth 5 -fprintf $TOPOLOGY_FILE '%d %p\n' Example ======= Following operation measures Landlock overhead for openat(2) syscall for workload that uses find tool on linux source files (with depth 5). Landlock ruleset uses read access right. # Topology creation for FS ruleset (results in .topology file) ./bench/run.sh -t fs:.topology:4 -e openat -s \ $FIND $LINUX_SRC -mindepth 5 -maxdepth 5 -exec file '{}' \; Implementation structure ======================== * Create folder 'bench' in Landlock selftests where all scripts and workloads dedicated to measurement are located. * Add bench/run.sh which is the main script that launches workload, measures syscalls duration and provides result numbers. * Add bench/sandboxer.c which is a program that sandboxes with specified configuration and runs specified workload. * Add CONFIG_CMDLINE="isolcpus=0 nohz_full=0 nosmt cpufreq.off=1" in the config file. ===== [1] https://lore.kernel.org/all/20210630224856.1313928-1-mic@digikod.net/ [2] https://github.com/landlock-lsm/linux/issues/10 [3] https://github.com/landlock-lsm/linux/issues/19 [4] https://github.com/landlock-lsm/linux/issues/1 Closes: https://github.com/landlock-lsm/linux/issues/24 Signed-off-by: Mikhail Ivanov --- tools/testing/selftests/landlock/Makefile | 4 +- tools/testing/selftests/landlock/bench/run.sh | 301 ++++++++++++++ .../selftests/landlock/bench/sandboxer.c | 380 ++++++++++++++++++ tools/testing/selftests/landlock/config | 2 + 4 files changed, 686 insertions(+), 1 deletion(-) create mode 100755 tools/testing/selftests/landlock/bench/run.sh create mode 100644 tools/testing/selftests/landlock/bench/sandboxer.c diff --git a/tools/testing/selftests/landlock/Makefile b/tools/testing/selftests/landlock/Makefile index 348e2dbdb4e0..410e8cd28707 100644 --- a/tools/testing/selftests/landlock/Makefile +++ b/tools/testing/selftests/landlock/Makefile @@ -10,7 +10,9 @@ src_test := $(wildcard *_test.c) TEST_GEN_PROGS := $(src_test:.c=) -TEST_GEN_PROGS_EXTENDED := true +TEST_GEN_PROGS_EXTENDED := true bench/sandboxer + +TEST_PROGS_EXTENDED := bench/run.sh # Short targets: $(TEST_GEN_PROGS): LDLIBS += -lcap diff --git a/tools/testing/selftests/landlock/bench/run.sh b/tools/testing/selftests/landlock/bench/run.sh new file mode 100755 index 000000000000..afbcbb2ba6aa --- /dev/null +++ b/tools/testing/selftests/landlock/bench/run.sh @@ -0,0 +1,301 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: GPL-2.0 +# +# Copyright © 2024 Huawei Tech. Co., Ltd. +# +# Measure overhead of Landlock hooks for the specified workload. + +# cf. tools/testing/selftests/kselftest.h +KSFT_PASS=0 +KSFT_FAIL=1 +KSFT_XFAIL=2 +KSFT_XPASS=3 +KSFT_SKIP=4 + +REL_DIR=$(dirname $(realpath $0)) +PERF_BIN=/usr/bin/perf +SANDBOXER_BIN=$REL_DIR/sandboxer +TASKSET=/usr/bin/taskset +NICE=/usr/bin/nice + +LL_TRACE_DUMP=.tmp.ll +BASE_TRACE_DUMP=.tmp.base +TMP_BUF=.tmp.buf +TMP_BUF2=.tmp.buf2 +OUTPUT= + +SANDBOXER_ARGS= +ACCESS= +TRACED_SYSCALLS= +WORKLOAD= +SILENCE=false +TRACE_CMD= + +CPU_AFFINITY=0 +SANDBOX_DELAY=300 # msecs +REPEAT=5 + +err() +{ + echo $@ >&2 + exit $KSFT_SKIP +} + +help() +{ + echo "Usage: $0 [OPTIONS] [WORKLOAD_CMD]" + 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 " -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)" + echo " -r COUNT repeat WORKLOAD_CMD for COUNT times, show avg and stddev" + echo " (default: $REPEAT)" + echo " -o FILE save result into FILE" + echo " -c CPU use CPU affinity (default: $CPU_AFFINITY)" + echo " -s hide stdout output for WORKLOAD_CMD" + echo " -h show this help message" + echo + echo " -t {fs|net}:FILE:ACCESS" + echo " add Landlock topology which describes how workload" + echo " should be sandboxed by Landlock." + echo " * FILE contains lines of following format: \"NR KEY\\n\"" + echo " (e.g. \"1 /usr/bin/find\n\"). When sandboxing, a rule" + echo " on the NR layer with ACCESS access_mask will be added" + echo " for each key KEY" + echo " * ACCESS has binary string format. Sets ruleset" + echo " handled_access and access_mask for each key." + echo + + exit $KSFT_XFAIL +} + +add_sandboxer_args() +{ + ACCESS=$(echo $1 | cut -d':' -f3) + + rule_type=$(echo $1 | cut -d':' -f1) + args=$(echo $1 | cut -d':' -f2,3) + SANDBOXER_ARGS+=$(echo ' '--$rule_type $args) +} + +parse_and_check_arguments() +{ + while getopts smp:e:r:o:t:D:c:h arg + do + case $arg in + e) TRACED_SYSCALLS=$OPTARG ;; + t) add_sandboxer_args $OPTARG ;; + p) PERF_BIN=`realpath $OPTARG` ;; + D) SANDBOX_DELAY=$OPTARG ;; + r) REPEAT=$OPTARG ;; + o) OUTPUT=$OPTARG ;; + c) CPU_AFFINITY=$OPTARG ;; + s) SILENCE=true ;; + h) help ;; + esac + done + + shift $(($OPTIND - 1)) + WORKLOAD=$@ + + if [ ! -f $SANDBOXER_BIN ]; then + err Sandboxer binary does not exist + fi + + if [ -z "$WORKLOAD" ]; then + err Specify workload cmd + fi + + if [ -z "$TRACED_SYSCALLS" ]; then + err Specify traced syscalls + fi + + if [ ! -z "$WORKLOAD" ] && [ ! -f $PERF_BIN ]; then + err Perf binary does not exist + fi + if [ ! -z "$WORKLOAD" ] && [ -z "$SANDBOXER_ARGS" ]; then + err Landlock topology is not specified + fi +} + +# perf trace +header='/^[[:space:]]*$/d;' +header+=';/^.* ([0-9]*), [0-9]* events, .*%$/d;' +header+=';/^ syscall calls errors total min avg max stddev$/d' +header+=';/^ (msec) (msec) (msec) (msec) (%)$/d' +header+=';/^ --------------- -------- ------ -------- --------- --------- --------- ------$/d' +header+=';/^ Summary of events:$/d' + +rm_headers() +{ + sed -i "$header" $1 +} + +print() +{ + fmt=$1 + shift 1 + if [ ! -z "$OUTPUT" ]; then + printf "$fmt" $@ >> $OUTPUT + else + printf "$fmt" $@ + fi +} + +dump_avg_durations_epoch() +{ + awk '{ + calls[$1]+=$2 + durations[$1]+=$4 + } + END { + for(i in calls) { + if (calls[i] != 0 && durations[i] != 0) { + print i, durations[i] / calls[i] * 1000, calls[i] + } + } + }' $1 +} + +dump_avg_durations() +{ + awk '{ + count[$1]+=1 + dur[$1, count[$1]]+=$2 + calls[$1]+=$3 + } + END { + for(sys in calls) { + if (calls[sys] == 0 || count[sys] == 0) + continue + min = 1000000000 + max = 0 + total = 0 + for (i = 1; i <= count[sys]; i++) { + min = (min < dur[sys, i]) ? min : dur[sys, i] + max = (max > dur[sys, i]) ? max : dur[sys, i] + total += dur[sys, i] + } + stddev = 0 + avg = total / count[sys] + for (i = 1; i <= count[sys]; i++) { + stddev += (dur[sys, i] - avg) * (dur[sys, i] - avg) + } + if (total && count[sys] > 1 && avg) + stddev = sqrt(stddev / (count[sys] - 1)) / avg * 100 + + printf("%-20s %8d %7.2Lf %7.2Lf %7.2Lf %5.2Lf%%\n", + sys, calls[sys], avg, min, max, stddev); + } + }' $1 +} + +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" "=======" "======" "======" "=====================" + + while read -r base_line + do + base_line=$(echo "$base_line" | sed 's/ \+ / /g') + sys_name=$(echo "$base_line" | cut -d " " -f 1) + ll_line=$(cat $LL_TRACE_DUMP | grep -w $sys_name | sed 's/ \+ / /g') + + base_duration=$(echo "$base_line" | cut -d " " -f 3) + base_calls=$(echo "$base_line" | cut -d " " -f 2) + + ll_duration=$(echo "$ll_line" | cut -d " " -f 3) + ll_calls=$(echo "$ll_line" | cut -d " " -f 2) + + overhead=$(bc -l <<< "scale=2; $ll_duration/$base_duration*100 - 100") + overhead_us=$(bc -l <<< "scale=2; $ll_duration - $base_duration") + + if (( $(bc -l <<< "$overhead < 0") )); then + overhead_str=$(printf "%.2Lf%.2Lf(%.1Lf%%)" \ + $base_duration $overhead_us $overhead) + else + overhead_str=$(printf "%.2Lf+%.2Lf(+%.1Lf%%)" \ + $base_duration $overhead_us $overhead) + fi + + print " %-20s %10d %10d %23s\n" $sys_name $base_calls $ll_calls $overhead_str + done < $BASE_TRACE_DUMP +} + +run_traced_workload() +{ + if [ $1 == 0 ]; then + output=$BASE_TRACE_DUMP + sandbox_cmd= + else + output=$LL_TRACE_DUMP + sandbox_cmd="$SANDBOXER_BIN $SANDBOXER_ARGS" + fi + + echo '' > $output + + start=$(date +%s%3N) + + for i in $(seq 1 $REPEAT); + do + if $SILENCE; then + $TRACE_CMD $sandbox_cmd $WORKLOAD > /dev/null + else + $TRACE_CMD $sandbox_cmd $WORKLOAD + fi + + res=$? + if [ $res != 0 ]; then + exit $KSFT_FAIL + fi + + rm_headers $TMP_BUF + output_avg="$(dump_avg_durations_epoch $TMP_BUF)" + echo "$output_avg" >> $output + done + + end=$(date +%s%3N) + + duration=$((end - start)) + sec=$((duration / 1000)) + msec=$((duration % 1000)) + + echo "${sec}.${msec}s elapsed" +} + +trap "exit $KSFT_SKIP" INT + +parse_and_check_arguments $@ + +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 [ ! -z "$WORKLOAD" ]; then + echo "Tracing baseline workload..." + run_traced_workload 0 + + echo "Tracing sandboxed workload..." + run_traced_workload 1 + + print_overhead +fi diff --git a/tools/testing/selftests/landlock/bench/sandboxer.c b/tools/testing/selftests/landlock/bench/sandboxer.c new file mode 100644 index 000000000000..73dfd7b8b196 --- /dev/null +++ b/tools/testing/selftests/landlock/bench/sandboxer.c @@ -0,0 +1,380 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Restrict and execute specified command. + * Run with -h flag to see sandboxer rules insertion format and supported + * rule types. + * + * Copyright © 2024 Huawei Tech. Co., Ltd. + */ +#define _GNU_SOURCE + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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) + +#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) +{ + unsigned long long handled_access; + char strtopology_buf[STRBUF_MAXLEN], *str_access, *str_file; + + assert(strlen(strtopology) < STRBUF_MAXLEN); + + strcpy(strtopology_buf, strtopology); + str_access = strtopology_buf; + str_file = strsep(&str_access, STRTOPOLOGY_DELIM); + + if (!str_file) { + pr_warn("Landlock topology is partially specified\n"); + 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); + if (errno) { + pr_warn("Failed trying to parse handled_access: %s\n", + 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; + } + 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; + const char *cmd = NULL; + enum landlock_rule_type rule_type; + + static struct option options[] = { + { "fs", required_argument, NULL, 'f' }, + { "net", required_argument, NULL, 'n' }, + { "help", optional_argument, NULL, 'h' }, + { NULL, 0, NULL, 0 }, + }; + + while ((c = getopt_long(argc, argv, "f:n:h", options, &longind)) != + -1) { + if (longind == -1) { + pr_warn("%s: invalid option -- \'%s\'\n", argv[0], + argv[optind - 1]); + goto out; + } + switch (c) { + case 'f': + rule_type = LANDLOCK_RULE_PATH_BENEATH; + get_ruleset_data_source(rule_type, optarg); + break; + case 'n': + rule_type = LANDLOCK_RULE_NET_PORT; + get_ruleset_data_source(rule_type, optarg); + break; + case 'h': + pr_warn("Usage: %s [-{fs|net} FILE:ACCESS]\n" + "\n" + "* FILE contains lines of following format: \"NR KEY\\n\" (e.g. \"1 /usr/bin/find\").\n" + " When sandboxing, a rule on the NR layer with ACCESS access_mask will be added\n" + " for each key KEY\n" + "* ACCESS is a binary string. Sets handled_access for ruleset and access_mask\n" + " for each key\n", + argv[0]); + goto out; + } + + /* Next argument is workload. */ + if (optind < argc && *argv[optind] != '-') + break; + longind = -1; + } + + if (optind >= argc) { + pr_warn("Command is not specified\n"); + goto out; + } + if (landlock_do_sandboxing()) + goto out; + + cmd = argv[optind]; + execv(cmd, argv + optind); + + pr_warn("Failed to execute \"%s\": %s\n", cmd, strerror(errno)); + pr_warn("Hint: access to the binary, the interpreter or " + "shared libraries may be denied.\n"); +out: + return 1; +} diff --git a/tools/testing/selftests/landlock/config b/tools/testing/selftests/landlock/config index 29af19c4e9f9..e4bf29cf935f 100644 --- a/tools/testing/selftests/landlock/config +++ b/tools/testing/selftests/landlock/config @@ -1,5 +1,7 @@ CONFIG_CGROUPS=y CONFIG_CGROUP_SCHED=y +CONFIG_CMDLINE="isolcpus=0 nohz_full=0 nosmt cpufreq.off=1" +CONFIG_CMDLINE_BOOL=y CONFIG_INET=y CONFIG_IPV6=y CONFIG_KEYS=y From patchwork Fri Aug 16 00:59:41 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: Mikhail Ivanov X-Patchwork-Id: 13765315 Received: from szxga06-in.huawei.com (szxga06-in.huawei.com [45.249.212.32]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 7869A7462; Fri, 16 Aug 2024 01:00:20 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=45.249.212.32 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1723770024; cv=none; b=Wf/6M5MwXUXFhgIlzA3dwQhY+JODjsEpYciME9qEu9+VSBg7riFw6QhEjLLWjQhjHrKTwdkLBjOykrfA5YLef2s4u0Sdm5pmOAFdQND/GccDA45ZJRdbjRU5ONZQzyIS5o4ZnzTxF28BfAH8vsb/ojNCMiZHfQhUMIJd3hPw104= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1723770024; c=relaxed/simple; bh=oMzbhwYIzypp/BiSMM/MLGgXO+Hp9xkaGeZ0eVSgKYI=; h=From:To:CC:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=dARu28xxIdolCRX6+sE/qwyPbmfl4md/LBJmsbpnDkiH458iuIrU3W65qJIj91D33kJMbiz/oabPKdR+NcyE0O5wTiYo1OA6ku4agHgXumibNV7anGdABMj4/fxXUzPnn9rxxvcW4lut2PaUd5Gsi4dFvqacN2cXMAL5h0EXxSg= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=huawei-partners.com; spf=pass smtp.mailfrom=huawei-partners.com; arc=none smtp.client-ip=45.249.212.32 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=huawei-partners.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=huawei-partners.com Received: from mail.maildlp.com (unknown [172.19.162.112]) by szxga06-in.huawei.com (SkyGuard) with ESMTP id 4WlNsF73Dyz1xtyC; Fri, 16 Aug 2024 08:58:25 +0800 (CST) Received: from dggpemm500020.china.huawei.com (unknown [7.185.36.49]) by mail.maildlp.com (Postfix) with ESMTPS id 1DC3614010C; Fri, 16 Aug 2024 09:00:18 +0800 (CST) Received: from mscphis02103.huawei.com (10.123.65.215) by dggpemm500020.china.huawei.com (7.185.36.49) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.1.2507.39; Fri, 16 Aug 2024 09:00:16 +0800 From: Mikhail Ivanov To: CC: , , , , , , , Subject: [RFC PATCH v1 2/4] selftests/landlock: Implement per-syscall microbenchmarks Date: Fri, 16 Aug 2024 08:59:41 +0800 Message-ID: <20240816005943.1832694-3-ivanov.mikhail1@huawei-partners.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20240816005943.1832694-1-ivanov.mikhail1@huawei-partners.com> References: <20240816005943.1832694-1-ivanov.mikhail1@huawei-partners.com> Precedence: bulk X-Mailing-List: linux-security-module@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 X-ClientProxiedBy: mscpeml500003.china.huawei.com (7.188.49.51) To dggpemm500020.china.huawei.com (7.185.36.49) 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 --- 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 diff --git a/tools/testing/selftests/landlock/Makefile b/tools/testing/selftests/landlock/Makefile index 410e8cd28707..e149ef66841a 100644 --- a/tools/testing/selftests/landlock/Makefile +++ b/tools/testing/selftests/landlock/Makefile @@ -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 diff --git a/tools/testing/selftests/landlock/bench/common.c b/tools/testing/selftests/landlock/bench/common.c new file mode 100644 index 000000000000..8f7ff744e606 --- /dev/null +++ b/tools/testing/selftests/landlock/bench/common.c @@ -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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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; +} diff --git a/tools/testing/selftests/landlock/bench/common.h b/tools/testing/selftests/landlock/bench/common.h new file mode 100644 index 000000000000..bfb53624fbca --- /dev/null +++ b/tools/testing/selftests/landlock/bench/common.h @@ -0,0 +1,18 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef LANDLOCK_BENCH_COMMON_H +#define LANDLOCK_BENCH_COMMON_H + +#include +#include + +#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 diff --git a/tools/testing/selftests/landlock/bench/microbench.c b/tools/testing/selftests/landlock/bench/microbench.c new file mode 100644 index 000000000000..6c073eaad7df --- /dev/null +++ b/tools/testing/selftests/landlock/bench/microbench.c @@ -0,0 +1,192 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Microbenchmark syscall workload. + * + * Copyright © 2024 Huawei Tech. Co., Ltd. + */ + +#include +#define _GNU_SOURCE + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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; +} diff --git a/tools/testing/selftests/landlock/bench/run.sh b/tools/testing/selftests/landlock/bench/run.sh index afbcbb2ba6aa..582313f689ad 100755 --- a/tools/testing/selftests/landlock/bench/run.sh +++ b/tools/testing/selftests/landlock/bench/run.sh @@ -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 diff --git a/tools/testing/selftests/landlock/bench/sandboxer.c b/tools/testing/selftests/landlock/bench/sandboxer.c index 73dfd7b8b196..dc41e48d7bf5 100644 --- a/tools/testing/selftests/landlock/bench/sandboxer.c +++ b/tools/testing/selftests/landlock/bench/sandboxer.c @@ -9,69 +9,17 @@ #define _GNU_SOURCE #include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include #include -#include -#include #include +#include +#include +#include +#include -#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; From patchwork Fri Aug 16 00:59:42 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: Mikhail Ivanov X-Patchwork-Id: 13765316 Received: from szxga01-in.huawei.com (szxga01-in.huawei.com [45.249.212.187]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 0702618EBF; Fri, 16 Aug 2024 01:00:21 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=45.249.212.187 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1723770025; cv=none; b=bkgvM47FRIJ17XdjdnYvwR9YGIM949sxTGQg2mjweMaQu1LY/APJJzyCBAdiV4NO7Luapx8Dk78yl6tAzw9BoMYsZwC+A4eTG4VeyApTRbrKgP7b33MQXL0xL5yhOI8XnmbDRWc8nk9fMau/yy13HLNl6aD1jUNtzTJzGqN0p5I= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1723770025; c=relaxed/simple; bh=+wpXQQQV4TiocJY760Wvz00MxfgK30C4VktuhNrp8pk=; h=From:To:CC:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=sQIt04oJgbzXPK9t3WMA0J7qFPnvQo6ofkrTlm79BW25xVi3usxqm1KtNEMBYf0G83wOgBP5qTFMx8rQ75D0vxC3zroV39Ie4d0uTFJZJVEF3G/b1OhACEJHEvZONAY/1aEJ6cU5UHiVbTrbrM7M2tpkTPpEecjYEc/oIb/Hpww= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=huawei-partners.com; spf=pass smtp.mailfrom=huawei-partners.com; arc=none smtp.client-ip=45.249.212.187 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=huawei-partners.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=huawei-partners.com Received: from mail.maildlp.com (unknown [172.19.163.48]) by szxga01-in.huawei.com (SkyGuard) with ESMTP id 4WlNv672hrzcd58; Fri, 16 Aug 2024 09:00:02 +0800 (CST) Received: from dggpemm500020.china.huawei.com (unknown [7.185.36.49]) by mail.maildlp.com (Postfix) with ESMTPS id A8F9B18006C; Fri, 16 Aug 2024 09:00:19 +0800 (CST) Received: from mscphis02103.huawei.com (10.123.65.215) by dggpemm500020.china.huawei.com (7.185.36.49) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.1.2507.39; Fri, 16 Aug 2024 09:00:18 +0800 From: Mikhail Ivanov To: CC: , , , , , , , Subject: [RFC PATCH v1 3/4] selftests/landlock: Implement custom libbpf-based tracer Date: Fri, 16 Aug 2024 08:59:42 +0800 Message-ID: <20240816005943.1832694-4-ivanov.mikhail1@huawei-partners.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20240816005943.1832694-1-ivanov.mikhail1@huawei-partners.com> References: <20240816005943.1832694-1-ivanov.mikhail1@huawei-partners.com> Precedence: bulk X-Mailing-List: linux-security-module@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 X-ClientProxiedBy: mscpeml500003.china.huawei.com (7.188.49.51) To dggpemm500020.china.huawei.com (7.185.36.49) perf trace can show imprecise values of syscalls durations. It doesn't affect real overhead value, but it shows the wrong proportion of overhead relative to syscall baseline duration. Moreover, using perf trace causes some measurement noise. Using custom and more simple tracing mechanism leads to increase of accuracy and more precise control of the tracing. * Implement BPF programs for per-syscall entry/exit raw tracepoints. This programs gather timestamps using bpf_ktime_get_ns() helper [1] and calculate some statistics for target processes. * Implement simple libbpf-based tracer that attaches BPF programs to tracepoints, launches workload and shows gathered statistics. Currently this tracer doesn't support syscall name tables, therefore syscall number is shown instead of the name. * Create separate Makefile for performance measurement programs. It is required in order to not mess up current selftests Makefile with libbpf build logic. * Support custom tracer in bench/run.sh. Consider following results (produced on linux v5.15): # for i in $(seq 1 3); # do # ./bench/run.sh -e openat -m # done ... overhead: syscall bcalls scalls duration+overhead(us) ======= ====== ====== ===================== openat 9978464 9979092 2.40+0.01(+0.0%) ... openat 9981513 9981200 2.40+0.06(+2.0%) ... openat 9977238 9980300 2.47-0.02(-1.0%) # for i in $(seq 1 3); # do # ./bench/run.sh -e openat -m -b # done ... overhead: syscall bcalls scalls duration+overhead(us) ======= ====== ====== ===================== syscall-257 40000000 20000003 0.95+0.06(+6.0%) ... syscall-257 40000000 20000003 0.95+0.05(+5.0%) ... syscall-257 20000000 20000003 0.95+0.06(+6.0%) [1] https://man7.org/linux/man-pages/man7/bpf-helpers.7.html Signed-off-by: Mikhail Ivanov --- tools/testing/selftests/Makefile | 1 + tools/testing/selftests/landlock/Makefile | 9 +- .../testing/selftests/landlock/bench/Makefile | 179 +++++++++++ tools/testing/selftests/landlock/bench/config | 10 + .../selftests/landlock/bench/progs/tracer.c | 126 ++++++++ tools/testing/selftests/landlock/bench/run.sh | 16 + .../testing/selftests/landlock/bench/tracer.c | 278 ++++++++++++++++++ .../selftests/landlock/bench/tracer_common.h | 15 + tools/testing/selftests/landlock/config | 2 - 9 files changed, 626 insertions(+), 10 deletions(-) create mode 100644 tools/testing/selftests/landlock/bench/Makefile create mode 100644 tools/testing/selftests/landlock/bench/config create mode 100644 tools/testing/selftests/landlock/bench/progs/tracer.c create mode 100644 tools/testing/selftests/landlock/bench/tracer.c create mode 100644 tools/testing/selftests/landlock/bench/tracer_common.h diff --git a/tools/testing/selftests/Makefile b/tools/testing/selftests/Makefile index bc8fe9e8f7f2..5ee2fc668f39 100644 --- a/tools/testing/selftests/Makefile +++ b/tools/testing/selftests/Makefile @@ -47,6 +47,7 @@ TARGETS += kcmp TARGETS += kexec TARGETS += kvm TARGETS += landlock +TARGETS += landlock/bench TARGETS += lib TARGETS += livepatch TARGETS += lkdtm diff --git a/tools/testing/selftests/landlock/Makefile b/tools/testing/selftests/landlock/Makefile index e149ef66841a..348e2dbdb4e0 100644 --- a/tools/testing/selftests/landlock/Makefile +++ b/tools/testing/selftests/landlock/Makefile @@ -10,9 +10,7 @@ src_test := $(wildcard *_test.c) TEST_GEN_PROGS := $(src_test:.c=) -TEST_GEN_PROGS_EXTENDED := true bench/sandboxer bench/microbench - -TEST_PROGS_EXTENDED := bench/run.sh +TEST_GEN_PROGS_EXTENDED := true # Short targets: $(TEST_GEN_PROGS): LDLIBS += -lcap @@ -20,11 +18,6 @@ $(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 diff --git a/tools/testing/selftests/landlock/bench/Makefile b/tools/testing/selftests/landlock/bench/Makefile new file mode 100644 index 000000000000..4187b4983b9c --- /dev/null +++ b/tools/testing/selftests/landlock/bench/Makefile @@ -0,0 +1,179 @@ +# SPDX-License-Identifier: GPL-2.0 + +# based on tools/testing/selftest/bpf/Makefile +include ../../../../build/Build.include +include ../../../../scripts/Makefile.arch +include ../../../../scripts/Makefile.include + +CFLAGS += -g -rdynamic -Wall -Werror -I$(OUTPUT) +# CFLAGS += -I$(OUTPUT)/tools/include + +LDLIBS += -lelf -lz -lrt -lpthread -lm + +# Silence some warnings when compiled with clang +ifneq ($(LLVM),) +CFLAGS += -Wno-unused-command-line-argument +endif + +LOCAL_HDRS += common.h tracer_common.h + +# Order correspond to 'make run_tests' order +TEST_GEN_PROGS_EXTENDED := tracer sandboxer microbench + +TEST_PROGS_EXTENDED := run.sh + +# Emit succinct information message describing current building step +# $1 - generic step name (e.g., CC, LINK, etc); +# $2 - optional "flavor" specifier; if provided, will be emitted as [flavor]; +# $3 - target (assumed to be file); only file name will be emitted; +# $4 - optional extra arg, emitted as-is, if provided. +ifeq ($(V),1) +Q = +msg = +else +Q = @ +msg = @printf ' %-8s%s %s%s\n' "$(1)" "$(if $(2), [$(2)])" "$(notdir $(3))" "$(if $(4), $(4))"; +MAKEFLAGS += --no-print-directory +submake_extras := feature_display=0 +endif + +# override lib.mk's default rules +OVERRIDE_TARGETS := 1 +override define CLEAN + $(call msg,CLEAN) + $(Q)$(RM) -r $(TEST_GEN_PROGS) + $(Q)$(RM) -r $(EXTRA_CLEAN) +endef + +include ../../lib.mk + +TOOLSDIR := $(top_srcdir)/tools +LIBDIR := $(TOOLSDIR)/lib +BPFDIR := $(LIBDIR)/bpf +BPFTOOLDIR := $(TOOLSDIR)/bpf/bpftool +SCRATCH_DIR := $(OUTPUT)/tools +BUILD_DIR := $(SCRATCH_DIR)/build +INCLUDE_DIR := $(SCRATCH_DIR)/include +BPFOBJ := $(BUILD_DIR)/libbpf/libbpf.a + +VMLINUX_BTF_PATHS ?= $(if $(O),$(O)/vmlinux) \ + $(if $(KBUILD_OUTPUT),$(KBUILD_OUTPUT)/vmlinux) \ + /sys/kernel/btf/vmlinux \ + ../../../../../vmlinux \ + /boot/vmlinux-$(shell uname -r) +VMLINUX_BTF ?= $(abspath $(firstword $(wildcard $(VMLINUX_BTF_PATHS)))) +ifeq ($(VMLINUX_BTF),) +$(error Cannot find a vmlinux for VMLINUX_BTF at any of "$(VMLINUX_BTF_PATHS)") +endif + +# Define simple and short `make test_progs`, `make test_sysctl`, etc targets +# to build individual tests. +# NOTE: Semicolon at the end is critical to override lib.mk's default static +# rule for binaries. +$(notdir $(TEST_GEN_PROGS)): %: $(OUTPUT)/% ; + +# sort removes libbpf duplicates when not cross-building +MAKE_DIRS := $(sort $(BUILD_DIR)/libbpf \ + $(BUILD_DIR)/bpftool $(INCLUDE_DIR)) + +$(MAKE_DIRS): + $(call msg,MKDIR,,$@) + $(Q)mkdir -p $@ + +DEFAULT_BPFTOOL := $(SCRATCH_DIR)/sbin/bpftool + +TEST_GEN_PROGS_EXTENDED += $(DEFAULT_BPFTOOL) + +$(TEST_GEN_PROGS_EXTENDED): $(BPFOBJ) + +BPFTOOL ?= $(DEFAULT_BPFTOOL) +$(DEFAULT_BPFTOOL): $(wildcard $(BPFTOOLDIR)/*.[ch] $(BPFTOOLDIR)/Makefile) \ + $(BPFOBJ) | $(BUILD_DIR)/bpftool + $(Q)$(MAKE) $(submake_extras) -C $(BPFTOOLDIR) \ + ARCH= CROSS_COMPILE= CC="$(HOSTCC)" LD="$(HOSTLD)" \ + EXTRA_CFLAGS='-g' \ + OUTPUT=$(BUILD_DIR)/bpftool/ \ + LIBBPF_OUTPUT=$(BUILD_DIR)/libbpf/ \ + LIBBPF_DESTDIR=$(SCRATCH_DIR)/ \ + prefix= DESTDIR=$(SCRATCH_DIR)/ install-bin + +$(BPFOBJ): $(wildcard $(BPFDIR)/*.[ch] $(BPFDIR)/Makefile) \ + | $(BUILD_DIR)/libbpf + $(Q)$(MAKE) $(submake_extras) -C $(BPFDIR) OUTPUT=$(BUILD_DIR)/libbpf/ \ + EXTRA_CFLAGS='-g' \ + DESTDIR=$(SCRATCH_DIR) prefix= all install_headers + +$(INCLUDE_DIR)/vmlinux.h: $(VMLINUX_BTF) $(BPFTOOL) | $(INCLUDE_DIR) +ifeq ($(VMLINUX_H),) + $(call msg,GEN,,$@) + $(Q)$(BPFTOOL) btf dump file $(VMLINUX_BTF) format c > $@ +else + $(call msg,CP,,$@) + $(Q)cp "$(VMLINUX_H)" $@ +endif + +# Get Clang's default includes on this system, as opposed to those seen by +# '--target=bpf'. This fixes "missing" files on some architectures/distros, +# such as asm/byteorder.h, asm/socket.h, asm/sockios.h, sys/cdefs.h etc. +# +# Use '-idirafter': Don't interfere with include mechanics except where the +# build would have failed anyways. +define get_sys_includes +$(shell $(1) -v -E - &1 \ + | sed -n '/<...> search starts here:/,/End of search list./{ s| \(/.*\)|-idirafter \1|p }') \ +$(shell $(1) -dM -E - $@ + +$(OUTPUT)/%.o: %.c $(BPF_SKELS) + $(call msg,CC,,$@) + $(Q)$(CC) $(CFLAGS) -c $(filter %.c,$^) $(LDLIBS) -o $@ + +$(OUTPUT)/%: $(OUTPUT)/%.o common.c + $(call msg,BINARY,,$@) + $(Q)$(LINK.c) $^ $(LDLIBS) -o $@ + +EXTRA_CLEAN := $(SCRATCH_DIR) feature bpftool \ + $(addprefix $(OUTPUT)/,*.o *.skel.h) common.o diff --git a/tools/testing/selftests/landlock/bench/config b/tools/testing/selftests/landlock/bench/config new file mode 100644 index 000000000000..d7353ef77ff2 --- /dev/null +++ b/tools/testing/selftests/landlock/bench/config @@ -0,0 +1,10 @@ +CONFIG_BPF=y +CONFIG_BPF_EVENTS=y +CONFIG_BPF_SYSCALL=y +CONFIG_CMDLINE="isolcpus=0 nohz_full=0 nosmt cpufreq.off=1" +CONFIG_CMDLINE_BOOL=y +CONFIG_DEBUG_INFO=y +CONFIG_DEBUG_INFO_BTF=y +CONFIG_DEBUG_INFO_DWARF4=y +CONFIG_SECURITY=y +CONFIG_SECURITY_LANDLOCK=y diff --git a/tools/testing/selftests/landlock/bench/progs/tracer.c b/tools/testing/selftests/landlock/bench/progs/tracer.c new file mode 100644 index 000000000000..3bd71d541b03 --- /dev/null +++ b/tools/testing/selftests/landlock/bench/progs/tracer.c @@ -0,0 +1,126 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * BPF programs for benchmarking data collection. + * + * Copyright © 2024 Huawei Tech. Co., Ltd. + */ + +#include +#include +#include +#include + +#include "../tracer_common.h" + +void bpf_rcu_read_lock(void) __ksym; +void bpf_rcu_read_unlock(void) __ksym; + +char _license[] SEC("license") = "GPL"; + +struct syscall_enter_args { + unsigned long long common_tp_fields; + long syscall_nr; + unsigned long args[6]; +}; + +struct syscall_exit_args { + unsigned long long common_tp_fields; + long syscall_nr; + long ret; +}; + +struct { + __uint(type, BPF_MAP_TYPE_ARRAY); + __type(key, int); + __type(value, struct tracer_stat_data); + __uint(max_entries, NR_SYSCALLS); +} sys_stats SEC(".maps"); + +struct { + __uint(type, BPF_MAP_TYPE_HASH); + __type(key, pid_t); + __type(value, unsigned long long); + __uint(max_entries, MAX_TASKS); +} sys_enter_timestamp SEC(".maps"); + +int target_pid; + +static inline bool is_target(struct task_struct *task) +{ + bool res = false; + + bpf_rcu_read_lock(); + + /* Accumulates data only for target process or it's child. */ + while (task && task->pid != target_pid) + task = task->real_parent; + if (task) + res = true; + + bpf_rcu_read_unlock(); + return res; +} + +SEC("tp/raw_syscalls/sys_enter") +int sys_enter(struct syscall_enter_args *args) +{ + pid_t pid; + unsigned long long timestamp; + struct task_struct *task; + + task = bpf_get_current_task_btf(); + + if (!is_target(task)) + return 0; + + pid = task->pid; + timestamp = bpf_ktime_get_ns(); + bpf_map_update_elem(&sys_enter_timestamp, &pid, ×tamp, 0); + return 0; +} + +SEC("tp/raw_syscalls/sys_exit") +int sys_exit(struct syscall_exit_args *args) +{ + pid_t pid; + struct tracer_stat_data *stat; + unsigned long long *enter_timestamp, exit_timestamp, duration; + long long delta; + long syscall_nr; + struct task_struct *task; + + exit_timestamp = bpf_ktime_get_ns(); + + task = bpf_get_current_task_btf(); + if (!is_target(task)) + return 0; + + pid = task->pid; + enter_timestamp = bpf_map_lookup_elem(&sys_enter_timestamp, &pid); + if (!enter_timestamp) + return 0; + + syscall_nr = args->syscall_nr; + + stat = bpf_map_lookup_elem(&sys_stats, &syscall_nr); + if (stat) { + duration = exit_timestamp - *enter_timestamp; + + /* Cf. tools/perf/util/stat.c */ + stat->samples++; + + delta = (long long)duration - stat->mean; + + stat->duration += duration; + + /* EBPF doesn't support signed division */ + if (delta > 0) + stat->mean += (unsigned long long)delta / stat->samples; + else + stat->mean -= (unsigned long long)(-delta) / stat->samples; + + stat->M2 += delta * (duration - stat->mean); + bpf_map_update_elem(&sys_stats, &syscall_nr, stat, 0); + } + return 0; +} diff --git a/tools/testing/selftests/landlock/bench/run.sh b/tools/testing/selftests/landlock/bench/run.sh index 582313f689ad..6a8022b44ece 100755 --- a/tools/testing/selftests/landlock/bench/run.sh +++ b/tools/testing/selftests/landlock/bench/run.sh @@ -31,6 +31,7 @@ ACCESS= TRACED_SYSCALLS= MICROBENCH=false WORKLOAD= +CUSTOM_TRACER=false SILENCE=false TRACE_CMD= @@ -55,6 +56,7 @@ help() echo "Options:" echo " -e TRACED_SYSCALLS specify syscalls which would be traced while benchmarking" echo " -m run microbenchmark workload for TRACED_SYSCALLS" + echo " -b trace via custom tracer" 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)" @@ -95,6 +97,7 @@ parse_and_check_arguments() case $arg in e) TRACED_SYSCALLS=$OPTARG ;; m) MICROBENCH=true ;; + b) CUSTOM_TRACER=true ;; t) add_sandboxer_args $OPTARG ;; p) PERF_BIN=`realpath $OPTARG` ;; D) SANDBOX_DELAY=$OPTARG ;; @@ -132,6 +135,10 @@ parse_and_check_arguments() if [ ! -z "$WORKLOAD" ] && [ -z "$SANDBOXER_ARGS" ]; then err Landlock topology is not specified fi + + if [ ! -f "$CUSTOM_TRACER_BIN" ] && $CUSTOM_TRACER ; then + err Tracer binary does not exist + fi } # perf trace @@ -142,6 +149,9 @@ header+=';/^ (msec) (msec) (msec) ( header+=';/^ --------------- -------- ------ -------- --------- --------- --------- ------$/d' header+=';/^ Summary of events:$/d' +# custom tracer +header+=';/^ samples duration AVG(ns) duration(ms) stddev(%)$/d' + rm_headers() { sed -i "$header" $1 @@ -327,6 +337,12 @@ run_traced_microbench() sandbox_opt=-s fi + if $CUSTOM_TRACER; then + stddev_col=5 + else + stddev_col=9 + fi + echo '' > $output syscalls_to_parse=$(echo $TRACED_SYSCALLS | sed 's/,/\n/g') diff --git a/tools/testing/selftests/landlock/bench/tracer.c b/tools/testing/selftests/landlock/bench/tracer.c new file mode 100644 index 000000000000..fe48e1b6db45 --- /dev/null +++ b/tools/testing/selftests/landlock/bench/tracer.c @@ -0,0 +1,278 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Attach BPF programs to syscall tracepoints, launch workload and show + * gathered statistics. + * + * Copyright © 2024 Huawei Tech. Co., Ltd. + */ + +#include +#define _GNU_SOURCE + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "tracer.skel.h" +#include "common.h" +#include "tracer_common.h" + +#define ARG_MAX 32 + +#define max(x, y) ((x) > (y) ? (x) : (y)) +#define ns2ms(ns) ((long double)(ns) / (1000 * 1000)) + +#define pr_warn(fmt, ...) fprintf(stderr, fmt, ##__VA_ARGS__) + +#define SYS_ENTER_PREFIX "sys_enter_" +#define SYS_EXIT_PREFIX "sys_exit_" +#define SYS_PREFIX_MAXLEN sizeof(SYS_ENTER_PREFIX) + +#define SYS_DELIM "," + +static int attach_bpf_progs(struct tracer *skel, const char *strsys) +{ + struct bpf_link *bpf_link; + char strsys_buf[STRBUF_MAXLEN], *strsys_parsed, *strsys_next; + char str_tracepoint[STRBUF_MAXLEN + SYS_PREFIX_MAXLEN]; + + assert(strlen(strsys) < STRBUF_MAXLEN); + strcpy(strsys_buf, strsys); + strsys_parsed = strsys_buf; + + while ((strsys_next = strsep(&strsys_parsed, SYS_DELIM))) { + strcpy(str_tracepoint, SYS_ENTER_PREFIX); + strcat(str_tracepoint, strsys_next); + bpf_link = bpf_program__attach_tracepoint( + skel->progs.sys_enter, "syscalls", str_tracepoint); + if (!bpf_link) { + pr_warn("BPF attaching failed for syscall \"%s\": %s\n", + strsys_next, strerror(errno)); + goto err_out; + } + + strcpy(str_tracepoint, SYS_EXIT_PREFIX); + strcat(str_tracepoint, strsys_next); + bpf_link = bpf_program__attach_tracepoint( + skel->progs.sys_exit, "syscalls", str_tracepoint); + if (!bpf_link) { + pr_warn("BPF attaching failed for syscall \"%s\": %s\n", + strsys_next, strerror(errno)); + goto err_out; + } + } + + return 0; + +err_out: + return 1; +} + +static const char col_entity_name[] = ""; +static const char col_samples_name[] = "samples"; +static const char col_duration_avg_name[] = "duration AVG(ns)"; +static const char col_duration_name[] = "duration(ms)"; +static const char col_stddev_name[] = "stddev(%)"; + +static const int col_entity_pad = 35; +static const int col_samples_pad = max(14, sizeof(col_samples_name)); +static const int col_duration_avg_pad = max(12, sizeof(col_duration_avg_name)); +static const int col_duration_pad = max(13, sizeof(col_duration_name)); +static const int col_stddev_pad = max(10, sizeof(col_duration_name)); + +void show_stat_header(FILE *output) +{ + fprintf(output, "%*s %*s %*s %*s %*s\n", col_entity_pad, + col_entity_name, col_samples_pad, col_samples_name, + col_duration_avg_pad, col_duration_avg_name, col_duration_pad, + col_duration_name, col_stddev_pad, col_stddev_name); +} + +void show_syscall_stat(FILE *output, long syscall_nr, + struct tracer_stat_data *stat) +{ + char name[STRBUF_MAXLEN]; + double variance, variance_mean; + double stddev = 0; + + assert(snprintf(name, sizeof(name), "syscall-%ld", syscall_nr) >= 0); + + /* Cf. tools/perf/util/stat.c */ + if (stat->samples >= 2 && stat->mean) { + variance = (double)stat->M2 / (stat->samples - 1); + variance_mean = variance / stat->samples; + + stddev = 100.0 * sqrt(variance_mean) / stat->mean; + } + + fprintf(output, "%-*s %*u %*llu %*.3Lf %*.2lf%%\n", col_entity_pad, + name, col_samples_pad, stat->samples, col_duration_avg_pad, + stat->mean, col_duration_pad - 1, ns2ms(stat->duration), + col_stddev_pad, stddev); +} + +static int dump_bench_results(struct tracer *skel, const char *output) +{ + int err = 1; + int sys_map_fd; + struct tracer_stat_data sys_stat; + FILE *output_file; + + if (!output) + output_file = stderr; + else { + output_file = fopen(output, "w"); + if (!output_file) { + pr_warn("Failed to open output file \"%s\": %s\n", + output, strerror(errno)); + goto out; + } + } + + sys_map_fd = bpf_map__fd(skel->maps.sys_stats); + if (sys_map_fd < 0) { + pr_warn("Failed to get fd from BPF map \"sys_stats\"\n"); + goto out; + } + + show_stat_header(output_file); + for (int syscall_nr = 0; syscall_nr < NR_SYSCALLS; syscall_nr++) { + err = bpf_map__lookup_elem(skel->maps.sys_stats, &syscall_nr, + sizeof(syscall_nr), &sys_stat, + sizeof(sys_stat), 0); + if (err) { + pr_warn("Failed to extract stat from BPF map \"sys_stat\" for syscall[%d]\n", + syscall_nr); + goto out; + } + if (!sys_stat.samples) + continue; + + show_syscall_stat(output_file, syscall_nr, &sys_stat); + } + err = 0; +out: + if (output_file && output_file != stderr) + fclose(output_file); + return err; +} + +int msleep(int msec) +{ + int err; + struct timespec ts; + + ts.tv_sec = msec / 1000; + ts.tv_nsec = (msec % 1000) * 1000000; + + do { + err = nanosleep(&ts, &ts); + } while (err && errno == EINTR); + + if (err) + pr_warn("nanosleep failed: %s\n", strerror(errno)); + return err; +} + +// TODO: syscalls to string + +int main(const int argc, char *const argv[]) +{ + int c, child, status; + const char *strsys = NULL, *output = NULL, *cmd; + struct tracer *skel = NULL; + int init_duration = 0; + + while ((c = getopt(argc, argv, "e:o:D:h")) != -1) { + switch (c) { + case 'e': + strsys = optarg; + break; + case 'o': + output = optarg; + break; + case 'D': + errno = 0; + init_duration = atoi(optarg); + if (errno) { + pr_warn("atoi() failed on %s\n", optarg); + goto cleanup; + } + break; + case 'h': + pr_warn("Usage: %s [OPTIONS] [WORKLOAD_CMD]\n" + "Run WORKLOAD_CMD and trace number and duration of syscalls\n" + "\n" + "Options:\n" + " -e TRACED_SYSCALLS specify syscalls which would be traced while benchmarking\n" + " -o FILE redirect result output into FILE\n" + " -D MSECS wait MSECS msecs before tracing sandboxed workload\n", + argv[0]); + break; + } + + /* Next argument is workload. */ + if (optind < argc && *argv[optind] != '-') + break; + } + + if (optind >= argc) { + pr_warn("Command is not specified\n"); + goto cleanup; + } + + if (!strsys) { + pr_warn("Syscall list is not specified\n"); + goto cleanup; + } + + skel = tracer__open_and_load(); + if (!skel) { + pr_warn("BPF skeleton loading failed: %s\n", strerror(errno)); + goto cleanup; + } + + cmd = argv[optind]; + + child = fork(); + if (child < 0) { + pr_warn("Failed to fork child workload process: \"%s\"\n", + strerror(errno)); + goto cleanup; + } + + if (child == 0) { + skel->bss->target_pid = getpid(); + + execv(cmd, argv + optind); + + pr_warn("Failed to execute \"%s\": %s\n", cmd, strerror(errno)); + _exit(EXIT_FAILURE); + } + + if (msleep(init_duration)) + goto cleanup; + + if (attach_bpf_progs(skel, strsys)) + goto cleanup; + + if (waitpid(child, &status, 0) != child) + pr_warn("\"%s\" execution failed: %s\n", cmd, strerror(errno)); + + if (dump_bench_results(skel, output)) + goto cleanup; + return 0; +cleanup: + tracer__destroy(skel); + return 1; +} diff --git a/tools/testing/selftests/landlock/bench/tracer_common.h b/tools/testing/selftests/landlock/bench/tracer_common.h new file mode 100644 index 000000000000..e5c9ae72632e --- /dev/null +++ b/tools/testing/selftests/landlock/bench/tracer_common.h @@ -0,0 +1,15 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef LANDLOCK_BENCH_TRACER_COMMON_H +#define LANDLOCK_BENCH_TRACER_COMMON_H + +#define NR_SYSCALLS 1024 +#define MAX_TASKS 100000 + +struct tracer_stat_data { + long long M2; + long long mean; + unsigned long long duration; + unsigned int samples; +}; + +#endif diff --git a/tools/testing/selftests/landlock/config b/tools/testing/selftests/landlock/config index e4bf29cf935f..29af19c4e9f9 100644 --- a/tools/testing/selftests/landlock/config +++ b/tools/testing/selftests/landlock/config @@ -1,7 +1,5 @@ CONFIG_CGROUPS=y CONFIG_CGROUP_SCHED=y -CONFIG_CMDLINE="isolcpus=0 nohz_full=0 nosmt cpufreq.off=1" -CONFIG_CMDLINE_BOOL=y CONFIG_INET=y CONFIG_IPV6=y CONFIG_KEYS=y From patchwork Fri Aug 16 00:59:43 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: Mikhail Ivanov X-Patchwork-Id: 13765317 Received: from szxga08-in.huawei.com (szxga08-in.huawei.com [45.249.212.255]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id AB2CEECC; Fri, 16 Aug 2024 01:00:23 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=45.249.212.255 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1723770026; cv=none; b=lHICqadG0o7fbB6vXuJwQOfiSsfNpY2HjTQrxtKWeNWKj/7ULeU4yhh/vw0nC1iXxUfMb6SIR0whJxuqxss3xqJcdPikfMG5upsFsaVcqNrMjmpsc+1ysSklGE+oIjUS8Z0/F5ecuSIpnpOgOTYWNejA5b2jmn+8GSRtZ56pokY= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1723770026; c=relaxed/simple; bh=jYu1hQ3GbO6rFkdtn1RqFe9xx1Q1L0UDXL3rPi7xvPk=; h=From:To:CC:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=fLDkcM5iRuKbAmtk03iD0nYXMWpp2s3gAa4WS3yzO8mfms7RfepQjV9Vk1r8pcHsEJvdWqWl0XRoxJ4KV5TRWt95I/qmgjJCZ70uWD1yll+k+zZduH2z48LnMr1coMX6LZIT78SJl8NZOFvT2EnedJPYqMdhCqCr8NeiLAnEHmI= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=huawei-partners.com; spf=pass smtp.mailfrom=huawei-partners.com; arc=none smtp.client-ip=45.249.212.255 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=huawei-partners.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=huawei-partners.com Received: from mail.maildlp.com (unknown [172.19.162.254]) by szxga08-in.huawei.com (SkyGuard) with ESMTP id 4WlNts326Lz1T7PK; Fri, 16 Aug 2024 08:59:49 +0800 (CST) Received: from dggpemm500020.china.huawei.com (unknown [7.185.36.49]) by mail.maildlp.com (Postfix) with ESMTPS id 4500C18010A; Fri, 16 Aug 2024 09:00:21 +0800 (CST) Received: from mscphis02103.huawei.com (10.123.65.215) by dggpemm500020.china.huawei.com (7.185.36.49) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.1.2507.39; Fri, 16 Aug 2024 09:00:19 +0800 From: Mikhail Ivanov To: CC: , , , , , , , Subject: [RFC PATCH v1 4/4] selftests/landlock: Add realworld workload based on find tool Date: Fri, 16 Aug 2024 08:59:43 +0800 Message-ID: <20240816005943.1832694-5-ivanov.mikhail1@huawei-partners.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20240816005943.1832694-1-ivanov.mikhail1@huawei-partners.com> References: <20240816005943.1832694-1-ivanov.mikhail1@huawei-partners.com> Precedence: bulk X-Mailing-List: linux-security-module@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 X-ClientProxiedBy: mscpeml500003.china.huawei.com (7.188.49.51) To dggpemm500020.china.huawei.com (7.185.36.49) Implement script that measures Landlock overhead for workload in which find tool is executed on Linux source code folder. This workload is tested with 5, 10 depth values and few number of layers. This workload is useful to measure Landlock overhead under different number of layers and different keys of the filesystem ruleset. Signed-off-by: Mikhail Ivanov --- .../landlock/bench/bench_find_on_linux.sh | 84 +++++++++++++++++++ 1 file changed, 84 insertions(+) create mode 100755 tools/testing/selftests/landlock/bench/bench_find_on_linux.sh diff --git a/tools/testing/selftests/landlock/bench/bench_find_on_linux.sh b/tools/testing/selftests/landlock/bench/bench_find_on_linux.sh new file mode 100755 index 000000000000..ae53c265c444 --- /dev/null +++ b/tools/testing/selftests/landlock/bench/bench_find_on_linux.sh @@ -0,0 +1,84 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: GPL-2.0 +# +# Copyright © 2024 Huawei Tech. Co., Ltd. +# +# Measure openat(2) overhead for workload that executes find tool on Linux source +# code with different depths and numbers of ruleset layers. + +# cf. tools/testing/selftests/kselftest.h +KSFT_PASS=0 +KSFT_FAIL=1 +KSFT_XFAIL=2 +KSFT_XPASS=3 +KSFT_SKIP=4 + +REL_DIR=$(dirname $(realpath $0)) +FIND=/usr/bin/find +LINUX_SRC=$(realpath $REL_DIR/../../../../../) +BENCH_CMD=$REL_DIR/run.sh +TOPOLOGY=.topology +TMP=.tmp + +# read +READ_ACCESS=4 + +# $1 - Linux src files path +# $2 - Maximum depth of files +# $3 - If $3 == 0 then only files of depth $2 is used in ruleset. +# Otherwise, ruleset uses files of depth 1-$2 and ruleset layer +# of each file matches depth of the file. +# $4 - Name of the file in which topology would be saved +gen_linux_src_topology() +{ + n_layers=$2 + if [[ $3 -eq 0 ]]; then + n_layers=1 + find $1 -mindepth $2 -maxdepth $2 -fprintf $4 '1 %p\n' + else + find $1 -mindepth 1 -maxdepth $2 -fprintf $4 '%d %p\n' + fi + + # Allow access to FIND + for depth in $(seq 1 $n_layers); + do + echo $depth /usr/bin/find >> $4 + echo $depth /usr/bin/file >> $4 + echo $depth /lib >> $4 + echo $depth /etc >> $4 + done +} + +if [ ! -f "$BENCH_CMD" ]; then + echo $BENCH_CMD does not exist + exit $KSFT_SKIP +fi + +if [ ! -f "$FIND" ]; then + echo $FIND does not exist + exit $KSFT_SKIP +fi + +# $1 - depth +# $2 - If $2 == 0 then only files of depth $2 is used in ruleset. +# Otherwise, ruleset uses files of depth 1-$2 and ruleset layer +# of each file matches depth of the file. +# $3 - Number of iterations of this sample +run_sample() +{ + n_layers=$1 + if [[ $2 -eq 0 ]]; then + n_layers=1 + fi + + echo Running find on $n_layers layers, $1 depth, $3 iterations... + gen_linux_src_topology $LINUX_SRC $1 $2 $TOPOLOGY + + $BENCH_CMD -s -r $3 -b -t fs:$TOPOLOGY:$READ_ACCESS -e openat \ + $FIND $LINUX_SRC -mindepth $1 -maxdepth $1 -exec file '{}' \; +} + +run_sample 5 0 10 +run_sample 5 1 10 +run_sample 10 0 500 +run_sample 10 1 500