Skip to content

Commit b34aa86

Browse files
ian-abbottgregkh
authored andcommitted
staging: comedi: fix circular locking dependency in comedi_mmap()
Mmapping a comedi data buffer with lockdep checking enabled produced the following kernel debug messages: ====================================================== [ INFO: possible circular locking dependency detected ] 3.5.0-rc3-ija1+ torvalds#9 Tainted: G C ------------------------------------------------------- comedi_test/4160 is trying to acquire lock: (&dev->mutex#2){+.+.+.}, at: [<ffffffffa00313f4>] comedi_mmap+0x57/0x1d9 [comedi] but task is already holding lock: (&mm->mmap_sem){++++++}, at: [<ffffffff810c96fe>] vm_mmap_pgoff+0x41/0x76 which lock already depends on the new lock. the existing dependency chain (in reverse order) is: -> alexander-zimmermann#1 (&mm->mmap_sem){++++++}: [<ffffffff8106d0e8>] lock_acquire+0x97/0x105 [<ffffffff810ce3bc>] might_fault+0x6d/0x90 [<ffffffffa0031ffb>] do_devinfo_ioctl.isra.7+0x11e/0x14c [comedi] [<ffffffffa003227f>] comedi_unlocked_ioctl+0x256/0xe48 [comedi] [<ffffffff810f7fcd>] vfs_ioctl+0x18/0x34 [<ffffffff810f87fd>] do_vfs_ioctl+0x382/0x43c [<ffffffff810f88f9>] sys_ioctl+0x42/0x65 [<ffffffff81415c62>] system_call_fastpath+0x16/0x1b -> #0 (&dev->mutex#2){+.+.+.}: [<ffffffff8106c528>] __lock_acquire+0x101d/0x1591 [<ffffffff8106d0e8>] lock_acquire+0x97/0x105 [<ffffffff8140c894>] mutex_lock_nested+0x46/0x2a4 [<ffffffffa00313f4>] comedi_mmap+0x57/0x1d9 [comedi] [<ffffffff810d5816>] mmap_region+0x281/0x492 [<ffffffff810d5c92>] do_mmap_pgoff+0x26b/0x2a7 [<ffffffff810c971a>] vm_mmap_pgoff+0x5d/0x76 [<ffffffff810d493f>] sys_mmap_pgoff+0xc7/0x10d [<ffffffff81004d36>] sys_mmap+0x16/0x20 [<ffffffff81415c62>] system_call_fastpath+0x16/0x1b other info that might help us debug this: Possible unsafe locking scenario: CPU0 CPU1 ---- ---- lock(&mm->mmap_sem); lock(&dev->mutex#2); lock(&mm->mmap_sem); lock(&dev->mutex#2); *** DEADLOCK *** To avoid the circular dependency, just try to get the lock in `comedi_mmap()` instead of blocking. Since the comedi device's main mutex is heavily used, do a down-read of its `attach_lock` rwsemaphore instead. Trying to down-read `attach_lock` should only fail if some task has down-write locked it, and that is only done while the comedi device is being attached to or detached from a low-level hardware device. Unfortunately, acquiring the `attach_lock` doesn't prevent another task replacing the comedi data buffer we are trying to mmap. The details of the buffer are held in a `struct comedi_buf_map` and pointed to by `s->async->buf_map` where `s` is the comedi subdevice whose buffer we are trying to map. The `struct comedi_buf_map` is already reference counted with a `struct kref`, so we can stop it being freed prematurely. Modify `comedi_mmap()` to call new function `comedi_buf_map_from_subdev_get()` to read the subdevice's current buffer map pointer and increment its reference instead of accessing `async->buf_map` directly. Call `comedi_buf_map_put()` to decrement the reference once the buffer map structure has been dealt with. (Note that `comedi_buf_map_put()` does nothing if passed a NULL pointer.) `comedi_buf_map_from_subdev_get()` checks the subdevice's buffer map pointer has been set and the buffer map has been initialized enough for `comedi_mmap()` to deal with it (specifically, check the `n_pages` member has been set to a non-zero value). If all is well, the buffer map's reference is incremented and a pointer to it is returned. The comedi subdevice's spin-lock is used to protect the checks. Also use the spin-lock in `__comedi_buf_alloc()` and `__comedi_buf_free()` to protect changes to the subdevice's buffer map structure pointer and the buffer map structure's `n_pages` member. (This checking of `n_pages` is a bit clunky and I [Ian Abbott] plan to deal with it in the future.) Signed-off-by: Ian Abbott <[email protected]> Cc: <[email protected]> # 3.14.x, 3.15.x Signed-off-by: Greg Kroah-Hartman <[email protected]>
1 parent 2c33d7c commit b34aa86

File tree

3 files changed

+51
-6
lines changed

3 files changed

+51
-6
lines changed

drivers/staging/comedi/comedi_buf.c

+35-2
Original file line numberDiff line numberDiff line change
@@ -61,15 +61,20 @@ static void __comedi_buf_free(struct comedi_device *dev,
6161
struct comedi_subdevice *s)
6262
{
6363
struct comedi_async *async = s->async;
64+
struct comedi_buf_map *bm;
65+
unsigned long flags;
6466

6567
if (async->prealloc_buf) {
6668
vunmap(async->prealloc_buf);
6769
async->prealloc_buf = NULL;
6870
async->prealloc_bufsz = 0;
6971
}
7072

71-
comedi_buf_map_put(async->buf_map);
73+
spin_lock_irqsave(&s->spin_lock, flags);
74+
bm = async->buf_map;
7275
async->buf_map = NULL;
76+
spin_unlock_irqrestore(&s->spin_lock, flags);
77+
comedi_buf_map_put(bm);
7378
}
7479

7580
static void __comedi_buf_alloc(struct comedi_device *dev,
@@ -80,6 +85,7 @@ static void __comedi_buf_alloc(struct comedi_device *dev,
8085
struct page **pages = NULL;
8186
struct comedi_buf_map *bm;
8287
struct comedi_buf_page *buf;
88+
unsigned long flags;
8389
unsigned i;
8490

8591
if (!IS_ENABLED(CONFIG_HAS_DMA) && s->async_dma_dir != DMA_NONE) {
@@ -92,8 +98,10 @@ static void __comedi_buf_alloc(struct comedi_device *dev,
9298
if (!bm)
9399
return;
94100

95-
async->buf_map = bm;
96101
kref_init(&bm->refcount);
102+
spin_lock_irqsave(&s->spin_lock, flags);
103+
async->buf_map = bm;
104+
spin_unlock_irqrestore(&s->spin_lock, flags);
97105
bm->dma_dir = s->async_dma_dir;
98106
if (bm->dma_dir != DMA_NONE)
99107
/* Need ref to hardware device to free buffer later. */
@@ -127,7 +135,9 @@ static void __comedi_buf_alloc(struct comedi_device *dev,
127135

128136
pages[i] = virt_to_page(buf->virt_addr);
129137
}
138+
spin_lock_irqsave(&s->spin_lock, flags);
130139
bm->n_pages = i;
140+
spin_unlock_irqrestore(&s->spin_lock, flags);
131141

132142
/* vmap the prealloc_buf if all the pages were allocated */
133143
if (i == n_pages)
@@ -150,6 +160,29 @@ int comedi_buf_map_put(struct comedi_buf_map *bm)
150160
return 1;
151161
}
152162

163+
/* returns s->async->buf_map and increments its kref refcount */
164+
struct comedi_buf_map *
165+
comedi_buf_map_from_subdev_get(struct comedi_subdevice *s)
166+
{
167+
struct comedi_async *async = s->async;
168+
struct comedi_buf_map *bm = NULL;
169+
unsigned long flags;
170+
171+
if (!async)
172+
return NULL;
173+
174+
spin_lock_irqsave(&s->spin_lock, flags);
175+
bm = async->buf_map;
176+
/* only want it if buffer pages allocated */
177+
if (bm && bm->n_pages)
178+
comedi_buf_map_get(bm);
179+
else
180+
bm = NULL;
181+
spin_unlock_irqrestore(&s->spin_lock, flags);
182+
183+
return bm;
184+
}
185+
153186
bool comedi_buf_is_mmapped(struct comedi_async *async)
154187
{
155188
struct comedi_buf_map *bm = async->buf_map;

drivers/staging/comedi/comedi_fops.c

+14-4
Original file line numberDiff line numberDiff line change
@@ -1926,14 +1926,21 @@ static int comedi_mmap(struct file *file, struct vm_area_struct *vma)
19261926
struct comedi_device *dev = file->private_data;
19271927
struct comedi_subdevice *s;
19281928
struct comedi_async *async;
1929-
struct comedi_buf_map *bm;
1929+
struct comedi_buf_map *bm = NULL;
19301930
unsigned long start = vma->vm_start;
19311931
unsigned long size;
19321932
int n_pages;
19331933
int i;
19341934
int retval;
19351935

1936-
mutex_lock(&dev->mutex);
1936+
/*
1937+
* 'trylock' avoids circular dependency with current->mm->mmap_sem
1938+
* and down-reading &dev->attach_lock should normally succeed without
1939+
* contention unless the device is in the process of being attached
1940+
* or detached.
1941+
*/
1942+
if (!down_read_trylock(&dev->attach_lock))
1943+
return -EAGAIN;
19371944

19381945
if (!dev->attached) {
19391946
dev_dbg(dev->class_dev, "no driver attached\n");
@@ -1973,7 +1980,9 @@ static int comedi_mmap(struct file *file, struct vm_area_struct *vma)
19731980
}
19741981

19751982
n_pages = size >> PAGE_SHIFT;
1976-
bm = async->buf_map;
1983+
1984+
/* get reference to current buf map (if any) */
1985+
bm = comedi_buf_map_from_subdev_get(s);
19771986
if (!bm || n_pages > bm->n_pages) {
19781987
retval = -EINVAL;
19791988
goto done;
@@ -1997,7 +2006,8 @@ static int comedi_mmap(struct file *file, struct vm_area_struct *vma)
19972006

19982007
retval = 0;
19992008
done:
2000-
mutex_unlock(&dev->mutex);
2009+
up_read(&dev->attach_lock);
2010+
comedi_buf_map_put(bm); /* put reference to buf map - okay if NULL */
20012011
return retval;
20022012
}
20032013

drivers/staging/comedi/comedi_internal.h

+2
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ void comedi_buf_reset(struct comedi_async *async);
1919
bool comedi_buf_is_mmapped(struct comedi_async *async);
2020
void comedi_buf_map_get(struct comedi_buf_map *bm);
2121
int comedi_buf_map_put(struct comedi_buf_map *bm);
22+
struct comedi_buf_map *comedi_buf_map_from_subdev_get(
23+
struct comedi_subdevice *s);
2224
unsigned int comedi_buf_write_n_allocated(struct comedi_async *async);
2325
void comedi_device_cancel_all(struct comedi_device *dev);
2426

0 commit comments

Comments
 (0)