Commit e5a9df51 authored by David Vernet's avatar David Vernet Committed by Andrii Nakryiko

selftests/bpf: Add selftests validating the user ringbuf

This change includes selftests that validate the expected behavior and
APIs of the new BPF_MAP_TYPE_USER_RINGBUF map type.
Signed-off-by: default avatarDavid Vernet <void@manifault.com>
Signed-off-by: default avatarAndrii Nakryiko <andrii@kernel.org>
Link: https://lore.kernel.org/bpf/20220920000100.477320-5-void@manifault.com
parent b66ccae0
......@@ -71,3 +71,4 @@ cb_refs # expected error message unexpected err
cgroup_hierarchical_stats # JIT does not support calling kernel function (kfunc)
htab_update # failed to attach: ERROR: strerror_r(-524)=22 (trampoline)
tracing_struct # failed to auto-attach: -524 (trampoline)
user_ringbuf # failed to find kernel BTF type ID of '__s390x_sys_prctl': -3 (?)
This diff is collapsed.
/* SPDX-License-Identifier: GPL-2.0 */
/* Copyright (c) 2022 Meta Platforms, Inc. and affiliates. */
#ifndef _TEST_USER_RINGBUF_H
#define _TEST_USER_RINGBUF_H
#define TEST_OP_64 4
#define TEST_OP_32 2
enum test_msg_op {
TEST_MSG_OP_INC64,
TEST_MSG_OP_INC32,
TEST_MSG_OP_MUL64,
TEST_MSG_OP_MUL32,
// Must come last.
TEST_MSG_OP_NUM_OPS,
};
struct test_msg {
enum test_msg_op msg_op;
union {
__s64 operand_64;
__s32 operand_32;
};
};
struct sample {
int pid;
int seq;
long value;
char comm[16];
};
#endif /* _TEST_USER_RINGBUF_H */
// SPDX-License-Identifier: GPL-2.0
/* Copyright (c) 2022 Meta Platforms, Inc. and affiliates. */
#include <linux/bpf.h>
#include <bpf/bpf_helpers.h>
#include "bpf_misc.h"
char _license[] SEC("license") = "GPL";
struct sample {
int pid;
int seq;
long value;
char comm[16];
};
struct {
__uint(type, BPF_MAP_TYPE_USER_RINGBUF);
} user_ringbuf SEC(".maps");
static long
bad_access1(struct bpf_dynptr *dynptr, void *context)
{
const struct sample *sample;
sample = bpf_dynptr_data(dynptr - 1, 0, sizeof(*sample));
bpf_printk("Was able to pass bad pointer %lx\n", (__u64)dynptr - 1);
return 0;
}
/* A callback that accesses a dynptr in a bpf_user_ringbuf_drain callback should
* not be able to read before the pointer.
*/
SEC("?raw_tp/sys_nanosleep")
int user_ringbuf_callback_bad_access1(void *ctx)
{
bpf_user_ringbuf_drain(&user_ringbuf, bad_access1, NULL, 0);
return 0;
}
static long
bad_access2(struct bpf_dynptr *dynptr, void *context)
{
const struct sample *sample;
sample = bpf_dynptr_data(dynptr + 1, 0, sizeof(*sample));
bpf_printk("Was able to pass bad pointer %lx\n", (__u64)dynptr + 1);
return 0;
}
/* A callback that accesses a dynptr in a bpf_user_ringbuf_drain callback should
* not be able to read past the end of the pointer.
*/
SEC("?raw_tp/sys_nanosleep")
int user_ringbuf_callback_bad_access2(void *ctx)
{
bpf_user_ringbuf_drain(&user_ringbuf, bad_access2, NULL, 0);
return 0;
}
static long
write_forbidden(struct bpf_dynptr *dynptr, void *context)
{
*((long *)dynptr) = 0;
return 0;
}
/* A callback that accesses a dynptr in a bpf_user_ringbuf_drain callback should
* not be able to write to that pointer.
*/
SEC("?raw_tp/sys_nanosleep")
int user_ringbuf_callback_write_forbidden(void *ctx)
{
bpf_user_ringbuf_drain(&user_ringbuf, write_forbidden, NULL, 0);
return 0;
}
static long
null_context_write(struct bpf_dynptr *dynptr, void *context)
{
*((__u64 *)context) = 0;
return 0;
}
/* A callback that accesses a dynptr in a bpf_user_ringbuf_drain callback should
* not be able to write to that pointer.
*/
SEC("?raw_tp/sys_nanosleep")
int user_ringbuf_callback_null_context_write(void *ctx)
{
bpf_user_ringbuf_drain(&user_ringbuf, null_context_write, NULL, 0);
return 0;
}
static long
null_context_read(struct bpf_dynptr *dynptr, void *context)
{
__u64 id = *((__u64 *)context);
bpf_printk("Read id %lu\n", id);
return 0;
}
/* A callback that accesses a dynptr in a bpf_user_ringbuf_drain callback should
* not be able to write to that pointer.
*/
SEC("?raw_tp/sys_nanosleep")
int user_ringbuf_callback_null_context_read(void *ctx)
{
bpf_user_ringbuf_drain(&user_ringbuf, null_context_read, NULL, 0);
return 0;
}
static long
try_discard_dynptr(struct bpf_dynptr *dynptr, void *context)
{
bpf_ringbuf_discard_dynptr(dynptr, 0);
return 0;
}
/* A callback that accesses a dynptr in a bpf_user_ringbuf_drain callback should
* not be able to read past the end of the pointer.
*/
SEC("?raw_tp/sys_nanosleep")
int user_ringbuf_callback_discard_dynptr(void *ctx)
{
bpf_user_ringbuf_drain(&user_ringbuf, try_discard_dynptr, NULL, 0);
return 0;
}
static long
try_submit_dynptr(struct bpf_dynptr *dynptr, void *context)
{
bpf_ringbuf_submit_dynptr(dynptr, 0);
return 0;
}
/* A callback that accesses a dynptr in a bpf_user_ringbuf_drain callback should
* not be able to read past the end of the pointer.
*/
SEC("?raw_tp/sys_nanosleep")
int user_ringbuf_callback_submit_dynptr(void *ctx)
{
bpf_user_ringbuf_drain(&user_ringbuf, try_submit_dynptr, NULL, 0);
return 0;
}
static long
invalid_drain_callback_return(struct bpf_dynptr *dynptr, void *context)
{
return 2;
}
/* A callback that accesses a dynptr in a bpf_user_ringbuf_drain callback should
* not be able to write to that pointer.
*/
SEC("?raw_tp/sys_nanosleep")
int user_ringbuf_callback_invalid_return(void *ctx)
{
bpf_user_ringbuf_drain(&user_ringbuf, invalid_drain_callback_return, NULL, 0);
return 0;
}
// SPDX-License-Identifier: GPL-2.0
/* Copyright (c) 2022 Meta Platforms, Inc. and affiliates. */
#include <linux/bpf.h>
#include <bpf/bpf_helpers.h>
#include "bpf_misc.h"
#include "test_user_ringbuf.h"
char _license[] SEC("license") = "GPL";
struct {
__uint(type, BPF_MAP_TYPE_USER_RINGBUF);
} user_ringbuf SEC(".maps");
struct {
__uint(type, BPF_MAP_TYPE_RINGBUF);
} kernel_ringbuf SEC(".maps");
/* inputs */
int pid, err, val;
int read = 0;
/* Counter used for end-to-end protocol test */
__u64 kern_mutated = 0;
__u64 user_mutated = 0;
__u64 expected_user_mutated = 0;
static int
is_test_process(void)
{
int cur_pid = bpf_get_current_pid_tgid() >> 32;
return cur_pid == pid;
}
static long
record_sample(struct bpf_dynptr *dynptr, void *context)
{
const struct sample *sample = NULL;
struct sample stack_sample;
int status;
static int num_calls;
if (num_calls++ % 2 == 0) {
status = bpf_dynptr_read(&stack_sample, sizeof(stack_sample), dynptr, 0, 0);
if (status) {
bpf_printk("bpf_dynptr_read() failed: %d\n", status);
err = 1;
return 0;
}
} else {
sample = bpf_dynptr_data(dynptr, 0, sizeof(*sample));
if (!sample) {
bpf_printk("Unexpectedly failed to get sample\n");
err = 2;
return 0;
}
stack_sample = *sample;
}
__sync_fetch_and_add(&read, 1);
return 0;
}
static void
handle_sample_msg(const struct test_msg *msg)
{
switch (msg->msg_op) {
case TEST_MSG_OP_INC64:
kern_mutated += msg->operand_64;
break;
case TEST_MSG_OP_INC32:
kern_mutated += msg->operand_32;
break;
case TEST_MSG_OP_MUL64:
kern_mutated *= msg->operand_64;
break;
case TEST_MSG_OP_MUL32:
kern_mutated *= msg->operand_32;
break;
default:
bpf_printk("Unrecognized op %d\n", msg->msg_op);
err = 2;
}
}
static long
read_protocol_msg(struct bpf_dynptr *dynptr, void *context)
{
const struct test_msg *msg = NULL;
msg = bpf_dynptr_data(dynptr, 0, sizeof(*msg));
if (!msg) {
err = 1;
bpf_printk("Unexpectedly failed to get msg\n");
return 0;
}
handle_sample_msg(msg);
return 0;
}
static int publish_next_kern_msg(__u32 index, void *context)
{
struct test_msg *msg = NULL;
int operand_64 = TEST_OP_64;
int operand_32 = TEST_OP_32;
msg = bpf_ringbuf_reserve(&kernel_ringbuf, sizeof(*msg), 0);
if (!msg) {
err = 4;
return 1;
}
switch (index % TEST_MSG_OP_NUM_OPS) {
case TEST_MSG_OP_INC64:
msg->operand_64 = operand_64;
msg->msg_op = TEST_MSG_OP_INC64;
expected_user_mutated += operand_64;
break;
case TEST_MSG_OP_INC32:
msg->operand_32 = operand_32;
msg->msg_op = TEST_MSG_OP_INC32;
expected_user_mutated += operand_32;
break;
case TEST_MSG_OP_MUL64:
msg->operand_64 = operand_64;
msg->msg_op = TEST_MSG_OP_MUL64;
expected_user_mutated *= operand_64;
break;
case TEST_MSG_OP_MUL32:
msg->operand_32 = operand_32;
msg->msg_op = TEST_MSG_OP_MUL32;
expected_user_mutated *= operand_32;
break;
default:
bpf_ringbuf_discard(msg, 0);
err = 5;
return 1;
}
bpf_ringbuf_submit(msg, 0);
return 0;
}
static void
publish_kern_messages(void)
{
if (expected_user_mutated != user_mutated) {
bpf_printk("%lu != %lu\n", expected_user_mutated, user_mutated);
err = 3;
return;
}
bpf_loop(8, publish_next_kern_msg, NULL, 0);
}
SEC("fentry/" SYS_PREFIX "sys_prctl")
int test_user_ringbuf_protocol(void *ctx)
{
long status = 0;
struct sample *sample = NULL;
struct bpf_dynptr ptr;
if (!is_test_process())
return 0;
status = bpf_user_ringbuf_drain(&user_ringbuf, read_protocol_msg, NULL, 0);
if (status < 0) {
bpf_printk("Drain returned: %ld\n", status);
err = 1;
return 0;
}
publish_kern_messages();
return 0;
}
SEC("fentry/" SYS_PREFIX "sys_getpgid")
int test_user_ringbuf(void *ctx)
{
int status = 0;
struct sample *sample = NULL;
struct bpf_dynptr ptr;
if (!is_test_process())
return 0;
err = bpf_user_ringbuf_drain(&user_ringbuf, record_sample, NULL, 0);
return 0;
}
static long
do_nothing_cb(struct bpf_dynptr *dynptr, void *context)
{
__sync_fetch_and_add(&read, 1);
return 0;
}
SEC("fentry/" SYS_PREFIX "sys_getrlimit")
int test_user_ringbuf_epoll(void *ctx)
{
long num_samples;
if (!is_test_process())
return 0;
num_samples = bpf_user_ringbuf_drain(&user_ringbuf, do_nothing_cb, NULL, 0);
if (num_samples <= 0)
err = 1;
return 0;
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment