-
Notifications
You must be signed in to change notification settings - Fork 46
Use-after-free in MIDIStreamer::SetMIDISource #87
Description
Hi, there is a potential bug in ZMusic_MIDIDumpWave when it is invoked multiple times.
This bug was originaly found and reproduced in gzdoom -- ZDoom/gzdoom@092b9c0. But reporting to this repository since it seems to be isolated to ZMusic.
Description
What crashes
- The process crashes with a heap-use-after-free read in MIDISource::setTempoCallback (midisource.h:76) while ZMusic_MIDIDumpWave sets up playback. The top frames are:
- MIDISource::setTempoCallback(std::function<bool(int)>) assigning to TempoCallback
- MIDIStreamer::SetMIDISource(MIDISource*)
- ZMusic_MIDIDumpWave
It looks like inside MIDIDumpWave it sets up a MIDIStreamer which is then destructed at the end of that call. When the ephemeral MIDIStreamer is destructed, it also seems to destroy some parts of the original MidiSource that was passed in, making it invalid for subsequent calls.
POC
The following testcase demonstrates the bug:
testcase.cpp
#include <cstdint>
#include <cstddef>
#include <cstdio>
#include <cstring>
#include "/fuzz/install/include/zmusic.h"
// Minimal repro for heap-use-after-free in MIDISource::setTempoCallback via ZMusic_MIDIDumpWave
int main(){
// Simple valid-ish SMF with one short track
static const unsigned char smf[] = {
'M','T','h','d', 0,0,0,6, 0,0, 0,1, 0,96,
'M','T','r','k', 0,0,0,15,
0, 0xFF, 0x51, 3, 0x07,0xA1,0x20, // tempo
0, 0xC0, 0x26, // program change
0, 0x90, 60, 0x40, // note on
0, 0xFF, 0x2F, 0 // end of track
};
ZMusic_MidiSource src = ZMusic_CreateMIDISource(smf, sizeof(smf), MIDI_MIDI);
if (!src) return 0;
// Dump twice to trigger ownership bug inside MIDIStreamer::SetMIDISource
(void)ZMusic_MIDIDumpWave(src, MDEV_DEFAULT, nullptr, "/tmp/zm_out1.wav", 0, 44100);
(void)ZMusic_MIDIDumpWave(src, MDEV_DEFAULT, nullptr, "/tmp/zm_out2.wav", 0, 44100);
return 0;
}
stdout
stderr
Could not find patch set /usr/share/sounds/sf2/FluidR3_GS.sf2.
Could not find patch set /usr/share/sounds/sf2/FluidR3_GM.sf2.
=================================================================
==1==ERROR: AddressSanitizer: heap-use-after-free on address 0x50e000000050 at pc 0x5589a00a756a bp 0x7ffc36590890 sp 0x7ffc36590050
READ of size 16 at 0x50e000000050 thread T0
#0 0x5589a00a7569 in __asan_memcpy (/fuzz/test+0xff569) (BuildId: be6873b7f125c4fc70fceb4eecd62a70190fe147)
#1 0x5589a011ecda in std::enable_if<__and_<std::__not_<std::__is_tuple_like<std::_Any_data>>, std::is_move_constructible<std::_Any_data>, std::is_move_assignable<std::_Any_data>>::value, void>::type std::swap<std::_Any_data>(std::_Any_data&, std::_Any_data&) /usr/bin/../lib/gcc/x86_64-linux-gnu/11/../../../../include/c++/11/bits/move.h:205:11
#2 0x5589a011eb70 in std::function<bool (int)>::swap(std::function<bool (int)>&) /usr/bin/../lib/gcc/x86_64-linux-gnu/11/../../../../include/c++/11/bits/std_function.h:557:2
#3 0x5589a011e922 in std::function<bool (int)>::operator=(std::function<bool (int)> const&) /usr/bin/../lib/gcc/x86_64-linux-gnu/11/../../../../include/c++/11/bits/std_function.h:471:16
#4 0x5589a011e14c in MIDISource::setTempoCallback(std::function<bool (int)>) /fuzz/src/libraries/ZMusic/source/midisources/midisource.h:76:17
#5 0x5589a011c565 in MIDIStreamer::SetMIDISource(MIDISource*) /fuzz/src/libraries/ZMusic/source/musicformats/music_midi.cpp:964:10
#6 0x5589a011cf09 in ZMusic_MIDIDumpWave /fuzz/src/libraries/ZMusic/source/musicformats/music_midi.cpp:1038:6
#7 0x5589a00e828a in main /fuzz/testcase.cpp:24:11
#8 0x7f33ff27cd8f in __libc_start_call_main csu/../sysdeps/nptl/libc_start_call_main.h:58:16
#9 0x7f33ff27ce3f in __libc_start_main csu/../csu/libc-start.c:392:3
#10 0x5589a000d104 in _start (/fuzz/test+0x65104) (BuildId: be6873b7f125c4fc70fceb4eecd62a70190fe147)
0x50e000000050 is located 16 bytes inside of 160-byte region [0x50e000000040,0x50e0000000e0)
freed by thread T0 here:
#0 0x5589a00e677d in operator delete(void*) (/fuzz/test+0x13e77d) (BuildId: be6873b7f125c4fc70fceb4eecd62a70190fe147)
#1 0x5589a00fba01 in MIDISong2::~MIDISong2() /fuzz/src/libraries/ZMusic/source/midisources/midisource.h:107:7
#2 0x5589a011f9a2 in std::default_delete<MIDISource>::operator()(MIDISource*) const /usr/bin/../lib/gcc/x86_64-linux-gnu/11/../../../../include/c++/11/bits/unique_ptr.h:85:2
#3 0x5589a011d9b8 in std::unique_ptr<MIDISource, std::default_delete<MIDISource>>::~unique_ptr() /usr/bin/../lib/gcc/x86_64-linux-gnu/11/../../../../include/c++/11/bits/unique_ptr.h:361:4
#4 0x5589a0115da6 in MIDIStreamer::~MIDIStreamer() /fuzz/src/libraries/ZMusic/source/musicformats/music_midi.cpp:170:1
#5 0x5589a011cf88 in ZMusic_MIDIDumpWave /fuzz/src/libraries/ZMusic/source/musicformats/music_midi.cpp:1041:2
#6 0x5589a00e8268 in main /fuzz/testcase.cpp:23:11
#7 0x7f33ff27cd8f in __libc_start_call_main csu/../sysdeps/nptl/libc_start_call_main.h:58:16
previously allocated by thread T0 here:
#0 0x5589a00e5f1d in operator new(unsigned long) (/fuzz/test+0x13df1d) (BuildId: be6873b7f125c4fc70fceb4eecd62a70190fe147)
#1 0x5589a00eb9a8 in ZMusic_CreateMIDISource /fuzz/src/libraries/ZMusic/source/midisources/midisource.cpp:489:13
#2 0x5589a00e8224 in main /fuzz/testcase.cpp:19:29
#3 0x7f33ff27cd8f in __libc_start_call_main csu/../sysdeps/nptl/libc_start_call_main.h:58:16
SUMMARY: AddressSanitizer: heap-use-after-free (/fuzz/test+0xff569) (BuildId: be6873b7f125c4fc70fceb4eecd62a70190fe147) in __asan_memcpy
Shadow bytes around the buggy address:
0x50dffffffd80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x50dffffffe00: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x50dffffffe80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x50dfffffff00: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x50dfffffff80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
=>0x50e000000000: fa fa fa fa fa fa fa fa fd fd[fd]fd fd fd fd fd
0x50e000000080: fd fd fd fd fd fd fd fd fd fd fd fd fa fa fa fa
0x50e000000100: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x50e000000180: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x50e000000200: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x50e000000280: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
Shadow byte legend (one shadow byte represents 8 application bytes):
Addressable: 00
Partially addressable: 01 02 03 04 05 06 07
Heap left redzone: fa
Freed heap region: fd
Stack left redzone: f1
Stack mid redzone: f2
Stack right redzone: f3
Stack after return: f5
Stack use after scope: f8
Global redzone: f9
Global init order: f6
Poisoned by user: f7
Container overflow: fc
Array cookie: ac
Intra object redzone: bb
ASan internal: fe
Left alloca redzone: ca
Right alloca redzone: cb
==1==ABORTING
Steps to Reproduce
The crash was triaged with the following Dockerfile:
Dockerfile
# Ubuntu 22.04 with some packages pre-installed
FROM hgarrereyn/stitch_repro_base@sha256:3ae94cdb7bf2660f4941dc523fe48cd2555049f6fb7d17577f5efd32a40fdd2c
RUN git clone https://github.com/ZDoom/gzdoom /fuzz/src && \
cd /fuzz/src && \
git checkout 092b9c0515c2861270cde175cd8eaa30a253c8b1 && \
git submodule update --init --remote --recursive
ENV LD_LIBRARY_PATH=/fuzz/install/lib
ENV ASAN_OPTIONS=hard_rss_limit_mb=1024:detect_leaks=0
RUN echo '#!/bin/bash\nexec clang-17 -fsanitize=address -O0 "$@"' > /usr/local/bin/clang_wrapper && \
chmod +x /usr/local/bin/clang_wrapper && \
echo '#!/bin/bash\nexec clang++-17 -fsanitize=address -O0 "$@"' > /usr/local/bin/clang_wrapper++ && \
chmod +x /usr/local/bin/clang_wrapper++
# Install required build tools and dependencies
RUN apt-get update && apt-get install -y --no-install-recommends \
cmake ninja-build make pkg-config ca-certificates libglib2.0-dev && \
rm -rf /var/lib/apt/lists/*
# Configure and build ZMusic as a static library
WORKDIR /fuzz/build/zmusic
RUN cmake -G Ninja \
-DCMAKE_C_COMPILER=clang_wrapper \
-DCMAKE_CXX_COMPILER=clang_wrapper++ \
-DCMAKE_INSTALL_PREFIX=/fuzz/install \
-DBUILD_SHARED_LIBS=OFF \
/fuzz/src/libraries/ZMusic && \
ninja -j"$(nproc)"
# Manually install headers and static libraries
RUN mkdir -p /fuzz/install/include /fuzz/install/lib && \
cp /fuzz/src/libraries/ZMusic/include/zmusic.h /fuzz/install/include/ && \
cp -v $(find /fuzz/build/zmusic -name 'libzmusic*.a' -type f) /fuzz/install/lib/Build Command
clang++-17 -fsanitize=address -g -O0 -o /fuzz/test /fuzz/testcase.cpp -I/fuzz/install/include -L/fuzz/install/lib -lzmusiclite -lglib-2.0 -lpthread -ldl && /fuzz/testReproduce
- Copy
Dockerfileandtestcase.cppinto a local folder. - Build the repro image:
docker build . -t repro --platform=linux/amd64- Compile and run the testcase in the image:
docker run \
-it --rm \
--platform linux/amd64 \
--mount type=bind,source="$(pwd)/testcase.cpp",target=/fuzz/testcase.cpp \
repro \
bash -c "clang++-17 -fsanitize=address -g -O0 -o /fuzz/test /fuzz/testcase.cpp -I/fuzz/install/include -L/fuzz/install/lib -lzmusiclite -lglib-2.0 -lpthread -ldl && /fuzz/test"Additional Info
This testcase was discovered by STITCH, an autonomous fuzzing system. All reports are reviewed manually (by a human) before submission.