diff --git a/tools/testing/selftests/bpf/prog_tests/inode_build_id.c b/tools/testing/selftests/bpf/prog_tests/inode_build_id.c
new file mode 100644
index 000000000000..d0add90f187d
--- /dev/null
+++ b/tools/testing/selftests/bpf/prog_tests/inode_build_id.c
@@ -0,0 +1,68 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include <unistd.h>
+#include <test_progs.h>
+#include "inode_build_id.skel.h"
+#include "trace_helpers.h"
+
+void test_inode_build_id(void)
+{
+	int go[2], err, child_pid, child_status, c = 1, sz;
+	char build_id[BPF_BUILD_ID_SIZE];
+	struct inode_build_id *skel;
+
+	skel = inode_build_id__open_and_load();
+	if (!ASSERT_OK_PTR(skel, "inode_build_id__open_and_load"))
+		return;
+
+	if (!ASSERT_OK(pipe(go), "pipe"))
+		goto out;
+
+	child_pid = fork();
+	if (child_pid < 0)
+		goto out;
+
+	/* child */
+	if (child_pid == 0) {
+		/* wait for parent's pid update */
+		err = read(go[0], &c, 1);
+		if (!ASSERT_EQ(err, 1, "child_read_pipe"))
+			exit(err);
+
+		execle("./urandom_read", "urandom_read", NULL, NULL);
+		exit(errno);
+	}
+
+	/* parent, update child's pid and kick it */
+	skel->bss->pid = child_pid;
+
+	err = inode_build_id__attach(skel);
+	if (!ASSERT_OK(err, "inode_build_id__attach"))
+		goto out;
+
+	err = write(go[1], &c, 1);
+	if (!ASSERT_EQ(err, 1, "child_write_pipe"))
+		goto out;
+
+	/* wait for child to exit */
+	waitpid(child_pid, &child_status, 0);
+	if (!ASSERT_EQ(WEXITSTATUS(child_status), 0, "child_exit_value"))
+		goto out;
+
+	sz = read_build_id("./urandom_read", build_id);
+	if (!ASSERT_GT(sz, 0, "read_build_id"))
+		goto out;
+
+	ASSERT_EQ(skel->bss->build_id_bin_size, sz, "build_id_bin_size");
+	ASSERT_MEMEQ(skel->bss->build_id_bin, build_id, sz, "build_id_bin");
+
+	sz = read_build_id("./liburandom_read.so", build_id);
+	if (!ASSERT_GT(sz, 0, "read_build_id"))
+		goto out;
+
+	ASSERT_EQ(skel->bss->build_id_lib_size, sz, "build_id_lib_size");
+	ASSERT_MEMEQ(skel->bss->build_id_lib, build_id, sz, "build_id_lib");
+
+out:
+	inode_build_id__destroy(skel);
+}
diff --git a/tools/testing/selftests/bpf/progs/inode_build_id.c b/tools/testing/selftests/bpf/progs/inode_build_id.c
new file mode 100644
index 000000000000..eceb215b56b8
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/inode_build_id.c
@@ -0,0 +1,62 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include "vmlinux.h"
+#include "err.h"
+#include <bpf/bpf_helpers.h>
+#include <bpf/bpf_tracing.h>
+#include <linux/string.h>
+
+char _license[] SEC("license") = "GPL";
+
+int pid;
+
+u32 build_id_bin_size;
+u32 build_id_lib_size;
+
+char build_id_bin[20];
+char build_id_lib[20];
+
+static int store_build_id(struct inode *inode, char *build_id, u32 *sz)
+{
+	struct build_id *bid;
+
+	bid = inode->i_build_id;
+	if (IS_ERR_OR_NULL(bid))
+		return 0;
+	*sz = bid->sz;
+	if (bid->sz > sizeof(bid->data))
+		return 0;
+	__builtin_memcpy(build_id, bid->data, sizeof(bid->data));
+	return 0;
+}
+
+SEC("tp_btf/sched_process_exec")
+int BPF_PROG(prog, struct task_struct *p, pid_t old_pid, struct linux_binprm *bprm)
+{
+	int cur_pid = bpf_get_current_pid_tgid() >> 32;
+
+	if (pid != cur_pid)
+		return 0;
+	if (!bprm->file || !bprm->file->f_inode)
+		return 0;
+	return store_build_id(bprm->file->f_inode, build_id_bin, &build_id_bin_size);
+}
+
+static long check_vma(struct task_struct *task, struct vm_area_struct *vma,
+		      void *data)
+{
+	if (!vma || !vma->vm_file || !vma->vm_file->f_inode)
+		return 0;
+	return store_build_id(vma->vm_file->f_inode, build_id_lib, &build_id_lib_size);
+}
+
+SEC("uprobe/liburandom_read.so:urandlib_read_without_sema")
+int BPF_UPROBE(urandlib_read_without_sema)
+{
+	struct task_struct *task = bpf_get_current_task_btf();
+	int cur_pid = bpf_get_current_pid_tgid() >> 32;
+
+	if (pid != cur_pid)
+		return 0;
+	return bpf_find_vma(task, ctx->ip, check_vma, NULL, 0);
+}
diff --git a/tools/testing/selftests/bpf/test_progs.h b/tools/testing/selftests/bpf/test_progs.h
index 3825c2797a4b..8156d6d4cb3b 100644
--- a/tools/testing/selftests/bpf/test_progs.h
+++ b/tools/testing/selftests/bpf/test_progs.h
@@ -310,6 +310,16 @@ int test__join_cgroup(const char *path);
 	___ok;								\
 })
 
+#define ASSERT_MEMEQ(actual, expected, sz, name) ({			\
+	static int duration = 0;					\
+	const char *___act = actual;					\
+	const char *___exp = expected;					\
+	bool ___ok = memcmp(___act, ___exp, sz) == 0;			\
+	CHECK(!___ok, (name),						\
+	      "unexpected %s does not match\n", (name));		\
+	___ok;								\
+})
+
 #define ASSERT_STRNEQ(actual, expected, len, name) ({			\
 	static int duration = 0;					\
 	const char *___act = actual;					\
