xref: /linux/tools/testing/selftests/bpf/test_bpftool_map.sh (revision d9104cec3e8fe4b458b74709853231385779001f)
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