The list integrity checks for 'hlist_bl' are missing a number of cases that are covered by other list implementations (e.g. 'hlist'), such as validating 'next' and 'pprev' pointers when adding and deleting nodes. Extend the list_bl integrity checks to bring them up to the same level as for other list implementations. Cc: Kees Cook <keescook@xxxxxxxxxxxx> Cc: Paul E. McKenney <paulmck@xxxxxxxxxx> Cc: Peter Zijlstra <peterz@xxxxxxxxxxxxx> Signed-off-by: Will Deacon <will@xxxxxxxxxx> --- lib/list_debug.c | 48 ++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 40 insertions(+), 8 deletions(-) diff --git a/lib/list_debug.c b/lib/list_debug.c index 9591fa6c9337..3be50b5c8014 100644 --- a/lib/list_debug.c +++ b/lib/list_debug.c @@ -7,6 +7,7 @@ #include <linux/export.h> #include <linux/list.h> +#include <linux/list_bl.h> #include <linux/bug.h> #include <linux/kernel.h> #include <linux/rculist.h> @@ -190,27 +191,58 @@ EXPORT_SYMBOL(__hlist_nulls_del_valid); bool __hlist_bl_add_head_valid(struct hlist_bl_node *new, struct hlist_bl_head *head) { + struct hlist_bl_node *first = hlist_bl_first(head); unsigned long hlock = (unsigned long)head->first & LIST_BL_LOCKMASK; unsigned long nlock = (unsigned long)new & LIST_BL_LOCKMASK; - return !(CHECK_DATA_CORRUPTION(nlock, + if (CHECK_DATA_CORRUPTION(nlock, "hlist_bl_add_head: node is locked\n") || - CHECK_DATA_CORRUPTION(hlock != LIST_BL_LOCKMASK, - "hlist_bl_add_head: head is unlocked\n")); + CHECK_DATA_CORRUPTION(hlock != LIST_BL_LOCKMASK, + "hlist_bl_add_head: head is unlocked\n")) + return false; + + if (CHECK_DATA_CORRUPTION(first && first->pprev != &head->first, + "hlist_bl_add_head corruption: first->pprev should be &head->first (%px), but was %px (first=%px)", + &head->first, first->pprev, first) || + CHECK_DATA_CORRUPTION(new == first, + "hlist_bl_add_head double add: new (%px) == first (%px)", + new, first)) + return false; + + return true; } EXPORT_SYMBOL(__hlist_bl_add_head_valid); bool __hlist_bl_del_valid(struct hlist_bl_node *node) { + struct hlist_bl_node *prev, *next = node->next; unsigned long nlock = (unsigned long)node & LIST_BL_LOCKMASK; + unsigned long pnext; - return !(CHECK_DATA_CORRUPTION(nlock, - "hlist_bl_del_valid: node locked") || - CHECK_DATA_CORRUPTION(node->next == LIST_POISON1, + if (CHECK_DATA_CORRUPTION(nlock, + "hlist_bl_del corruption: node is locked") || + CHECK_DATA_CORRUPTION(next == LIST_POISON1, "hlist_bl_del corruption, %px->next is LIST_POISON1 (%px)\n", node, LIST_POISON1) || - CHECK_DATA_CORRUPTION(node->pprev == LIST_POISON2, + CHECK_DATA_CORRUPTION(node->pprev == LIST_POISON2, "hlist_bl_del corruption, %px->pprev is LIST_POISON2 (%px)\n", - node, LIST_POISON2)); + node, LIST_POISON2)) + return false; + + BUILD_BUG_ON(offsetof(struct hlist_bl_node, next) != + offsetof(struct hlist_bl_head, first)); + prev = container_of(node->pprev, struct hlist_bl_node, next); + pnext = (unsigned long)prev->next & ~LIST_BL_LOCKMASK; + if (CHECK_DATA_CORRUPTION((unsigned long)next & LIST_BL_LOCKMASK, + "hlist_bl_del_corruption: node->next is locked") || + CHECK_DATA_CORRUPTION((struct hlist_bl_node *)pnext != node, + "hlist_bl_del corruption: prev->next should be %px, but was %lx\n", + node, pnext) || + CHECK_DATA_CORRUPTION(next && next->pprev != &node->next, + "hlist_bl_del corruption: next->pprev should be %px, but was %px\n", + &node->next, next->pprev)) + return false; + + return true; } EXPORT_SYMBOL(__hlist_bl_del_valid); -- 2.20.1