Skip to content

Commit 3138c93

Browse files
SeanLFclaude
andcommitted
ao_coreaudio: register hotplug listener after init succeeds
ao_uninit() calls the driver's uninit() only once ao->driver_initialized is set, i.e. after init() has succeeded, but it frees the ao either way. init() registered the hotplug listener before the steps that can fail, so a failed init left CoreAudio holding two property listeners whose clientData pointed at freed memory. mpv falls back to the next ao driver, so the process stays alive and the next device change enters hotplug_cb() on the freed ao. Easy to hit on macOS 26/27, where init_audiounit() fails with -50 for some channel layouts and mpv falls back to ao_avfoundation. Under ASan this is a heap-use-after-free in hotplug_cb() with the free attributed to ao_uninit(); without it the crash depends on what reuses the block, since mp_msg_level() dereferences log unconditionally. Register after everything that can fail instead. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
1 parent e7191f2 commit 3138c93

1 file changed

Lines changed: 5 additions & 3 deletions

File tree

audio/out/ao_coreaudio.c

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -167,9 +167,6 @@ static int init(struct ao *ao)
167167
if (!reinit_device(ao))
168168
goto coreaudio_error;
169169

170-
if (!register_hotplug_cb(ao))
171-
goto coreaudio_error;
172-
173170
if (p->change_physical_format)
174171
init_physical_format(ao);
175172

@@ -193,6 +190,11 @@ static int init(struct ao *ao)
193190
p->queue = dispatch_queue_create("io.mpv.coreaudio_stop_during_idle",
194191
DISPATCH_QUEUE_SERIAL);
195192

193+
// Register last: ao_uninit() does not call uninit() for a failed init, but
194+
// frees the ao, so a listener registered earlier would outlive it.
195+
if (!register_hotplug_cb(ao))
196+
goto coreaudio_error;
197+
196198
return CONTROL_OK;
197199

198200
coreaudio_error:

0 commit comments

Comments
 (0)