mirror of
https://github.com/torvalds/linux.git
synced 2024-12-14 23:25:54 +00:00
a31f9336ed
Currently, the entry->prev and entry->next are considered to be valid as long as they are not LIST_POISON{1|2}. However, the memory may be corrupted. The prev->next is invalid probably because 'prev' is invalid, not because prev->next's content is illegal. Unfortunately, the printk and its subfunctions will modify the registers that hold the 'prev' and 'next', and we don't see this valuable information in the BUG context. So print the contents of 'entry->prev' and 'entry->next'. Here's an example: list_del corruption. prev->next should be c0ecbf74, but was c08410dc kernel BUG at lib/list_debug.c:53! ... ... PC is at __list_del_entry_valid+0x58/0x98 LR is at __list_del_entry_valid+0x58/0x98 psr: 60000093 sp : c0ecbf30 ip : 00000000 fp : 00000001 r10: c08410d0 r9 : 00000001 r8 : c0825e0c r7 : 20000013 r6 : c08410d0 r5 : c0ecbf74 r4 : c0ecbf74 r3 : c0825d08 r2 : 00000000 r1 : df7ce6f4 r0 : 00000044 ... ... Stack: (0xc0ecbf30 to 0xc0ecc000) bf20: c0ecbf74 c0164fd0 c0ecbf70 c0165170 bf40: c0eca000 c0840c00 c0840c00 c0824500 c0825e0c c0189bbc c088f404 60000013 bf60: 60000013 c0e85100 000004ec 00000000 c0ebcdc0 c0ecbf74 c0ecbf74 c0825d08 bf80: c0e807c0 c018965c 00000000 c013f2a0 c0e807c0 c013f154 00000000 00000000 bfa0: 00000000 00000000 00000000 c01001b0 00000000 00000000 00000000 00000000 bfc0: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 bfe0: 00000000 00000000 00000000 00000000 00000013 00000000 00000000 00000000 (__list_del_entry_valid) from (__list_del_entry+0xc/0x20) (__list_del_entry) from (finish_swait+0x60/0x7c) (finish_swait) from (rcu_gp_kthread+0x560/0xa20) (rcu_gp_kthread) from (kthread+0x14c/0x15c) (kthread) from (ret_from_fork+0x14/0x24) At first, I thought prev->next was overwritten. Later, I carefully analyzed the RCU code and the disassembly code. The error occurred when deleting a node from the list rcu_state.gp_wq. The System.map shows that the address of rcu_state is c0840c00. Then I use gdb to obtain the offset of rcu_state.gp_wq.task_list. (gdb) p &((struct rcu_state *)0)->gp_wq.task_list $1 = (struct list_head *) 0x4dc Again: list_del corruption. prev->next should be c0ecbf74, but was c08410dc c08410dc = c0840c00 + 0x4dc = &rcu_state.gp_wq.task_list Because rcu_state.gp_wq has at most one node, so I can guess that "prev = &rcu_state.gp_wq.task_list". But for other scenes, maybe I wasn't so lucky, I cannot figure out the value of 'prev'. Link: https://lkml.kernel.org/r/20211207025835.1909-1-thunder.leizhen@huawei.com Signed-off-by: Zhen Lei <thunder.leizhen@huawei.com> Cc: "Paul E . McKenney" <paulmck@linux.vnet.ibm.com> Signed-off-by: Andrew Morton <akpm@linux-foundation.org> Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
63 lines
1.9 KiB
C
63 lines
1.9 KiB
C
/*
|
|
* Copyright 2006, Red Hat, Inc., Dave Jones
|
|
* Released under the General Public License (GPL).
|
|
*
|
|
* This file contains the linked list validation for DEBUG_LIST.
|
|
*/
|
|
|
|
#include <linux/export.h>
|
|
#include <linux/list.h>
|
|
#include <linux/bug.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/rculist.h>
|
|
|
|
/*
|
|
* Check that the data structures for the list manipulations are reasonably
|
|
* valid. Failures here indicate memory corruption (and possibly an exploit
|
|
* attempt).
|
|
*/
|
|
|
|
bool __list_add_valid(struct list_head *new, struct list_head *prev,
|
|
struct list_head *next)
|
|
{
|
|
if (CHECK_DATA_CORRUPTION(next->prev != prev,
|
|
"list_add corruption. next->prev should be prev (%px), but was %px. (next=%px).\n",
|
|
prev, next->prev, next) ||
|
|
CHECK_DATA_CORRUPTION(prev->next != next,
|
|
"list_add corruption. prev->next should be next (%px), but was %px. (prev=%px).\n",
|
|
next, prev->next, prev) ||
|
|
CHECK_DATA_CORRUPTION(new == prev || new == next,
|
|
"list_add double add: new=%px, prev=%px, next=%px.\n",
|
|
new, prev, next))
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
EXPORT_SYMBOL(__list_add_valid);
|
|
|
|
bool __list_del_entry_valid(struct list_head *entry)
|
|
{
|
|
struct list_head *prev, *next;
|
|
|
|
prev = entry->prev;
|
|
next = entry->next;
|
|
|
|
if (CHECK_DATA_CORRUPTION(next == LIST_POISON1,
|
|
"list_del corruption, %px->next is LIST_POISON1 (%px)\n",
|
|
entry, LIST_POISON1) ||
|
|
CHECK_DATA_CORRUPTION(prev == LIST_POISON2,
|
|
"list_del corruption, %px->prev is LIST_POISON2 (%px)\n",
|
|
entry, LIST_POISON2) ||
|
|
CHECK_DATA_CORRUPTION(prev->next != entry,
|
|
"list_del corruption. prev->next should be %px, but was %px. (prev=%px)\n",
|
|
entry, prev->next, prev) ||
|
|
CHECK_DATA_CORRUPTION(next->prev != entry,
|
|
"list_del corruption. next->prev should be %px, but was %px. (next=%px)\n",
|
|
entry, next->prev, next))
|
|
return false;
|
|
|
|
return true;
|
|
|
|
}
|
|
EXPORT_SYMBOL(__list_del_entry_valid);
|