Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit ba72a7b4 authored by Alexei Starovoitov's avatar Alexei Starovoitov Committed by Daniel Borkmann
Browse files

selftests/bpf: test for BPF_F_LOCK



Add C based test that runs 4 bpf programs in parallel
that update the same hash and array maps.
And another 2 threads that read from these two maps
via lookup(key, value, BPF_F_LOCK) api
to make sure the user space sees consistent value in both
hash and array elements while user space races with kernel bpf progs.

Signed-off-by: default avatarAlexei Starovoitov <ast@kernel.org>
Signed-off-by: default avatarDaniel Borkmann <daniel@iogearbox.net>
parent df5d22fa
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -35,7 +35,7 @@ BPF_OBJ_FILES = \
	sendmsg4_prog.o sendmsg6_prog.o test_lirc_mode2_kern.o \
	get_cgroup_id_kern.o socket_cookie_prog.o test_select_reuseport_kern.o \
	test_skb_cgroup_id_kern.o bpf_flow.o netcnt_prog.o test_xdp_vlan.o \
	xdp_dummy.o test_map_in_map.o test_spin_lock.o
	xdp_dummy.o test_map_in_map.o test_spin_lock.o test_map_lock.o

# Objects are built with default compilation flags and with sub-register
# code-gen enabled.
+66 −0
Original line number Diff line number Diff line
// SPDX-License-Identifier: GPL-2.0
// Copyright (c) 2019 Facebook
#include <linux/bpf.h>
#include <linux/version.h>
#include "bpf_helpers.h"

#define VAR_NUM 16

struct hmap_elem {
	struct bpf_spin_lock lock;
	int var[VAR_NUM];
};

struct bpf_map_def SEC("maps") hash_map = {
	.type = BPF_MAP_TYPE_HASH,
	.key_size = sizeof(int),
	.value_size = sizeof(struct hmap_elem),
	.max_entries = 1,
};

BPF_ANNOTATE_KV_PAIR(hash_map, int, struct hmap_elem);

struct array_elem {
	struct bpf_spin_lock lock;
	int var[VAR_NUM];
};

struct bpf_map_def SEC("maps") array_map = {
	.type = BPF_MAP_TYPE_ARRAY,
	.key_size = sizeof(int),
	.value_size = sizeof(struct array_elem),
	.max_entries = 1,
};

BPF_ANNOTATE_KV_PAIR(array_map, int, struct array_elem);

SEC("map_lock_demo")
int bpf_map_lock_test(struct __sk_buff *skb)
{
	struct hmap_elem zero = {}, *val;
	int rnd = bpf_get_prandom_u32();
	int key = 0, err = 1, i;
	struct array_elem *q;

	val = bpf_map_lookup_elem(&hash_map, &key);
	if (!val)
		goto err;
	/* spin_lock in hash map */
	bpf_spin_lock(&val->lock);
	for (i = 0; i < VAR_NUM; i++)
		val->var[i] = rnd;
	bpf_spin_unlock(&val->lock);

	/* spin_lock in array */
	q = bpf_map_lookup_elem(&array_map, &key);
	if (!q)
		goto err;
	bpf_spin_lock(&q->lock);
	for (i = 0; i < VAR_NUM; i++)
		q->var[i] = rnd;
	bpf_spin_unlock(&q->lock);
	err = 0;
err:
	return err;
}
char _license[] SEC("license") = "GPL";
+74 −0
Original line number Diff line number Diff line
@@ -2025,6 +2025,79 @@ static void test_spinlock(void)
	bpf_object__close(obj);
}

static void *parallel_map_access(void *arg)
{
	int err, map_fd = *(u32 *) arg;
	int vars[17], i, j, rnd, key = 0;

	for (i = 0; i < 10000; i++) {
		err = bpf_map_lookup_elem_flags(map_fd, &key, vars, BPF_F_LOCK);
		if (err) {
			printf("lookup failed\n");
			error_cnt++;
			goto out;
		}
		if (vars[0] != 0) {
			printf("lookup #%d var[0]=%d\n", i, vars[0]);
			error_cnt++;
			goto out;
		}
		rnd = vars[1];
		for (j = 2; j < 17; j++) {
			if (vars[j] == rnd)
				continue;
			printf("lookup #%d var[1]=%d var[%d]=%d\n",
			       i, rnd, j, vars[j]);
			error_cnt++;
			goto out;
		}
	}
out:
	pthread_exit(arg);
}

static void test_map_lock(void)
{
	const char *file = "./test_map_lock.o";
	int prog_fd, map_fd[2], vars[17] = {};
	pthread_t thread_id[6];
	struct bpf_object *obj;
	int err = 0, key = 0, i;
	void *ret;

	err = bpf_prog_load(file, BPF_PROG_TYPE_CGROUP_SKB, &obj, &prog_fd);
	if (err) {
		printf("test_map_lock:bpf_prog_load errno %d\n", errno);
		goto close_prog;
	}
	map_fd[0] = bpf_find_map(__func__, obj, "hash_map");
	if (map_fd[0] < 0)
		goto close_prog;
	map_fd[1] = bpf_find_map(__func__, obj, "array_map");
	if (map_fd[1] < 0)
		goto close_prog;

	bpf_map_update_elem(map_fd[0], &key, vars, BPF_F_LOCK);

	for (i = 0; i < 4; i++)
		assert(pthread_create(&thread_id[i], NULL,
				      &test_spin_lock, &prog_fd) == 0);
	for (i = 4; i < 6; i++)
		assert(pthread_create(&thread_id[i], NULL,
				      &parallel_map_access, &map_fd[i - 4]) == 0);
	for (i = 0; i < 4; i++)
		assert(pthread_join(thread_id[i], &ret) == 0 &&
		       ret == (void *)&prog_fd);
	for (i = 4; i < 6; i++)
		assert(pthread_join(thread_id[i], &ret) == 0 &&
		       ret == (void *)&map_fd[i - 4]);
	goto close_prog_noerr;
close_prog:
	error_cnt++;
close_prog_noerr:
	bpf_object__close(obj);
}

int main(void)
{
	srand(time(NULL));
@@ -2054,6 +2127,7 @@ int main(void)
	test_queue_stack_map(STACK);
	test_flow_dissector();
	test_spinlock();
	test_map_lock();

	printf("Summary: %d PASSED, %d FAILED\n", pass_cnt, error_cnt);
	return error_cnt ? EXIT_FAILURE : EXIT_SUCCESS;