diff options
author | Takashi Iwai <tiwai@suse.de> | 2017-10-09 04:02:56 -0400 |
---|---|---|
committer | Takashi Iwai <tiwai@suse.de> | 2017-10-09 08:10:13 -0400 |
commit | 5803b023881857db32ffefa0d269c90280a67ee0 (patch) | |
tree | dc218d1ac325d6290ec3ca495eaa86aaa4ea461b | |
parent | c247487c0dd6fefff6ed0cbcbe66f037721755fb (diff) |
ALSA: seq: Fix copy_from_user() call inside lock
The event handler in the virmidi sequencer code takes a read-lock for
the linked list traverse, while it's calling snd_seq_dump_var_event()
in the loop. The latter function may expand the user-space data
depending on the event type. It eventually invokes copy_from_user(),
which might be a potential dead-lock.
The sequencer core guarantees that the user-space data is passed only
with atomic=0 argument, but snd_virmidi_dev_receive_event() ignores it
and always takes read-lock(). For avoiding the problem above, this
patch introduces rwsem for non-atomic case, while keeping rwlock for
atomic case.
Also while we're at it: the superfluous irq flags is dropped in
snd_virmidi_input_open().
Reported-by: Jia-Ju Bai <baijiaju1990@163.com>
Cc: <stable@vger.kernel.org>
Signed-off-by: Takashi Iwai <tiwai@suse.de>
-rw-r--r-- | include/sound/seq_virmidi.h | 1 | ||||
-rw-r--r-- | sound/core/seq/seq_virmidi.c | 27 |
2 files changed, 20 insertions, 8 deletions
diff --git a/include/sound/seq_virmidi.h b/include/sound/seq_virmidi.h index a03acd0d398a..695257ae64ac 100644 --- a/include/sound/seq_virmidi.h +++ b/include/sound/seq_virmidi.h | |||
@@ -60,6 +60,7 @@ struct snd_virmidi_dev { | |||
60 | int port; /* created/attached port */ | 60 | int port; /* created/attached port */ |
61 | unsigned int flags; /* SNDRV_VIRMIDI_* */ | 61 | unsigned int flags; /* SNDRV_VIRMIDI_* */ |
62 | rwlock_t filelist_lock; | 62 | rwlock_t filelist_lock; |
63 | struct rw_semaphore filelist_sem; | ||
63 | struct list_head filelist; | 64 | struct list_head filelist; |
64 | }; | 65 | }; |
65 | 66 | ||
diff --git a/sound/core/seq/seq_virmidi.c b/sound/core/seq/seq_virmidi.c index 8d93a4021c78..f48a4cd24ffc 100644 --- a/sound/core/seq/seq_virmidi.c +++ b/sound/core/seq/seq_virmidi.c | |||
@@ -77,13 +77,17 @@ static void snd_virmidi_init_event(struct snd_virmidi *vmidi, | |||
77 | * decode input event and put to read buffer of each opened file | 77 | * decode input event and put to read buffer of each opened file |
78 | */ | 78 | */ |
79 | static int snd_virmidi_dev_receive_event(struct snd_virmidi_dev *rdev, | 79 | static int snd_virmidi_dev_receive_event(struct snd_virmidi_dev *rdev, |
80 | struct snd_seq_event *ev) | 80 | struct snd_seq_event *ev, |
81 | bool atomic) | ||
81 | { | 82 | { |
82 | struct snd_virmidi *vmidi; | 83 | struct snd_virmidi *vmidi; |
83 | unsigned char msg[4]; | 84 | unsigned char msg[4]; |
84 | int len; | 85 | int len; |
85 | 86 | ||
86 | read_lock(&rdev->filelist_lock); | 87 | if (atomic) |
88 | read_lock(&rdev->filelist_lock); | ||
89 | else | ||
90 | down_read(&rdev->filelist_sem); | ||
87 | list_for_each_entry(vmidi, &rdev->filelist, list) { | 91 | list_for_each_entry(vmidi, &rdev->filelist, list) { |
88 | if (!vmidi->trigger) | 92 | if (!vmidi->trigger) |
89 | continue; | 93 | continue; |
@@ -97,7 +101,10 @@ static int snd_virmidi_dev_receive_event(struct snd_virmidi_dev *rdev, | |||
97 | snd_rawmidi_receive(vmidi->substream, msg, len); | 101 | snd_rawmidi_receive(vmidi->substream, msg, len); |
98 | } | 102 | } |
99 | } | 103 | } |
100 | read_unlock(&rdev->filelist_lock); | 104 | if (atomic) |
105 | read_unlock(&rdev->filelist_lock); | ||
106 | else | ||
107 | up_read(&rdev->filelist_sem); | ||
101 | 108 | ||
102 | return 0; | 109 | return 0; |
103 | } | 110 | } |
@@ -115,7 +122,7 @@ int snd_virmidi_receive(struct snd_rawmidi *rmidi, struct snd_seq_event *ev) | |||
115 | struct snd_virmidi_dev *rdev; | 122 | struct snd_virmidi_dev *rdev; |
116 | 123 | ||
117 | rdev = rmidi->private_data; | 124 | rdev = rmidi->private_data; |
118 | return snd_virmidi_dev_receive_event(rdev, ev); | 125 | return snd_virmidi_dev_receive_event(rdev, ev, true); |
119 | } | 126 | } |
120 | #endif /* 0 */ | 127 | #endif /* 0 */ |
121 | 128 | ||
@@ -130,7 +137,7 @@ static int snd_virmidi_event_input(struct snd_seq_event *ev, int direct, | |||
130 | rdev = private_data; | 137 | rdev = private_data; |
131 | if (!(rdev->flags & SNDRV_VIRMIDI_USE)) | 138 | if (!(rdev->flags & SNDRV_VIRMIDI_USE)) |
132 | return 0; /* ignored */ | 139 | return 0; /* ignored */ |
133 | return snd_virmidi_dev_receive_event(rdev, ev); | 140 | return snd_virmidi_dev_receive_event(rdev, ev, atomic); |
134 | } | 141 | } |
135 | 142 | ||
136 | /* | 143 | /* |
@@ -209,7 +216,6 @@ static int snd_virmidi_input_open(struct snd_rawmidi_substream *substream) | |||
209 | struct snd_virmidi_dev *rdev = substream->rmidi->private_data; | 216 | struct snd_virmidi_dev *rdev = substream->rmidi->private_data; |
210 | struct snd_rawmidi_runtime *runtime = substream->runtime; | 217 | struct snd_rawmidi_runtime *runtime = substream->runtime; |
211 | struct snd_virmidi *vmidi; | 218 | struct snd_virmidi *vmidi; |
212 | unsigned long flags; | ||
213 | 219 | ||
214 | vmidi = kzalloc(sizeof(*vmidi), GFP_KERNEL); | 220 | vmidi = kzalloc(sizeof(*vmidi), GFP_KERNEL); |
215 | if (vmidi == NULL) | 221 | if (vmidi == NULL) |
@@ -223,9 +229,11 @@ static int snd_virmidi_input_open(struct snd_rawmidi_substream *substream) | |||
223 | vmidi->client = rdev->client; | 229 | vmidi->client = rdev->client; |
224 | vmidi->port = rdev->port; | 230 | vmidi->port = rdev->port; |
225 | runtime->private_data = vmidi; | 231 | runtime->private_data = vmidi; |
226 | write_lock_irqsave(&rdev->filelist_lock, flags); | 232 | down_write(&rdev->filelist_sem); |
233 | write_lock_irq(&rdev->filelist_lock); | ||
227 | list_add_tail(&vmidi->list, &rdev->filelist); | 234 | list_add_tail(&vmidi->list, &rdev->filelist); |
228 | write_unlock_irqrestore(&rdev->filelist_lock, flags); | 235 | write_unlock_irq(&rdev->filelist_lock); |
236 | up_write(&rdev->filelist_sem); | ||
229 | vmidi->rdev = rdev; | 237 | vmidi->rdev = rdev; |
230 | return 0; | 238 | return 0; |
231 | } | 239 | } |
@@ -264,9 +272,11 @@ static int snd_virmidi_input_close(struct snd_rawmidi_substream *substream) | |||
264 | struct snd_virmidi_dev *rdev = substream->rmidi->private_data; | 272 | struct snd_virmidi_dev *rdev = substream->rmidi->private_data; |
265 | struct snd_virmidi *vmidi = substream->runtime->private_data; | 273 | struct snd_virmidi *vmidi = substream->runtime->private_data; |
266 | 274 | ||
275 | down_write(&rdev->filelist_sem); | ||
267 | write_lock_irq(&rdev->filelist_lock); | 276 | write_lock_irq(&rdev->filelist_lock); |
268 | list_del(&vmidi->list); | 277 | list_del(&vmidi->list); |
269 | write_unlock_irq(&rdev->filelist_lock); | 278 | write_unlock_irq(&rdev->filelist_lock); |
279 | up_write(&rdev->filelist_sem); | ||
270 | snd_midi_event_free(vmidi->parser); | 280 | snd_midi_event_free(vmidi->parser); |
271 | substream->runtime->private_data = NULL; | 281 | substream->runtime->private_data = NULL; |
272 | kfree(vmidi); | 282 | kfree(vmidi); |
@@ -520,6 +530,7 @@ int snd_virmidi_new(struct snd_card *card, int device, struct snd_rawmidi **rrmi | |||
520 | rdev->rmidi = rmidi; | 530 | rdev->rmidi = rmidi; |
521 | rdev->device = device; | 531 | rdev->device = device; |
522 | rdev->client = -1; | 532 | rdev->client = -1; |
533 | init_rwsem(&rdev->filelist_sem); | ||
523 | rwlock_init(&rdev->filelist_lock); | 534 | rwlock_init(&rdev->filelist_lock); |
524 | INIT_LIST_HEAD(&rdev->filelist); | 535 | INIT_LIST_HEAD(&rdev->filelist); |
525 | rdev->seq_mode = SNDRV_VIRMIDI_SEQ_DISPATCH; | 536 | rdev->seq_mode = SNDRV_VIRMIDI_SEQ_DISPATCH; |