Tue, 02 Feb 2021 02:28:20 -0800
> syzbot found the following issue on:
>
> HEAD commit: b01f250d Add linux-next specific files for 20210129
> git tree: linux-next
> console output:
https://syzkaller.appspot.com/x/log.txt?x=14366378d00000
> kernel config:
https://syzkaller.appspot.com/x/.config?x=725bc96dc234fda7
> dashboard link:
https://syzkaller.appspot.com/bug?extid=f91dbbabacae745d334f
> compiler: gcc (GCC) 10.1.0-syz 20200507
>
> Unfortunately, I don't have any reproducer for this issue yet.
>
> IMPORTANT: if you fix the issue, please add the following tag to the commit:
> Reported-by:
syzbot+f91dbb...@syzkaller.appspotmail.com
>
> block nbd5: Receive control failed (result -107)
> ==================================================================
> BUG: KASAN: use-after-free in recv_work+0x2a2/0x2c0 drivers/block/nbd.c:787
> Read of size 8 at addr ffff88801181ed20 by task kworker/u5:2/8465
>
> CPU: 1 PID: 8465 Comm: kworker/u5:2 Not tainted 5.11.0-rc5-next-20210129-syzkaller #0
> Hardware name: Google Google Compute Engine/Google Compute Engine, BIOS Google 01/01/2011
> Workqueue: knbd5-recv recv_work
> Call Trace:
> __dump_stack lib/dump_stack.c:79 [inline]
> dump_stack+0x107/0x163 lib/dump_stack.c:120
> print_address_description.constprop.0.cold+0x5b/0x2f8 mm/kasan/report.c:232
> __kasan_report mm/kasan/report.c:399 [inline]
> kasan_report.cold+0x7c/0xd8 mm/kasan/report.c:416
> recv_work+0x2a2/0x2c0 drivers/block/nbd.c:787
The uaf occurs in the worker context.
Change memory slice without parking worker.
What is reported here is different from the uaf fixed in b98e762e3d71,
and it is highly appreciated to add a Fixes tag.
Open code the park, parkme and unpark mechanism to serialise the ioctl
act and the kworker, based on two atomic counters, to fix the UAF.
Plus s/recv_work/nbd_recv_work/ to make it simpler to spot a Fixes tag.
--- a/drivers/block/nbd.c
+++ b/drivers/block/nbd.c
@@ -113,6 +113,7 @@ struct nbd_device {
struct mutex config_lock;
struct gendisk *disk;
struct workqueue_struct *recv_workq;
+ atomic_t nbd_workers, nbd_recfg;
struct list_head list;
struct task_struct *task_recv;
@@ -771,7 +772,7 @@ out:
return ret ? ERR_PTR(ret) : cmd;
}
-static void recv_work(struct work_struct *work)
+static void nbd_recv_work(struct work_struct *work)
{
struct recv_thread_args *args = container_of(work,
struct recv_thread_args,
@@ -780,8 +781,19 @@ static void recv_work(struct work_struct
struct nbd_config *config = nbd->config;
struct nbd_cmd *cmd;
struct request *rq;
+ int cut = 0;
+ atomic_inc(&nbd->nbd_workers);
while (1) {
+ if (atomic_read(&nbd->nbd_recfg)) {
+ if (!cut) {
+ cut = 1;
+ atomic_dec(&nbd->nbd_recfg);
+ }
+ schedule_timeout_interruptible(1);
+ continue;
+ }
+
cmd = nbd_read_stat(nbd, args->index);
if (IS_ERR(cmd)) {
struct nbd_sock *nsock = config->socks[args->index];
@@ -796,6 +808,7 @@ static void recv_work(struct work_struct
if (likely(!blk_should_fake_timeout(rq->q)))
blk_mq_complete_request(rq);
}
+ atomic_dec(&nbd->nbd_workers);
nbd_config_put(nbd);
atomic_dec(&config->recv_threads);
wake_up(&config->recv_wq);
@@ -1016,6 +1029,7 @@ static int nbd_add_socket(struct nbd_dev
struct socket *sock;
struct nbd_sock **socks;
struct nbd_sock *nsock;
+ int workers, acks;
int err;
sock = nbd_get_socket(nbd, arg, &err);
@@ -1047,6 +1061,18 @@ static int nbd_add_socket(struct nbd_dev
goto put_socket;
}
+ do {
+ workers = atomic_read(&nbd->nbd_workers);
+ acks = 1 + workers;
+ atomic_set(&nbd->nbd_recfg, acks);
+ } while (workers != atomic_read(&nbd->nbd_workers));
+ do {
+ workers = atomic_read(&nbd->nbd_workers);
+ if (workers + 1 == acks - atomic_read(&nbd->nbd_recfg))
+ break;
+ schedule_timeout_interruptible(1);
+ } while (1);
+
socks = krealloc(config->socks, (config->num_connections + 1) *
sizeof(struct nbd_sock *), GFP_KERNEL);
if (!socks) {
@@ -1066,6 +1092,7 @@ static int nbd_add_socket(struct nbd_dev
nsock->cookie = 0;
socks[config->num_connections++] = nsock;
atomic_inc(&config->live_connections);
+ atomic_set(&nbd->nbd_recfg, 0);
blk_mq_unfreeze_queue(nbd->disk->queue);
return 0;
@@ -1114,7 +1141,7 @@ static int nbd_reconnect_socket(struct n
nsock->fallback_index = -1;
nsock->sock = sock;
nsock->dead = false;
- INIT_WORK(&args->work, recv_work);
+ INIT_WORK(&args->work, nbd_recv_work);
args->index = i;
args->nbd = nbd;
nsock->cookie++;
@@ -1123,7 +1150,7 @@ static int nbd_reconnect_socket(struct n
clear_bit(NBD_RT_DISCONNECTED, &config->runtime_flags);
- /* We take the tx_mutex in an error path in the recv_work, so we
+ /* We take the tx_mutex in an error path in the nbd_recv_work, so we
* need to queue_work outside of the tx_mutex.
*/
queue_work(nbd->recv_workq, &args->work);
@@ -1267,6 +1294,8 @@ static int nbd_start_device(struct nbd_d
dev_err(disk_to_dev(nbd->disk), "Could not allocate knbd recv work queue.\n");
return -ENOMEM;
}
+ atomic_set(&nbd->nbd_workers, 0);
+ atomic_set(&nbd->nbd_recfg, 0);
blk_mq_update_nr_hw_queues(&nbd->tag_set, config->num_connections);
nbd->task_recv = current;
@@ -1305,7 +1334,7 @@ static int nbd_start_device(struct nbd_d
nbd->tag_set.timeout;
atomic_inc(&config->recv_threads);
refcount_inc(&nbd->config_refs);
- INIT_WORK(&args->work, recv_work);
+ INIT_WORK(&args->work, nbd_recv_work);
args->nbd = nbd;
args->index = i;
queue_work(nbd->recv_workq, &args->work);