diff --git a/tools/testing/selftests/bpf/prog_tests/linked_list.c b/tools/testing/selftests/bpf/prog_tests/linked_list.c
index 669ef4bb9b87..40070e2d22f2 100644
--- a/tools/testing/selftests/bpf/prog_tests/linked_list.c
+++ b/tools/testing/selftests/bpf/prog_tests/linked_list.c
@@ -1,4 +1,7 @@
 // SPDX-License-Identifier: GPL-2.0
+#include <bpf/btf.h>
+#include <test_btf.h>
+#include <linux/btf.h>
 #include <test_progs.h>
 #include <network_helpers.h>
 
@@ -233,6 +236,273 @@ static void test_linked_list_success(int mode, bool leave_in_map)
 	linked_list__destroy(skel);
 }
 
+/* struct bpf_spin_lock {
+ *   int foo;
+ * };
+ * struct bpf_list_head {
+ *   __u64 :64;
+ *   __u64 :64;
+ * } __attribute__((aligned(8)));
+ * struct bpf_list_node {
+ *   __u64 :64;
+ *   __u64 :64;
+ * } __attribute__((aligned(8)));
+ */
+static const char btf_str_sec[] = "\0bpf_spin_lock\0bpf_list_head\0bpf_list_node\0foo\0bar\0baz"
+				  "\0contains:foo:foo\0contains:bar:bar\0contains:baz:baz\0bam"
+				  "\0contains:bam:bam";
+
+#define INIT_BTF_TILL_4							\
+	/* int */							\
+	BTF_TYPE_INT_ENC(0, BTF_INT_SIGNED, 0, 32, 4),  /* [1] */	\
+	/* struct bpf_spin_lock */                      /* [2] */	\
+	BTF_TYPE_ENC(1, BTF_INFO_ENC(BTF_KIND_STRUCT, 0, 1), 4),	\
+	BTF_MEMBER_ENC(43, 1, 0),					\
+	/* struct bpf_list_head */                      /* [3] */	\
+	BTF_TYPE_ENC(15, BTF_INFO_ENC(BTF_KIND_STRUCT, 0, 0), 16),	\
+	/* struct bpf_list_node */                      /* [4] */	\
+	BTF_TYPE_ENC(29, BTF_INFO_ENC(BTF_KIND_STRUCT, 0, 0), 16),
+
+static void check_btf(u32 *types, u32 types_len, int error)
+{
+	LIBBPF_OPTS(bpf_btf_load_opts, opts,
+		    .log_buf = log_buf,
+		    .log_size = sizeof(log_buf),
+	);
+	struct btf_header hdr = {
+		.magic = BTF_MAGIC,
+		.version = BTF_VERSION,
+		.hdr_len = sizeof(struct btf_header),
+		.type_len = types_len,
+		.str_off = types_len,
+		.str_len = sizeof(btf_str_sec),
+	};
+	void *ptr, *raw_btf;
+	int fd, ret;
+
+	raw_btf = malloc(sizeof(hdr) + hdr.type_len + hdr.str_len);
+	if (!ASSERT_OK_PTR(raw_btf, "malloc(raw_btf)"))
+		return;
+
+	ptr = raw_btf;
+	memcpy(ptr, &hdr, sizeof(hdr));
+	ptr += sizeof(hdr);
+	memcpy(ptr, types, hdr.type_len);
+	ptr += hdr.type_len;
+	memcpy(ptr, btf_str_sec, hdr.str_len);
+	ptr += hdr.str_len;
+
+	fd = bpf_btf_load(raw_btf, ptr - raw_btf, &opts);
+	ret = fd < 0 ? -errno : 0;
+	if (fd >= 0)
+		close(fd);
+	if (error)
+		ASSERT_LT(fd, 0, "bpf_btf_load");
+	else
+		ASSERT_GE(fd, 0, "bpf_btf_load");
+	if (!ASSERT_EQ(ret, error, "-errno == error"))
+		printf("BTF Log:\n%s\n", log_buf);
+	free(raw_btf);
+	return;
+}
+
+#define SPIN_LOCK 2
+#define LIST_HEAD 3
+#define LIST_NODE 4
+#define FOO 43
+#define BAR 47
+#define BAZ 51
+#define BAM 106
+#define CONT_FOO_FOO 55
+#define CONT_BAR_BAR 72
+#define CONT_BAZ_BAZ 89
+#define CONT_BAM_BAM 110
+
+static void test_btf(void)
+{
+	if (test__start_subtest("btf: too many locks")) {
+		u32 types[] = {
+			INIT_BTF_TILL_4
+			BTF_TYPE_ENC(FOO, BTF_INFO_ENC(BTF_KIND_STRUCT, 0, 3), 24), /* [5] */
+			BTF_MEMBER_ENC(BAZ, SPIN_LOCK, 0),
+			BTF_MEMBER_ENC(FOO, SPIN_LOCK, 32),
+			BTF_MEMBER_ENC(BAR, LIST_HEAD, 64),
+		};
+		check_btf(types, sizeof(types), -E2BIG);
+	}
+	if (test__start_subtest("btf: missing lock")) {
+		u32 types[] = {
+			INIT_BTF_TILL_4
+			BTF_TYPE_ENC(FOO, BTF_INFO_ENC(BTF_KIND_STRUCT, 0, 1), 16), /* [5] */
+			BTF_MEMBER_ENC(BAR, LIST_HEAD, 0),
+			BTF_DECL_TAG_ENC(CONT_BAZ_BAZ, 5, 0),
+			BTF_TYPE_ENC(BAZ, BTF_INFO_ENC(BTF_KIND_STRUCT, 0, 1), 16),
+			BTF_MEMBER_ENC(BAZ, LIST_NODE, 0),
+		};
+		check_btf(types, sizeof(types), -EINVAL);
+	}
+	if (test__start_subtest("btf: bad offset")) {
+		u32 types[] = {
+			INIT_BTF_TILL_4
+			BTF_TYPE_ENC(FOO, BTF_INFO_ENC(BTF_KIND_STRUCT, 0, 3), 36), /* [5] */
+			BTF_MEMBER_ENC(BAR, LIST_HEAD, 0),
+			BTF_MEMBER_ENC(FOO, LIST_NODE, 0),
+			BTF_MEMBER_ENC(BAZ, SPIN_LOCK, 0),
+			BTF_DECL_TAG_ENC(CONT_FOO_FOO, 5, 0),
+		};
+		check_btf(types, sizeof(types), -EFAULT);
+	}
+	if (test__start_subtest("btf: missing contains:")) {
+		u32 types[] = {
+			INIT_BTF_TILL_4
+			BTF_TYPE_ENC(FOO, BTF_INFO_ENC(BTF_KIND_STRUCT, 0, 2), 24), /* [5] */
+			BTF_MEMBER_ENC(BAZ, SPIN_LOCK, 0),
+			BTF_MEMBER_ENC(BAR, LIST_HEAD, 64),
+		};
+		check_btf(types, sizeof(types), -EINVAL);
+	}
+	if (test__start_subtest("btf: missing struct")) {
+		u32 types[] = {
+			INIT_BTF_TILL_4
+			BTF_TYPE_ENC(FOO, BTF_INFO_ENC(BTF_KIND_STRUCT, 0, 2), 24), /* [5] */
+			BTF_MEMBER_ENC(BAZ, SPIN_LOCK, 0),
+			BTF_MEMBER_ENC(BAR, LIST_HEAD, 64),
+			BTF_DECL_TAG_ENC(CONT_BAR_BAR, 5, 1),
+		};
+		check_btf(types, sizeof(types), -ENOENT);
+	}
+	if (test__start_subtest("btf: missing node")) {
+		u32 types[] = {
+			INIT_BTF_TILL_4
+			BTF_TYPE_ENC(FOO, BTF_INFO_ENC(BTF_KIND_STRUCT, 0, 2), 24), /* [5] */
+			BTF_MEMBER_ENC(BAZ, SPIN_LOCK, 0),
+			BTF_MEMBER_ENC(BAR, LIST_HEAD, 64),
+			BTF_DECL_TAG_ENC(CONT_FOO_FOO, 5, 1),
+		};
+		check_btf(types, sizeof(types), -ENOENT);
+	}
+	if (test__start_subtest("btf: node incorrect type")) {
+		u32 types[] = {
+			INIT_BTF_TILL_4
+			BTF_TYPE_ENC(FOO, BTF_INFO_ENC(BTF_KIND_STRUCT, 0, 2), 20), /* [5] */
+			BTF_MEMBER_ENC(BAR, LIST_HEAD, 0),
+			BTF_MEMBER_ENC(BAR, SPIN_LOCK, 128),
+			BTF_DECL_TAG_ENC(CONT_BAZ_BAZ, 5, 0),
+			BTF_TYPE_ENC(BAZ, BTF_INFO_ENC(BTF_KIND_STRUCT, 0, 1), 4),
+			BTF_MEMBER_ENC(BAZ, SPIN_LOCK, 0),
+		};
+		check_btf(types, sizeof(types), -EINVAL);
+	}
+	if (test__start_subtest("btf: multiple bpf_list_node with name foo")) {
+		u32 types[] = {
+			INIT_BTF_TILL_4
+			BTF_TYPE_ENC(FOO, BTF_INFO_ENC(BTF_KIND_STRUCT, 0, 3), 52), /* [5] */
+			BTF_MEMBER_ENC(BAR, LIST_HEAD, 0),
+			BTF_MEMBER_ENC(FOO, LIST_NODE, 128),
+			BTF_MEMBER_ENC(FOO, LIST_NODE, 256),
+			BTF_MEMBER_ENC(BAZ, SPIN_LOCK, 384),
+			BTF_DECL_TAG_ENC(CONT_FOO_FOO, 5, 0),
+		};
+		check_btf(types, sizeof(types), -EINVAL);
+	}
+	if (test__start_subtest("btf: owning | owned AA cycle")) {
+		u32 types[] = {
+			INIT_BTF_TILL_4
+			BTF_TYPE_ENC(FOO, BTF_INFO_ENC(BTF_KIND_STRUCT, 0, 3), 36), /* [5] */
+			BTF_MEMBER_ENC(BAR, LIST_HEAD, 0),
+			BTF_MEMBER_ENC(FOO, LIST_NODE, 128),
+			BTF_MEMBER_ENC(BAZ, SPIN_LOCK, 256),
+			BTF_DECL_TAG_ENC(CONT_FOO_FOO, 5, 0),
+		};
+		check_btf(types, sizeof(types), -ELOOP);
+	}
+	if (test__start_subtest("btf: owning | owned ABA cycle")) {
+		u32 types[] = {
+			INIT_BTF_TILL_4
+			BTF_TYPE_ENC(FOO, BTF_INFO_ENC(BTF_KIND_STRUCT, 0, 3), 36), /* [5] */
+			BTF_MEMBER_ENC(BAR, LIST_HEAD, 0),
+			BTF_MEMBER_ENC(FOO, LIST_NODE, 128),
+			BTF_MEMBER_ENC(BAZ, SPIN_LOCK, 256),
+			BTF_DECL_TAG_ENC(CONT_BAR_BAR, 5, 0),			    /* [6] */
+			BTF_TYPE_ENC(BAR, BTF_INFO_ENC(BTF_KIND_STRUCT, 0, 3), 36), /* [7] */
+			BTF_MEMBER_ENC(FOO, LIST_HEAD, 0),
+			BTF_MEMBER_ENC(BAR, LIST_NODE, 128),
+			BTF_MEMBER_ENC(BAZ, SPIN_LOCK, 256),
+			BTF_DECL_TAG_ENC(CONT_FOO_FOO, 7, 0),
+		};
+		check_btf(types, sizeof(types), -ELOOP);
+	}
+	if (test__start_subtest("btf: owning -> owned")) {
+		u32 types[] = {
+			INIT_BTF_TILL_4
+			BTF_TYPE_ENC(FOO, BTF_INFO_ENC(BTF_KIND_STRUCT, 0, 2), 20), /* [5] */
+			BTF_MEMBER_ENC(BAR, LIST_HEAD, 0),
+			BTF_MEMBER_ENC(BAR, SPIN_LOCK, 128),
+			BTF_DECL_TAG_ENC(CONT_BAZ_BAZ, 5, 0),
+			BTF_TYPE_ENC(BAZ, BTF_INFO_ENC(BTF_KIND_STRUCT, 0, 1), 16),
+			BTF_MEMBER_ENC(BAZ, LIST_NODE, 0),
+		};
+		check_btf(types, sizeof(types), 0);
+	}
+	if (test__start_subtest("btf: owning -> owning | owned -> owned")) {
+		u32 types[] = {
+			INIT_BTF_TILL_4
+			BTF_TYPE_ENC(FOO, BTF_INFO_ENC(BTF_KIND_STRUCT, 0, 2), 20), /* [5] */
+			BTF_MEMBER_ENC(BAR, LIST_HEAD, 0),
+			BTF_MEMBER_ENC(BAZ, SPIN_LOCK, 128),
+			BTF_DECL_TAG_ENC(CONT_BAR_BAR, 5, 0),			    /* [6] */
+			BTF_TYPE_ENC(BAR, BTF_INFO_ENC(BTF_KIND_STRUCT, 0, 3), 36), /* [7] */
+			BTF_MEMBER_ENC(FOO, LIST_HEAD, 0),
+			BTF_MEMBER_ENC(BAR, LIST_NODE, 128),
+			BTF_MEMBER_ENC(BAZ, SPIN_LOCK, 256),
+			BTF_DECL_TAG_ENC(CONT_BAZ_BAZ, 7, 0),
+			BTF_TYPE_ENC(BAZ, BTF_INFO_ENC(BTF_KIND_STRUCT, 0, 1), 16),
+			BTF_MEMBER_ENC(BAZ, LIST_NODE, 0),
+		};
+		check_btf(types, sizeof(types), 0);
+	}
+	if (test__start_subtest("btf: owning | owned -> owning | owned -> owned")) {
+		u32 types[] = {
+			INIT_BTF_TILL_4
+			BTF_TYPE_ENC(FOO, BTF_INFO_ENC(BTF_KIND_STRUCT, 0, 3), 36), /* [5] */
+			BTF_MEMBER_ENC(BAR, LIST_HEAD, 0),
+			BTF_MEMBER_ENC(FOO, LIST_NODE, 128),
+			BTF_MEMBER_ENC(BAZ, SPIN_LOCK, 256),
+			BTF_DECL_TAG_ENC(CONT_BAR_BAR, 5, 0),			    /* [6] */
+			BTF_TYPE_ENC(BAR, BTF_INFO_ENC(BTF_KIND_STRUCT, 0, 3), 36), /* [7] */
+			BTF_MEMBER_ENC(FOO, LIST_HEAD, 0),
+			BTF_MEMBER_ENC(BAR, LIST_NODE, 128),
+			BTF_MEMBER_ENC(BAZ, SPIN_LOCK, 256),
+			BTF_DECL_TAG_ENC(CONT_BAZ_BAZ, 7, 0),
+			BTF_TYPE_ENC(BAZ, BTF_INFO_ENC(BTF_KIND_STRUCT, 0, 1), 16),
+			BTF_MEMBER_ENC(BAZ, LIST_NODE, 0),
+		};
+		check_btf(types, sizeof(types), -ELOOP);
+	}
+	if (test__start_subtest("btf: owning -> owning | owned -> owning | owned -> owned")) {
+		u32 types[] = {
+			INIT_BTF_TILL_4
+			BTF_TYPE_ENC(FOO, BTF_INFO_ENC(BTF_KIND_STRUCT, 0, 2), 20), /* [5] */
+			BTF_MEMBER_ENC(BAR, LIST_HEAD, 0),
+			BTF_MEMBER_ENC(BAZ, SPIN_LOCK, 128),
+			BTF_DECL_TAG_ENC(CONT_BAR_BAR, 5, 0),			    /* [6] */
+			BTF_TYPE_ENC(BAR, BTF_INFO_ENC(BTF_KIND_STRUCT, 0, 3), 36), /* [7] */
+			BTF_MEMBER_ENC(FOO, LIST_HEAD, 0),
+			BTF_MEMBER_ENC(BAR, LIST_NODE, 128),
+			BTF_MEMBER_ENC(BAZ, SPIN_LOCK, 256),
+			BTF_DECL_TAG_ENC(CONT_BAZ_BAZ, 7, 0),
+			BTF_TYPE_ENC(BAZ, BTF_INFO_ENC(BTF_KIND_STRUCT, 0, 3), 36), /* [9] */
+			BTF_MEMBER_ENC(BAR, LIST_HEAD, 0),
+			BTF_MEMBER_ENC(BAZ, LIST_NODE, 128),
+			BTF_MEMBER_ENC(FOO, SPIN_LOCK, 256),
+			BTF_DECL_TAG_ENC(CONT_BAM_BAM, 9, 0),
+			BTF_TYPE_ENC(BAM, BTF_INFO_ENC(BTF_KIND_STRUCT, 0, 1), 16),
+			BTF_MEMBER_ENC(BAM, LIST_NODE, 0),
+		};
+		check_btf(types, sizeof(types), -ELOOP);
+	}
+}
+
 void test_linked_list(void)
 {
 	int i;
@@ -243,6 +513,7 @@ void test_linked_list(void)
 		test_linked_list_fail_prog(linked_list_fail_tests[i].prog_name,
 					   linked_list_fail_tests[i].err_msg);
 	}
+	test_btf();
 	test_linked_list_success(PUSH_POP, false);
 	test_linked_list_success(PUSH_POP, true);
 	test_linked_list_success(PUSH_POP_MULT, false);
