* [PATCH bpf-next v3 1/5] bpf: roll back stream capacity when allocation fails
[not found] ` <20260815081920.609936-1-shijianlin11@foxmail.com>
@ 2026-08-15 8:19 ` Jianlin Shi
2026-08-15 8:19 ` [PATCH bpf-next v3 2/5] bpf: fix stream capacity leak in staging path Jianlin Shi
` (3 subsequent siblings)
4 siblings, 0 replies; 15+ messages in thread
From: Jianlin Shi @ 2026-08-15 8:19 UTC (permalink / raw)
To: bpf
Cc: ast, daniel, andrii, martin.lau, eddyz87, song, yonghong.song,
john.fastabend, kpsingh, sdf, haoluo, jolsa, emil, memxor,
pulehui, shuah, linux-kselftest, linux-kernel
bpf_stream_push_str() accounts the string length before allocating a
stream element. If the allocation fails, the length remains charged even
though no element is queued and therefore cannot be released by a reader.
Repeated failures can exhaust the stream capacity permanently until the
BPF program is freed.
Refactor bpf_stream_release_capacity() to take a length so the consume
and release sides are symmetric, and use it to roll back the charge when
creating the stream element fails.
Fixes: 5ab154f1463a ("bpf: Introduce BPF standard streams")
Signed-off-by: Jianlin Shi <shijianlin11@foxmail.com>
---
kernel/bpf/stream.c | 15 ++++++++++-----
1 file changed, 10 insertions(+), 5 deletions(-)
diff --git a/kernel/bpf/stream.c b/kernel/bpf/stream.c
index be9ce98e9469..0b157ec4e38e 100644
--- a/kernel/bpf/stream.c
+++ b/kernel/bpf/stream.c
@@ -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)
@@ -188,7 +193,7 @@ static int bpf_stream_read(struct bpf_stream *stream, void __user *buf, int len)
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);
}
--
2.43.0
^ permalink raw reply [flat|nested] 15+ messages in thread* [PATCH bpf-next v3 2/5] bpf: fix stream capacity leak in staging path
[not found] ` <20260815081920.609936-1-shijianlin11@foxmail.com>
2026-08-15 8:19 ` [PATCH bpf-next v3 1/5] bpf: roll back stream capacity when allocation fails Jianlin Shi
@ 2026-08-15 8:19 ` Jianlin Shi
2026-08-15 9:02 ` bot+bpf-ci
2026-08-15 8:19 ` [PATCH bpf-next v3 3/5] bpf: return partial progress from bpf_stream_read on fault Jianlin Shi
` (2 subsequent siblings)
4 siblings, 1 reply; 15+ messages in thread
From: Jianlin Shi @ 2026-08-15 8:19 UTC (permalink / raw)
To: bpf
Cc: ast, daniel, andrii, martin.lau, eddyz87, song, yonghong.song,
john.fastabend, kpsingh, sdf, haoluo, jolsa, emil, memxor,
pulehui, shuah, linux-kselftest, linux-kernel
bpf_stream_stage_printk() increments ss->len before pushing the
formatted string to the staging log. If element allocation fails,
ss->len remains inflated and bpf_stream_stage_commit() permanently
charges the stream capacity for data that was never queued.
Only account the string length after a successful push, and use
vscnprintf() so the staged length is the truncated payload without
the trailing NUL.
Fixes: 5ab154f1463a ("bpf: Introduce BPF standard streams")
Signed-off-by: Jianlin Shi <shijianlin11@foxmail.com>
---
kernel/bpf/stream.c | 10 ++++++----
1 file changed, 6 insertions(+), 4 deletions(-)
diff --git a/kernel/bpf/stream.c b/kernel/bpf/stream.c
index 0b157ec4e38e..ef5a7b06ffcb 100644
--- a/kernel/bpf/stream.c
+++ b/kernel/bpf/stream.c
@@ -316,17 +316,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;
}
--
2.43.0
^ permalink raw reply [flat|nested] 15+ messages in thread* Re: [PATCH bpf-next v3 2/5] bpf: fix stream capacity leak in staging path
2026-08-15 8:19 ` [PATCH bpf-next v3 2/5] bpf: fix stream capacity leak in staging path Jianlin Shi
@ 2026-08-15 9:02 ` bot+bpf-ci
0 siblings, 0 replies; 15+ messages in thread
From: bot+bpf-ci @ 2026-08-15 9:02 UTC (permalink / raw)
To: shijianlin11, bpf
Cc: ast, daniel, andrii, martin.lau, eddyz87, song, yonghong.song,
john.fastabend, kpsingh, sdf, haoluo, jolsa, emil, memxor,
pulehui, shuah, linux-kselftest, linux-kernel, ast, andrii,
daniel, martin.lau, eddyz87, yonghong.song, clm, ihor.solodrai
[-- Attachment #1: Type: text/plain, Size: 1337 bytes --]
> diff --git a/kernel/bpf/stream.c b/kernel/bpf/stream.c
> index 0b157ec4e38e..ef5a7b06ffcb 100644
> --- a/kernel/bpf/stream.c
> +++ b/kernel/bpf/stream.c
> @@ -316,17 +316,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;
This isn't a bug, but with the existing '/* Exclude NULL byte during
push. */' a couple of lines down, does the new comment above vscnprintf()
add anything, or could one of the two be dropped?
> bpf_put_buffers();
> return ret;
> }
---
AI reviewed your patch. Please fix the bug or email reply why it's not a bug.
See: https://github.com/kernel-patches/vmtest/blob/master/ci/claude/README.md
CI run summary: https://github.com/kernel-patches/bpf/actions/runs/31874860107
^ permalink raw reply [flat|nested] 15+ messages in thread
* [PATCH bpf-next v3 3/5] bpf: return partial progress from bpf_stream_read on fault
[not found] ` <20260815081920.609936-1-shijianlin11@foxmail.com>
2026-08-15 8:19 ` [PATCH bpf-next v3 1/5] bpf: roll back stream capacity when allocation fails Jianlin Shi
2026-08-15 8:19 ` [PATCH bpf-next v3 2/5] bpf: fix stream capacity leak in staging path Jianlin Shi
@ 2026-08-15 8:19 ` Jianlin Shi
2026-08-15 8:19 ` [PATCH bpf-next v3 4/5] bpf: reject oversized bpf_stream_vprintk output with -E2BIG Jianlin Shi
2026-08-15 8:19 ` [PATCH bpf-next v3 5/5] selftests/bpf: cover stream capacity and partial read edge cases Jianlin Shi
4 siblings, 0 replies; 15+ messages in thread
From: Jianlin Shi @ 2026-08-15 8:19 UTC (permalink / raw)
To: bpf
Cc: ast, daniel, andrii, martin.lau, eddyz87, song, yonghong.song,
john.fastabend, kpsingh, sdf, haoluo, jolsa, emil, memxor,
pulehui, shuah, linux-kselftest, linux-kernel
bpf_stream_read() pops and frees stream elements after a successful
copy_to_user(). If a later copy_to_user() fails, it currently restores
only the current element's consumed_len and returns -EFAULT, hiding
bytes already delivered to userspace and making the consumed data
unrecoverable on retry.
On a short copy, keep the successfully copied prefix of the current
element and return the number of bytes copied. Return -EFAULT only when
no bytes were copied for the call.
Fixes: 5ab154f1463a ("bpf: Introduce BPF standard streams")
Signed-off-by: Jianlin Shi <shijianlin11@foxmail.com>
---
kernel/bpf/stream.c | 16 +++++++++-------
1 file changed, 9 insertions(+), 7 deletions(-)
diff --git a/kernel/bpf/stream.c b/kernel/bpf/stream.c
index ef5a7b06ffcb..c1077160074c 100644
--- a/kernel/bpf/stream.c
+++ b/kernel/bpf/stream.c
@@ -167,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);
@@ -180,13 +181,14 @@ 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;
}
--
2.43.0
^ permalink raw reply [flat|nested] 15+ messages in thread* [PATCH bpf-next v3 4/5] bpf: reject oversized bpf_stream_vprintk output with -E2BIG
[not found] ` <20260815081920.609936-1-shijianlin11@foxmail.com>
` (2 preceding siblings ...)
2026-08-15 8:19 ` [PATCH bpf-next v3 3/5] bpf: return partial progress from bpf_stream_read on fault Jianlin Shi
@ 2026-08-15 8:19 ` Jianlin Shi
2026-08-15 8:19 ` [PATCH bpf-next v3 5/5] selftests/bpf: cover stream capacity and partial read edge cases Jianlin Shi
4 siblings, 0 replies; 15+ messages in thread
From: Jianlin Shi @ 2026-08-15 8:19 UTC (permalink / raw)
To: bpf
Cc: ast, daniel, andrii, martin.lau, eddyz87, song, yonghong.song,
john.fastabend, kpsingh, sdf, haoluo, jolsa, emil, memxor,
pulehui, shuah, linux-kselftest, linux-kernel
bstr_printf() returns the would-be length excluding the trailing NUL.
When that value is >= MAX_BPRINTF_BUF the message was truncated, but
bpf_stream_push_str() still tried to allocate with the inflated length
and failed with -ENOMEM. The boundary case of exactly MAX_BPRINTF_BUF
could also copy the trailing NUL into the stream element.
Reject such lengths with -E2BIG before charging stream capacity, and
tighten bpf_stream_elem_alloc() to accept only payloads strictly shorter
than the bprintf buffer.
Fixes: 5ab154f1463a ("bpf: Introduce BPF standard streams")
Signed-off-by: Jianlin Shi <shijianlin11@foxmail.com>
---
kernel/bpf/stream.c | 13 +++++++++----
1 file changed, 9 insertions(+), 4 deletions(-)
diff --git a/kernel/bpf/stream.c b/kernel/bpf/stream.c
index c1077160074c..bd1e98fde4b0 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]);
@@ -245,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);
--
2.43.0
^ permalink raw reply [flat|nested] 15+ messages in thread* [PATCH bpf-next v3 5/5] selftests/bpf: cover stream capacity and partial read edge cases
[not found] ` <20260815081920.609936-1-shijianlin11@foxmail.com>
` (3 preceding siblings ...)
2026-08-15 8:19 ` [PATCH bpf-next v3 4/5] bpf: reject oversized bpf_stream_vprintk output with -E2BIG Jianlin Shi
@ 2026-08-15 8:19 ` Jianlin Shi
4 siblings, 0 replies; 15+ messages in thread
From: Jianlin Shi @ 2026-08-15 8:19 UTC (permalink / raw)
To: bpf
Cc: ast, daniel, andrii, martin.lau, eddyz87, song, yonghong.song,
john.fastabend, kpsingh, sdf, haoluo, jolsa, emil, memxor,
pulehui, shuah, linux-kselftest, linux-kernel
Add coverage for the stream fixes requested on the capacity rollback
series:
- oversized bpf_stream_printk() returns -E2BIG and does not leak
capacity for a subsequent successful write;
- bpf_prog_stream_read() returns the successfully copied prefix when
the userspace buffer straddles an unmapped page.
Signed-off-by: Jianlin Shi <shijianlin11@foxmail.com>
---
.../testing/selftests/bpf/prog_tests/stream.c | 70 +++++++++++++++++++
tools/testing/selftests/bpf/progs/stream.c | 12 ++++
2 files changed, 82 insertions(+)
diff --git a/tools/testing/selftests/bpf/prog_tests/stream.c b/tools/testing/selftests/bpf/prog_tests/stream.c
index c3cce5c292bd..3ae98e3c4909 100644
--- a/tools/testing/selftests/bpf/prog_tests/stream.c
+++ b/tools/testing/selftests/bpf/prog_tests/stream.c
@@ -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);
diff --git a/tools/testing/selftests/bpf/progs/stream.c b/tools/testing/selftests/bpf/progs/stream.c
index 6f999ba951a3..6fab9b8b21a0 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;
@@ -117,6 +122,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
--
2.43.0
^ permalink raw reply [flat|nested] 15+ messages in thread