1#!/bin/sh 2# SPDX-License-Identifier: GPL-2.0 3 4# Kselftest framework requirement - SKIP code is 4. 5ksft_skip=4 6 7TESTNAME="bpftool_map" 8BPF_FILE="security_bpf_map.bpf.o" 9BPF_ITER_FILE="bpf_iter_map_elem.bpf.o" 10PROTECTED_MAP_NAME="prot_map" 11NOT_PROTECTED_MAP_NAME="not_prot_map" 12BPF_FS_TMP_PARENT="/tmp" 13BPF_FS_PARENT=$(awk '$3 == "bpf" {print $2; exit}' /proc/mounts) 14BPF_FS_PARENT=${BPF_FS_PARENT:-$BPF_FS_TMP_PARENT} 15# bpftool will mount bpf file system under BPF_DIR if it is not mounted 16# under BPF_FS_PARENT. 17BPF_DIR="$BPF_FS_PARENT/test_$TESTNAME" 18SCRIPT_DIR=$(dirname $(realpath "$0")) 19BPF_FILE_PATH="$SCRIPT_DIR/$BPF_FILE" 20BPF_ITER_FILE_PATH="$SCRIPT_DIR/$BPF_ITER_FILE" 21BPFTOOL_PATH="bpftool" 22# Assume the script is located under tools/testing/selftests/bpf/ 23KDIR_ROOT_DIR=$(realpath "$SCRIPT_DIR"/../../../../) 24 25_cleanup() 26{ 27 set +eu 28 29 # If BPF_DIR is a mount point this will not remove the mount point itself. 30 [ -d "$BPF_DIR" ] && rm -rf "$BPF_DIR" 2> /dev/null 31 32 # Unmount if BPF filesystem was temporarily created. 33 if [ "$BPF_FS_PARENT" = "$BPF_FS_TMP_PARENT" ]; then 34 # A loop and recursive unmount are required as bpftool might 35 # create multiple mounts. For example, a bind mount of the directory 36 # to itself. The bind mount is created to change mount propagation 37 # flags on an actual mount point. 38 max_attempts=3 39 attempt=0 40 while mountpoint -q "$BPF_DIR" && [ $attempt -lt $max_attempts ]; do 41 umount -R "$BPF_DIR" 2>/dev/null 42 attempt=$((attempt+1)) 43 done 44 45 # The directory still exists. Remove it now. 46 [ -d "$BPF_DIR" ] && rm -rf "$BPF_DIR" 2>/dev/null 47 fi 48} 49 50cleanup_skip() 51{ 52 echo "selftests: $TESTNAME [SKIP]" 53 _cleanup 54 55 exit $ksft_skip 56} 57 58cleanup() 59{ 60 if [ "$?" = 0 ]; then 61 echo "selftests: $TESTNAME [PASS]" 62 else 63 echo "selftests: $TESTNAME [FAILED]" 64 fi 65 _cleanup 66} 67 68check_root_privileges() { 69 if [ $(id -u) -ne 0 ]; then 70 echo "Need root privileges" 71 exit $ksft_skip 72 fi 73} 74 75# Function to verify bpftool path. 76# Parameters: 77# $1: bpftool path 78verify_bpftool_path() { 79 local bpftool_path="$1" 80 if ! "$bpftool_path" version > /dev/null 2>&1; then 81 echo "Could not run test without bpftool" 82 exit $ksft_skip 83 fi 84} 85 86# Function to verify BTF support. 87# The test requires BTF support for fmod_ret programs. 88verify_btf_support() { 89 if [ ! -f /sys/kernel/btf/vmlinux ]; then 90 echo "Could not run test without BTF support" 91 exit $ksft_skip 92 fi 93} 94 95# Function to initialize map entries with keys [0..2] and values set to 0. 96# Parameters: 97# $1: Map name 98# $2: bpftool path 99initialize_map_entries() { 100 local map_name="$1" 101 local bpftool_path="$2" 102 103 for key in 0 1 2; do 104 "$bpftool_path" map update name "$map_name" key $key 0 0 0 value 0 0 0 $key 105 done 106} 107 108# Test read access to the map. 109# Parameters: 110# $1: Name command (name/pinned) 111# $2: Map name 112# $3: bpftool path 113# $4: key 114access_for_read() { 115 local name_cmd="$1" 116 local map_name="$2" 117 local bpftool_path="$3" 118 local key="$4" 119 120 # Test read access to the map. 121 if ! "$bpftool_path" map lookup "$name_cmd" "$map_name" key $key 1>/dev/null; then 122 echo " Read access to $key in $map_name failed" 123 exit 1 124 fi 125 126 # Test read access to map's BTF data. 127 if ! "$bpftool_path" btf dump map "$name_cmd" "$map_name" 1>/dev/null; then 128 echo " Read access to $map_name for BTF data failed" 129 exit 1 130 fi 131} 132 133# Test write access to the map. 134# Parameters: 135# $1: Name command (name/pinned) 136# $2: Map name 137# $3: bpftool path 138# $4: key 139# $5: Whether write should succeed (true/false) 140access_for_write() { 141 local name_cmd="$1" 142 local map_name="$2" 143 local bpftool_path="$3" 144 local key="$4" 145 local write_should_succeed="$5" 146 local value="1 1 1 1" 147 148 if "$bpftool_path" map update "$name_cmd" "$map_name" key $key value \ 149 $value 2>/dev/null; then 150 if [ "$write_should_succeed" = "false" ]; then 151 echo " Write access to $key in $map_name succeeded but should have failed" 152 exit 1 153 fi 154 else 155 if [ "$write_should_succeed" = "true" ]; then 156 echo " Write access to $key in $map_name failed but should have succeeded" 157 exit 1 158 fi 159 fi 160} 161 162# Test entry deletion for the map. 163# Parameters: 164# $1: Name command (name/pinned) 165# $2: Map name 166# $3: bpftool path 167# $4: key 168# $5: Whether write should succeed (true/false) 169access_for_deletion() { 170 local name_cmd="$1" 171 local map_name="$2" 172 local bpftool_path="$3" 173 local key="$4" 174 local write_should_succeed="$5" 175 local value="1 1 1 1" 176 177 # Test deletion by key for the map. 178 # Before deleting, check the key exists. 179 if ! "$bpftool_path" map lookup "$name_cmd" "$map_name" key $key 1>/dev/null; then 180 echo " Key $key does not exist in $map_name" 181 exit 1 182 fi 183 184 # Delete by key. 185 if "$bpftool_path" map delete "$name_cmd" "$map_name" key $key 2>/dev/null; then 186 if [ "$write_should_succeed" = "false" ]; then 187 echo " Deletion for $key in $map_name succeeded but should have failed" 188 exit 1 189 fi 190 else 191 if [ "$write_should_succeed" = "true" ]; then 192 echo " Deletion for $key in $map_name failed but should have succeeded" 193 exit 1 194 fi 195 fi 196 197 # After deleting, check the entry existence according to the expected status. 198 if "$bpftool_path" map lookup "$name_cmd" "$map_name" key $key 1>/dev/null; then 199 if [ "$write_should_succeed" = "true" ]; then 200 echo " Key $key for $map_name was not deleted but should have been deleted" 201 exit 1 202 fi 203 else 204 if [ "$write_should_succeed" = "false" ]; then 205 echo "Key $key for $map_name was deleted but should have not been deleted" 206 exit 1 207 fi 208 fi 209 210 # Test creation of map's deleted entry, if deletion was successful. 211 # Otherwise, the entry exists. 212 if "$bpftool_path" map update "$name_cmd" "$map_name" key $key value \ 213 $value 2>/dev/null; then 214 if [ "$write_should_succeed" = "false" ]; then 215 echo " Write access to $key in $map_name succeeded after deletion attempt but should have failed" 216 exit 1 217 fi 218 else 219 if [ "$write_should_succeed" = "true" ]; then 220 echo " Write access to $key in $map_name failed after deletion attempt but should have succeeded" 221 exit 1 222 fi 223 fi 224} 225 226# Test map elements iterator. 227# Parameters: 228# $1: Name command (name/pinned) 229# $2: Map name 230# $3: bpftool path 231# $4: BPF_DIR 232# $5: bpf iterator object file path 233iterate_map_elem() { 234 local name_cmd="$1" 235 local map_name="$2" 236 local bpftool_path="$3" 237 local bpf_dir="$4" 238 local bpf_file="$5" 239 local pin_path="$bpf_dir/map_iterator" 240 241 "$bpftool_path" iter pin "$bpf_file" "$pin_path" map "$name_cmd" "$map_name" 242 if [ ! -f "$pin_path" ]; then 243 echo " Failed to pin iterator to $pin_path" 244 exit 1 245 fi 246 247 cat "$pin_path" 1>/dev/null 248 rm "$pin_path" 2>/dev/null 249} 250 251# Function to test map access with configurable write expectations 252# Parameters: 253# $1: Name command (name/pinned) 254# $2: Map name 255# $3: bpftool path 256# $4: key for rw 257# $5: key to delete 258# $6: Whether write should succeed (true/false) 259# $7: BPF_DIR 260# $8: bpf iterator object file path 261access_map() { 262 local name_cmd="$1" 263 local map_name="$2" 264 local bpftool_path="$3" 265 local key_for_rw="$4" 266 local key_to_del="$5" 267 local write_should_succeed="$6" 268 local bpf_dir="$7" 269 local bpf_iter_file_path="$8" 270 271 access_for_read "$name_cmd" "$map_name" "$bpftool_path" "$key_for_rw" 272 access_for_write "$name_cmd" "$map_name" "$bpftool_path" "$key_for_rw" \ 273 "$write_should_succeed" 274 access_for_deletion "$name_cmd" "$map_name" "$bpftool_path" "$key_to_del" \ 275 "$write_should_succeed" 276 iterate_map_elem "$name_cmd" "$map_name" "$bpftool_path" "$bpf_dir" \ 277 "$bpf_iter_file_path" 278} 279 280# Function to test map access with configurable write expectations 281# Parameters: 282# $1: Map name 283# $2: bpftool path 284# $3: BPF_DIR 285# $4: Whether write should succeed (true/false) 286# $5: bpf iterator object file path 287test_map_access() { 288 local map_name="$1" 289 local bpftool_path="$2" 290 local bpf_dir="$3" 291 local pin_path="$bpf_dir/${map_name}_pinned" 292 local write_should_succeed="$4" 293 local bpf_iter_file_path="$5" 294 295 # Test access to the map by name. 296 access_map "name" "$map_name" "$bpftool_path" "0 0 0 0" "1 0 0 0" \ 297 "$write_should_succeed" "$bpf_dir" "$bpf_iter_file_path" 298 299 # Pin the map to the BPF filesystem 300 "$bpftool_path" map pin name "$map_name" "$pin_path" 301 if [ ! -e "$pin_path" ]; then 302 echo " Failed to pin $map_name" 303 exit 1 304 fi 305 306 # Test access to the pinned map. 307 access_map "pinned" "$pin_path" "$bpftool_path" "0 0 0 0" "2 0 0 0" \ 308 "$write_should_succeed" "$bpf_dir" "$bpf_iter_file_path" 309} 310 311# Function to test map creation and map-of-maps 312# Parameters: 313# $1: bpftool path 314# $2: BPF_DIR 315test_map_creation_and_map_of_maps() { 316 local bpftool_path="$1" 317 local bpf_dir="$2" 318 local outer_map_name="outer_map_tt" 319 local inner_map_name="inner_map_tt" 320 321 "$bpftool_path" map create "$bpf_dir/$inner_map_name" type array key 4 \ 322 value 4 entries 4 name "$inner_map_name" 323 if [ ! -f "$bpf_dir/$inner_map_name" ]; then 324 echo " Failed to create inner map file at $bpf_dir/$outer_map_name" 325 return 1 326 fi 327 328 "$bpftool_path" map create "$bpf_dir/$outer_map_name" type hash_of_maps \ 329 key 4 value 4 entries 2 name "$outer_map_name" inner_map name "$inner_map_name" 330 if [ ! -f "$bpf_dir/$outer_map_name" ]; then 331 echo " Failed to create outer map file at $bpf_dir/$outer_map_name" 332 return 1 333 fi 334 335 # Add entries to the outer map by name and by pinned path. 336 "$bpftool_path" map update pinned "$bpf_dir/$outer_map_name" key 0 0 0 0 \ 337 value pinned "$bpf_dir/$inner_map_name" 338 "$bpftool_path" map update name "$outer_map_name" key 1 0 0 0 value \ 339 name "$inner_map_name" 340 341 # The outer map should be full by now. 342 # The following map update command is expected to fail. 343 if "$bpftool_path" map update name "$outer_map_name" key 2 0 0 0 value name \ 344 "$inner_map_name" 2>/dev/null; then 345 echo " Update for $outer_map_name succeeded but should have failed" 346 exit 1 347 fi 348} 349 350# Function to test map access with the btf list command 351# Parameters: 352# $1: bpftool path 353test_map_access_with_btf_list() { 354 local bpftool_path="$1" 355 356 # The btf list command iterates over maps for 357 # loaded BPF programs. 358 if ! "$bpftool_path" btf list 1>/dev/null; then 359 echo " Failed to access btf data" 360 exit 1 361 fi 362} 363 364set -eu 365 366trap cleanup_skip EXIT 367 368check_root_privileges 369 370verify_bpftool_path "$BPFTOOL_PATH" 371 372verify_btf_support 373 374trap cleanup EXIT 375 376# Load and attach the BPF programs to control maps access. 377"$BPFTOOL_PATH" prog loadall "$BPF_FILE_PATH" "$BPF_DIR" autoattach 378 379initialize_map_entries "$PROTECTED_MAP_NAME" "$BPFTOOL_PATH" 380initialize_map_entries "$NOT_PROTECTED_MAP_NAME" "$BPFTOOL_PATH" 381 382# Activate the map protection mechanism. Protection status is controlled 383# by a value stored in the prot_status_map at index 0. 384"$BPFTOOL_PATH" map update name prot_status_map key 0 0 0 0 value 1 0 0 0 385 386# Test protected map (write should fail). 387test_map_access "$PROTECTED_MAP_NAME" "$BPFTOOL_PATH" "$BPF_DIR" "false" \ 388 "$BPF_ITER_FILE_PATH" 389 390# Test not protected map (write should succeed). 391test_map_access "$NOT_PROTECTED_MAP_NAME" "$BPFTOOL_PATH" "$BPF_DIR" "true" \ 392 "$BPF_ITER_FILE_PATH" 393 394test_map_creation_and_map_of_maps "$BPFTOOL_PATH" "$BPF_DIR" 395 396test_map_access_with_btf_list "$BPFTOOL_PATH" 397 398exit 0 399