mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
From: Slava Imameev <slava.imameev@crowdstrike.com>
To: <qmo@kernel.org>, <ast@kernel.org>, <daniel@iogearbox.net>,
	<andrii@kernel.org>, <shuah@kernel.org>, <bpf@vger.kernel.org>
Cc: <martin.lau@linux.dev>, <eddyz87@gmail.com>, <song@kernel.org>,
	<yonghong.song@linux.dev>, <john.fastabend@gmail.com>,
	<kpsingh@kernel.org>, <sdf@fomichev.me>, <haoluo@google.com>,
	<jolsa@kernel.org>, <mykolal@fb.com>,
	<slava.imameev@crowdstrike.com>,
	<justin.deschamp@crowdstrike.com>, <mark.fontana@crowdstrike.com>,
	<linux-kernel@vger.kernel.org>, <linux-kselftest@vger.kernel.org>
Subject: [PATCH bpf-next v2 2/2] selftests/bpf: Add test for bpftool access to read-only protected maps
Date: Sat, 31 May 2025 02:48:44 +1000	[thread overview]
Message-ID: <20250530164844.74734-2-slava.imameev@crowdstrike.com> (raw)
In-Reply-To: <20250530164844.74734-1-slava.imameev@crowdstrike.com>

Add selftest cases that validate bpftool's expected behavior when
accessing maps protected from modification via security_bpf_map.

The test includes a BPF program attached to security_bpf_map with two maps:
- A protected map that only allows read-only access
- An unprotected map that allows full access

The test script attaches the BPF program to security_bpf_map and
verifies that for the bpftool map command:
- Read access works on both maps
- Write access fails on the protected map
- Write access succeeds on the unprotected map
- These behaviors remain consistent when the maps are pinned

Signed-off-by: Slava Imameev <slava.imameev@crowdstrike.com>
---
Changes in v2:
- fix for a test compilation error: "conflicting types for 'bpf_fentry_test1'"
---
---
 tools/testing/selftests/bpf/Makefile          |   1 +
 .../selftests/bpf/progs/security_bpf_map.c    |  56 +++++
 .../testing/selftests/bpf/test_bpftool_map.sh | 208 ++++++++++++++++++
 3 files changed, 265 insertions(+)
 create mode 100644 tools/testing/selftests/bpf/progs/security_bpf_map.c
 create mode 100755 tools/testing/selftests/bpf/test_bpftool_map.sh

diff --git a/tools/testing/selftests/bpf/Makefile b/tools/testing/selftests/bpf/Makefile
index cf5ed3bee573..731a86407799 100644
--- a/tools/testing/selftests/bpf/Makefile
+++ b/tools/testing/selftests/bpf/Makefile
@@ -109,6 +109,7 @@ TEST_PROGS := test_kmod.sh \
 	test_xdping.sh \
 	test_bpftool_build.sh \
 	test_bpftool.sh \
+	test_bpftool_map.sh \
 	test_bpftool_metadata.sh \
 	test_doc_build.sh \
 	test_xsk.sh \
diff --git a/tools/testing/selftests/bpf/progs/security_bpf_map.c b/tools/testing/selftests/bpf/progs/security_bpf_map.c
new file mode 100644
index 000000000000..09048c096ee4
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/security_bpf_map.c
@@ -0,0 +1,56 @@
+// SPDX-License-Identifier: GPL-2.0-only
+
+#include "vmlinux.h"
+#include <bpf/bpf_tracing.h>
+#include <bpf/bpf_helpers.h>
+
+char _license[] SEC("license") = "GPL";
+
+#define EPERM 1 /* Operation not permitted */
+
+/* From include/linux/mm.h. */
+#define FMODE_WRITE	0x2
+
+struct map;
+
+struct {
+	__uint(type, BPF_MAP_TYPE_ARRAY);
+	__type(key, __u32);
+	__type(value, __u32);
+	__uint(max_entries, 1);
+} prot_map SEC(".maps");
+
+struct {
+	__uint(type, BPF_MAP_TYPE_ARRAY);
+	__type(key, __u32);
+	__type(value, __u32);
+	__uint(max_entries, 1);
+} not_prot_map SEC(".maps");
+
+SEC("fmod_ret/security_bpf_map")
+int BPF_PROG(fmod_bpf_map, struct bpf_map *map, int fmode)
+{
+	if (map == &prot_map) {
+		/* Allow read-only access */
+		if (fmode & FMODE_WRITE)
+			return -EPERM;
+	}
+
+	return 0;
+}
+
+/*
+ * This program keeps references to maps. This is needed to prevent
+ * optimizing them out.
+ */
+SEC("fentry/bpf_fentry_test1")
+int BPF_PROG(bpf_map_test0, int a)
+{
+	__u32 key = 0;
+	__u32 val1 = a;
+	__u32 val2 = a + 1;
+
+	bpf_map_update_elem(&prot_map, &key, &val1, BPF_ANY);
+	bpf_map_update_elem(&not_prot_map, &key, &val2, BPF_ANY);
+	return 0;
+}
diff --git a/tools/testing/selftests/bpf/test_bpftool_map.sh b/tools/testing/selftests/bpf/test_bpftool_map.sh
new file mode 100755
index 000000000000..c7c7f3d2071e
--- /dev/null
+++ b/tools/testing/selftests/bpf/test_bpftool_map.sh
@@ -0,0 +1,208 @@
+#!/bin/sh
+# SPDX-License-Identifier: GPL-2.0
+
+# Kselftest framework requirement - SKIP code is 4.
+ksft_skip=4
+
+PROTECTED_MAP_NAME="prot_map"
+NOT_PROTECTED_MAP_NAME="not_prot_map"
+BPF_FILE="security_bpf_map.bpf.o"
+TESTNAME="security_bpf_map"
+BPF_FS=$(awk '$3 == "bpf" {print $2; exit}' /proc/mounts)
+BPF_DIR="$BPF_FS/test_$TESTNAME"
+SCRIPT_DIR=$(dirname $(realpath "$0"))
+BPF_FILE_PATH="$SCRIPT_DIR/$BPF_FILE"
+# Assume the script is located under tools/testing/selftests/bpf/
+KDIR_ROOT_DIR=$(realpath "$SCRIPT_DIR"/../../../../)
+
+_cleanup()
+{
+	set +eu
+	[ -d "$TMPDIR" ] && rm -rf "$TMPDIR" 2> /dev/null
+	[ -d "$BPF_DIR" ] && rm -rf "$BPF_DIR" 2> /dev/null
+}
+
+cleanup_skip()
+{
+	echo "selftests: $TESTNAME [SKIP]"
+	_cleanup
+
+	exit $ksft_skip
+}
+
+cleanup()
+{
+	if [ "$?" = 0 ]; then
+		echo "selftests: $TESTNAME [PASS]"
+	else
+		echo "selftests: $TESTNAME [FAILED]"
+	fi
+	_cleanup
+}
+
+# Parameters:
+#   $1: The top of kernel repository
+#   $2: Output directory
+build_bpftool()
+{
+	local kdir_root_dir="$1"
+	local output_dir="$2"
+	local pwd="$(pwd)"
+	local ncpus=1
+
+	echo Building bpftool ...
+
+	#We want to start build from the top of kernel repository.
+	cd "$kdir_root_dir"
+	if [ ! -e tools/bpf/bpftool/Makefile ]; then
+		echo bpftool files not found
+		exit $ksft_skip
+	fi
+
+	# Determine the number of CPUs for parallel compilation
+	if command -v nproc >/dev/null 2>&1; then
+		ncpus=$(nproc)
+	fi
+
+	make -C tools/bpf/bpftool -s -j"$ncpus" OUTPUT="$output_dir"/ >/dev/null
+	echo ... finished building bpftool
+	cd "$pwd"
+}
+
+# Function to test map access with configurable write expectations
+# Parameters:
+#   $1: Map name
+#   $2: Whether write should succeed (true/false)
+#   $3: bpftool path
+#   $4: BPF_DIR
+test_map_access() {
+	local map_name="$1"
+	local write_should_succeed="$2"
+	local bpftool_path="$3"
+	local pin_path="$4/${map_name}_pinned"
+	local key="0 0 0 0"
+	local value="1 1 1 1"
+
+	echo "Testing access to map: $map_name"
+
+	# Test read access to the map
+	if "$bpftool_path" map lookup name "$map_name" key $key; then
+		echo "  Read access to $map_name succeeded"
+	else
+		echo "  Read access to $map_name failed"
+		exit 1
+	fi
+
+	# Test write access to the map
+	if "$bpftool_path" map update name "$map_name" key $key value $value; then
+		if [ "$write_should_succeed" = "true" ]; then
+			echo "  Write access to $map_name succeeded as expected"
+		else
+			echo "  Write access to $map_name succeeded but should have failed"
+			exit 1
+		fi
+	else
+		if [ "$write_should_succeed" = "true" ]; then
+			echo "  Write access to $map_name failed but should have succeeded"
+			exit 1
+		else
+			echo "  Write access to $map_name failed as expected"
+		fi
+	fi
+
+	# Pin the map to the BPF filesystem
+	"$bpftool_path" map pin name "$map_name" "$pin_path"
+	if [ -e "$pin_path" ]; then
+		echo "  Successfully pinned $map_name to $pin_path"
+	else
+		echo "  Failed to pin $map_name"
+		exit 1
+	fi
+
+	# Test read access to the pinned map
+	if "$bpftool_path" map lookup pinned "$pin_path" key $key; then
+		echo "  Read access to pinned $map_name succeeded"
+	else
+		echo "  Read access to pinned $map_name failed"
+		exit 1
+	fi
+
+	# Test write access to the pinned map
+	if "$bpftool_path" map update pinned "$pin_path" key $key value $value; then
+		if [ "$write_should_succeed" = "true" ]; then
+			echo "  Write access to pinned $map_name succeeded as expected"
+		else
+			echo "  Write access to pinned $map_name succeeded but should have failed"
+			exit 1
+		fi
+	else
+		if [ "$write_should_succeed" = "true" ]; then
+			echo "  Write access to pinned $map_name failed but should have succeeded"
+			exit 1
+		else
+			echo "  Write access to pinned $map_name failed as expected"
+		fi
+	fi
+
+	echo "  Finished testing $map_name"
+	echo
+}
+
+check_root_privileges() {
+	if [ $(id -u) -ne 0 ]; then
+		echo "Need root privileges"
+		exit $ksft_skip
+	fi
+}
+
+check_bpffs() {
+	if [ -z "$BPF_FS" ]; then
+		echo "Could not run test without bpffs mounted"
+		exit $ksft_skip
+	fi
+}
+
+create_tmp_dir() {
+	TMPDIR=$(mktemp -d)
+	if [ $? -ne 0 ] || [ ! -d "$TMPDIR" ]; then
+		echo "Failed to create temporary directory"
+		exit $ksft_skip
+	fi
+}
+
+locate_or_build_bpftool() {
+	if ! bpftool version > /dev/null 2>&1; then
+		build_bpftool "$KDIR_ROOT_DIR" "$TMPDIR"
+		BPFTOOL_PATH="$TMPDIR"/bpftool
+	else
+		echo "Using bpftool from PATH"
+		BPFTOOL_PATH="bpftool"
+	fi
+}
+
+set -eu
+
+trap cleanup_skip EXIT
+
+check_root_privileges
+
+check_bpffs
+
+create_tmp_dir
+
+locate_or_build_bpftool
+
+mkdir "$BPF_DIR"
+
+trap cleanup EXIT
+
+# Load and attach the BPF programs to control maps access
+"$BPFTOOL_PATH" prog loadall "$BPF_FILE_PATH" "$BPF_DIR"/prog autoattach
+
+# Test protected map (write should fail)
+test_map_access "$PROTECTED_MAP_NAME" "false" "$BPFTOOL_PATH" "$BPF_DIR"
+
+# Test not protected map (write should succeed)
+test_map_access "$NOT_PROTECTED_MAP_NAME" "true" "$BPFTOOL_PATH" "$BPF_DIR"
+
+exit 0
-- 
2.34.1


  reply	other threads:[~2025-05-30 16:49 UTC|newest]

Thread overview: 6+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2025-05-30 16:48 [PATCH bpf-next v2 1/2] bpftool: Use appropriate permissions for map access Slava Imameev
2025-05-30 16:48 ` Slava Imameev [this message]
2025-06-05 11:22   ` [PATCH bpf-next v2 2/2] selftests/bpf: Add test for bpftool access to read-only protected maps Quentin Monnet
2025-06-11 21:39     ` Slava Imameev
2025-06-05 11:22 ` [PATCH bpf-next v2 1/2] bpftool: Use appropriate permissions for map access Quentin Monnet
2025-06-11 21:06   ` Slava Imameev

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20250530164844.74734-2-slava.imameev@crowdstrike.com \
    --to=slava.imameev@crowdstrike.com \
    --cc=andrii@kernel.org \
    --cc=ast@kernel.org \
    --cc=bpf@vger.kernel.org \
    --cc=daniel@iogearbox.net \
    --cc=eddyz87@gmail.com \
    --cc=haoluo@google.com \
    --cc=john.fastabend@gmail.com \
    --cc=jolsa@kernel.org \
    --cc=justin.deschamp@crowdstrike.com \
    --cc=kpsingh@kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-kselftest@vger.kernel.org \
    --cc=mark.fontana@crowdstrike.com \
    --cc=martin.lau@linux.dev \
    --cc=mykolal@fb.com \
    --cc=qmo@kernel.org \
    --cc=sdf@fomichev.me \
    --cc=shuah@kernel.org \
    --cc=song@kernel.org \
    --cc=yonghong.song@linux.dev \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

all inboxes | Powered by JetHome®