FilterAudioStream use-after-free when releasing shared_ptr before error callback
- Dominant language
- C++
- Stars
- 4.1k
- Forks
- 639
- Avg merge
- 2d 23h
- Merged PRs (30d)
- 8
Description
Android version(s): Android 8-16
Android device(s): Samsung Galaxy A03s, A05, A16, A06. Xiaomi Redmi 14C, Redmi Note 8 Pro, Tecno Spark 7, many others (any device with AAudio)
Oboe version: 1.10.0
App name used for testing: Modified `minimal-oboe` sample in `FilterAudioStreamUAF-repro` (forked repository linked below)
Source:
Compare 1.10.0 <- FilterAudioStreamUAF-repro:
**Short description**
Using a `FilterAudioStream` in combination with `AudioStreamBuilder::openStream(shared_ptr)` causes a use-after-free crash (ASAN) or SIGSEGV (production) when the app releases its `shared_ptr` to the `FilterAudioStream` before `oboe_aaudio_error_thread_proc_common` runs to completion.
The error occurs because the call to [oboeStream->lockWeakThis()](https://github.com/google/oboe/blob/1.10.0/src/aaudio/AudioStreamAAudio.cpp#L179) in `AudioStreamAAudio::internalErrorCallback()` does not safely retain the parent `FilterAudioStream` object, which is then dereferenced as the [AudioStreamErrorCallback](https://github.com/google/oboe/blob/1.10.0/src/aaudio/AudioStreamAAudio.cpp#L85) in `oboe_aaudio_error_thread_proc_common` due to the [callbacks being swapped here](https://github.com/google/oboe/blob/1.10.0/src/common/FilterAudioStream.h#L46).
Notably, this bug only occurs when using `FilterAudioStream`. The call to `lockWeakThis()` does safely retain the `AudioStreamAAudio` object itself. When `FilterAudioStream` is not used, the crash does not occur, as demonstrated by the sample.
This bug appears to be the root cause of #2130.
**Steps to reproduce**
1. Build and run the modified `minimal-oboe` sample contained in the [FilterAudioStreamUAF-repro](https://github.com/jg-hot/oboe/tree/FilterAudioStreamUAF-repro) branch
2. Open the app on a physical device with AAudio (Android 9+)
3. Leave the "Use FilterAudioStream" toggle checked (default)
4. Tap "Start Audio"
5. Plug in or unplug headphones
6. After ~2 seconds, ASAN reports a use-after-free in logcat (run the included `symbolicate.py` if needed)
7. To confirm the issue is specific to `FilterAudioStream`, uncheck "Use FilterAudioStream" and repeat steps 4–5; the crash does not occur
Additional setup in the sample that exposes the bug:
- A `forceConversion` parameter is added to `openStream()` to ensure `FilterAudioStream` is used
- The sample disables automatic stream restart in `onErrorAfterClose()`
- Instead, the app explicitly resets its `shared_ptr` to the stream after closing it in response to `ACTION_HEADSET_PLUG`
- The debug delay in `oboe_aaudio_error_thread_proc_common` is enabled to ensure the `shared_ptr` is released before dereferencing the error callback
**Expected behavior**
This behavior should match that of a regular `AudioStream` without a `FilterAudioStream` wrapper, which does not crash in the same scenario.
**Actual behavior**
Use-after-free crash occurs when `FilterAudioStream` is used and the app releases its `shared_ptr` during headset plug/unplug.
**Production SIGSEGV**
The stack trace below occurs, on average, once per ~2.2K daily active users in a production app. The first write access to `FilterAudioStream` in `AudioStream::updateFramesWritten()` causes a SIGSEGV.
```
Crashed: Thread: SIGSEGV 0x0000007981b62100
#00 pc 0x13a100 libc++_shared.so (__unw_getcontext) (BuildId: b04675a35ad96f8a9dcaa073e3bd31d4536f00ad)
#01 pc 0x2c490 liboboe.so (oboe::AudioStream::close() [AudioStream.cpp:44]) (BuildId: 2e902f2cb969e05f46a756fbe567733d71bfb03e)
#02 pc 0x2ef04 liboboe.so (non-virtual thunk to oboe::FilterAudioStream::onErrorAfterClose(oboe::AudioStream*, oboe::Result) [FilterAudioStream.h:200]) (BuildId: 2e902f2cb969e05f46a756fbe567733d71bfb03e)
#03 pc 0x2b7bc liboboe.so (void* std::__ndk1::__thread_proxy[abi:ne180000] >, void (*)(std::__ndk1::shared_ptr, oboe::Result), std::__ndk1::shared_ptr, oboe::Result> >(void*) [invoke.h:344]) (BuildId: 2e902f2cb969e05f46a756fbe567733d71bfb03e)
#04 pc 0xb3358 libc.so (BuildId: 91887fc17756b96752d59d99fca49b04)
#05 pc 0x516c8 libc.so (BuildId: 91887fc17756b96752d59d99fca49b04)
```
Note: the production app uses Oboe version 1.9.0. See below for the exact lines causing the crash:
**ASAN output from sample:**
The sample app crashes earlier via ASAN on the line below (the first read access to the callback). The root cause (freed `FilterAudioStream` memory) is the same in both cases.
```
Abort message: '=================================================================
==25152==ERROR: AddressSanitizer: heap-use-after-free on address 0x0045a4e98210 at pc 0x0070bc318d14 bp 0x0070c4e2ead0 sp 0x0070c4e2eac8
READ of size 8 at 0x0045a4e98210 thread T29
oboe_aaudio_error_thread_proc_common(oboe::AudioStreamAAudio*, oboe::Result)
/home/dev/oboe/src/aaudio/AudioStreamAAudio.cpp:87:42
oboe_aaudio_error_thread_proc_shared(std::__ndk1::shared_ptr, oboe::Result)
/home/dev/oboe/src/aaudio/AudioStreamAAudio.cpp:112:5
decltype(std::declval, oboe::Result)>()(std::declval>(), std::declval())) std::__ndk1::__invoke[abi:ne180000], oboe::Result), std::__ndk1::shared_ptr, oboe::Result>(void (*&&)(std::__ndk1::shared_ptr, oboe::Result), std::__ndk1::shared_ptr&&, oboe::Result&&)
/opt/dev/android-sdk/ndk/27.0.12077973/toolchains/llvm/prebuilt/linux-x86_64/sysroot/usr/include/c++/v1/__type_traits/invoke.h:344:25
void std::__ndk1::__thread_execute[abi:ne180000]>, void (*)(std::__ndk1::shared_ptr, oboe::Result), std::__ndk1::shared_ptr, oboe::Result, 2ul, 3ul>(std::__ndk1::tuple>, void (*)(std::__ndk1::shared_ptr, oboe::Result), std::__ndk1::shared_ptr, oboe::Result>&, std::__ndk1::__tuple_indices<2ul, 3ul>)
/opt/dev/android-sdk/ndk/27.0.12077973/toolchains/llvm/prebuilt/linux-x86_64/sysroot/usr/include/c++/v1/__thread/thread.h:190:3
void* std::__ndk1::__thread_proxy[abi:ne180000]>, void (*)(std::__ndk1::shared_ptr, oboe::Result), std::__ndk1::shared_ptr, oboe::Result>>(void*)
/opt/dev/android-sdk/ndk/27.0.12077973/toolchains/llvm/prebuilt/linux-x86_64/sysroot/usr/include/c++/v1/__thread/thread.h:199:3
asan_thread_start(void*)
out/lib/compiler-rt-aarch64-ndk-cxx/out/llvm-project/compiler-rt/lib/asan/asan_interceptors.cpp:239:28
#3 0x71c87cbf50 (/apex/com.android.runtime/lib64/bionic/libc.so+0xfcf50) (BuildId: 35a04626779868e5f2a30e171716818d) [No symbols available]
#4 0x71c875e500 (/apex/com.android.runtime/lib64/bionic/libc.so+0x8f500) (BuildId: 35a04626779868e5f2a30e171716818d) [No symbols available]
0x0045a4e98210 is located 368 bytes inside of 416-byte region [0x0045a4e980a0,0x0045a4e98240)
freed by thread T25 (DefaultDispatch) here:
operator delete(void*)
out/lib/compiler-rt-aarch64-ndk-cxx/out/llvm-project/compiler-rt/lib/asan/asan_new_delete.cpp:143:3
std::__ndk1::__shared_count::__release_shared[abi:ne180000]()
/opt/dev/android-sdk/ndk/27.0.12077973/toolchains/llvm/prebuilt/linux-x86_64/sysroot/usr/include/c++/v1/__memory/shared_ptr.h:154:7
std::__ndk1::__shared_weak_count::__release_shared[abi:ne180000]()
/opt/dev/android-sdk/ndk/27.0.12077973/toolchains/llvm/prebuilt/linux-x86_64/sysroot/usr/include/c++/v1/__memory/shared_ptr.h:183:25
std::__ndk1::shared_ptr::~shared_ptr[abi:ne180000]()
/opt/dev/android-sdk/ndk/27.0.12077973/toolchains/llvm/prebuilt/linux-x86_64/sysroot/usr/include/c++/v1/__memory/shared_ptr.h:645:17
std::__ndk1::shared_ptr::reset[abi:ne180000]()
/opt/dev/android-sdk/ndk/27.0.12077973/toolchains/llvm/prebuilt/linux-x86_64/sysroot/usr/include/c++/v1/__memory/shared_ptr.h:695:50
SimpleNoiseMaker::release()
/home/dev/oboe/samples/minimaloboe/src/main/cpp/SimpleNoiseMaker.cpp:100:13
Java_com_example_minimaloboe_AudioPlayer_stopAndReleaseAudioStreamNative
/home/dev/oboe/samples/minimaloboe/src/main/cpp/MinimalOboeJNI.cpp:61:13
#7 0x713ff7ef70 (/apex/com.android.art/lib64/libart.so+0x37ef70) (BuildId: 4ccb65ae9ac5ad5da3af5a342d5b0b92) [No symbols available]
previously allocated by thread T25 (DefaultDispatch) here:
operator new(unsigned long)
out/lib/compiler-rt-aarch64-ndk-cxx/out/llvm-project/compiler-rt/lib/asan/asan_new_delete.cpp:86:3
oboe::AudioStreamBuilder::openStreamInternal(oboe::AudioStream**, bool)
/home/dev/oboe/src/common/AudioStreamBuilder.cpp:165:47
oboe::AudioStreamBuilder::openStream(std::__ndk1::shared_ptr&, bool)
/home/dev/oboe/src/common/AudioStreamBuilder.cpp:243:19
SimpleNoiseMaker::open(bool)
/home/dev/oboe/samples/minimaloboe/src/main/cpp/SimpleNoiseMaker.cpp:40:15
Java_com_example_minimaloboe_AudioPlayer_startAudioStreamNative
/home/dev/oboe/samples/minimaloboe/src/main/cpp/MinimalOboeJNI.cpp:45:29
#5 0x713ff7ef70 (/apex/com.android.art/lib64/libart.so+0x37ef70) (BuildId: 4ccb65ae9ac5ad5da3af5a342d5b0b92) [No symbols available]
Thread T29 created by T27 (AudioTrack) here:
___interceptor_pthread_create
out/lib/compiler-rt-aarch64-ndk-cxx/out/llvm-project/compiler-rt/lib/asan/asan_interceptors.cpp:250:3
std::__ndk1::__libcpp_thread_create[abi:ne180000](long*, void* (*)(void*), void*)
/opt/dev/android-sdk/ndk/27.0.12077973/toolchains/llvm/prebuilt/linux-x86_64/sysroot/usr/include/c++/v1/__threading_support:317:10
std::__ndk1::thread::thread, oboe::Result), std::__ndk1::shared_ptr&, oboe::Result&, void>(void (&)(std::__ndk1::shared_ptr, oboe::Result), std::__ndk1::shared_ptr&, oboe::Result&)
/opt/dev/android-sdk/ndk/27.0.12077973/toolchains/llvm/prebuilt/linux-x86_64/sysroot/usr/include/c++/v1/__thread/thread.h:209:14
oboe::AudioStreamAAudio::internalErrorCallback(AAudioStreamStruct*, void*, int)
/home/dev/oboe/src/aaudio/AudioStreamAAudio.cpp:189:21
#3 0x7138778e88 (/system/lib64/libaaudio_internal.so+0x28e88) (BuildId: 29e37125e739e7eee69fc0b8299a7df0) [No symbols available]
#4 0x713877be14 (/system/lib64/libaaudio_internal.so+0x2be14) (BuildId: 29e37125e739e7eee69fc0b8299a7df0) [No symbols available]
#5 0x71c859d440 (/system/lib64/libaudioclient.so+0x9c440) (BuildId: 2d191cdeeae11b23f29d33757d8aff17) [No symbols available]
#6 0x71c859c5a0 (/system/lib64/libaudioclient.so+0x9b5a0) (BuildId: 2d191cdeeae11b23f29d33757d8aff17) [No symbols available]
#7 0x71dc46e550 (/system/lib64/libutils.so+0x13550) (BuildId: 62128a51f31d41bbd0d14333ccf95e95) [No symbols available]
#8 0x71c56807bc (/system/lib64/libandroid_runtime.so+0xcd7bc) (BuildId: b5d5146e5e759c3c9bd976d55711685a) [No symbols available]
asan_thread_start(void*)
out/lib/compiler-rt-aarch64-ndk-cxx/out/llvm-project/compiler-rt/lib/asan/asan_interceptors.cpp:239:28
#10 0x71c875e500 (/apex/com.android.runtime/lib64/bionic/libc.so+0x8f500) (BuildId: 35a04626779868e5f2a30e171716818d) [No symbols available]
Thread T27 (AudioTrack) created by T25 (DefaultDispatch) here:
___interceptor_pthread_create
out/lib/compiler-rt-aarch64-ndk-cxx/out/llvm-project/compiler-rt/lib/asan/asan_interceptors.cpp:250:3
#1 0x71dc46ddcc (/system/lib64/libutils.so+0x12dcc) (BuildId: 62128a51f31d41bbd0d14333ccf95e95) [No symbols available]
#2 0x71c568089c (/system/lib64/libandroid_runtime.so+0xcd89c) (BuildId: b5d5146e5e759c3c9bd976d55711685a) [No symbols available]
#3 0x71dc46e2e4 (/system/lib64/libutils.so+0x132e4) (BuildId: 62128a51f31d41bbd0d14333ccf95e95) [No symbols available]
#4 0x71c8596aa4 (/system/lib64/libaudioclient.so+0x95aa4) (BuildId: 2d191cdeeae11b23f29d33757d8aff17) [No symbols available]
#5 0x7138782f0c (/system/lib64/libaaudio_internal.so+0x32f0c) (BuildId: 29e37125e739e7eee69fc0b8299a7df0) [No symbols available]
#6 0x7138779790 (/system/lib64/libaaudio_internal.so+0x29790) (BuildId: 29e37125e739e7eee69fc0b8299a7df0) [No symbols available]
#7 0x71386f7e44 (/system/lib64/libaaudio.so+0x4e44) (BuildId: c932d9af346b357ee5f6f6b11f70d48c) [No symbols available]
oboe::AudioStreamAAudio::open()
/home/dev/oboe/src/aaudio/AudioStreamAAudio.cpp:420:38
oboe::AudioStreamBuilder::openStreamInternal(oboe::AudioStream**, bool)
/home/dev/oboe/src/common/AudioStreamBuilder.cpp:196:23
oboe::AudioStreamBuilder::openStreamInternal(oboe::AudioStream**, bool)
/home/dev/oboe/src/common/AudioStreamBuilder.cpp:137:31
oboe::AudioStreamBuilder::openStream(std::__ndk1::shared_ptr&, bool)
/home/dev/oboe/src/common/AudioStreamBuilder.cpp:243:19
SimpleNoiseMaker::open(bool)
/home/dev/oboe/samples/minimaloboe/src/main/cpp/SimpleNoiseMaker.cpp:40:15
Java_com_example_minimaloboe_AudioPlayer_startAudioStreamNative
/home/dev/oboe/samples/minimaloboe/src/main/cpp/MinimalOboeJNI.cpp:45:29
#14 0x713ff7ef70 (/apex/com.android.art/lib64/libart.so+0x37ef70) (BuildId: 4ccb65ae9ac5ad5da3af5a342d5b0b92) [No symbols available]
Thread T25 (DefaultDispatch) created by T0 (ple.minimaloboe) here:
___interceptor_pthread_create
out/lib/compiler-rt-aarch64-ndk-cxx/out/llvm-project/compiler-rt/lib/asan/asan_interceptors.cpp:250:3
#1 0x714032f054 (/apex/com.android.art/lib64/libart.so+0x72f054) (BuildId: 4ccb65ae9ac5ad5da3af5a342d5b0b92) [No symbols available]
#2 0x713ff7ef70 (/apex/com.android.art/lib64/libart.so+0x37ef70) (BuildId: 4ccb65ae9ac5ad5da3af5a342d5b0b92) [No symbols available]
#3 0x15bc73a7fffffffd ()
oboe_aaudio_error_thread_proc_common(oboe::AudioStreamAAudio*, oboe::Result)
/home/dev/oboe/src/aaudio/AudioStreamAAudio.cpp:87:42
oboe_aaudio_error_thread_proc_shared(std::__ndk1::shared_ptr, oboe::Result)
/home/dev/oboe/src/aaudio/AudioStreamAAudio.cpp:112:5
```
**Device**
This bug should reproduce on any device using AAudio. The device below was used for testing:
```
ro.product.brand = samsung
ro.product.manufacturer = samsung
ro.product.model = SM-S135DL
ro.product.device = a03su
ro.product.cpu.abi = arm64-v8a
ro.build.description = a03sutfnssu-user 13 TP1A.220624.014 S135DLUDS8AYI1 release-keys
ro.hardware = mt6765
ro.hardware.chipname =
ro.arch =
```
Contributor guide
Assessment
This issue has not been assessed yet.