Linux Kernel(4.19) Hacks

rousalome.egloos.com

포토로그 Kernel Crash


통계 위젯 (화이트)

231224
1178
109353


[Linux-Kernel] LKML: qla2xxx: fix a potential NULL pointer dereference Linux Kernel Contribution

출처
https://patchwork.kernel.org/patch/11150763/
https://lkml.org/lkml/2019/9/18/796

//
// 흥미로운 패치다.
//

diff --git a/drivers/scsi/qla2xxx/qla_os.c b/drivers/scsi/qla2xxx/qla_os.c
index 98e60a3..31714c9 100644
--- a/drivers/scsi/qla2xxx/qla_os.c
+++ b/drivers/scsi/qla2xxx/qla_os.c
@@ -3232,6 +3232,10 @@  static void qla2x00_iocb_work_fn(struct work_struct *work)
      req->req_q_in, req->req_q_out, rsp->rsp_q_in, rsp->rsp_q_out);
 
  ha->wq = alloc_workqueue("qla2xxx_wq", 0, 0);
+ if (unlikely(!ha->wq)) {
+ ret = -ENOMEM;
+ goto probe_failed;
+ }

//
// alloc_workqueue() 함수를 호출해 워크큐를 생성하지 못하면 에러를 반환한다.
// alloc_workqueue() 함수에서 NULL를 반환하는 조건이 있나?  
//
  if (ha->isp_ops->initialize_adapter(base_vha)) {
  ql_log(ql_log_fatal, base_vha, 0x00d6,

if (ret) {
kfree(rescuer);
return ret;
}

wq->rescuer = rescuer;
kthread_bind_mask(rescuer->task, cpu_possible_mask);
wake_up_process(rescuer->task);

return 0;
}

__printf(1, 4)
struct workqueue_struct *alloc_workqueue(const char *fmt,
 unsigned int flags,
 int max_active, ...)
{
size_t tbl_size = 0;
va_list args;
struct workqueue_struct *wq;
struct pool_workqueue *pwq;

if ((flags & WQ_UNBOUND) && max_active == 1)
flags |= __WQ_ORDERED;

/* see the comment above the definition of WQ_POWER_EFFICIENT */
if ((flags & WQ_POWER_EFFICIENT) && wq_power_efficient)
flags |= WQ_UNBOUND;

/* allocate wq and format name */
if (flags & WQ_UNBOUND)
tbl_size = nr_node_ids * sizeof(wq->numa_pwq_tbl[0]);

wq = kzalloc(sizeof(*wq) + tbl_size, GFP_KERNEL);
if (!wq)
return NULL; //<<--

//  메모리를 할당 받지 못하면 NULL을 반환한다.
if (flags & WQ_UNBOUND) {
wq->unbound_attrs = alloc_workqueue_attrs();
if (!wq->unbound_attrs)
goto err_free_wq;
}
...

if (alloc_and_link_pwqs(wq) < 0)
goto err_unreg_lockdep; //<<--
if (wq_online && init_rescuer(wq) < 0)
goto err_destroy; //<<--
if ((wq->flags & WQ_SYSFS) && workqueue_sysfs_register(wq))
goto err_destroy;
// 그런데! 워크큐 생성 도중에 이런 예외 처리 코드도 있네?

...
mutex_unlock(&wq_pool_mutex);

return wq;

err_unreg_lockdep:
wq_unregister_lockdep(wq);
wq_free_lockdep(wq);
err_free_wq:
free_workqueue_attrs(wq->unbound_attrs);
kfree(wq);
return NULL;
// NULL을 반환한다.
err_destroy:
destroy_workqueue(wq);
return NULL;
// NULL을 반환한다.
}
EXPORT_SYMBOL_GPL(alloc_workqueue);

//
// alloc_workqueue() 함수는 kzalloc fail로 NULL를 반환하지만 다른 조건으로 NULL을 반환한다.
//
+ if (unlikely(!ha->wq)) {
+ ret = -ENOMEM;
+ goto probe_failed;
+ }
//
// 그렇다면 위 패치 코드는 valid 할까? 조금 더 생각해보자.
//
//
// 그런데 정말로 다른 코드를 보니 예외 처리를 하네?
//
https://elixir.bootlin.com/linux/v5.3.1/source/drivers/acpi/ec.c#L2039
static inline int acpi_ec_query_init(void)
{
if (!ec_query_wq) {
ec_query_wq = alloc_workqueue("kec_query", 0,
      ec_max_queries);
if (!ec_query_wq)
return -ENODEV;
}
return 0;
}

https://elixir.bootlin.com/linux/v5.3.1/source/drivers/acpi/thermal.c#L1249
acpi_thermal_pm_queue = alloc_workqueue("acpi_thermal_pm",
WQ_HIGHPRI | WQ_MEM_RECLAIM, 0);
if (!acpi_thermal_pm_queue)
return -ENODEV;

https://elixir.bootlin.com/linux/v5.3.1/source/drivers/ata/libata-sff.c#L3270
int __init ata_sff_init(void)
{
ata_sff_wq = alloc_workqueue("ata_sff", WQ_MEM_RECLAIM, WQ_MAX_ACTIVE);
if (!ata_sff_wq)
return -ENOMEM;

return 0;

https://elixir.bootlin.com/linux/v5.3.1/source/drivers/block/nbd.c#L2264
recv_workqueue = alloc_workqueue("knbd-recv",
 WQ_MEM_RECLAIM | WQ_HIGHPRI |
 WQ_UNBOUND, 0);
if (!recv_workqueue)
return -ENOMEM;

 
최근 리눅스 커널 소스 코드를 찾아 봤더니, alloc_workqueue() 함수에 대한 예외 처리 루틴이 없는 부분이 있네?
다음과 같이 패치를 작성하면 어떨까? 

There is no exceptional routine to check whether creation for "libertas_sdio" workqueue fails.
Add error handling path upon workqueue creation failure. 

diff --git a/drivers/net/wireless/marvell/libertas/if_sdio.c b/drivers/net/wireless/marvell/libertas/if_sdio.c
index 242d884..a699252 100644
--- a/drivers/net/wireless/marvell/libertas/if_sdio.c
+++ b/drivers/net/wireless/marvell/libertas/if_sdio.c
@@ -1179,6 +1179,10 @@ static int if_sdio_probe(struct sdio_func *func,

        spin_lock_init(&card->lock);
        card->workqueue = alloc_workqueue("libertas_sdio", WQ_MEM_RECLAIM, 0);
+       if (unlikely(!card->workqueue)) {
+               ret = -ENOMEM;
+               goto err_work_queue;
+       }
        INIT_WORK(&card->packet_worker, if_sdio_host_to_card_worker);
        init_waitqueue_head(&card->pwron_waitq);

@@ -1230,6 +1234,7 @@ static int if_sdio_probe(struct sdio_func *func,
        lbs_remove_card(priv);
 free:
        destroy_workqueue(card->workqueue);
+err_work_queue:
        while (card->packets) {
                packet = card->packets;
                card->packets = card->packets->next;

핑백

덧글

댓글 입력 영역