그나마 쉬운 버그를 찾아서 방황하던중 누군가 패치를 제출했지만, 승인이 되지 않은 memory leak 관련 버그를 찾았다. 도전할만 하다고 생각돼서 분석, 해결 시작.
Crash Report
[ 165.195884][T11142] sysfs: cannot create duplicate filename '/devices/virtual/tty/gsmtty1'
[ 165.196865][T11142] CPU: 1 PID: 11142 Comm: repro Tainted: G W 6.7.0-rc8-00055-g5eff55d725a4 #14
[ 165.198016][T11142] Hardware name: QEMU Ubuntu 24.04 PC v2 (i440FX + PIIX, arch_caps fix, 1996), BIOS 1.16.3-debian-1.16.3-2 04/01/2014
[ 165.199369][T11142] Call Trace:
[ 165.199697][T11142] <TASK>
[ 165.199962][T11142] dump_stack_lvl+0x72/0xa0
[ 165.200387][T11142] sysfs_warn_dup+0x64/0x70
[ 165.200799][T11142] sysfs_create_dir_ns+0x123/0x140
[ 165.201274][T11142] kobject_add_internal+0x104/0x340
[ 165.201753][T11142] kobject_add+0xd0/0x140
[ 165.202149][T11142] ? device_add+0x71a/0xc90
[ 165.202618][T11142] device_add+0x142/0xc90
[ 165.203011][T11142] tty_register_device_attr+0x14e/0x300
[ 165.203520][T11142] ? __kmalloc+0x4b/0x150
[ 165.203931][T11142] gsm_activate_mux+0xd7/0x1b0
[ 165.204404][T11142] gsmld_ioctl+0x1b8/0xa10
[ 165.204890][T11142] ? gsm_dlci_config+0x610/0x610
[ 165.205443][T11142] tty_ioctl+0x799/0xc60
[ 165.205904][T11142] ? do_vfs_ioctl+0x221/0xe50
[ 165.206432][T11142] ? __tty_hangup.part.0+0x450/0x450
[ 165.207007][T11142] __x64_sys_ioctl+0xf2/0x140
[ 165.207590][T11142] do_syscall_64+0x40/0x110
[ 165.208038][T11142] entry_SYSCALL_64_after_hwframe+0x63/0x6b
[ 165.208723][T11142] RIP: 0033:0x42339d
[ 165.209171][T11142] Code: b3 66 2e 0f 1f 84 00 00 00 00 00 66 90 f3 0f 1e fa 48 89 f8 48 89 f7 48 89 d6 48 89 ca 4d 89 c2 4d 89 c8 4c 8b 4c 24 08 0f 05 <48> 3d 01 f0 ff ff 73 01 c3 48 c7 c1 b8 ff ff ff f7 d8 64 89 01 48
[ 165.211270][T11142] RSP: 002b:00007f293d2801e8 EFLAGS: 00000246 ORIG_RAX: 0000000000000010
[ 165.212204][T11142] RAX: ffffffffffffffda RBX: 00007f293d280cdc RCX: 000000000042339d
[ 165.213081][T11142] RDX: 0000000020000000 RSI: 0000000040204706 RDI: 0000000000000003
[ 165.213944][T11142] RBP: 00007f293d280210 R08: 00007f293d2806c0 R09: 203a6362696c6720
[ 165.214663][T11142] R10: 0000000000000000 R11: 0000000000000246 R12: 00007f293d2806c0
[ 165.215381][T11142] R13: ffffffffffffffb8 R14: 000000000000006e R15: 00007ffcb726e820
[ 165.216099][T11142] </TASK>
[ 165.254497][T11142] kobject: kobject_add_internal failed for gsmtty1 with -EEXIST, don't try to register things with the same name in the same directory.
[ 170.847592][ T8183] kmemleak: 2 new suspected memory leaks (see /sys/kernel/debug/kmemleak)
BUG: memory leak
unreferenced object 0xffff88810ae44800 (size 1024):
comm "repro", pid 11079, jiffies 4294953772 (age 11.680s)
hex dump (first 32 bytes):
00 74 e4 0a 81 88 ff ff 00 00 00 00 00 00 00 00 .t..............
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
backtrace:
[<ffffffff8163532d>] __kmem_cache_alloc_node+0x2dd/0x3f0
[<ffffffff815801e5>] kmalloc_trace+0x25/0x90
[<ffffffff8280d577>] gsm_dlci_alloc+0x27/0x1f0
[<ffffffff8280d75a>] gsm_activate_mux+0x1a/0x1b0
[<ffffffff82813d88>] gsmld_ioctl+0x1b8/0xa10
[<ffffffff827f8ec9>] tty_ioctl+0x799/0xc60
[<ffffffff816c00b2>] __x64_sys_ioctl+0xf2/0x140
[<ffffffff84b5d100>] do_syscall_64+0x40/0x110
[<ffffffff84c0008b>] entry_SYSCALL_64_after_hwframe+0x63/0x6b
BUG: memory leak
unreferenced object 0xffff88800e0f0000 (size 4096):
comm "repro", pid 11079, jiffies 4294953772 (age 11.680s)
hex dump (first 32 bytes):
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
backtrace:
[<ffffffff8163532d>] __kmem_cache_alloc_node+0x2dd/0x3f0
[<ffffffff8158087b>] __kmalloc+0x4b/0x150
[<ffffffff8251b739>] __kfifo_alloc+0x89/0xe0
[<ffffffff8280d5c1>] gsm_dlci_alloc+0x71/0x1f0
[<ffffffff8280d75a>] gsm_activate_mux+0x1a/0x1b0
[<ffffffff82813d88>] gsmld_ioctl+0x1b8/0xa10
[<ffffffff827f8ec9>] tty_ioctl+0x799/0xc60
[<ffffffff816c00b2>] __x64_sys_ioctl+0xf2/0x140
[<ffffffff84b5d100>] do_syscall_64+0x40/0x110
[<ffffffff84c0008b>] entry_SYSCALL_64_after_hwframe+0x63/0x6b
크래시 리포트에서 볼 수 있듯이, 메모리 릭이 2군데에서 발생한다.
- 0xffff88810ae44800
- 0xffff88800e0f0000
gsm_dlci_alloc()함수에서 발생을 햇을것이라 추측하고 이 함수 내부에서 일단 메모리를 어디서 할당하는지 알아보았다.
static struct gsm_dlci *gsm_dlci_alloc(struct gsm_mux *gsm, int addr)
{
struct gsm_dlci *dlci = kzalloc(sizeof(struct gsm_dlci), GFP_ATOMIC);
if (dlci == NULL)
return NULL;
spin_lock_init(&dlci->lock);
mutex_init(&dlci->mutex);
if (kfifo_alloc(&dlci->fifo, TX_SIZE, GFP_KERNEL) < 0) {
kfree(dlci);
return NULL;
}
2개의 할당이 이뤄진다.
- kzalloc()에 의한 할당
- kfifo_alloc()에 의한 할당
이 2개의 할당에서 유발된 메모리 릭인지 확인하기 위해서, printk()를 심어 메모리 주소를 찍어보니, 메모리 릭이 발생한 주소와 동일했다. 따라서, 범인은 kzalloc()과 kfifo_alloc()에서 할당한 메모리가 어딘가에서 제대로 kfree()되지 않거나, 주소를 상실했을거라 생각한다.
해결 방법
첫번째로, gsm_dlci_alloc()함수의 모든 호출지에 printk()를 넣어서, repro를 돌렸을 때, 어떤 호출지에서 크래시가 발생하는지 찾아보니, repro에서는 gsm_activate_mux()에서만 gsm_dlci_alloc()을 호출하는 것을 알 수 있었다. 그리고 이 과정에서 크래시가 발생...
그래서 gsm_active_mux() 함수의 코드를 살펴보았다.
static int gsm_activate_mux(struct gsm_mux *gsm)
{
struct gsm_dlci *dlci;
int ret;
dlci = gsm_dlci_alloc(gsm, 0);
if (dlci == NULL)
return -ENOMEM;
if (gsm->encoding == GSM_BASIC_OPT)
gsm->receive = gsm0_receive;
else
gsm->receive = gsm1_receive;
ret = gsm_register_devices(gsm_tty_driver, gsm->num);
if (ret)
return ret;
gsm->has_devices = true;
gsm->dead = false; /* Tty opens are now permissible */
return 0;
}
dlci = gsm_dlci_alloc(gsm, 0)으로 할당을 받고, gsm_register_devices()가 실패하면 할당받았던 dlci를 해제하는 부분이 없다. 그래서 처음에는 이 부분이 원인이라고 특정하고 실패시 해제하도록 코드를 바꿔주었다.
ret = gsm_register_devices(gsm_tty_driver, gsm->num);
if (ret) {
gsm_dlci_free(&dlci->port);
return ret;
}
이렇게 해제 로직을 추가해주었다. 다시 repro를 돌리고 에러가 사라졌을거라고 생각했지만...
그대로였다.
그래서 다시 gsm_dlci_alloc()함수를 살펴보았다.
static struct gsm_dlci *gsm_dlci_alloc(struct gsm_mux *gsm, int addr)
{
struct gsm_dlci *dlci = kzalloc(sizeof(struct gsm_dlci), GFP_ATOMIC);
if (dlci == NULL)
return NULL;
spin_lock_init(&dlci->lock);
mutex_init(&dlci->mutex);
if (kfifo_alloc(&dlci->fifo, TX_SIZE, GFP_KERNEL) < 0) {
kfree(dlci);
return NULL;
}
skb_queue_head_init(&dlci->skb_list);
timer_setup(&dlci->t1, gsm_dlci_t1, 0);
tty_port_init(&dlci->port);
dlci->port.ops = &gsm_port_ops;
dlci->gsm = gsm;
dlci->addr = addr;
dlci->adaption = gsm->adaption;
dlci->mtu = gsm->mtu;
if (addr == 0)
dlci->prio = 0;
else
dlci->prio = roundup(addr + 1, 8) - 1;
dlci->ftype = gsm->ftype;
dlci->k = gsm->k;
dlci->state = DLCI_CLOSED;
if (addr) {
dlci->data = gsm_dlci_data;
/* Prevent us from sending data before the link is up */
dlci->constipated = true;
} else {
dlci->data = gsm_dlci_command;
}
gsm->dlci[addr] = dlci;
return dlci;
}
함수의 가장 하단을 살펴보면, gsm->dlci[addr] = dlci 부분이 보인다. gsm_activate_mux()에서는 gsm_dlci_alloc()을 어떻게 호출하냐면, dlci = gsm_dlci_alloc(gsm, 0) 이렇게 호출한다. 인자로 0을 고정으로 넣는다.
그러면, gsm_dlci_alloc()에서는 하단 코드가 gsm->dlci[0] = dlci 이렇게 되는데, 이미 이전에 gsm->dlci[0]에 메모리 주소가 적혀있는데 이걸 덮어씌운다. 따라서, 기존에 gsm->dlci[0]에 있던 메모리 주소가 영영 접근할 수 없는 형태가 되버린다. 그래서 메모리 릭이 발생하는 것으로 추정된다.(가설)
static struct gsm_dlci *gsm_dlci_alloc(struct gsm_mux *gsm, int addr)
{
struct gsm_dlci *dlci;
if (gsm->dlci[addr])
return gsm->dlci[addr];
dlci = kzalloc(sizeof(struct gsm_dlci), GFP_ATOMIC);
위와 같이, gsm->dlci[addr]에 이미 메모리 주소가 있는지 확인하고, 있다면 그 주소를 반환하는 코드로 바꿧다.
이렇게 2가지의 수정을 거친 후,
다시 repro를 돌리니 크래시가 발생하지 않는다..!
수정한 패치를 syzbot에게 테스트 요청을 보냈고,
https://lore.kernel.org/all/69e90d92.a00a0220.9259.0020.GAE@google.com/
문제가 없다는 답장을 받았다.
'Linux > start_contribute()' 카테고리의 다른 글
| syzbot 버그픽스 기여 방법 (0) | 2026.04.22 |
|---|---|
| 커널 기여: race condition 가능성 해결 (0) | 2026.02.01 |
| 커널 기여 근황 (0) | 2026.01.27 |
| 리눅스 커널 2번째 기여: 수동 메모리 정렬 연산 PTR_ALIGN으로 최적화 (2) | 2026.01.12 |
| 첫 리눅스 커널 기여 (4) | 2025.12.22 |