aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorIan Abbott <abbotti@mev.co.uk>2013-07-05 11:49:34 -0400
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>2013-08-04 04:50:50 -0400
commit2578ae7c4bbb8528caa3ea835c7451868b4f8607 (patch)
tree324b3f0ad436d4340dc79e54d84509793a960466
parent5deba4cf6db86cc902526d8b08fc5d56e9d43994 (diff)
staging: comedi: fix a race between do_cmd_ioctl() and read/write
commit 4b18f08be01a7b3c7b6df497137b6e3cb28adaa3 upstream. `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> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
-rw-r--r--drivers/staging/comedi/comedi_fops.c25
1 files changed, 15 insertions, 10 deletions
diff --git a/drivers/staging/comedi/comedi_fops.c b/drivers/staging/comedi/comedi_fops.c
index 924c54c9c31f..fe8914310dd4 100644
--- a/drivers/staging/comedi/comedi_fops.c
+++ b/drivers/staging/comedi/comedi_fops.c
@@ -1401,22 +1401,19 @@ static int do_cmd_ioctl(struct comedi_device *dev,
1401 DPRINTK("subdevice busy\n"); 1401 DPRINTK("subdevice busy\n");
1402 return -EBUSY; 1402 return -EBUSY;
1403 } 1403 }
1404 s->busy = file;
1405 1404
1406 /* make sure channel/gain list isn't too long */ 1405 /* make sure channel/gain list isn't too long */
1407 if (cmd.chanlist_len > s->len_chanlist) { 1406 if (cmd.chanlist_len > s->len_chanlist) {
1408 DPRINTK("channel/gain list too long %u > %d\n", 1407 DPRINTK("channel/gain list too long %u > %d\n",
1409 cmd.chanlist_len, s->len_chanlist); 1408 cmd.chanlist_len, s->len_chanlist);
1410 ret = -EINVAL; 1409 return -EINVAL;
1411 goto cleanup;
1412 } 1410 }
1413 1411
1414 /* make sure channel/gain list isn't too short */ 1412 /* make sure channel/gain list isn't too short */
1415 if (cmd.chanlist_len < 1) { 1413 if (cmd.chanlist_len < 1) {
1416 DPRINTK("channel/gain list too short %u < 1\n", 1414 DPRINTK("channel/gain list too short %u < 1\n",
1417 cmd.chanlist_len); 1415 cmd.chanlist_len);
1418 ret = -EINVAL; 1416 return -EINVAL;
1419 goto cleanup;
1420 } 1417 }
1421 1418
1422 async->cmd = cmd; 1419 async->cmd = cmd;
@@ -1426,8 +1423,7 @@ static int do_cmd_ioctl(struct comedi_device *dev,
1426 kmalloc(async->cmd.chanlist_len * sizeof(int), GFP_KERNEL); 1423 kmalloc(async->cmd.chanlist_len * sizeof(int), GFP_KERNEL);
1427 if (!async->cmd.chanlist) { 1424 if (!async->cmd.chanlist) {
1428 DPRINTK("allocation failed\n"); 1425 DPRINTK("allocation failed\n");
1429 ret = -ENOMEM; 1426 return -ENOMEM;
1430 goto cleanup;
1431 } 1427 }
1432 1428
1433 if (copy_from_user(async->cmd.chanlist, user_chanlist, 1429 if (copy_from_user(async->cmd.chanlist, user_chanlist,
@@ -1479,6 +1475,9 @@ static int do_cmd_ioctl(struct comedi_device *dev,
1479 1475
1480 comedi_set_subdevice_runflags(s, ~0, SRF_USER | SRF_RUNNING); 1476 comedi_set_subdevice_runflags(s, ~0, SRF_USER | SRF_RUNNING);
1481 1477
1478 /* set s->busy _after_ setting SRF_RUNNING flag to avoid race with
1479 * comedi_read() or comedi_write() */
1480 s->busy = file;
1482 ret = s->do_cmd(dev, s); 1481 ret = s->do_cmd(dev, s);
1483 if (ret == 0) 1482 if (ret == 0)
1484 return 0; 1483 return 0;
@@ -2041,11 +2040,13 @@ static ssize_t comedi_write(struct file *file, const char __user *buf,
2041 2040
2042 if (!comedi_is_subdevice_running(s)) { 2041 if (!comedi_is_subdevice_running(s)) {
2043 if (count == 0) { 2042 if (count == 0) {
2043 mutex_lock(&dev->mutex);
2044 if (comedi_is_subdevice_in_error(s)) 2044 if (comedi_is_subdevice_in_error(s))
2045 retval = -EPIPE; 2045 retval = -EPIPE;
2046 else 2046 else
2047 retval = 0; 2047 retval = 0;
2048 do_become_nonbusy(dev, s); 2048 do_become_nonbusy(dev, s);
2049 mutex_unlock(&dev->mutex);
2049 } 2050 }
2050 break; 2051 break;
2051 } 2052 }
@@ -2144,11 +2145,13 @@ static ssize_t comedi_read(struct file *file, char __user *buf, size_t nbytes,
2144 2145
2145 if (n == 0) { 2146 if (n == 0) {
2146 if (!comedi_is_subdevice_running(s)) { 2147 if (!comedi_is_subdevice_running(s)) {
2148 mutex_lock(&dev->mutex);
2147 do_become_nonbusy(dev, s); 2149 do_become_nonbusy(dev, s);
2148 if (comedi_is_subdevice_in_error(s)) 2150 if (comedi_is_subdevice_in_error(s))
2149 retval = -EPIPE; 2151 retval = -EPIPE;
2150 else 2152 else
2151 retval = 0; 2153 retval = 0;
2154 mutex_unlock(&dev->mutex);
2152 break; 2155 break;
2153 } 2156 }
2154 if (file->f_flags & O_NONBLOCK) { 2157 if (file->f_flags & O_NONBLOCK) {
@@ -2186,9 +2189,11 @@ static ssize_t comedi_read(struct file *file, char __user *buf, size_t nbytes,
2186 buf += n; 2189 buf += n;
2187 break; /* makes device work like a pipe */ 2190 break; /* makes device work like a pipe */
2188 } 2191 }
2189 if (comedi_is_subdevice_idle(s) && 2192 if (comedi_is_subdevice_idle(s)) {
2190 async->buf_read_count - async->buf_write_count == 0) { 2193 mutex_lock(&dev->mutex);
2191 do_become_nonbusy(dev, s); 2194 if (async->buf_read_count - async->buf_write_count == 0)
2195 do_become_nonbusy(dev, s);
2196 mutex_unlock(&dev->mutex);
2192 } 2197 }
2193 set_current_state(TASK_RUNNING); 2198 set_current_state(TASK_RUNNING);
2194 remove_wait_queue(&async->wait_head, &wait); 2199 remove_wait_queue(&async->wait_head, &wait);