diff --git a/tools/testing/selftests/bpf/bpf_experimental.h b/tools/testing/selftests/bpf/bpf_experimental.h
index b0668f29f7b3..acbc6a1916bd 100644
--- a/tools/testing/selftests/bpf/bpf_experimental.h
+++ b/tools/testing/selftests/bpf/bpf_experimental.h
@@ -575,6 +575,15 @@ extern int bpf_iter_css_new(struct bpf_iter_css *it,
 extern struct cgroup_subsys_state *bpf_iter_css_next(struct bpf_iter_css *it) __weak __ksym;
 extern void bpf_iter_css_destroy(struct bpf_iter_css *it) __weak __ksym;
 
+struct bpf_iter_dynptr;
+extern int bpf_iter_dynptr_new(struct bpf_iter_dynptr *it, struct bpf_dynptr *p,
+			       u32 offset, void *buffer, u32 buffer__szk) __ksym;
+extern int *bpf_iter_dynptr_next(struct bpf_iter_dynptr *it) __ksym;
+extern int bpf_iter_dynptr_set_buffer(struct bpf_iter_dynptr *it__iter,
+				      void *buffer, u32 buffer__szk) __ksym;
+extern u32 bpf_iter_dynptr_get_last_offset(struct bpf_iter_dynptr *it__iter) __ksym;
+extern void bpf_iter_dynptr_destroy(struct bpf_iter_dynptr *it) __ksym;
+
 extern int bpf_wq_init(struct bpf_wq *wq, void *p__map, unsigned int flags) __weak __ksym;
 extern int bpf_wq_start(struct bpf_wq *wq, unsigned int flags) __weak __ksym;
 extern int bpf_wq_set_callback_impl(struct bpf_wq *wq,
diff --git a/tools/testing/selftests/bpf/prog_tests/iters.c b/tools/testing/selftests/bpf/prog_tests/iters.c
index 89ff23c4a8bc..7c17ef8eea70 100644
--- a/tools/testing/selftests/bpf/prog_tests/iters.c
+++ b/tools/testing/selftests/bpf/prog_tests/iters.c
@@ -21,6 +21,8 @@
 #include "iters_css_task.skel.h"
 #include "iters_css.skel.h"
 #include "iters_task_failure.skel.h"
+#include "iters_dynptr.skel.h"
+#include "iters_dynptr_failure.skel.h"
 
 static void subtest_num_iters(void)
 {
@@ -291,6 +293,50 @@ static void subtest_css_iters(void)
 	iters_css__destroy(skel);
 }
 
+static int subtest_dynptr_iters(struct iters_dynptr *skel, const char *prog_name)
+{
+	struct bpf_program *prog;
+	int prog_fd;
+
+	prog = bpf_object__find_program_by_name(skel->obj, prog_name);
+	if (!ASSERT_OK_PTR(prog, "bpf_object__find_program_by_name"))
+		return -1;
+
+	prog_fd = bpf_program__fd(prog);
+	if (!ASSERT_GT(prog_fd, 0, "bpf_program__fd"))
+		return -1;
+
+	if (test__start_subtest(prog_name)) {
+		bpf_prog_test_run_opts(prog_fd, NULL);
+		ASSERT_EQ(skel->bss->iter_step_match, 0, "step_match");
+		ASSERT_EQ(skel->bss->iter_content_match, 0, "content_match");
+	}
+
+	return 0;
+}
+
+const char *dynptr_iter_tests[] = {
+	"bpf_iter_dynptr_buffer_fit",
+	"bpf_iter_dynptr_buffer_remain"
+};
+
+static void test_dynptr_iters(void)
+{
+	struct iters_dynptr *skel = NULL;
+	int i;
+
+	skel = iters_dynptr__open_and_load();
+	if (!ASSERT_OK_PTR(skel, "skel_open_and_load"))
+		return;
+
+	for (i = 0; i < ARRAY_SIZE(dynptr_iter_tests); i++) {
+		if (subtest_dynptr_iters(skel, dynptr_iter_tests[i]))
+			break;
+	}
+
+	iters_dynptr__destroy(skel);
+}
+
 void test_iters(void)
 {
 	RUN_TESTS(iters_state_safety);
@@ -315,5 +361,9 @@ void test_iters(void)
 		subtest_css_task_iters();
 	if (test__start_subtest("css"))
 		subtest_css_iters();
+
+	test_dynptr_iters();
+
 	RUN_TESTS(iters_task_failure);
+	RUN_TESTS(iters_dynptr_failure);
 }
diff --git a/tools/testing/selftests/bpf/progs/iters_dynptr.c b/tools/testing/selftests/bpf/progs/iters_dynptr.c
new file mode 100644
index 000000000000..29a44b96f5fe
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/iters_dynptr.c
@@ -0,0 +1,140 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include "vmlinux.h"
+#include <errno.h>
+#include <bpf/bpf_helpers.h>
+#include <bpf/bpf_tracing.h>
+#include "bpf_misc.h"
+#include "bpf_experimental.h"
+
+char _license[] SEC("license") = "GPL";
+
+struct {
+	__uint(type, BPF_MAP_TYPE_RINGBUF);
+	__uint(max_entries, 4096);
+} ringbuf SEC(".maps");
+
+int iter_content_match = 0;
+int iter_step_match = 0;
+
+SEC("syscall")
+int bpf_iter_dynptr_buffer_fit(const void *ctx)
+{
+	struct bpf_iter_dynptr dynptr_it;
+	struct bpf_dynptr ptr;
+
+	char write_data[5] = {'a', 'b', 'c', 'd', 'e'};
+	char read_data1[2], read_data2[3];
+	int *read_len, offset;
+
+	bpf_ringbuf_reserve_dynptr(&ringbuf, sizeof(write_data), 0, &ptr);
+
+	bpf_dynptr_write(&ptr, 0, write_data, sizeof(write_data), 0);
+
+	bpf_iter_dynptr_new(&dynptr_it, &ptr, 0, read_data1, sizeof(read_data1));
+
+	read_len = bpf_iter_dynptr_next(&dynptr_it);
+	offset = bpf_iter_dynptr_get_last_offset(&dynptr_it);
+
+	if (read_len == NULL) {
+		iter_step_match = -1;
+		goto out;
+	}
+
+	if (*read_len != sizeof(read_data1)) {
+		iter_step_match = -1;
+		goto out;
+	}
+
+	if (offset != 0) {
+		iter_step_match = -1;
+		goto out;
+	}
+
+	if (read_data1[0] != write_data[0] || read_data1[1] != write_data[1]) {
+		iter_content_match = -1;
+		goto out;
+	}
+
+	bpf_iter_dynptr_set_buffer(&dynptr_it, read_data2, sizeof(read_data2));
+
+	read_len = bpf_iter_dynptr_next(&dynptr_it);
+	offset = bpf_iter_dynptr_get_last_offset(&dynptr_it);
+
+	if (read_len == NULL) {
+		iter_step_match = -1;
+		goto out;
+	}
+
+	if (*read_len != sizeof(read_data2)) {
+		iter_step_match = -1;
+		goto out;
+	}
+
+	if (offset != 2) {
+		iter_step_match = -1;
+		goto out;
+	}
+
+	if (read_data2[0] != write_data[2] || read_data2[1] != write_data[3] ||
+	   read_data2[2] != write_data[4]) {
+		iter_content_match = -1;
+		goto out;
+	}
+
+	read_len = bpf_iter_dynptr_next(&dynptr_it);
+	if (read_len != NULL)
+		iter_step_match = -1;
+out:
+	bpf_iter_dynptr_destroy(&dynptr_it);
+	bpf_ringbuf_discard_dynptr(&ptr, 0);
+	return 0;
+}
+
+SEC("syscall")
+int bpf_iter_dynptr_buffer_remain(const void *ctx)
+{
+	struct bpf_iter_dynptr dynptr_it;
+	struct bpf_dynptr ptr;
+
+	char write_data[1] = {'a'};
+	char read_data[2];
+	int *read_len, offset;
+
+	bpf_ringbuf_reserve_dynptr(&ringbuf, sizeof(write_data), 0, &ptr);
+
+	bpf_dynptr_write(&ptr, 0, write_data, sizeof(write_data), 0);
+
+	bpf_iter_dynptr_new(&dynptr_it, &ptr, 0, read_data, sizeof(read_data));
+
+	read_len = bpf_iter_dynptr_next(&dynptr_it);
+	offset = bpf_iter_dynptr_get_last_offset(&dynptr_it);
+
+	if (read_len == NULL) {
+		iter_step_match = -1;
+		goto out;
+	}
+
+	if (*read_len != 1) {
+		iter_step_match = -1;
+		goto out;
+	}
+
+	if (offset != 0) {
+		iter_step_match = -1;
+		goto out;
+	}
+
+	if (read_data[0] != write_data[0]) {
+		iter_content_match = -1;
+		goto out;
+	}
+
+	read_len = bpf_iter_dynptr_next(&dynptr_it);
+	if (read_len != NULL)
+		iter_step_match = -1;
+out:
+	bpf_iter_dynptr_destroy(&dynptr_it);
+	bpf_ringbuf_discard_dynptr(&ptr, 0);
+	return 0;
+}
diff --git a/tools/testing/selftests/bpf/progs/iters_dynptr_failure.c b/tools/testing/selftests/bpf/progs/iters_dynptr_failure.c
new file mode 100644
index 000000000000..97bec2f39f62
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/iters_dynptr_failure.c
@@ -0,0 +1,108 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include "vmlinux.h"
+#include <errno.h>
+#include <bpf/bpf_helpers.h>
+#include <bpf/bpf_tracing.h>
+#include "bpf_misc.h"
+#include "bpf_experimental.h"
+
+char _license[] SEC("license") = "GPL";
+
+struct {
+	__uint(type, BPF_MAP_TYPE_RINGBUF);
+	__uint(max_entries, 4096);
+} ringbuf SEC(".maps");
+
+SEC("raw_tp/sys_enter")
+__failure __msg("Expected an initialized dynptr as arg #2")
+int bpf_iter_dynptr_new_uninit_dynptr(void *ctx)
+{
+	struct bpf_iter_dynptr dynptr_it;
+	struct bpf_dynptr ptr;
+	char read_data[5];
+
+	bpf_iter_dynptr_new(&dynptr_it, &ptr, 0, read_data, sizeof(read_data));
+
+	return 0;
+}
+
+SEC("raw_tp/sys_enter")
+__failure __msg("arg#3 arg#4 memory, len pair leads to invalid memory access")
+int bpf_iter_dynptr_new_null_buffer(void *ctx)
+{
+	struct bpf_iter_dynptr dynptr_it;
+	struct bpf_dynptr ptr;
+	char *read_data = NULL;
+
+	bpf_ringbuf_reserve_dynptr(&ringbuf, 10, 0, &ptr);
+
+	bpf_iter_dynptr_new(&dynptr_it, &ptr, 0, read_data, 10);
+
+	bpf_ringbuf_discard_dynptr(&ptr, 0);
+	return 0;
+}
+
+SEC("raw_tp/sys_enter")
+__failure __msg("expected an initialized iter_dynptr as arg #1")
+int bpf_iter_dynptr_next_uninit_iter(void *ctx)
+{
+	struct bpf_iter_dynptr dynptr_it;
+
+	bpf_iter_dynptr_next(&dynptr_it);
+
+	return 0;
+}
+
+SEC("raw_tp/sys_enter")
+__failure __msg("expected an initialized iter_dynptr as arg #1")
+int bpf_iter_dynptr_get_last_offset_uninit_iter(void *ctx)
+{
+	struct bpf_iter_dynptr dynptr_it;
+
+	bpf_iter_dynptr_get_last_offset(&dynptr_it);
+
+	return 0;
+}
+
+SEC("raw_tp/sys_enter")
+__failure __msg("expected an initialized iter_dynptr as arg #1")
+int bpf_iter_dynptr_set_buffer_uninit_iter(void *ctx)
+{
+	struct bpf_iter_dynptr dynptr_it;
+	char read_data[5];
+
+	bpf_iter_dynptr_set_buffer(&dynptr_it, read_data, sizeof(read_data));
+
+	return 0;
+}
+
+SEC("raw_tp/sys_enter")
+__failure __msg("arg#1 arg#2 memory, len pair leads to invalid memory access")
+int bpf_iter_dynptr_set_buffer_null_buffer(void *ctx)
+{
+	struct bpf_iter_dynptr dynptr_it;
+	struct bpf_dynptr ptr;
+	char *null_data = NULL;
+	char read_data[5];
+
+	bpf_ringbuf_reserve_dynptr(&ringbuf, 10, 0, &ptr);
+
+	bpf_iter_dynptr_new(&dynptr_it, &ptr, 0, read_data, sizeof(read_data));
+
+	bpf_iter_dynptr_set_buffer(&dynptr_it, null_data, 10);
+
+	bpf_ringbuf_discard_dynptr(&ptr, 0);
+	return 0;
+}
+
+SEC("raw_tp/sys_enter")
+__failure __msg("expected an initialized iter_dynptr as arg #1")
+int bpf_iter_dynptr_destroy_uninit_iter(void *ctx)
+{
+	struct bpf_iter_dynptr dynptr_it;
+
+	bpf_iter_dynptr_destroy(&dynptr_it);
+
+	return 0;
+}
