[RFC,21/21] lkdtm: Extend list corruption checks
diff mbox series

Message ID 20200324153643.15527-22-will@kernel.org
State New
Headers show
Series
  • Improve list integrity checking
Related show

Commit Message

Will Deacon March 24, 2020, 3:36 p.m. UTC
Although lkdtm has a couple of simple tests for triggering list
corruption, there are plenty of things it doesn't trigger, particularly
now that we have forms of integrity checking for other list types.

Extend lkdtm to check a variety of list insertion and deletion routines.

Cc: Kees Cook <keescook@chromium.org>
Signed-off-by: Will Deacon <will@kernel.org>
---
 drivers/misc/lkdtm/Makefile             |   1 +
 drivers/misc/lkdtm/bugs.c               |  68 ----
 drivers/misc/lkdtm/core.c               |  31 +-
 drivers/misc/lkdtm/list.c               | 489 ++++++++++++++++++++++++
 drivers/misc/lkdtm/lkdtm.h              |  33 +-
 tools/testing/selftests/lkdtm/tests.txt |  31 +-
 6 files changed, 579 insertions(+), 74 deletions(-)
 create mode 100644 drivers/misc/lkdtm/list.c

Patch
diff mbox series

diff --git a/drivers/misc/lkdtm/Makefile b/drivers/misc/lkdtm/Makefile
index c70b3822013f..833c6f7c78a3 100644
--- a/drivers/misc/lkdtm/Makefile
+++ b/drivers/misc/lkdtm/Makefile
@@ -10,6 +10,7 @@  lkdtm-$(CONFIG_LKDTM)		+= rodata_objcopy.o
 lkdtm-$(CONFIG_LKDTM)		+= usercopy.o
 lkdtm-$(CONFIG_LKDTM)		+= stackleak.o
 lkdtm-$(CONFIG_LKDTM)		+= cfi.o
+lkdtm-$(CONFIG_LKDTM)		+= list.o
 
 KASAN_SANITIZE_stackleak.o	:= n
 KCOV_INSTRUMENT_rodata.o	:= n
diff --git a/drivers/misc/lkdtm/bugs.c b/drivers/misc/lkdtm/bugs.c
index de87693cf557..de0c54c8fac3 100644
--- a/drivers/misc/lkdtm/bugs.c
+++ b/drivers/misc/lkdtm/bugs.c
@@ -6,7 +6,6 @@ 
  * test source files.
  */
 #include "lkdtm.h"
-#include <linux/list.h>
 #include <linux/sched.h>
 #include <linux/sched/signal.h>
 #include <linux/sched/task_stack.h>
@@ -16,10 +15,6 @@ 
 #include <asm/desc.h>
 #endif
 
-struct lkdtm_list {
-	struct list_head node;
-};
-
 /*
  * Make sure our attempts to over run the kernel stack doesn't trigger
  * a compiler warning when CONFIG_FRAME_WARN is set. Then make sure we
@@ -175,69 +170,6 @@  void lkdtm_HUNG_TASK(void)
 	schedule();
 }
 
-void lkdtm_CORRUPT_LIST_ADD(void)
-{
-	/*
-	 * Initially, an empty list via LIST_HEAD:
-	 *	test_head.next = &test_head
-	 *	test_head.prev = &test_head
-	 */
-	LIST_HEAD(test_head);
-	struct lkdtm_list good, bad;
-	void *target[2] = { };
-	void *redirection = &target;
-
-	pr_info("attempting good list addition\n");
-
-	/*
-	 * Adding to the list performs these actions:
-	 *	test_head.next->prev = &good.node
-	 *	good.node.next = test_head.next
-	 *	good.node.prev = test_head
-	 *	test_head.next = good.node
-	 */
-	list_add(&good.node, &test_head);
-
-	pr_info("attempting corrupted list addition\n");
-	/*
-	 * In simulating this "write what where" primitive, the "what" is
-	 * the address of &bad.node, and the "where" is the address held
-	 * by "redirection".
-	 */
-	test_head.next = redirection;
-	list_add(&bad.node, &test_head);
-
-	if (target[0] == NULL && target[1] == NULL)
-		pr_err("Overwrite did not happen, but no BUG?!\n");
-	else
-		pr_err("list_add() corruption not detected!\n");
-}
-
-void lkdtm_CORRUPT_LIST_DEL(void)
-{
-	LIST_HEAD(test_head);
-	struct lkdtm_list item;
-	void *target[2] = { };
-	void *redirection = &target;
-
-	list_add(&item.node, &test_head);
-
-	pr_info("attempting good list removal\n");
-	list_del(&item.node);
-
-	pr_info("attempting corrupted list removal\n");
-	list_add(&item.node, &test_head);
-
-	/* As with the list_add() test above, this corrupts "next". */
-	item.node.next = redirection;
-	list_del(&item.node);
-
-	if (target[0] == NULL && target[1] == NULL)
-		pr_err("Overwrite did not happen, but no BUG?!\n");
-	else
-		pr_err("list_del() corruption not detected!\n");
-}
-
 /* Test if unbalanced set_fs(KERNEL_DS)/set_fs(USER_DS) check exists. */
 void lkdtm_CORRUPT_USER_DS(void)
 {
diff --git a/drivers/misc/lkdtm/core.c b/drivers/misc/lkdtm/core.c
index ee0d6e721441..28aace88474f 100644
--- a/drivers/misc/lkdtm/core.c
+++ b/drivers/misc/lkdtm/core.c
@@ -110,8 +110,6 @@  static const struct crashtype crashtypes[] = {
 	CRASHTYPE(EXHAUST_STACK),
 	CRASHTYPE(CORRUPT_STACK),
 	CRASHTYPE(CORRUPT_STACK_STRONG),
-	CRASHTYPE(CORRUPT_LIST_ADD),
-	CRASHTYPE(CORRUPT_LIST_DEL),
 	CRASHTYPE(CORRUPT_USER_DS),
 	CRASHTYPE(STACK_GUARD_PAGE_LEADING),
 	CRASHTYPE(STACK_GUARD_PAGE_TRAILING),
@@ -174,6 +172,35 @@  static const struct crashtype crashtypes[] = {
 #ifdef CONFIG_X86_32
 	CRASHTYPE(DOUBLE_FAULT),
 #endif
+	CRASHTYPE(LIST_ADD_NEXT_CORRUPTION),
+	CRASHTYPE(LIST_ADD_PREV_CORRUPTION),
+	CRASHTYPE(LIST_ADD_TWICE),
+	CRASHTYPE(LIST_DEL_NEXT_CORRUPTION),
+	CRASHTYPE(LIST_DEL_PREV_CORRUPTION),
+	CRASHTYPE(LIST_DEL_TWICE),
+	CRASHTYPE(HLIST_ADD_HEAD_CORRUPTION),
+	CRASHTYPE(HLIST_ADD_HEAD_TWICE),
+	CRASHTYPE(HLIST_ADD_BEFORE_CORRUPTION),
+	CRASHTYPE(HLIST_ADD_BEFORE_TWICE),
+	CRASHTYPE(HLIST_ADD_BEHIND_CORRUPTION),
+	CRASHTYPE(HLIST_ADD_BEHIND_TWICE),
+	CRASHTYPE(HLIST_DEL_PREV_CORRUPTION),
+	CRASHTYPE(HLIST_DEL_NEXT_CORRUPTION),
+	CRASHTYPE(HLIST_DEL_TWICE),
+	CRASHTYPE(HLIST_NULLS_ADD_HEAD_CORRUPTION),
+	CRASHTYPE(HLIST_NULLS_ADD_HEAD_TWICE),
+	CRASHTYPE(HLIST_NULLS_DEL_PREV_CORRUPTION),
+	CRASHTYPE(HLIST_NULLS_DEL_NEXT_CORRUPTION),
+	CRASHTYPE(HLIST_NULLS_DEL_TWICE),
+	CRASHTYPE(HLIST_BL_ADD_HEAD_UNLOCKED),
+	CRASHTYPE(HLIST_BL_ADD_HEAD_NODE_LOCKED),
+	CRASHTYPE(HLIST_BL_ADD_HEAD_CORRUPTION),
+	CRASHTYPE(HLIST_BL_ADD_HEAD_TWICE),
+	CRASHTYPE(HLIST_BL_DEL_NODE_LOCKED),
+	CRASHTYPE(HLIST_BL_DEL_NEXT_LOCKED),
+	CRASHTYPE(HLIST_BL_DEL_PREV_CORRUPTION),
+	CRASHTYPE(HLIST_BL_DEL_NEXT_CORRUPTION),
+	CRASHTYPE(HLIST_BL_DEL_TWICE),
 };
 
 
diff --git a/drivers/misc/lkdtm/list.c b/drivers/misc/lkdtm/list.c
new file mode 100644
index 000000000000..e62a7de51eac
--- /dev/null
+++ b/drivers/misc/lkdtm/list.c
@@ -0,0 +1,489 @@ 
+// SPDX-License-Identifier: GPL-2.0
+
+/*
+ * This is for all the tests related to list integrity checking.
+ */
+
+#include "lkdtm.h"
+#include <linux/list.h>
+
+#define LIST_REDIR_BUF(type, name)				\
+union {								\
+	type	list;						\
+	char	buf[sizeof(type)];				\
+} name = { }
+
+static void __check_list_redir_buf(const char *str, char *buf, size_t sz)
+{
+	for (; sz && !buf[sz - 1]; sz--);
+	if (sz)
+		pr_err("%s: corruption not detected!\n", str);
+	else
+		pr_err("%s: overwrite did not happen, but no BUG?!\n", str);
+}
+
+#define check_list_redir_buf(s, b)	\
+	__check_list_redir_buf((s), (b).buf, sizeof(b.buf))
+
+void lkdtm_LIST_ADD_NEXT_CORRUPTION(void)
+{
+	LIST_HEAD(head);
+	LIST_HEAD(mid);
+	LIST_HEAD(tail);
+	LIST_REDIR_BUF(struct list_head, target);
+
+	list_add(&tail, &head);
+	head.next = &target.list;
+	list_add(&mid, &head);
+	check_list_redir_buf("list_add()", target);
+}
+
+void lkdtm_LIST_ADD_PREV_CORRUPTION(void)
+{
+	LIST_HEAD(head);
+	LIST_HEAD(mid);
+	LIST_HEAD(tail);
+	LIST_REDIR_BUF(struct list_head, target);
+
+	list_add(&tail, &head);
+	tail.prev = &target.list;
+	list_add_tail(&mid, &tail);
+	check_list_redir_buf("list_add()", target);
+}
+
+void lkdtm_LIST_ADD_TWICE(void)
+{
+	LIST_HEAD(head);
+	LIST_HEAD(mid);
+	LIST_HEAD(tail);
+
+	list_add(&tail, &head);
+	mid = tail;
+	list_add(&tail, &head);
+
+	if (mid.prev != tail.prev || mid.next != tail.next)
+		pr_err("list_add(): adding twice not detected!\n");
+	else
+		pr_err("list_add(): could not add twice, but no BUG?!\n");
+}
+
+void lkdtm_LIST_DEL_NEXT_CORRUPTION(void)
+{
+	LIST_HEAD(head);
+	LIST_HEAD(mid);
+	LIST_HEAD(tail);
+	LIST_REDIR_BUF(struct list_head, target);
+
+	list_add(&tail, &head);
+	list_add(&mid, &head);
+	mid.next = &target.list;
+	list_del(&mid);
+	check_list_redir_buf("list_del()", target);
+}
+
+void lkdtm_LIST_DEL_PREV_CORRUPTION(void)
+{
+	LIST_HEAD(head);
+	LIST_HEAD(mid);
+	LIST_HEAD(tail);
+	LIST_REDIR_BUF(struct list_head, target);
+
+	list_add(&tail, &head);
+	list_add(&mid, &head);
+	mid.prev = &target.list;
+	list_del(&mid);
+	check_list_redir_buf("list_del()", target);
+}
+
+void lkdtm_LIST_DEL_TWICE(void)
+{
+	LIST_HEAD(head);
+	LIST_HEAD(tail);
+
+	list_add(&tail, &head);
+	list_del(&tail);
+
+	if (tail.prev != LIST_POISON2 || tail.next != LIST_POISON1) {
+		pr_err("list_del(): prev/next pointers not poisoned!\n");
+	} else {
+		list_del(&tail);
+		pr_err("list_del(): could not delete twice, but no BUG?!\n");
+	}
+}
+
+void lkdtm_HLIST_ADD_HEAD_CORRUPTION(void)
+{
+	HLIST_HEAD(head);
+	LIST_REDIR_BUF(struct hlist_node, target);
+	struct hlist_node mid, tail;
+
+	INIT_HLIST_NODE(&mid);
+	INIT_HLIST_NODE(&tail);
+	hlist_add_head(&tail, &head);
+	head.first = &target.list;
+	hlist_add_head(&mid, &head);
+	check_list_redir_buf("hlist_add_head()", target);
+}
+
+void lkdtm_HLIST_ADD_HEAD_TWICE(void)
+{
+	HLIST_HEAD(head);
+	struct hlist_node mid, tail;
+
+	INIT_HLIST_NODE(&mid);
+	INIT_HLIST_NODE(&tail);
+	hlist_add_head(&tail, &head);
+	mid = tail;
+	hlist_add_head(&tail, &head);
+
+	if (mid.next != tail.next || mid.pprev != tail.pprev)
+		pr_err("hlist_add_head(): adding twice not detected!\n");
+	else
+		pr_err("hlist_add_head(): could not add twice, but no BUG?!\n");
+}
+
+void lkdtm_HLIST_ADD_BEFORE_CORRUPTION(void)
+{
+	HLIST_HEAD(head);
+	LIST_REDIR_BUF(struct hlist_node, target);
+	struct hlist_node mid, tail;
+
+	INIT_HLIST_NODE(&mid);
+	INIT_HLIST_NODE(&tail);
+	hlist_add_head(&tail, &head);
+	tail.pprev = &target.list.next;
+	hlist_add_before(&mid, &tail);
+	check_list_redir_buf("hlist_add_before()", target);
+}
+
+void lkdtm_HLIST_ADD_BEFORE_TWICE(void)
+{
+	HLIST_HEAD(head);
+	struct hlist_node mid, tail;
+
+	INIT_HLIST_NODE(&mid);
+	INIT_HLIST_NODE(&tail);
+	hlist_add_head(&tail, &head);
+	mid = tail;
+	hlist_add_before(&tail, &tail);
+
+	if (mid.next != tail.next || mid.pprev != tail.pprev)
+		pr_err("hlist_add_before(): adding twice not detected!\n");
+	else
+		pr_err("hlist_add_before(): could not add twice, but no BUG?!\n");
+}
+
+void lkdtm_HLIST_ADD_BEHIND_CORRUPTION(void)
+{
+	HLIST_HEAD(head);
+	LIST_REDIR_BUF(struct hlist_node, target);
+	struct hlist_node mid, tail;
+
+	INIT_HLIST_NODE(&mid);
+	INIT_HLIST_NODE(&tail);
+	hlist_add_head(&mid, &head);
+	mid.next = &target.list;
+	hlist_add_behind(&tail, &mid);
+	check_list_redir_buf("hlist_add_behind()", target);
+}
+
+void lkdtm_HLIST_ADD_BEHIND_TWICE(void)
+{
+	HLIST_HEAD(head);
+	struct hlist_node mid, tail;
+
+	INIT_HLIST_NODE(&mid);
+	INIT_HLIST_NODE(&tail);
+	hlist_add_head(&tail, &head);
+	mid = tail;
+	hlist_add_behind(&tail, &tail);
+
+	if (mid.next != tail.next || mid.pprev != tail.pprev)
+		pr_err("hlist_add_behind(): adding twice not detected!\n");
+	else
+		pr_err("hlist_add_behind(): could not add twice, but no BUG?!\n");
+}
+
+void lkdtm_HLIST_DEL_PREV_CORRUPTION(void)
+{
+	HLIST_HEAD(head);
+	LIST_REDIR_BUF(struct hlist_node, target);
+	struct hlist_node mid, tail;
+
+	INIT_HLIST_NODE(&mid);
+	INIT_HLIST_NODE(&tail);
+	hlist_add_head(&tail, &head);
+	hlist_add_head(&mid, &head);
+	mid.pprev = &target.list.next;
+	hlist_del(&mid);
+	check_list_redir_buf("hlist_del()", target);
+}
+
+void lkdtm_HLIST_DEL_NEXT_CORRUPTION(void)
+{
+	HLIST_HEAD(head);
+	LIST_REDIR_BUF(struct hlist_node, target);
+	struct hlist_node mid, tail;
+
+	INIT_HLIST_NODE(&mid);
+	INIT_HLIST_NODE(&tail);
+	hlist_add_head(&tail, &head);
+	hlist_add_head(&mid, &head);
+	mid.next = &target.list;
+	hlist_del(&mid);
+	check_list_redir_buf("hlist_del()", target);
+}
+
+void lkdtm_HLIST_DEL_TWICE(void)
+{
+	HLIST_HEAD(head);
+	struct hlist_node tail;
+
+	INIT_HLIST_NODE(&tail);
+	hlist_add_head(&tail, &head);
+	hlist_del(&tail);
+
+	if (tail.next != LIST_POISON1 || tail.pprev != LIST_POISON2) {
+		pr_err("hlist_del(): pprev/next pointers not poisoned!\n");
+	} else {
+		hlist_del(&tail);
+		pr_err("hlist_del(): could not delete twice, but no BUG?!\n");
+	}
+}
+
+#include <linux/list_nulls.h>
+
+void lkdtm_HLIST_NULLS_ADD_HEAD_CORRUPTION(void)
+{
+	LIST_REDIR_BUF(struct hlist_nulls_node, target);
+	struct hlist_nulls_head head;
+	struct hlist_nulls_node mid, tail;
+
+	INIT_HLIST_NULLS_HEAD(&head, 0);
+	hlist_nulls_add_head(&tail, &head);
+	head.first = &target.list;
+	hlist_nulls_add_head(&mid, &head);
+	check_list_redir_buf("hlist_nulls_add_head()", target);
+}
+
+void lkdtm_HLIST_NULLS_ADD_HEAD_TWICE(void)
+{
+	struct hlist_nulls_head head;
+	struct hlist_nulls_node mid, tail;
+
+	INIT_HLIST_NULLS_HEAD(&head, 0);
+	hlist_nulls_add_head(&tail, &head);
+	mid = tail;
+	hlist_nulls_add_head(&tail, &head);
+
+	if (mid.next != tail.next || mid.pprev != tail.pprev)
+		pr_err("hlist_nulls_add_head(): adding twice not detected!\n");
+	else
+		pr_err("hlist_nulls_add_head(): could not add twice, but no BUG?!\n");
+}
+
+void lkdtm_HLIST_NULLS_DEL_PREV_CORRUPTION(void)
+{
+	LIST_REDIR_BUF(struct hlist_nulls_node, target);
+	struct hlist_nulls_head head;
+	struct hlist_nulls_node mid, tail;
+
+	INIT_HLIST_NULLS_HEAD(&head, 0);
+	hlist_nulls_add_head(&tail, &head);
+	hlist_nulls_add_head(&mid, &head);
+	mid.pprev = &target.list.next;
+	hlist_nulls_del(&mid);
+	check_list_redir_buf("hlist_nulls_del()", target);
+}
+
+void lkdtm_HLIST_NULLS_DEL_NEXT_CORRUPTION(void)
+{
+	LIST_REDIR_BUF(struct hlist_nulls_node, target);
+	struct hlist_nulls_head head;
+	struct hlist_nulls_node mid, tail;
+
+	INIT_HLIST_NULLS_HEAD(&head, 0);
+	hlist_nulls_add_head(&tail, &head);
+	hlist_nulls_add_head(&mid, &head);
+	mid.next = &target.list;
+	hlist_nulls_del(&mid);
+	check_list_redir_buf("hlist_nulls_del()", target);
+}
+
+void lkdtm_HLIST_NULLS_DEL_TWICE(void)
+{
+	struct hlist_nulls_head head;
+	struct hlist_nulls_node tail;
+
+	INIT_HLIST_NULLS_HEAD(&head, 0);
+	hlist_nulls_add_head(&tail, &head);
+	hlist_nulls_del(&tail);
+
+	if (tail.next != LIST_POISON1 || tail.pprev != LIST_POISON2) {
+		pr_err("hlist_nulls_del(): pprev/next pointers not poisoned!\n");
+	} else {
+		hlist_nulls_del(&tail);
+		pr_err("hlist_nulls_del(): could not delete twice, but no BUG?!\n");
+	}
+}
+
+#include <linux/list_bl.h>
+
+void lkdtm_HLIST_BL_ADD_HEAD_UNLOCKED(void)
+{
+	LIST_REDIR_BUF(struct hlist_bl_node, target);
+	struct hlist_bl_head head;
+
+	INIT_HLIST_BL_HEAD(&head);
+	hlist_bl_add_head(&target.list, &head);
+	check_list_redir_buf("hlist_bl_add()", target);
+}
+
+void lkdtm_HLIST_BL_ADD_HEAD_NODE_LOCKED(void)
+{
+	LIST_REDIR_BUF(struct hlist_bl_node, target);
+	struct hlist_bl_head head;
+	unsigned long nval;
+
+	INIT_HLIST_BL_HEAD(&head);
+	hlist_bl_lock(&head);
+	nval = (unsigned long)&target.list | LIST_BL_LOCKMASK;
+	hlist_bl_add_head((struct hlist_bl_node *)nval, &head);
+	hlist_bl_unlock(&head);
+
+	check_list_redir_buf("hlist_bl_add()", target);
+}
+
+void lkdtm_HLIST_BL_ADD_HEAD_CORRUPTION(void)
+{
+	LIST_REDIR_BUF(struct hlist_bl_node, target);
+	struct hlist_bl_head head;
+	struct hlist_bl_node mid, tail;
+	unsigned long nval;
+
+	INIT_HLIST_BL_HEAD(&head);
+	hlist_bl_lock(&head);
+	hlist_bl_add_head(&tail, &head);
+	nval = (unsigned long)&target.list | LIST_BL_LOCKMASK;
+	head.first = (struct hlist_bl_node *)nval;
+	hlist_bl_add_head(&mid, &head);
+	hlist_bl_unlock(&head);
+
+	check_list_redir_buf("hlist_bl_add_head()", target);
+}
+
+void lkdtm_HLIST_BL_ADD_HEAD_TWICE(void)
+{
+	struct hlist_bl_head head;
+	struct hlist_bl_node mid, tail;
+
+	INIT_HLIST_BL_HEAD(&head);
+	hlist_bl_lock(&head);
+	hlist_bl_add_head(&tail, &head);
+	mid = tail;
+	hlist_bl_add_head(&tail, &head);
+	hlist_bl_unlock(&head);
+
+	if (mid.next != tail.next || mid.pprev != tail.pprev)
+		pr_err("hlist_bl_add_head(): adding twice not detected!\n");
+	else
+		pr_err("hlist_bl_add_head(): could not add twice, but no BUG?!\n");
+}
+
+void lkdtm_HLIST_BL_DEL_NODE_LOCKED(void)
+{
+	struct hlist_bl_head head;
+	struct hlist_bl_node mid, tail;
+	unsigned long nval;
+
+	INIT_HLIST_BL_HEAD(&head);
+	hlist_bl_lock(&head);
+	hlist_bl_add_head(&tail, &head);
+	hlist_bl_add_head(&mid, &head);
+	hlist_bl_unlock(&head);
+
+	nval = (unsigned long)&mid | LIST_BL_LOCKMASK;
+	/* hlist_bl_del() poisons ->next, so don't use it with a locked node */
+	__hlist_bl_del((struct hlist_bl_node *)nval);
+
+	if (head.first != &mid || tail.pprev != &mid.next)
+		pr_err("hlist_bl_del(): deleting locked node not detected!\n");
+	else
+		pr_err("hlist_bl_del(): could not delete locked node, but no BUG?!\n");
+}
+
+void lkdtm_HLIST_BL_DEL_NEXT_LOCKED(void)
+{
+	struct hlist_bl_head head;
+	struct hlist_bl_node mid, tail;
+	unsigned long nval;
+
+	INIT_HLIST_BL_HEAD(&head);
+	hlist_bl_lock(&head);
+	hlist_bl_add_head(&tail, &head);
+	hlist_bl_add_head(&mid, &head);
+	hlist_bl_unlock(&head);
+
+	nval = (unsigned long)mid.next | LIST_BL_LOCKMASK;
+	mid.next = (struct hlist_bl_node *)nval;
+	hlist_bl_del(&mid);
+
+	if (head.first != &mid || tail.pprev != &mid.next)
+		pr_err("hlist_bl_del(): deleting node with locked ->next not detected!\n");
+	else
+		pr_err("hlist_bl_del(): could not delete node with locked ->next, but no BUG?!\n");
+}
+
+void lkdtm_HLIST_BL_DEL_PREV_CORRUPTION(void)
+{
+	LIST_REDIR_BUF(struct hlist_bl_node, target);
+	struct hlist_bl_head head;
+	struct hlist_bl_node mid, tail;
+
+	INIT_HLIST_BL_HEAD(&head);
+	hlist_bl_lock(&head);
+	hlist_bl_add_head(&tail, &head);
+	hlist_bl_add_head(&mid, &head);
+	hlist_bl_unlock(&head);
+
+	mid.pprev = &target.list.next;
+	hlist_bl_del(&mid);
+	check_list_redir_buf("hlist_bl_del()", target);
+}
+
+void lkdtm_HLIST_BL_DEL_NEXT_CORRUPTION(void)
+{
+	LIST_REDIR_BUF(struct hlist_bl_node, target);
+	struct hlist_bl_head head;
+	struct hlist_bl_node mid, tail;
+
+	INIT_HLIST_BL_HEAD(&head);
+	hlist_bl_lock(&head);
+	hlist_bl_add_head(&tail, &head);
+	hlist_bl_add_head(&mid, &head);
+	hlist_bl_unlock(&head);
+
+	mid.next = &target.list;
+	hlist_bl_del(&mid);
+	check_list_redir_buf("hlist_bl_del()", target);
+}
+
+void lkdtm_HLIST_BL_DEL_TWICE(void)
+{
+	struct hlist_bl_head head;
+	struct hlist_bl_node tail;
+
+	INIT_HLIST_BL_HEAD(&head);
+	hlist_bl_lock(&head);
+	hlist_bl_add_head(&tail, &head);
+	hlist_bl_unlock(&head);
+
+	hlist_bl_del(&tail);
+
+	if (tail.next != LIST_POISON1 || tail.pprev != LIST_POISON2) {
+		pr_err("hlist_bl_del(): pprev/next pointers not poisoned!\n");
+	} else {
+		hlist_bl_del(&tail);
+		pr_err("hlist_bl_del(): could not delete twice, but no BUG?!\n");
+	}
+}
diff --git a/drivers/misc/lkdtm/lkdtm.h b/drivers/misc/lkdtm/lkdtm.h
index c56d23e37643..b5acce183473 100644
--- a/drivers/misc/lkdtm/lkdtm.h
+++ b/drivers/misc/lkdtm/lkdtm.h
@@ -22,8 +22,6 @@  void lkdtm_SOFTLOCKUP(void);
 void lkdtm_HARDLOCKUP(void);
 void lkdtm_SPINLOCKUP(void);
 void lkdtm_HUNG_TASK(void);
-void lkdtm_CORRUPT_LIST_ADD(void);
-void lkdtm_CORRUPT_LIST_DEL(void);
 void lkdtm_CORRUPT_USER_DS(void);
 void lkdtm_STACK_GUARD_PAGE_LEADING(void);
 void lkdtm_STACK_GUARD_PAGE_TRAILING(void);
@@ -102,4 +100,35 @@  void lkdtm_STACKLEAK_ERASING(void);
 /* cfi.c */
 void lkdtm_CFI_FORWARD_PROTO(void);
 
+/* list.c */
+void lkdtm_LIST_ADD_NEXT_CORRUPTION(void);
+void lkdtm_LIST_ADD_PREV_CORRUPTION(void);
+void lkdtm_LIST_ADD_TWICE(void);
+void lkdtm_LIST_DEL_NEXT_CORRUPTION(void);
+void lkdtm_LIST_DEL_PREV_CORRUPTION(void);
+void lkdtm_LIST_DEL_TWICE(void);
+void lkdtm_HLIST_ADD_HEAD_CORRUPTION(void);
+void lkdtm_HLIST_ADD_HEAD_TWICE(void);
+void lkdtm_HLIST_ADD_BEFORE_CORRUPTION(void);
+void lkdtm_HLIST_ADD_BEFORE_TWICE(void);
+void lkdtm_HLIST_ADD_BEHIND_CORRUPTION(void);
+void lkdtm_HLIST_ADD_BEHIND_TWICE(void);
+void lkdtm_HLIST_DEL_PREV_CORRUPTION(void);
+void lkdtm_HLIST_DEL_NEXT_CORRUPTION(void);
+void lkdtm_HLIST_DEL_TWICE(void);
+void lkdtm_HLIST_NULLS_ADD_HEAD_CORRUPTION(void);
+void lkdtm_HLIST_NULLS_ADD_HEAD_TWICE(void);
+void lkdtm_HLIST_NULLS_DEL_PREV_CORRUPTION(void);
+void lkdtm_HLIST_NULLS_DEL_NEXT_CORRUPTION(void);
+void lkdtm_HLIST_NULLS_DEL_TWICE(void);
+void lkdtm_HLIST_BL_ADD_HEAD_UNLOCKED(void);
+void lkdtm_HLIST_BL_ADD_HEAD_NODE_LOCKED(void);
+void lkdtm_HLIST_BL_ADD_HEAD_CORRUPTION(void);
+void lkdtm_HLIST_BL_ADD_HEAD_TWICE(void);
+void lkdtm_HLIST_BL_DEL_NODE_LOCKED(void);
+void lkdtm_HLIST_BL_DEL_NEXT_LOCKED(void);
+void lkdtm_HLIST_BL_DEL_PREV_CORRUPTION(void);
+void lkdtm_HLIST_BL_DEL_NEXT_CORRUPTION(void);
+void lkdtm_HLIST_BL_DEL_TWICE(void);
+
 #endif
diff --git a/tools/testing/selftests/lkdtm/tests.txt b/tools/testing/selftests/lkdtm/tests.txt
index 92ca32143ae5..93901506a72f 100644
--- a/tools/testing/selftests/lkdtm/tests.txt
+++ b/tools/testing/selftests/lkdtm/tests.txt
@@ -7,8 +7,6 @@  EXCEPTION
 #EXHAUST_STACK Corrupts memory on failure
 #CORRUPT_STACK Crashes entire system on success
 #CORRUPT_STACK_STRONG Crashes entire system on success
-CORRUPT_LIST_ADD list_add corruption
-CORRUPT_LIST_DEL list_del corruption
 CORRUPT_USER_DS Invalid address limit on user-mode return
 STACK_GUARD_PAGE_LEADING
 STACK_GUARD_PAGE_TRAILING
@@ -69,3 +67,32 @@  USERCOPY_KERNEL
 USERCOPY_KERNEL_DS
 STACKLEAK_ERASING OK: the rest of the thread stack is properly erased
 CFI_FORWARD_PROTO
+LIST_ADD_NEXT_CORRUPTION list_add corruption: next->prev should be prev
+LIST_ADD_PREV_CORRUPTION list_add corruption: prev->next should be next
+LIST_ADD_TWICE list_add double add:
+LIST_DEL_NEXT_CORRUPTION list_del corruption: next->prev should be
+LIST_DEL_PREV_CORRUPTION list_del corruption: prev->next should be
+LIST_DEL_TWICE list_del corruption:
+HLIST_ADD_HEAD_CORRUPTION hlist_add_head corruption: first->pprev should be &head->first
+HLIST_ADD_HEAD_TWICE hlist_add_head double add:
+HLIST_ADD_BEFORE_CORRUPTION hlist_add corruption: prev->next should be next
+HLIST_ADD_BEFORE_TWICE hlist_add double add:
+HLIST_ADD_BEHIND_CORRUPTION hlist_add corruption: next->pprev should be &prev->next
+HLIST_ADD_BEHIND_TWICE hlist_add double add:
+HLIST_DEL_PREV_CORRUPTION hlist_del corruption: prev->next should be
+HLIST_DEL_NEXT_CORRUPTION hlist_del corruption: next->pprev should be
+HLIST_DEL_TWICE hlist_del corruption:
+HLIST_NULLS_ADD_HEAD_CORRUPTION hlist_nulls_add_head corruption: first->pprev should be &head->first
+HLIST_NULLS_ADD_HEAD_TWICE hlist_nulls_add_head double add:
+HLIST_NULLS_DEL_PREV_CORRUPTION hlist_nulls_del corruption: prev->next should be
+HLIST_NULLS_DEL_NEXT_CORRUPTION hlist_nulls_del corruption: next->pprev should be
+HLIST_NULLS_DEL_TWICE hlist_nulls_del corruption:
+HLIST_BL_ADD_HEAD_UNLOCKED hlist_bl_add_head: head is unlocked
+#HLIST_BL_ADD_HEAD_NODE_LOCKED Corrupts memory on failure
+HLIST_BL_ADD_HEAD_CORRUPTION hlist_bl_add_head corruption: first->pprev should be &head->first
+HLIST_BL_ADD_HEAD_TWICE hlist_bl_add_head double add:
+#HLIST_BL_DEL_NODE_LOCKED Corrupts memory on failure
+#HLIST_BL_DEL_NEXT_LOCKED Corrupts memory on failure
+HLIST_BL_DEL_PREV_CORRUPTION hlist_bl_del corruption: prev->next should be
+HLIST_BL_DEL_NEXT_CORRUPTION hlist_bl_del corruption: next->pprev should be
+HLIST_BL_DEL_TWICE hlist_bl_del corruption: