mirror of
git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2025-08-05 16:54:27 +00:00

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> Reviewed-by: Quentin Monnet <qmo@kernel.org> Link: https://lore.kernel.org/r/20250620151812.13952-2-slava.imameev@crowdstrike.com Signed-off-by: Alexei Starovoitov <ast@kernel.org>
398 lines
11 KiB
Bash
Executable file
398 lines
11 KiB
Bash
Executable file
#!/bin/sh
|
|
# SPDX-License-Identifier: GPL-2.0
|
|
|
|
# Kselftest framework requirement - SKIP code is 4.
|
|
ksft_skip=4
|
|
|
|
TESTNAME="bpftool_map"
|
|
BPF_FILE="security_bpf_map.bpf.o"
|
|
BPF_ITER_FILE="bpf_iter_map_elem.bpf.o"
|
|
PROTECTED_MAP_NAME="prot_map"
|
|
NOT_PROTECTED_MAP_NAME="not_prot_map"
|
|
BPF_FS_TMP_PARENT="/tmp"
|
|
BPF_FS_PARENT=$(awk '$3 == "bpf" {print $2; exit}' /proc/mounts)
|
|
BPF_FS_PARENT=${BPF_FS_PARENT:-$BPF_FS_TMP_PARENT}
|
|
# bpftool will mount bpf file system under BPF_DIR if it is not mounted
|
|
# under BPF_FS_PARENT.
|
|
BPF_DIR="$BPF_FS_PARENT/test_$TESTNAME"
|
|
SCRIPT_DIR=$(dirname $(realpath "$0"))
|
|
BPF_FILE_PATH="$SCRIPT_DIR/$BPF_FILE"
|
|
BPF_ITER_FILE_PATH="$SCRIPT_DIR/$BPF_ITER_FILE"
|
|
BPFTOOL_PATH="bpftool"
|
|
# Assume the script is located under tools/testing/selftests/bpf/
|
|
KDIR_ROOT_DIR=$(realpath "$SCRIPT_DIR"/../../../../)
|
|
|
|
_cleanup()
|
|
{
|
|
set +eu
|
|
|
|
# If BPF_DIR is a mount point this will not remove the mount point itself.
|
|
[ -d "$BPF_DIR" ] && rm -rf "$BPF_DIR" 2> /dev/null
|
|
|
|
# Unmount if BPF filesystem was temporarily created.
|
|
if [ "$BPF_FS_PARENT" = "$BPF_FS_TMP_PARENT" ]; then
|
|
# A loop and recursive unmount are required as bpftool might
|
|
# create multiple mounts. For example, a bind mount of the directory
|
|
# to itself. The bind mount is created to change mount propagation
|
|
# flags on an actual mount point.
|
|
max_attempts=3
|
|
attempt=0
|
|
while mountpoint -q "$BPF_DIR" && [ $attempt -lt $max_attempts ]; do
|
|
umount -R "$BPF_DIR" 2>/dev/null
|
|
attempt=$((attempt+1))
|
|
done
|
|
|
|
# The directory still exists. Remove it now.
|
|
[ -d "$BPF_DIR" ] && rm -rf "$BPF_DIR" 2>/dev/null
|
|
fi
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
check_root_privileges() {
|
|
if [ $(id -u) -ne 0 ]; then
|
|
echo "Need root privileges"
|
|
exit $ksft_skip
|
|
fi
|
|
}
|
|
|
|
# Function to verify bpftool path.
|
|
# Parameters:
|
|
# $1: bpftool path
|
|
verify_bpftool_path() {
|
|
local bpftool_path="$1"
|
|
if ! "$bpftool_path" version > /dev/null 2>&1; then
|
|
echo "Could not run test without bpftool"
|
|
exit $ksft_skip
|
|
fi
|
|
}
|
|
|
|
# Function to verify BTF support.
|
|
# The test requires BTF support for fmod_ret programs.
|
|
verify_btf_support() {
|
|
if [ ! -f /sys/kernel/btf/vmlinux ]; then
|
|
echo "Could not run test without BTF support"
|
|
exit $ksft_skip
|
|
fi
|
|
}
|
|
|
|
# Function to initialize map entries with keys [0..2] and values set to 0.
|
|
# Parameters:
|
|
# $1: Map name
|
|
# $2: bpftool path
|
|
initialize_map_entries() {
|
|
local map_name="$1"
|
|
local bpftool_path="$2"
|
|
|
|
for key in 0 1 2; do
|
|
"$bpftool_path" map update name "$map_name" key $key 0 0 0 value 0 0 0 $key
|
|
done
|
|
}
|
|
|
|
# Test read access to the map.
|
|
# Parameters:
|
|
# $1: Name command (name/pinned)
|
|
# $2: Map name
|
|
# $3: bpftool path
|
|
# $4: key
|
|
access_for_read() {
|
|
local name_cmd="$1"
|
|
local map_name="$2"
|
|
local bpftool_path="$3"
|
|
local key="$4"
|
|
|
|
# Test read access to the map.
|
|
if ! "$bpftool_path" map lookup "$name_cmd" "$map_name" key $key 1>/dev/null; then
|
|
echo " Read access to $key in $map_name failed"
|
|
exit 1
|
|
fi
|
|
|
|
# Test read access to map's BTF data.
|
|
if ! "$bpftool_path" btf dump map "$name_cmd" "$map_name" 1>/dev/null; then
|
|
echo " Read access to $map_name for BTF data failed"
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
# Test write access to the map.
|
|
# Parameters:
|
|
# $1: Name command (name/pinned)
|
|
# $2: Map name
|
|
# $3: bpftool path
|
|
# $4: key
|
|
# $5: Whether write should succeed (true/false)
|
|
access_for_write() {
|
|
local name_cmd="$1"
|
|
local map_name="$2"
|
|
local bpftool_path="$3"
|
|
local key="$4"
|
|
local write_should_succeed="$5"
|
|
local value="1 1 1 1"
|
|
|
|
if "$bpftool_path" map update "$name_cmd" "$map_name" key $key value \
|
|
$value 2>/dev/null; then
|
|
if [ "$write_should_succeed" = "false" ]; then
|
|
echo " Write access to $key in $map_name succeeded but should have failed"
|
|
exit 1
|
|
fi
|
|
else
|
|
if [ "$write_should_succeed" = "true" ]; then
|
|
echo " Write access to $key in $map_name failed but should have succeeded"
|
|
exit 1
|
|
fi
|
|
fi
|
|
}
|
|
|
|
# Test entry deletion for the map.
|
|
# Parameters:
|
|
# $1: Name command (name/pinned)
|
|
# $2: Map name
|
|
# $3: bpftool path
|
|
# $4: key
|
|
# $5: Whether write should succeed (true/false)
|
|
access_for_deletion() {
|
|
local name_cmd="$1"
|
|
local map_name="$2"
|
|
local bpftool_path="$3"
|
|
local key="$4"
|
|
local write_should_succeed="$5"
|
|
local value="1 1 1 1"
|
|
|
|
# Test deletion by key for the map.
|
|
# Before deleting, check the key exists.
|
|
if ! "$bpftool_path" map lookup "$name_cmd" "$map_name" key $key 1>/dev/null; then
|
|
echo " Key $key does not exist in $map_name"
|
|
exit 1
|
|
fi
|
|
|
|
# Delete by key.
|
|
if "$bpftool_path" map delete "$name_cmd" "$map_name" key $key 2>/dev/null; then
|
|
if [ "$write_should_succeed" = "false" ]; then
|
|
echo " Deletion for $key in $map_name succeeded but should have failed"
|
|
exit 1
|
|
fi
|
|
else
|
|
if [ "$write_should_succeed" = "true" ]; then
|
|
echo " Deletion for $key in $map_name failed but should have succeeded"
|
|
exit 1
|
|
fi
|
|
fi
|
|
|
|
# After deleting, check the entry existence according to the expected status.
|
|
if "$bpftool_path" map lookup "$name_cmd" "$map_name" key $key 1>/dev/null; then
|
|
if [ "$write_should_succeed" = "true" ]; then
|
|
echo " Key $key for $map_name was not deleted but should have been deleted"
|
|
exit 1
|
|
fi
|
|
else
|
|
if [ "$write_should_succeed" = "false" ]; then
|
|
echo "Key $key for $map_name was deleted but should have not been deleted"
|
|
exit 1
|
|
fi
|
|
fi
|
|
|
|
# Test creation of map's deleted entry, if deletion was successful.
|
|
# Otherwise, the entry exists.
|
|
if "$bpftool_path" map update "$name_cmd" "$map_name" key $key value \
|
|
$value 2>/dev/null; then
|
|
if [ "$write_should_succeed" = "false" ]; then
|
|
echo " Write access to $key in $map_name succeeded after deletion attempt but should have failed"
|
|
exit 1
|
|
fi
|
|
else
|
|
if [ "$write_should_succeed" = "true" ]; then
|
|
echo " Write access to $key in $map_name failed after deletion attempt but should have succeeded"
|
|
exit 1
|
|
fi
|
|
fi
|
|
}
|
|
|
|
# Test map elements iterator.
|
|
# Parameters:
|
|
# $1: Name command (name/pinned)
|
|
# $2: Map name
|
|
# $3: bpftool path
|
|
# $4: BPF_DIR
|
|
# $5: bpf iterator object file path
|
|
iterate_map_elem() {
|
|
local name_cmd="$1"
|
|
local map_name="$2"
|
|
local bpftool_path="$3"
|
|
local bpf_dir="$4"
|
|
local bpf_file="$5"
|
|
local pin_path="$bpf_dir/map_iterator"
|
|
|
|
"$bpftool_path" iter pin "$bpf_file" "$pin_path" map "$name_cmd" "$map_name"
|
|
if [ ! -f "$pin_path" ]; then
|
|
echo " Failed to pin iterator to $pin_path"
|
|
exit 1
|
|
fi
|
|
|
|
cat "$pin_path" 1>/dev/null
|
|
rm "$pin_path" 2>/dev/null
|
|
}
|
|
|
|
# Function to test map access with configurable write expectations
|
|
# Parameters:
|
|
# $1: Name command (name/pinned)
|
|
# $2: Map name
|
|
# $3: bpftool path
|
|
# $4: key for rw
|
|
# $5: key to delete
|
|
# $6: Whether write should succeed (true/false)
|
|
# $7: BPF_DIR
|
|
# $8: bpf iterator object file path
|
|
access_map() {
|
|
local name_cmd="$1"
|
|
local map_name="$2"
|
|
local bpftool_path="$3"
|
|
local key_for_rw="$4"
|
|
local key_to_del="$5"
|
|
local write_should_succeed="$6"
|
|
local bpf_dir="$7"
|
|
local bpf_iter_file_path="$8"
|
|
|
|
access_for_read "$name_cmd" "$map_name" "$bpftool_path" "$key_for_rw"
|
|
access_for_write "$name_cmd" "$map_name" "$bpftool_path" "$key_for_rw" \
|
|
"$write_should_succeed"
|
|
access_for_deletion "$name_cmd" "$map_name" "$bpftool_path" "$key_to_del" \
|
|
"$write_should_succeed"
|
|
iterate_map_elem "$name_cmd" "$map_name" "$bpftool_path" "$bpf_dir" \
|
|
"$bpf_iter_file_path"
|
|
}
|
|
|
|
# Function to test map access with configurable write expectations
|
|
# Parameters:
|
|
# $1: Map name
|
|
# $2: bpftool path
|
|
# $3: BPF_DIR
|
|
# $4: Whether write should succeed (true/false)
|
|
# $5: bpf iterator object file path
|
|
test_map_access() {
|
|
local map_name="$1"
|
|
local bpftool_path="$2"
|
|
local bpf_dir="$3"
|
|
local pin_path="$bpf_dir/${map_name}_pinned"
|
|
local write_should_succeed="$4"
|
|
local bpf_iter_file_path="$5"
|
|
|
|
# Test access to the map by name.
|
|
access_map "name" "$map_name" "$bpftool_path" "0 0 0 0" "1 0 0 0" \
|
|
"$write_should_succeed" "$bpf_dir" "$bpf_iter_file_path"
|
|
|
|
# Pin the map to the BPF filesystem
|
|
"$bpftool_path" map pin name "$map_name" "$pin_path"
|
|
if [ ! -e "$pin_path" ]; then
|
|
echo " Failed to pin $map_name"
|
|
exit 1
|
|
fi
|
|
|
|
# Test access to the pinned map.
|
|
access_map "pinned" "$pin_path" "$bpftool_path" "0 0 0 0" "2 0 0 0" \
|
|
"$write_should_succeed" "$bpf_dir" "$bpf_iter_file_path"
|
|
}
|
|
|
|
# Function to test map creation and map-of-maps
|
|
# Parameters:
|
|
# $1: bpftool path
|
|
# $2: BPF_DIR
|
|
test_map_creation_and_map_of_maps() {
|
|
local bpftool_path="$1"
|
|
local bpf_dir="$2"
|
|
local outer_map_name="outer_map_tt"
|
|
local inner_map_name="inner_map_tt"
|
|
|
|
"$bpftool_path" map create "$bpf_dir/$inner_map_name" type array key 4 \
|
|
value 4 entries 4 name "$inner_map_name"
|
|
if [ ! -f "$bpf_dir/$inner_map_name" ]; then
|
|
echo " Failed to create inner map file at $bpf_dir/$outer_map_name"
|
|
return 1
|
|
fi
|
|
|
|
"$bpftool_path" map create "$bpf_dir/$outer_map_name" type hash_of_maps \
|
|
key 4 value 4 entries 2 name "$outer_map_name" inner_map name "$inner_map_name"
|
|
if [ ! -f "$bpf_dir/$outer_map_name" ]; then
|
|
echo " Failed to create outer map file at $bpf_dir/$outer_map_name"
|
|
return 1
|
|
fi
|
|
|
|
# Add entries to the outer map by name and by pinned path.
|
|
"$bpftool_path" map update pinned "$bpf_dir/$outer_map_name" key 0 0 0 0 \
|
|
value pinned "$bpf_dir/$inner_map_name"
|
|
"$bpftool_path" map update name "$outer_map_name" key 1 0 0 0 value \
|
|
name "$inner_map_name"
|
|
|
|
# The outer map should be full by now.
|
|
# The following map update command is expected to fail.
|
|
if "$bpftool_path" map update name "$outer_map_name" key 2 0 0 0 value name \
|
|
"$inner_map_name" 2>/dev/null; then
|
|
echo " Update for $outer_map_name succeeded but should have failed"
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
# Function to test map access with the btf list command
|
|
# Parameters:
|
|
# $1: bpftool path
|
|
test_map_access_with_btf_list() {
|
|
local bpftool_path="$1"
|
|
|
|
# The btf list command iterates over maps for
|
|
# loaded BPF programs.
|
|
if ! "$bpftool_path" btf list 1>/dev/null; then
|
|
echo " Failed to access btf data"
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
set -eu
|
|
|
|
trap cleanup_skip EXIT
|
|
|
|
check_root_privileges
|
|
|
|
verify_bpftool_path "$BPFTOOL_PATH"
|
|
|
|
verify_btf_support
|
|
|
|
trap cleanup EXIT
|
|
|
|
# Load and attach the BPF programs to control maps access.
|
|
"$BPFTOOL_PATH" prog loadall "$BPF_FILE_PATH" "$BPF_DIR" autoattach
|
|
|
|
initialize_map_entries "$PROTECTED_MAP_NAME" "$BPFTOOL_PATH"
|
|
initialize_map_entries "$NOT_PROTECTED_MAP_NAME" "$BPFTOOL_PATH"
|
|
|
|
# Activate the map protection mechanism. Protection status is controlled
|
|
# by a value stored in the prot_status_map at index 0.
|
|
"$BPFTOOL_PATH" map update name prot_status_map key 0 0 0 0 value 1 0 0 0
|
|
|
|
# Test protected map (write should fail).
|
|
test_map_access "$PROTECTED_MAP_NAME" "$BPFTOOL_PATH" "$BPF_DIR" "false" \
|
|
"$BPF_ITER_FILE_PATH"
|
|
|
|
# Test not protected map (write should succeed).
|
|
test_map_access "$NOT_PROTECTED_MAP_NAME" "$BPFTOOL_PATH" "$BPF_DIR" "true" \
|
|
"$BPF_ITER_FILE_PATH"
|
|
|
|
test_map_creation_and_map_of_maps "$BPFTOOL_PATH" "$BPF_DIR"
|
|
|
|
test_map_access_with_btf_list "$BPFTOOL_PATH"
|
|
|
|
exit 0
|