Skip to content

Commit 4e1da51

Browse files
ian-abbottgregkh
authored andcommitted
comedi: Add reference counting for Comedi command handling
For interrupts from badly behaved hardware (as emulated by Syzbot), it is possible for the Comedi core functions that manage the progress of asynchronous data acquisition to be called from driver ISRs while no asynchronous command has been set up, which can cause problems such as invalid pointer dereferencing or dividing by zero. To help protect against that, introduce new functions to maintain a reference counter for asynchronous commands that are being set up. `comedi_get_is_subdevice_running(s)` will check if a command has been set up on a subdevice and is still marked as running, and if so will increment the reference counter and return `true`, otherwise it will return `false` without modifying the reference counter. `comedi_put_is_subdevice_running(s)` will decrement the reference counter and set a completion event when decremented to 0. Change the `do_cmd_ioctl()` function (responsible for setting up the asynchronous command) to reinitialize the completion event and set the reference counter to 1 before it marks the subdevice as running. Change the `do_become_nonbusy()` function (responsible for destroying a completed command) to call `comedi_put_is_subdevice_running(s)` and wait for the completion event after marking the subdevice as not running. Because the subdevice normally gets marked as not running before the call to `do_become_nonbusy()` (and may also be called when the Comedi device is being detached from the low-level driver), add a new flag `COMEDI_SRF_BUSY` to the set of subdevice run-flags that indicates that an asynchronous command was set up and will need to be destroyed. This flag is set by `do_cmd_ioctl()` and cleared and checked by `do_become_nonbusy()`. Subsequent patches will change the Comedi core functions that are called from low-level drivers for asynchrous command handling to make use of the `comedi_get_is_subdevice_running()` and `comedi_put_is_subdevice_running()` functions, and will modify the ISRs of some of these low-level drivers if they dereference the subdevice's `async` pointer directly. Signed-off-by: Ian Abbott <abbotti@mev.co.uk> Link: https://patch.msgid.link/20251023133001.8439-2-abbotti@mev.co.uk Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
1 parent a51f025 commit 4e1da51

3 files changed

Lines changed: 78 additions & 8 deletions

File tree

drivers/comedi/comedi_fops.c

Lines changed: 70 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -38,16 +38,19 @@
3838
* COMEDI_SRF_ERROR: indicates an COMEDI_CB_ERROR event has occurred
3939
* since the last command was started
4040
* COMEDI_SRF_RUNNING: command is running
41+
* COMEDI_SRF_BUSY: command was started and subdevice still busy
4142
* COMEDI_SRF_FREE_SPRIV: free s->private on detach
4243
*
4344
* COMEDI_SRF_BUSY_MASK: runflags that indicate the subdevice is "busy"
4445
*/
4546
#define COMEDI_SRF_RT BIT(1)
4647
#define COMEDI_SRF_ERROR BIT(2)
4748
#define COMEDI_SRF_RUNNING BIT(27)
49+
#define COMEDI_SRF_BUSY BIT(28)
4850
#define COMEDI_SRF_FREE_SPRIV BIT(31)
4951

50-
#define COMEDI_SRF_BUSY_MASK (COMEDI_SRF_ERROR | COMEDI_SRF_RUNNING)
52+
#define COMEDI_SRF_BUSY_MASK \
53+
(COMEDI_SRF_ERROR | COMEDI_SRF_RUNNING | COMEDI_SRF_BUSY)
5154

5255
/**
5356
* struct comedi_file - Per-file private data for COMEDI device
@@ -665,6 +668,11 @@ static bool comedi_is_runflags_in_error(unsigned int runflags)
665668
return runflags & COMEDI_SRF_ERROR;
666669
}
667670

671+
static bool comedi_is_runflags_busy(unsigned int runflags)
672+
{
673+
return runflags & COMEDI_SRF_BUSY;
674+
}
675+
668676
/**
669677
* comedi_is_subdevice_running() - Check if async command running on subdevice
670678
* @s: COMEDI subdevice.
@@ -687,6 +695,46 @@ static bool __comedi_is_subdevice_running(struct comedi_subdevice *s)
687695
return comedi_is_runflags_running(runflags);
688696
}
689697

698+
/**
699+
* comedi_get_is_subdevice_running() - Get if async command running on subdevice
700+
* @s: COMEDI subdevice.
701+
*
702+
* If an asynchronous COMEDI command is running on the subdevice, increment
703+
* a reference counter. If the function return value indicates that a
704+
* command is running, then the details of the command will not be destroyed
705+
* before a matching call to comedi_put_is_subdevice_running().
706+
*
707+
* Return: %true if an asynchronous COMEDI command is active on the
708+
* subdevice, else %false.
709+
*/
710+
bool comedi_get_is_subdevice_running(struct comedi_subdevice *s)
711+
{
712+
unsigned long flags;
713+
bool running;
714+
715+
spin_lock_irqsave(&s->spin_lock, flags);
716+
running = __comedi_is_subdevice_running(s);
717+
if (running)
718+
refcount_inc(&s->async->run_active);
719+
spin_unlock_irqrestore(&s->spin_lock, flags);
720+
return running;
721+
}
722+
EXPORT_SYMBOL_GPL(comedi_get_is_subdevice_running);
723+
724+
/**
725+
* comedi_put_is_subdevice_running() - Put if async command running on subdevice
726+
* @s: COMEDI subdevice.
727+
*
728+
* Decrements the reference counter that was incremented when
729+
* comedi_get_is_subdevice_running() returned %true.
730+
*/
731+
void comedi_put_is_subdevice_running(struct comedi_subdevice *s)
732+
{
733+
if (refcount_dec_and_test(&s->async->run_active))
734+
complete_all(&s->async->run_complete);
735+
}
736+
EXPORT_SYMBOL_GPL(comedi_put_is_subdevice_running);
737+
690738
bool comedi_can_auto_free_spriv(struct comedi_subdevice *s)
691739
{
692740
unsigned int runflags = __comedi_get_subdevice_runflags(s);
@@ -736,20 +784,28 @@ static void do_become_nonbusy(struct comedi_device *dev,
736784
struct comedi_subdevice *s)
737785
{
738786
struct comedi_async *async = s->async;
787+
unsigned int runflags;
788+
unsigned long flags;
739789

740790
lockdep_assert_held(&dev->mutex);
741-
comedi_update_subdevice_runflags(s, COMEDI_SRF_RUNNING, 0);
742-
if (async) {
791+
spin_lock_irqsave(&s->spin_lock, flags);
792+
runflags = __comedi_get_subdevice_runflags(s);
793+
__comedi_clear_subdevice_runflags(s, COMEDI_SRF_RUNNING |
794+
COMEDI_SRF_BUSY);
795+
spin_unlock_irqrestore(&s->spin_lock, flags);
796+
if (comedi_is_runflags_busy(runflags)) {
797+
/*
798+
* "Run active" counter was set to 1 when setting up the
799+
* command. Decrement it and wait for it to become 0.
800+
*/
801+
comedi_put_is_subdevice_running(s);
802+
wait_for_completion(&async->run_complete);
743803
comedi_buf_reset(s);
744804
async->inttrig = NULL;
745805
kfree(async->cmd.chanlist);
746806
async->cmd.chanlist = NULL;
747807
s->busy = NULL;
748808
wake_up_interruptible_all(&async->wait_head);
749-
} else {
750-
dev_err(dev->class_dev,
751-
"BUG: (?) %s called with async=NULL\n", __func__);
752-
s->busy = NULL;
753809
}
754810
}
755811

@@ -1860,8 +1916,14 @@ static int do_cmd_ioctl(struct comedi_device *dev,
18601916
if (async->cmd.flags & CMDF_WAKE_EOS)
18611917
async->cb_mask |= COMEDI_CB_EOS;
18621918

1919+
/*
1920+
* Set the "run active" counter with an initial count of 1 that will
1921+
* complete the "safe to reset" event when it is decremented to 0.
1922+
*/
1923+
refcount_set(&s->async->run_active, 1);
1924+
reinit_completion(&s->async->run_complete);
18631925
comedi_update_subdevice_runflags(s, COMEDI_SRF_BUSY_MASK,
1864-
COMEDI_SRF_RUNNING);
1926+
COMEDI_SRF_RUNNING | COMEDI_SRF_BUSY);
18651927

18661928
/*
18671929
* Set s->busy _after_ setting COMEDI_SRF_RUNNING flag to avoid

drivers/comedi/drivers.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -677,6 +677,7 @@ static int __comedi_device_postconfig_async(struct comedi_device *dev,
677677
return -ENOMEM;
678678

679679
init_waitqueue_head(&async->wait_head);
680+
init_completion(&async->run_complete);
680681
s->async = async;
681682

682683
async->max_bufsize = comedi_default_buf_maxsize_kb * 1024;

include/linux/comedi/comedidev.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
#include <linux/spinlock_types.h>
1616
#include <linux/rwsem.h>
1717
#include <linux/kref.h>
18+
#include <linux/completion.h>
1819
#include <linux/comedi.h>
1920

2021
#define COMEDI_VERSION(a, b, c) (((a) << 16) + ((b) << 8) + (c))
@@ -272,6 +273,8 @@ struct comedi_buf_map {
272273
* @events: Bit-vector of events that have occurred.
273274
* @cmd: Details of comedi command in progress.
274275
* @wait_head: Task wait queue for file reader or writer.
276+
* @run_complete: "run complete" completion event.
277+
* @run_active: "run active" reference counter.
275278
* @cb_mask: Bit-vector of events that should wake waiting tasks.
276279
* @inttrig: Software trigger function for command, or NULL.
277280
*
@@ -357,6 +360,8 @@ struct comedi_async {
357360
unsigned int events;
358361
struct comedi_cmd cmd;
359362
wait_queue_head_t wait_head;
363+
struct completion run_complete;
364+
refcount_t run_active;
360365
unsigned int cb_mask;
361366
int (*inttrig)(struct comedi_device *dev, struct comedi_subdevice *s,
362367
unsigned int x);
@@ -584,6 +589,8 @@ struct comedi_device *comedi_dev_get_from_minor(unsigned int minor);
584589
int comedi_dev_put(struct comedi_device *dev);
585590

586591
bool comedi_is_subdevice_running(struct comedi_subdevice *s);
592+
bool comedi_get_is_subdevice_running(struct comedi_subdevice *s);
593+
void comedi_put_is_subdevice_running(struct comedi_subdevice *s);
587594

588595
void *comedi_alloc_spriv(struct comedi_subdevice *s, size_t size);
589596
void comedi_set_spriv_auto_free(struct comedi_subdevice *s);

0 commit comments

Comments
 (0)