diff options
author | Takashi Iwai <tiwai@suse.de> | 2016-01-31 05:57:41 -0500 |
---|---|---|
committer | Takashi Iwai <tiwai@suse.de> | 2016-02-03 08:51:28 -0500 |
commit | 06ab30034ed9c200a570ab13c017bde248ddb2a6 (patch) | |
tree | 200543930b73216658ed085278dd0b98844f193c | |
parent | f146357f069e71aff8e474c625bcebcd3094b3ab (diff) |
ALSA: rawmidi: Make snd_rawmidi_transmit() race-free
A kernel WARNING in snd_rawmidi_transmit_ack() is triggered by
syzkaller fuzzer:
WARNING: CPU: 1 PID: 20739 at sound/core/rawmidi.c:1136
Call Trace:
[< inline >] __dump_stack lib/dump_stack.c:15
[<ffffffff82999e2d>] dump_stack+0x6f/0xa2 lib/dump_stack.c:50
[<ffffffff81352089>] warn_slowpath_common+0xd9/0x140 kernel/panic.c:482
[<ffffffff813522b9>] warn_slowpath_null+0x29/0x30 kernel/panic.c:515
[<ffffffff84f80bd5>] snd_rawmidi_transmit_ack+0x275/0x400 sound/core/rawmidi.c:1136
[<ffffffff84fdb3c1>] snd_virmidi_output_trigger+0x4b1/0x5a0 sound/core/seq/seq_virmidi.c:163
[< inline >] snd_rawmidi_output_trigger sound/core/rawmidi.c:150
[<ffffffff84f87ed9>] snd_rawmidi_kernel_write1+0x549/0x780 sound/core/rawmidi.c:1223
[<ffffffff84f89fd3>] snd_rawmidi_write+0x543/0xb30 sound/core/rawmidi.c:1273
[<ffffffff817b0323>] __vfs_write+0x113/0x480 fs/read_write.c:528
[<ffffffff817b1db7>] vfs_write+0x167/0x4a0 fs/read_write.c:577
[< inline >] SYSC_write fs/read_write.c:624
[<ffffffff817b50a1>] SyS_write+0x111/0x220 fs/read_write.c:616
[<ffffffff86336c36>] entry_SYSCALL_64_fastpath+0x16/0x7a arch/x86/entry/entry_64.S:185
Also a similar warning is found but in another path:
Call Trace:
[< inline >] __dump_stack lib/dump_stack.c:15
[<ffffffff82be2c0d>] dump_stack+0x6f/0xa2 lib/dump_stack.c:50
[<ffffffff81355139>] warn_slowpath_common+0xd9/0x140 kernel/panic.c:482
[<ffffffff81355369>] warn_slowpath_null+0x29/0x30 kernel/panic.c:515
[<ffffffff8527e69a>] rawmidi_transmit_ack+0x24a/0x3b0 sound/core/rawmidi.c:1133
[<ffffffff8527e851>] snd_rawmidi_transmit_ack+0x51/0x80 sound/core/rawmidi.c:1163
[<ffffffff852d9046>] snd_virmidi_output_trigger+0x2b6/0x570 sound/core/seq/seq_virmidi.c:185
[< inline >] snd_rawmidi_output_trigger sound/core/rawmidi.c:150
[<ffffffff85285a0b>] snd_rawmidi_kernel_write1+0x4bb/0x760 sound/core/rawmidi.c:1252
[<ffffffff85287b73>] snd_rawmidi_write+0x543/0xb30 sound/core/rawmidi.c:1302
[<ffffffff817ba5f3>] __vfs_write+0x113/0x480 fs/read_write.c:528
[<ffffffff817bc087>] vfs_write+0x167/0x4a0 fs/read_write.c:577
[< inline >] SYSC_write fs/read_write.c:624
[<ffffffff817bf371>] SyS_write+0x111/0x220 fs/read_write.c:616
[<ffffffff86660276>] entry_SYSCALL_64_fastpath+0x16/0x7a arch/x86/entry/entry_64.S:185
In the former case, the reason is that virmidi has an open code
calling snd_rawmidi_transmit_ack() with the value calculated outside
the spinlock. We may use snd_rawmidi_transmit() in a loop just for
consuming the input data, but even there, there is a race between
snd_rawmidi_transmit_peek() and snd_rawmidi_tranmit_ack().
Similarly in the latter case, it calls snd_rawmidi_transmit_peek() and
snd_rawmidi_tranmit_ack() separately without protection, so they are
racy as well.
The patch tries to address these issues by the following ways:
- Introduce the unlocked versions of snd_rawmidi_transmit_peek() and
snd_rawmidi_transmit_ack() to be called inside the explicit lock.
- Rewrite snd_rawmidi_transmit() to be race-free (the former case).
- Make the split calls (the latter case) protected in the rawmidi spin
lock.
BugLink: http://lkml.kernel.org/r/CACT4Y+YPq1+cYLkadwjWa5XjzF1_Vki1eHnVn-Lm0hzhSpu5PA@mail.gmail.com
BugLink: http://lkml.kernel.org/r/CACT4Y+acG4iyphdOZx47Nyq_VHGbpJQK-6xNpiqUjaZYqsXOGw@mail.gmail.com
Reported-by: Dmitry Vyukov <dvyukov@google.com>
Tested-by: Dmitry Vyukov <dvyukov@google.com>
Cc: <stable@vger.kernel.org>
Signed-off-by: Takashi Iwai <tiwai@suse.de>
-rw-r--r-- | include/sound/rawmidi.h | 4 | ||||
-rw-r--r-- | sound/core/rawmidi.c | 98 | ||||
-rw-r--r-- | sound/core/seq/seq_virmidi.c | 17 |
3 files changed, 88 insertions, 31 deletions
diff --git a/include/sound/rawmidi.h b/include/sound/rawmidi.h index fdabbb4ddba9..f730b91e472f 100644 --- a/include/sound/rawmidi.h +++ b/include/sound/rawmidi.h | |||
@@ -167,6 +167,10 @@ int snd_rawmidi_transmit_peek(struct snd_rawmidi_substream *substream, | |||
167 | int snd_rawmidi_transmit_ack(struct snd_rawmidi_substream *substream, int count); | 167 | int snd_rawmidi_transmit_ack(struct snd_rawmidi_substream *substream, int count); |
168 | int snd_rawmidi_transmit(struct snd_rawmidi_substream *substream, | 168 | int snd_rawmidi_transmit(struct snd_rawmidi_substream *substream, |
169 | unsigned char *buffer, int count); | 169 | unsigned char *buffer, int count); |
170 | int __snd_rawmidi_transmit_peek(struct snd_rawmidi_substream *substream, | ||
171 | unsigned char *buffer, int count); | ||
172 | int __snd_rawmidi_transmit_ack(struct snd_rawmidi_substream *substream, | ||
173 | int count); | ||
170 | 174 | ||
171 | /* main midi functions */ | 175 | /* main midi functions */ |
172 | 176 | ||
diff --git a/sound/core/rawmidi.c b/sound/core/rawmidi.c index f75d1656272c..26ca02248885 100644 --- a/sound/core/rawmidi.c +++ b/sound/core/rawmidi.c | |||
@@ -1055,23 +1055,16 @@ int snd_rawmidi_transmit_empty(struct snd_rawmidi_substream *substream) | |||
1055 | EXPORT_SYMBOL(snd_rawmidi_transmit_empty); | 1055 | EXPORT_SYMBOL(snd_rawmidi_transmit_empty); |
1056 | 1056 | ||
1057 | /** | 1057 | /** |
1058 | * snd_rawmidi_transmit_peek - copy data from the internal buffer | 1058 | * __snd_rawmidi_transmit_peek - copy data from the internal buffer |
1059 | * @substream: the rawmidi substream | 1059 | * @substream: the rawmidi substream |
1060 | * @buffer: the buffer pointer | 1060 | * @buffer: the buffer pointer |
1061 | * @count: data size to transfer | 1061 | * @count: data size to transfer |
1062 | * | 1062 | * |
1063 | * Copies data from the internal output buffer to the given buffer. | 1063 | * This is a variant of snd_rawmidi_transmit_peek() without spinlock. |
1064 | * | ||
1065 | * Call this in the interrupt handler when the midi output is ready, | ||
1066 | * and call snd_rawmidi_transmit_ack() after the transmission is | ||
1067 | * finished. | ||
1068 | * | ||
1069 | * Return: The size of copied data, or a negative error code on failure. | ||
1070 | */ | 1064 | */ |
1071 | int snd_rawmidi_transmit_peek(struct snd_rawmidi_substream *substream, | 1065 | int __snd_rawmidi_transmit_peek(struct snd_rawmidi_substream *substream, |
1072 | unsigned char *buffer, int count) | 1066 | unsigned char *buffer, int count) |
1073 | { | 1067 | { |
1074 | unsigned long flags; | ||
1075 | int result, count1; | 1068 | int result, count1; |
1076 | struct snd_rawmidi_runtime *runtime = substream->runtime; | 1069 | struct snd_rawmidi_runtime *runtime = substream->runtime; |
1077 | 1070 | ||
@@ -1081,7 +1074,6 @@ int snd_rawmidi_transmit_peek(struct snd_rawmidi_substream *substream, | |||
1081 | return -EINVAL; | 1074 | return -EINVAL; |
1082 | } | 1075 | } |
1083 | result = 0; | 1076 | result = 0; |
1084 | spin_lock_irqsave(&runtime->lock, flags); | ||
1085 | if (runtime->avail >= runtime->buffer_size) { | 1077 | if (runtime->avail >= runtime->buffer_size) { |
1086 | /* warning: lowlevel layer MUST trigger down the hardware */ | 1078 | /* warning: lowlevel layer MUST trigger down the hardware */ |
1087 | goto __skip; | 1079 | goto __skip; |
@@ -1106,25 +1098,47 @@ int snd_rawmidi_transmit_peek(struct snd_rawmidi_substream *substream, | |||
1106 | } | 1098 | } |
1107 | } | 1099 | } |
1108 | __skip: | 1100 | __skip: |
1101 | return result; | ||
1102 | } | ||
1103 | EXPORT_SYMBOL(__snd_rawmidi_transmit_peek); | ||
1104 | |||
1105 | /** | ||
1106 | * snd_rawmidi_transmit_peek - copy data from the internal buffer | ||
1107 | * @substream: the rawmidi substream | ||
1108 | * @buffer: the buffer pointer | ||
1109 | * @count: data size to transfer | ||
1110 | * | ||
1111 | * Copies data from the internal output buffer to the given buffer. | ||
1112 | * | ||
1113 | * Call this in the interrupt handler when the midi output is ready, | ||
1114 | * and call snd_rawmidi_transmit_ack() after the transmission is | ||
1115 | * finished. | ||
1116 | * | ||
1117 | * Return: The size of copied data, or a negative error code on failure. | ||
1118 | */ | ||
1119 | int snd_rawmidi_transmit_peek(struct snd_rawmidi_substream *substream, | ||
1120 | unsigned char *buffer, int count) | ||
1121 | { | ||
1122 | struct snd_rawmidi_runtime *runtime = substream->runtime; | ||
1123 | int result; | ||
1124 | unsigned long flags; | ||
1125 | |||
1126 | spin_lock_irqsave(&runtime->lock, flags); | ||
1127 | result = __snd_rawmidi_transmit_peek(substream, buffer, count); | ||
1109 | spin_unlock_irqrestore(&runtime->lock, flags); | 1128 | spin_unlock_irqrestore(&runtime->lock, flags); |
1110 | return result; | 1129 | return result; |
1111 | } | 1130 | } |
1112 | EXPORT_SYMBOL(snd_rawmidi_transmit_peek); | 1131 | EXPORT_SYMBOL(snd_rawmidi_transmit_peek); |
1113 | 1132 | ||
1114 | /** | 1133 | /** |
1115 | * snd_rawmidi_transmit_ack - acknowledge the transmission | 1134 | * __snd_rawmidi_transmit_ack - acknowledge the transmission |
1116 | * @substream: the rawmidi substream | 1135 | * @substream: the rawmidi substream |
1117 | * @count: the transferred count | 1136 | * @count: the transferred count |
1118 | * | 1137 | * |
1119 | * Advances the hardware pointer for the internal output buffer with | 1138 | * This is a variant of __snd_rawmidi_transmit_ack() without spinlock. |
1120 | * the given size and updates the condition. | ||
1121 | * Call after the transmission is finished. | ||
1122 | * | ||
1123 | * Return: The advanced size if successful, or a negative error code on failure. | ||
1124 | */ | 1139 | */ |
1125 | int snd_rawmidi_transmit_ack(struct snd_rawmidi_substream *substream, int count) | 1140 | int __snd_rawmidi_transmit_ack(struct snd_rawmidi_substream *substream, int count) |
1126 | { | 1141 | { |
1127 | unsigned long flags; | ||
1128 | struct snd_rawmidi_runtime *runtime = substream->runtime; | 1142 | struct snd_rawmidi_runtime *runtime = substream->runtime; |
1129 | 1143 | ||
1130 | if (runtime->buffer == NULL) { | 1144 | if (runtime->buffer == NULL) { |
@@ -1132,7 +1146,6 @@ int snd_rawmidi_transmit_ack(struct snd_rawmidi_substream *substream, int count) | |||
1132 | "snd_rawmidi_transmit_ack: output is not active!!!\n"); | 1146 | "snd_rawmidi_transmit_ack: output is not active!!!\n"); |
1133 | return -EINVAL; | 1147 | return -EINVAL; |
1134 | } | 1148 | } |
1135 | spin_lock_irqsave(&runtime->lock, flags); | ||
1136 | snd_BUG_ON(runtime->avail + count > runtime->buffer_size); | 1149 | snd_BUG_ON(runtime->avail + count > runtime->buffer_size); |
1137 | runtime->hw_ptr += count; | 1150 | runtime->hw_ptr += count; |
1138 | runtime->hw_ptr %= runtime->buffer_size; | 1151 | runtime->hw_ptr %= runtime->buffer_size; |
@@ -1142,9 +1155,32 @@ int snd_rawmidi_transmit_ack(struct snd_rawmidi_substream *substream, int count) | |||
1142 | if (runtime->drain || snd_rawmidi_ready(substream)) | 1155 | if (runtime->drain || snd_rawmidi_ready(substream)) |
1143 | wake_up(&runtime->sleep); | 1156 | wake_up(&runtime->sleep); |
1144 | } | 1157 | } |
1145 | spin_unlock_irqrestore(&runtime->lock, flags); | ||
1146 | return count; | 1158 | return count; |
1147 | } | 1159 | } |
1160 | EXPORT_SYMBOL(__snd_rawmidi_transmit_ack); | ||
1161 | |||
1162 | /** | ||
1163 | * snd_rawmidi_transmit_ack - acknowledge the transmission | ||
1164 | * @substream: the rawmidi substream | ||
1165 | * @count: the transferred count | ||
1166 | * | ||
1167 | * Advances the hardware pointer for the internal output buffer with | ||
1168 | * the given size and updates the condition. | ||
1169 | * Call after the transmission is finished. | ||
1170 | * | ||
1171 | * Return: The advanced size if successful, or a negative error code on failure. | ||
1172 | */ | ||
1173 | int snd_rawmidi_transmit_ack(struct snd_rawmidi_substream *substream, int count) | ||
1174 | { | ||
1175 | struct snd_rawmidi_runtime *runtime = substream->runtime; | ||
1176 | int result; | ||
1177 | unsigned long flags; | ||
1178 | |||
1179 | spin_lock_irqsave(&runtime->lock, flags); | ||
1180 | result = __snd_rawmidi_transmit_ack(substream, count); | ||
1181 | spin_unlock_irqrestore(&runtime->lock, flags); | ||
1182 | return result; | ||
1183 | } | ||
1148 | EXPORT_SYMBOL(snd_rawmidi_transmit_ack); | 1184 | EXPORT_SYMBOL(snd_rawmidi_transmit_ack); |
1149 | 1185 | ||
1150 | /** | 1186 | /** |
@@ -1160,12 +1196,22 @@ EXPORT_SYMBOL(snd_rawmidi_transmit_ack); | |||
1160 | int snd_rawmidi_transmit(struct snd_rawmidi_substream *substream, | 1196 | int snd_rawmidi_transmit(struct snd_rawmidi_substream *substream, |
1161 | unsigned char *buffer, int count) | 1197 | unsigned char *buffer, int count) |
1162 | { | 1198 | { |
1199 | struct snd_rawmidi_runtime *runtime = substream->runtime; | ||
1200 | int result; | ||
1201 | unsigned long flags; | ||
1202 | |||
1203 | spin_lock_irqsave(&runtime->lock, flags); | ||
1163 | if (!substream->opened) | 1204 | if (!substream->opened) |
1164 | return -EBADFD; | 1205 | result = -EBADFD; |
1165 | count = snd_rawmidi_transmit_peek(substream, buffer, count); | 1206 | else { |
1166 | if (count < 0) | 1207 | count = __snd_rawmidi_transmit_peek(substream, buffer, count); |
1167 | return count; | 1208 | if (count <= 0) |
1168 | return snd_rawmidi_transmit_ack(substream, count); | 1209 | result = count; |
1210 | else | ||
1211 | result = __snd_rawmidi_transmit_ack(substream, count); | ||
1212 | } | ||
1213 | spin_unlock_irqrestore(&runtime->lock, flags); | ||
1214 | return result; | ||
1169 | } | 1215 | } |
1170 | EXPORT_SYMBOL(snd_rawmidi_transmit); | 1216 | EXPORT_SYMBOL(snd_rawmidi_transmit); |
1171 | 1217 | ||
diff --git a/sound/core/seq/seq_virmidi.c b/sound/core/seq/seq_virmidi.c index f71aedfb408c..c82ed3e70506 100644 --- a/sound/core/seq/seq_virmidi.c +++ b/sound/core/seq/seq_virmidi.c | |||
@@ -155,21 +155,26 @@ static void snd_virmidi_output_trigger(struct snd_rawmidi_substream *substream, | |||
155 | struct snd_virmidi *vmidi = substream->runtime->private_data; | 155 | struct snd_virmidi *vmidi = substream->runtime->private_data; |
156 | int count, res; | 156 | int count, res; |
157 | unsigned char buf[32], *pbuf; | 157 | unsigned char buf[32], *pbuf; |
158 | unsigned long flags; | ||
158 | 159 | ||
159 | if (up) { | 160 | if (up) { |
160 | vmidi->trigger = 1; | 161 | vmidi->trigger = 1; |
161 | if (vmidi->seq_mode == SNDRV_VIRMIDI_SEQ_DISPATCH && | 162 | if (vmidi->seq_mode == SNDRV_VIRMIDI_SEQ_DISPATCH && |
162 | !(vmidi->rdev->flags & SNDRV_VIRMIDI_SUBSCRIBE)) { | 163 | !(vmidi->rdev->flags & SNDRV_VIRMIDI_SUBSCRIBE)) { |
163 | snd_rawmidi_transmit_ack(substream, substream->runtime->buffer_size - substream->runtime->avail); | 164 | while (snd_rawmidi_transmit(substream, buf, |
164 | return; /* ignored */ | 165 | sizeof(buf)) > 0) { |
166 | /* ignored */ | ||
167 | } | ||
168 | return; | ||
165 | } | 169 | } |
166 | if (vmidi->event.type != SNDRV_SEQ_EVENT_NONE) { | 170 | if (vmidi->event.type != SNDRV_SEQ_EVENT_NONE) { |
167 | if (snd_seq_kernel_client_dispatch(vmidi->client, &vmidi->event, in_atomic(), 0) < 0) | 171 | if (snd_seq_kernel_client_dispatch(vmidi->client, &vmidi->event, in_atomic(), 0) < 0) |
168 | return; | 172 | return; |
169 | vmidi->event.type = SNDRV_SEQ_EVENT_NONE; | 173 | vmidi->event.type = SNDRV_SEQ_EVENT_NONE; |
170 | } | 174 | } |
175 | spin_lock_irqsave(&substream->runtime->lock, flags); | ||
171 | while (1) { | 176 | while (1) { |
172 | count = snd_rawmidi_transmit_peek(substream, buf, sizeof(buf)); | 177 | count = __snd_rawmidi_transmit_peek(substream, buf, sizeof(buf)); |
173 | if (count <= 0) | 178 | if (count <= 0) |
174 | break; | 179 | break; |
175 | pbuf = buf; | 180 | pbuf = buf; |
@@ -179,16 +184,18 @@ static void snd_virmidi_output_trigger(struct snd_rawmidi_substream *substream, | |||
179 | snd_midi_event_reset_encode(vmidi->parser); | 184 | snd_midi_event_reset_encode(vmidi->parser); |
180 | continue; | 185 | continue; |
181 | } | 186 | } |
182 | snd_rawmidi_transmit_ack(substream, res); | 187 | __snd_rawmidi_transmit_ack(substream, res); |
183 | pbuf += res; | 188 | pbuf += res; |
184 | count -= res; | 189 | count -= res; |
185 | if (vmidi->event.type != SNDRV_SEQ_EVENT_NONE) { | 190 | if (vmidi->event.type != SNDRV_SEQ_EVENT_NONE) { |
186 | if (snd_seq_kernel_client_dispatch(vmidi->client, &vmidi->event, in_atomic(), 0) < 0) | 191 | if (snd_seq_kernel_client_dispatch(vmidi->client, &vmidi->event, in_atomic(), 0) < 0) |
187 | return; | 192 | goto out; |
188 | vmidi->event.type = SNDRV_SEQ_EVENT_NONE; | 193 | vmidi->event.type = SNDRV_SEQ_EVENT_NONE; |
189 | } | 194 | } |
190 | } | 195 | } |
191 | } | 196 | } |
197 | out: | ||
198 | spin_unlock_irqrestore(&substream->runtime->lock, flags); | ||
192 | } else { | 199 | } else { |
193 | vmidi->trigger = 0; | 200 | vmidi->trigger = 0; |
194 | } | 201 | } |