On Tue, Nov 10, 2026 at 4:57 AM Kang Chen k.chen@smail.nju.edu.cn wrote:
bpf_iter_dmabuf_next() uses kit->dmabuf == NULL to mean "iteration has not started". dma_buf_iter_next() also returns NULL at the end of the list, so a next() call after exhaustion restarts from the list head.
The verifier relies on the iterator contract that once next() returns NULL it keeps returning NULL: process_iter_next_call() marks the iterator DRAINED and treats the result of later next() calls as the constant zero. A restarted dmabuf iteration therefore hands the BPF program a kernel pointer while the verifier believes R0 == 0.
Use an invalid pointer value as the drained sentinel so the iterator state keeps its one-pointer ABI, and skip dma_buf_put() for it.
Fixes: 6eab7ac7c5ee ("bpf: Add open coded dmabuf iterator") Signed-off-by: Kang Chen k.chen@smail.nju.edu.cn
Thanks for this fix. The cgroup (bpf_iter_css_next) and scx_dsq (bpf_iter_scx_dsq_next) iterators look like they also have this problem.
kernel/bpf/dmabuf_iter.c | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-)
diff --git a/kernel/bpf/dmabuf_iter.c b/kernel/bpf/dmabuf_iter.c index cd500248abd9..a2b7c38f9ae6 100644 --- a/kernel/bpf/dmabuf_iter.c +++ b/kernel/bpf/dmabuf_iter.c @@ -156,6 +156,13 @@ struct bpf_iter_dmabuf_kern { struct dma_buf *dmabuf; } __aligned(8);
+/*
- The iterator state is one pointer wide because struct bpf_iter_dmabuf is
- part of the generated vmlinux.h ABI. Use an invalid pointer value as the
- "drained" sentinel instead of growing the state.
- */
This comment explains why this patch uses a sentinel instead of growing the state, but that doesn't help anyone reading this code in the future. This belongs in the commit message, not a code comment. The real constraint is that the size of bpf_iter_dmabuf is user-visible API, so it can't grow without breaking backwards compatibility.
+#define DMABUF_ITER_DONE ((struct dma_buf *)1L)
I would prefer this to be DMABUF_ITER_START to mirror the kmem_cache iterator. Then the second if (!kit->dmabuf) block in bpf_iter_dmabuf_next() below could also be removed.
__bpf_kfunc_start_defs();
__bpf_kfunc int bpf_iter_dmabuf_new(struct bpf_iter_dmabuf *it) @@ -173,11 +180,25 @@ __bpf_kfunc struct dma_buf *bpf_iter_dmabuf_next(struct bpf_iter_dmabuf *it) { struct bpf_iter_dmabuf_kern *kit = (void *)it;
/** The verifier assumes that an iterator which returned NULL keeps* returning NULL (see process_iter_next_call()). Without this check* the NULL left by the last dma_buf_iter_next() would be interpreted* as "not started" and the iteration would restart from the list head.*/
Same here, this is commit message material. If you want to add a code comment, referencing the rule from bpf_iterators.rst should be enough:
/* Once NULL is returned, subsequent next calls should keep returning NULL. */
if (kit->dmabuf == DMABUF_ITER_DONE)return NULL;if (kit->dmabuf) kit->dmabuf = dma_buf_iter_next(kit->dmabuf); else kit->dmabuf = dma_buf_iter_begin();if (!kit->dmabuf) {kit->dmabuf = DMABUF_ITER_DONE;return NULL;}return kit->dmabuf;}
@@ -185,7 +206,7 @@ __bpf_kfunc void bpf_iter_dmabuf_destroy(struct bpf_iter_dmabuf *it) { struct bpf_iter_dmabuf_kern *kit = (void *)it;
if (kit->dmabuf)
if (kit->dmabuf && kit->dmabuf != DMABUF_ITER_DONE) dma_buf_put(kit->dmabuf);}
-- 2.34.1
I think we should also add a test for this. I've sent one as a reply to this thread. It fails without your fix and passes with it. Feel free to add it as 2/2 in v2.
linaro-mm-sig@lists.linaro.org