雷柏鼠标接电脑开机自动重启

问题现象

市场反馈一个舆情,目前发现雷柏鼠标MT760及MT760mini这两款型号鼠标,通过接收器或者有线直连插到机器上开机会自动重启,无法进入系统。

问题日志

bug 分析

我们先查看日志,发现日志中指出内核panic,踩空指针了

我们先查看pc指针,pc 显示异常发生的指令地址,对应的函数和偏移

1
pc : uos_report_ai_key.isra.18+0x1c/0xf0

我们查看源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
static void uos_report_ai_key(struct hid_field *field, int status)
{
struct input_dev *input;
static int init_flag = 0;

input = field->hidinput->input;

if (!init_flag)
__set_bit(KEY_TOUCHPAD_OFF, input->keybit);

input_event(input, EV_MSC, MSC_SCAN, 0x700e1);
input_report_key(input, KEY_LEFTSHIFT, status);
input_sync(input);
input_event(input, EV_MSC, MSC_SCAN, 0x700e3);
input_report_key(input, KEY_LEFTMETA, status);
input_sync(input);
input_event(input, EV_MSC, MSC_SCAN, 0x700b0);
input_report_key(input, KEY_F23, status);
input_sync(input);
}

那么是哪里踩空指针了呢?

我们看到后面的偏移为0x1c/0xf0,那么0x1c对应哪一行呢?我们来gdb调试这个.o文件看一下到底在哪里

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
gdb drivers/hid/hid-core.o

GNU gdb (Debian 8.2.1-2+b1) 8.2.1
Copyright (C) 2018 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
Type "show copying" and "show warranty" for details.
This GDB was configured as "aarch64-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.

For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from drivers/hid/hid-core.o...done.
(gdb) disassemble /s uos_report_ai_key
Dump of assembler code for function uos_report_ai_key:
drivers/hid/hid-core.c:
1528 {
1529 struct input_dev *input;
1530 static int init_flag = 0;
1531
1532 input = field->hidinput->input;
0x0000000000000b58 <+0>: cbz x0, 0xc50 <uos_report_ai_key+248>
0x0000000000000b5c <+4>: ldr x0, [x0, #96]
0x0000000000000b60 <+8>: cbz x0, 0xc50 <uos_report_ai_key+248>
0x0000000000000b64 <+12>: stp x29, x30, [sp, #-32]!
0x0000000000000b68 <+16>: mov w3, #0xe1 // #225
0x0000000000000b6c <+20>: mov w2, #0x4 // #4
0x0000000000000b70 <+24>: mov x29, sp
0x0000000000000b74 <+28>: stp x19, x20, [sp, #16]
0x0000000000000b78 <+32>: movk w3, #0x7, lsl #16
0x0000000000000b7c <+36>: mov w20, w1

1533
1534 if (!init_flag)
1535 __set_bit(KEY_TOUCHPAD_OFF, input->keybit);
0x0000000000000b80 <+40>: ldr x19, [x0, #24]

./include/asm-generic/bitops/non-atomic.h:
21 *p |= mask;
0x0000000000000b84 <+44>: mov w1, w2
0x0000000000000b88 <+48>: mov x0, x19
0x0000000000000b8c <+52>: ldr x4, [x19, #112]
0x0000000000000b90 <+56>: orr x4, x4, #0x100000
0x0000000000000b94 <+60>: str x4, [x19, #112]

drivers/hid/hid-core.c:
1540 input_event(input, EV_MSC, MSC_SCAN, 0x700e3);
0x0000000000000b98 <+64>: bl 0xb98 <uos_report_ai_key+64>
1
2
3
0x1c 字节(十进制 28 字节)

0x0000000000000b74 <+28>: stp x19, x20, [sp, #16] #那么就是这一行了

那这一行汇编到底对应代码哪里?我们继续gdb

1
2
3
4
5
6
7
8
9
10
11
12
13
14
(gdb) info line *0x0000000000000b74
Line 1532 of "drivers/hid/hid-core.c" starts at address 0xb58 <uos_report_ai_key> and ends at 0xb80 <uos_report_ai_key+40>.
(gdb) list *0x0000000000000b74
0xb74 is in uos_report_ai_key (drivers/hid/hid-core.c:1532).
1527 static void uos_report_ai_key(struct hid_field *field, int status)
1528 {
1529 struct input_dev *input;
1530 static int init_flag = 0;
1531
1532 input = field->hidinput->input;
1533
1534 if (!init_flag)
1535 __set_bit(KEY_TOUCHPAD_OFF, input->keybit);
1536

我们通过命令info line *0x0000000000000b74或者list *0x0000000000000b74都可以看到这个代码对应哪一行

1
input = field->hidinput->input;

就是这一行代码,那么是哪里空指针我们继续看看

1
Unable to handle kernel NULL pointer dereference at virtual address 0000000000000018

我们看到日志里面有这一句话,也就是说表示内核试图访问虚拟地址 0x18(十进制24)处的数据,但因为这是无效的(通常是 NULL 指针相关)而导致了崩溃

1
input = field->hidinput->input;

这里有两次指针解引用:

  1. field->hidinput // 先拿到 hidinput 指针
  2. field->hidinput->input // 再解引用 input 成员

那我们来查看这2个函数成员的偏移

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
#gdb方法
(gdb) p (size_t)&((struct hid_field *)0)->hidinput
$4 = 96
(gdb) p (size_t)&((struct hid_input *)0)->input
$5 = 24

#pahole查看(更推荐) sudo apt install dwarves(pahole在这里面)
pahole -C hid_field drivers/hid/hid-core.o
struct hid_field {
unsigned int physical; /* 0 4 */
unsigned int logical; /* 4 4 */
unsigned int application; /* 8 4 */

/* XXX 4 bytes hole, try to pack */

struct hid_usage * usage; /* 16 8 */
unsigned int maxusage; /* 24 4 */
unsigned int flags; /* 28 4 */
unsigned int report_offset; /* 32 4 */
unsigned int report_size; /* 36 4 */
unsigned int report_count; /* 40 4 */
unsigned int report_type; /* 44 4 */
__s32 * value; /* 48 8 */
__s32 logical_minimum; /* 56 4 */
__s32 logical_maximum; /* 60 4 */
/* --- cacheline 1 boundary (64 bytes) --- */
__s32 physical_minimum; /* 64 4 */
__s32 physical_maximum; /* 68 4 */
__s32 unit_exponent; /* 72 4 */
unsigned int unit; /* 76 4 */
struct hid_report * report; /* 80 8 */
unsigned int index; /* 88 4 */

/* XXX 4 bytes hole, try to pack */

struct hid_input * hidinput; /* 96 8 */
__u16 dpad; /* 104 2 */

/* size: 112, cachelines: 2, members: 21 */
/* sum members: 98, holes: 2, sum holes: 8 */
/* padding: 6 */
/* last cacheline: 48 bytes */
};

pahole -C hid_input drivers/hid/hid-core.o
struct hid_input {
struct list_head list; /* 0 16 */
struct hid_report * report; /* 16 8 */
struct input_dev * input; /* 24 8 */
const char * name; /* 32 8 */
bool registered; /* 40 1 */

/* XXX 7 bytes hole, try to pack */

struct list_head reports; /* 48 16 */
/* --- cacheline 1 boundary (64 bytes) --- */
unsigned int application; /* 64 4 */

/* size: 72, cachelines: 2, members: 7 */
/* sum members: 61, holes: 1, sum holes: 7 */
/* padding: 4 */
/* last cacheline: 8 bytes */
};

最后我们看到struct input_dev *input; /* 24 8 */刚好就是24字节偏移,也就是这里踩空指针了

最后修复方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
static void uos_report_ai_key(struct hid_field *field, int status)
{
struct input_dev *input;
static int init_flag = 0;

if (!field->hidinput) //此处添加对field->hidinput的判空
return;

input = field->hidinput->input;

if (!init_flag)
__set_bit(KEY_TOUCHPAD_OFF, input->keybit);

input_event(input, EV_MSC, MSC_SCAN, 0x700e1);
input_report_key(input, KEY_LEFTSHIFT, status);
input_sync(input);
input_event(input, EV_MSC, MSC_SCAN, 0x700e3);
input_report_key(input, KEY_LEFTMETA, status);
input_sync(input);
input_event(input, EV_MSC, MSC_SCAN, 0x700b0);
input_report_key(input, KEY_F23, status);
input_sync(input);
}

雷柏鼠标接电脑开机自动重启
https://tomwithkernel.github.io/bug/雷柏鼠标插入后开机重启/
作者
Tom
发布于
2025年6月27日
更新于
2025年6月27日
许可协议