Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 34 additions & 20 deletions kernel/bpf/stream.c
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,11 @@ static struct bpf_stream_elem *bpf_stream_elem_alloc(int len)
size_t alloc_size;

/*
* Length denotes the amount of data to be written as part of stream element,
* thus includes '\0' byte. We're capped by how much bpf_bprintf_buffers can
* accomodate, therefore deny allocations that won't fit into them.
* Length is the payload pushed into the stream, excluding the
* trailing NUL of the bprintf buffer. Reject anything that cannot
* fit without copying that NUL into the stream element.
*/
if (len < 0 || len > max_len)
if (len < 0 || len >= max_len)
return NULL;

alloc_size = offsetof(struct bpf_stream_elem, str[len]);
Expand Down Expand Up @@ -68,18 +68,23 @@ static int bpf_stream_consume_capacity(struct bpf_stream *stream, int len)
return 0;
}

static void bpf_stream_release_capacity(struct bpf_stream *stream, struct bpf_stream_elem *elem)
static void bpf_stream_release_capacity(struct bpf_stream *stream, int len)
{
int len = elem->total_len;

atomic_sub(len, &stream->capacity);
}

static int bpf_stream_push_str(struct bpf_stream *stream, const char *str, int len)
{
int ret = bpf_stream_consume_capacity(stream, len);

return ret ?: __bpf_stream_push_str(&stream->log, str, len);
if (ret)
return ret;

ret = __bpf_stream_push_str(&stream->log, str, len);
if (ret)
bpf_stream_release_capacity(stream, len);

return ret;
}

static struct bpf_stream *bpf_stream_get(enum bpf_stream_id stream_id, struct bpf_prog_aux *aux)
Expand Down Expand Up @@ -162,6 +167,7 @@ static int bpf_stream_read(struct bpf_stream *stream, void __user *buf, int len)

while (rem_len) {
int pos = len - rem_len;
int chunk, n;
bool cont;

node = bpf_stream_backlog_peek(stream);
Expand All @@ -175,20 +181,21 @@ static int bpf_stream_read(struct bpf_stream *stream, void __user *buf, int len)

cons_len = elem->consumed_len;
cont = bpf_stream_consume_elem(elem, &rem_len) == false;

ret = copy_to_user(buf + pos, elem->str + cons_len,
elem->consumed_len - cons_len);
/* Restore in case of error. */
if (ret) {
ret = -EFAULT;
elem->consumed_len = cons_len;
chunk = elem->consumed_len - cons_len;

n = copy_to_user(buf + pos, elem->str + cons_len, chunk);
if (n) {
/* Keep any successfully copied bytes; -EFAULT only if none. */
elem->consumed_len -= n;
rem_len += n;
ret = (len == rem_len) ? -EFAULT : 0;
break;
}

if (cont)
continue;
bpf_stream_backlog_pop(stream);
bpf_stream_release_capacity(stream, elem);
bpf_stream_release_capacity(stream, elem->total_len);
bpf_stream_free_elem(elem);
}

Expand Down Expand Up @@ -238,6 +245,11 @@ __bpf_kfunc int bpf_stream_vprintk(int stream_id, const char *fmt__str, const vo
return ret;

ret = bstr_printf(data.buf, MAX_BPRINTF_BUF, fmt__str, data.bin_args);
/* Truncation: reject before capacity charge (not -ENOMEM). */
if (ret >= MAX_BPRINTF_BUF) {
bpf_bprintf_cleanup(&data);
return -E2BIG;
}
/* Exclude NULL byte during push. */
ret = bpf_stream_push_str(stream, data.buf, ret);
bpf_bprintf_cleanup(&data);
Expand Down Expand Up @@ -311,17 +323,19 @@ int bpf_stream_stage_printk(struct bpf_stream_stage *ss, const char *fmt, ...)
{
struct bpf_bprintf_buffers *buf;
va_list args;
int ret;
int len, ret;

if (bpf_try_get_buffers(&buf))
return -EBUSY;

va_start(args, fmt);
ret = vsnprintf(buf->buf, ARRAY_SIZE(buf->buf), fmt, args);
/* Cap to the written length, excluding the trailing NUL. */
len = vscnprintf(buf->buf, ARRAY_SIZE(buf->buf), fmt, args);
va_end(args);
ss->len += ret;
/* Exclude NULL byte during push. */
ret = __bpf_stream_push_str(&ss->log, buf->buf, ret);
ret = __bpf_stream_push_str(&ss->log, buf->buf, len);
if (!ret)
ss->len += len;
bpf_put_buffers();
return ret;
}
Expand Down
70 changes: 70 additions & 0 deletions tools/testing/selftests/bpf/prog_tests/stream.c
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,76 @@ void test_stream_syscall(void)
stream__destroy(skel);
}

void test_stream_oversize(void)
{
LIBBPF_OPTS(bpf_test_run_opts, opts);
struct stream *skel;
int ret, prog_fd;
char buf[8] = {};

skel = stream__open_and_load();
if (!ASSERT_OK_PTR(skel, "stream__open_and_load"))
return;

prog_fd = bpf_program__fd(skel->progs.stream_oversize);
ret = bpf_prog_test_run_opts(prog_fd, &opts);
ASSERT_OK(ret, "oversize run");
ASSERT_EQ(opts.retval, -E2BIG, "oversize retval");

/* Oversized push must not permanently consume capacity. */
prog_fd = bpf_program__fd(skel->progs.stream_syscall);
ret = bpf_prog_test_run_opts(prog_fd, &opts);
ASSERT_OK(ret, "syscall run");
ASSERT_OK(opts.retval, "syscall retval");

ret = bpf_prog_stream_read(prog_fd, BPF_STREAM_STDOUT, buf, sizeof(buf), NULL);
ASSERT_EQ(ret, 3, "bytes after oversize");
ASSERT_OK(memcmp(buf, "foo", 3), "payload after oversize");

stream__destroy(skel);
}

void test_stream_partial_read(void)
{
LIBBPF_OPTS(bpf_test_run_opts, opts);
struct stream *skel;
int ret, prog_fd;
long page_size;
char *page, *buf;
char rest[8] = {};

skel = stream__open_and_load();
if (!ASSERT_OK_PTR(skel, "stream__open_and_load"))
return;

prog_fd = bpf_program__fd(skel->progs.stream_syscall);
ret = bpf_prog_test_run_opts(prog_fd, &opts);
ASSERT_OK(ret, "ret");
ASSERT_OK(opts.retval, "retval");

page_size = sysconf(_SC_PAGESIZE);
page = mmap(NULL, page_size * 2, PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
if (!ASSERT_NEQ(page, MAP_FAILED, "mmap")) {
stream__destroy(skel);
return;
}
/* Leave only the first page mapped so a straddling copy faults. */
ASSERT_OK(munmap(page + page_size, page_size), "munmap second page");

buf = page + page_size - 1;
ret = bpf_prog_stream_read(prog_fd, BPF_STREAM_STDOUT, buf, 3, NULL);
ASSERT_EQ(ret, 1, "partial bytes");
ASSERT_EQ(buf[0], 'f', "first byte");

ret = bpf_prog_stream_read(prog_fd, BPF_STREAM_STDOUT, rest, sizeof(rest), NULL);
ASSERT_EQ(ret, 2, "remaining bytes");
ASSERT_OK(memcmp(rest, "oo", 2), "remaining data");

munmap(page, page_size);
stream__destroy(skel);
}

static void test_address(struct bpf_program *prog, unsigned long *fault_addr_p)
{
LIBBPF_OPTS(bpf_test_run_opts, opts);
Expand Down
12 changes: 12 additions & 0 deletions tools/testing/selftests/bpf/progs/stream.c
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,12 @@ struct {
} array SEC(".maps");

#define ENOSPC 28
#define E2BIG 7
#define _STR "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
#define _X64 "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
/* 1024 bytes: truncated by bstr_printf, must return -E2BIG. */
#define _BIG_STR (_X64 _X64 _X64 _X64 _X64 _X64 _X64 _X64 \
_X64 _X64 _X64 _X64 _X64 _X64 _X64 _X64)

int size;
u64 fault_addr;
Expand Down Expand Up @@ -119,6 +124,13 @@ int stream_syscall(void *ctx)
return 0;
}

SEC("syscall")
__success __retval(-E2BIG)
int stream_oversize(void *ctx)
{
return bpf_stream_printk(BPF_STDOUT, _BIG_STR);
}

SEC("syscall")
__arch_x86_64
__arch_arm64
Expand Down
Loading