[Devel] [PATCH rh7] block/nbd: Fix NULL pointer dereference
Kirill Tkhai
ktkhai at virtuozzo.com
Wed Sep 11 18:02:13 MSK 2019
On 11.09.2019 15:45, Andrey Ryabinin wrote:
> The following commands trigger NULL-ptr dereference in ioctl(NBD_DO_IT):
>
> $ modprobe nbd
> $ qemu-img create -f qcow2 xxx 10G
> $ while true; do qemu-nbd -v -f qcow2 --detect-zeroes=on xxx -r -c /dev/nbd0 --cache=none --aio=native; done &
> $ while true; do qemu-nbd -d /dev/nbd0; done &
>
> BUG: unable to handle kernel NULL pointer dereference at 0000000000000020
> IP: [<ffffffffc0a2ae73>] __nbd_ioctl+0x343/0x970 [nbd]
>
> Call Trace:
> nbd_ioctl+0x6a/0x1a0 [nbd]
> blkdev_ioctl+0x2ea/0xa30
> block_ioctl+0x41/0x50
> do_vfs_ioctl+0x3b0/0x5a0
> SyS_ioctl+0xa1/0xc0
> system_call_fastpath+0x22/0x27
>
> NBD_DO_IT unlocks nbd->tx_lock and accesses nbd->sock in nbd_do_it();
> Parallel ioctl(NBD_CLEAR_SOCK) nullifies nbd->sock which might cause
> NULL-ptr deref in nbd_do_it().
>
> Fix the issue by taking nbd->tx_lock in nbd_do_it() to access nbd->sock.
> This should protect us from parallel NBD_CLEAR_SOCK.
>
> https://jira.sw.ru/browse/PSBM-97690
> Signed-off-by: Andrey Ryabinin <aryabinin at virtuozzo.com>
> ---
> drivers/block/nbd.c | 8 ++++++++
> 1 file changed, 8 insertions(+)
>
> diff --git a/drivers/block/nbd.c b/drivers/block/nbd.c
> index e0c6b623585d..2452b49efd56 100644
> --- a/drivers/block/nbd.c
> +++ b/drivers/block/nbd.c
> @@ -419,7 +419,15 @@ static int nbd_do_it(struct nbd_device *nbd)
>
> BUG_ON(nbd->magic != NBD_MAGIC);
>
> + mutex_lock(&nbd->tx_lock);
> + if (!nbd->sock) {
> + mutex_unlock(&nbd->tx_lock);
> + dev_err(disk_to_dev(nbd->disk), "device_create_file failed!\n");
> + return -EINVAL;
> + }
> sk_set_memalloc(nbd->sock->sk);
> + mutex_unlock(&nbd->tx_lock);
> +
> nbd->pid = task_pid_nr(current);
> ret = device_create_file(disk_to_dev(nbd->disk), &pid_attr);
> if (ret) {
The problem may be a bit bigger, then only zero nbd->sock. NBD_CLEAR_SOCK also clears
nbd->file and makes some other cleanup. It may switch the driver in inconsistent state.
nbd_do_it() may access freed memory in case of clear happens in parallel. I'd proposed
something like this:
diff --git a/drivers/block/nbd.c b/drivers/block/nbd.c
index e0c6b623585d..6d1c0229280a 100644
--- a/drivers/block/nbd.c
+++ b/drivers/block/nbd.c
@@ -420,11 +420,9 @@ static int nbd_do_it(struct nbd_device *nbd)
BUG_ON(nbd->magic != NBD_MAGIC);
sk_set_memalloc(nbd->sock->sk);
- nbd->pid = task_pid_nr(current);
ret = device_create_file(disk_to_dev(nbd->disk), &pid_attr);
if (ret) {
dev_err(disk_to_dev(nbd->disk), "device_create_file failed!\n");
- nbd->pid = 0;
return ret;
}
@@ -432,7 +430,6 @@ static int nbd_do_it(struct nbd_device *nbd)
nbd_end_request(req);
device_remove_file(disk_to_dev(nbd->disk), &pid_attr);
- nbd->pid = 0;
return 0;
}
@@ -631,7 +628,15 @@ static int __nbd_ioctl(struct block_device *bdev, struct nbd_device *nbd,
case NBD_CLEAR_SOCK: {
struct file *file;
+ int ret;
+ while (nbd->pid) {
+ mutex_unlock(&nbd->tx_lock);
+ ret = wait_event_interruptible(!nbd->pid);
+ mutex_lock(&nbd->tx_lock);
+ if (ret)
+ return ret;
+ }
nbd->sock = NULL;
file = nbd->file;
nbd->file = NULL;
@@ -705,6 +710,7 @@ static int __nbd_ioctl(struct block_device *bdev, struct nbd_device *nbd,
if (!nbd->file)
return -EINVAL;
+ nbd->pid = task_pid_nr(current);
mutex_unlock(&nbd->tx_lock);
if (nbd->flags & NBD_FLAG_READ_ONLY)
@@ -721,6 +727,7 @@ static int __nbd_ioctl(struct block_device *bdev, struct nbd_device *nbd,
nbd->disk->disk_name);
if (IS_ERR(thread)) {
mutex_lock(&nbd->tx_lock);
+ nbd->pid = 0;
return PTR_ERR(thread);
}
wake_up_process(thread);
@@ -728,6 +735,7 @@ static int __nbd_ioctl(struct block_device *bdev, struct nbd_device *nbd,
kthread_stop(thread);
mutex_lock(&nbd->tx_lock);
+ nbd->pid = 0;
if (error)
return error;
sock_shutdown(nbd, 0);
More information about the Devel
mailing list