diff options
author | Ian Abbott <abbotti@mev.co.uk> | 2013-07-05 11:49:34 -0400 |
---|---|---|
committer | Greg Kroah-Hartman <gregkh@linuxfoundation.org> | 2013-08-04 04:50:50 -0400 |
commit | 2578ae7c4bbb8528caa3ea835c7451868b4f8607 (patch) | |
tree | 324b3f0ad436d4340dc79e54d84509793a960466 | |
parent | 5deba4cf6db86cc902526d8b08fc5d56e9d43994 (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.c | 25 |
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); |