summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKumar Kartikeya Dwivedi <memxor@gmail.com>2026-08-23 20:43:28 +0200
committerKumar Kartikeya Dwivedi <memxor@gmail.com>2026-08-23 21:19:11 +0200
commita284ed47ec1fd4aa63d2318d87f457cc421a93b5 (patch)
tree077f34b6eb3da1aedb1aa733b0ece4e181daf30c
parent669e4fa766000ae4137bb02eb22ce75ba78ad32d (diff)
parent0cf194ccf78852377458b10c8c2e308678b10efa (diff)
downloadlinux-next-a284ed47ec1fd4aa63d2318d87f457cc421a93b5.tar.gz
linux-next-a284ed47ec1fd4aa63d2318d87f457cc421a93b5.zip
Merge branch 'bpf-fix-stream-capacity-read-and-oversize-handling'
Jianlin Shi says: ==================== v3 addressed Kartikeya's review on v2 and the related Sashiko findings. v4 fixes the stream_oversize selftest to verify capacity rollback on the same BPF program stream, since streams live on prog->aux and are not shared across programs. Tested locally: stream_oversize and stream_partial_read (equivalent to ./test_progs -t stream_oversize,stream_partial_read). Changelog: v3 -> v4: - In stream_oversize, perform the oversized bpf_stream_printk() and a subsequent successful "foo" push in the same program; read that program's stream in userspace instead of switching to stream_syscall. - Drop a redundant vscnprintf() comment in bpf_stream_stage_printk(). v2 -> v3: - Refactor bpf_stream_release_capacity() to take a length. - Fix staging-path capacity leak; use vscnprintf(). - Return partial bpf_stream_read() progress on copy_to_user() fault. - Reject truncated bpf_stream_vprintk() output with -E2BIG. - Add selftests for oversize and straddling-buffer partial read. v1 -> v2: - Retarget to bpf-next as suggested by Pu Lehui. Links: v3: https://lore.kernel.org/bpf/?q=%22PATCH+bpf-next+v3+0%2F5%22+fix+stream+capacity v2: https://lore.kernel.org/bpf/tencent_C919BB32458A4DAD645A68F441345B971E05@qq.com/ v1: https://lore.kernel.org/bpf/tencent_E69EAE29327E25B3548A9AF3F4FA289A6806@qq.com/ ==================== Link: https://lore.kernel.org/r/cover.1787492521.git.shijianlin11@foxmail.com Signed-off-by: Kumar Kartikeya Dwivedi <memxor@gmail.com>
-rw-r--r--kernel/bpf/stream.c53
-rw-r--r--tools/testing/selftests/bpf/prog_tests/stream.c63
-rw-r--r--tools/testing/selftests/bpf/progs/stream.c28
3 files changed, 124 insertions, 20 deletions
diff --git a/kernel/bpf/stream.c b/kernel/bpf/stream.c
index be9ce98e9469..7a5c3ac8676b 100644
--- a/kernel/bpf/stream.c
+++ b/kernel/bpf/stream.c
@@ -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]);
@@ -68,10 +68,8 @@ 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);
}
@@ -79,7 +77,14 @@ static int bpf_stream_push_str(struct bpf_stream *stream, const char *str, int l
{
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)
@@ -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);
@@ -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);
}
@@ -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);
@@ -311,17 +323,18 @@ 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);
+ 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;
}
diff --git a/tools/testing/selftests/bpf/prog_tests/stream.c b/tools/testing/selftests/bpf/prog_tests/stream.c
index e4e9374309e2..4d8054a23cc7 100644
--- a/tools/testing/selftests/bpf/prog_tests/stream.c
+++ b/tools/testing/selftests/bpf/prog_tests/stream.c
@@ -58,6 +58,69 @@ 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;
+
+ 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_OK(opts.retval, "oversize retval");
+
+ 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. */
+ if (!ASSERT_OK(munmap(page + page_size, page_size), "munmap second page")) {
+ munmap(page, page_size * 2);
+ stream__destroy(skel);
+ return;
+ }
+
+ 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);
diff --git a/tools/testing/selftests/bpf/progs/stream.c b/tools/testing/selftests/bpf/progs/stream.c
index 8e8e1339dc74..12fc29e45487 100644
--- a/tools/testing/selftests/bpf/progs/stream.c
+++ b/tools/testing/selftests/bpf/progs/stream.c
@@ -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;
@@ -120,6 +125,29 @@ int stream_syscall(void *ctx)
}
SEC("syscall")
+__success __retval(0)
+int stream_oversize(void *ctx)
+{
+ int ret;
+
+ ret = bpf_stream_printk(BPF_STDOUT, _BIG_STR);
+ if (ret != -E2BIG)
+ return ret ?: 1;
+
+ /* The oversized output must not reduce the remaining stream capacity. */
+ size = 0;
+ bpf_repeat(BPF_MAX_LOOPS) {
+ ret = bpf_stream_printk(BPF_STDOUT, _STR);
+ if (ret == -ENOSPC)
+ return size == 99954 ? 0 : 1;
+ if (ret)
+ return ret;
+ size += sizeof(_STR) - 1;
+ }
+ return 1;
+}
+
+SEC("syscall")
__arch_x86_64
__arch_arm64
__success __retval(0)