Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

staging: comedi: fix a race between do_cmd_ioctl() and read/write

`do_cmd_ioctl()` is called with the comedi device's mutex locked to
process the `COMEDI_CMD` ioctl to set up comedi's asynchronous command
handling on a comedi subdevice. `comedi_read()` and `comedi_write()`
are the `read` and `write` handlers for the comedi device, but do not
lock the mutex (for performance reasons, as some things can hold the
mutex for quite a long time).

There is a race condition if `comedi_read()` or `comedi_write()` is
running at the same time and for the same file object and comedi
subdevice as `do_cmd_ioctl()`. `do_cmd_ioctl()` sets the subdevice's
`busy` pointer to the file object way before it sets the `SRF_RUNNING` flag
in the subdevice's `runflags` member. `comedi_read() and
`comedi_write()` check the subdevice's `busy` pointer is pointing to the
current file object, then if the `SRF_RUNNING` flag is not set, will call
`do_become_nonbusy()` to shut down the asyncronous command. Bad things
can happen if the asynchronous command is being shutdown and set up at
the same time.

To prevent the race, don't set the `busy` pointer until
after the `SRF_RUNNING` flag has been set. Also, make sure the mutex is
held in `comedi_read()` and `comedi_write()` while calling
`do_become_nonbusy()` in order to avoid moving the race condition to a
point within that function.

Change some error handling `goto cleanup` statements in `do_cmd_ioctl()`
to simple `return -ERRFOO` statements as a result of changing when the
`busy` pointer is set.

Signed-off-by: Ian Abbott <abbotti@mev.co.uk>
Cc: stable <stable@vger.kernel.org>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>

authored by

Ian Abbott and committed by
Greg Kroah-Hartman
4b18f08b 69acbaac

+15 -10
+15 -10
drivers/staging/comedi/comedi_fops.c
··· 1413 1413 DPRINTK("subdevice busy\n"); 1414 1414 return -EBUSY; 1415 1415 } 1416 - s->busy = file; 1417 1416 1418 1417 /* make sure channel/gain list isn't too long */ 1419 1418 if (cmd.chanlist_len > s->len_chanlist) { 1420 1419 DPRINTK("channel/gain list too long %u > %d\n", 1421 1420 cmd.chanlist_len, s->len_chanlist); 1422 - ret = -EINVAL; 1423 - goto cleanup; 1421 + return -EINVAL; 1424 1422 } 1425 1423 1426 1424 /* make sure channel/gain list isn't too short */ 1427 1425 if (cmd.chanlist_len < 1) { 1428 1426 DPRINTK("channel/gain list too short %u < 1\n", 1429 1427 cmd.chanlist_len); 1430 - ret = -EINVAL; 1431 - goto cleanup; 1428 + return -EINVAL; 1432 1429 } 1433 1430 1434 1431 async->cmd = cmd; ··· 1435 1438 kmalloc(async->cmd.chanlist_len * sizeof(int), GFP_KERNEL); 1436 1439 if (!async->cmd.chanlist) { 1437 1440 DPRINTK("allocation failed\n"); 1438 - ret = -ENOMEM; 1439 - goto cleanup; 1441 + return -ENOMEM; 1440 1442 } 1441 1443 1442 1444 if (copy_from_user(async->cmd.chanlist, user_chanlist, ··· 1487 1491 1488 1492 comedi_set_subdevice_runflags(s, ~0, SRF_USER | SRF_RUNNING); 1489 1493 1494 + /* set s->busy _after_ setting SRF_RUNNING flag to avoid race with 1495 + * comedi_read() or comedi_write() */ 1496 + s->busy = file; 1490 1497 ret = s->do_cmd(dev, s); 1491 1498 if (ret == 0) 1492 1499 return 0; ··· 2057 2058 2058 2059 if (!comedi_is_subdevice_running(s)) { 2059 2060 if (count == 0) { 2061 + mutex_lock(&dev->mutex); 2060 2062 if (comedi_is_subdevice_in_error(s)) 2061 2063 retval = -EPIPE; 2062 2064 else 2063 2065 retval = 0; 2064 2066 do_become_nonbusy(dev, s); 2067 + mutex_unlock(&dev->mutex); 2065 2068 } 2066 2069 break; 2067 2070 } ··· 2162 2161 2163 2162 if (n == 0) { 2164 2163 if (!comedi_is_subdevice_running(s)) { 2164 + mutex_lock(&dev->mutex); 2165 2165 do_become_nonbusy(dev, s); 2166 2166 if (comedi_is_subdevice_in_error(s)) 2167 2167 retval = -EPIPE; 2168 2168 else 2169 2169 retval = 0; 2170 + mutex_unlock(&dev->mutex); 2170 2171 break; 2171 2172 } 2172 2173 if (file->f_flags & O_NONBLOCK) { ··· 2206 2203 buf += n; 2207 2204 break; /* makes device work like a pipe */ 2208 2205 } 2209 - if (comedi_is_subdevice_idle(s) && 2210 - async->buf_read_count - async->buf_write_count == 0) { 2211 - do_become_nonbusy(dev, s); 2206 + if (comedi_is_subdevice_idle(s)) { 2207 + mutex_lock(&dev->mutex); 2208 + if (async->buf_read_count - async->buf_write_count == 0) 2209 + do_become_nonbusy(dev, s); 2210 + mutex_unlock(&dev->mutex); 2212 2211 } 2213 2212 set_current_state(TASK_RUNNING); 2214 2213 remove_wait_queue(&async->wait_head, &wait);