Skip to content

Commit dddf65d

Browse files
committed
Fix audio race condition with heap allocation
This replaces stack-local buffers with heap allocation for audio threads to eliminate use-after-free when threads outlive the calling function. - play_sfx/play_music: heap-allocate sound_t + data buffer together - sfx_handler/music_handler: take ownership and free argument - Use detached thread for native (non-blocking), joinable for EMSCRIPTEN - Handle pthread_create failure by freeing allocation - music_handler: free previous mid/music_midi_data to prevent leak - shutdown_audio: stop playback first, proper resource cleanup - Fix EMSCRIPTEN double-join by not setting init flags after immediate join - Validate size bounds before allocation to prevent DoS from guest
1 parent 62bea29 commit dddf65d

File tree

1 file changed

+142
-65
lines changed

1 file changed

+142
-65
lines changed

src/syscall_sdl.c

Lines changed: 142 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -94,15 +94,19 @@ typedef struct sound {
9494
} sound_t;
9595

9696
/* SDL-mixer-related and music-related variables */
97-
static pthread_t music_thread;
9897
static uint8_t *music_midi_data;
9998
#if RV32_HAS(SDL_MIXER)
10099
static Mix_Music *mid;
101100

102101
/* SDL-mixer-related and sfx-related variables */
103-
static pthread_t sfx_thread;
104102
static Mix_Chunk *sfx_chunk;
105103
#endif
104+
105+
#ifdef __EMSCRIPTEN__
106+
/* Thread handles only needed for EMSCRIPTEN joinable threads */
107+
static pthread_t music_thread;
108+
static pthread_t sfx_thread;
109+
#endif
106110
static uint8_t *sfx_samples;
107111
static uint32_t nr_sfx_samples;
108112
static int chan;
@@ -742,12 +746,16 @@ static void *sfx_handler(void *arg)
742746

743747
memcpy(sfx_samples, ptr, sizeof(uint8_t) * nr_sfx_samples);
744748
sfx_chunk = Mix_QuickLoad_RAW(sfx_samples, nr_sfx_samples);
745-
if (!sfx_chunk)
749+
if (!sfx_chunk) {
750+
free(sfx);
746751
return NULL;
752+
}
747753

748754
chan = Mix_PlayChannel(-1, sfx_chunk, 0);
749-
if (chan == -1)
755+
if (chan == -1) {
756+
free(sfx);
750757
return NULL;
758+
}
751759

752760
if (*ptr & 0x3) {
753761
/* Doom, multiplied by 8 because sfx->volume's max is 15 */
@@ -759,6 +767,7 @@ static void *sfx_handler(void *arg)
759767
Mix_Volume(chan, (sfx->volume + 1) % 128);
760768
}
761769

770+
free(sfx);
762771
return NULL;
763772
}
764773

@@ -767,23 +776,35 @@ static void *music_handler(void *arg)
767776
sound_t *music = (sound_t *) arg;
768777
int looping = music->looping ? -1 : 1;
769778

779+
/* Free previous MIDI data */
770780
free(music_midi_data);
781+
music_midi_data = NULL;
782+
783+
/* Free previous music to prevent memory leak */
784+
if (mid) {
785+
Mix_HaltMusic();
786+
Mix_FreeMusic(mid);
787+
mid = NULL;
788+
}
771789

772790
music_midi_data = mus2midi(music->data, (int *) &music->size);
773791
if (!music_midi_data) {
774792
rv_log_error("mus2midi() failed");
793+
free(music);
775794
return NULL;
776795
}
777796

778797
SDL_RWops *rwops = SDL_RWFromMem(music_midi_data, music->size);
779798
if (!rwops) {
780799
rv_log_error("SDL_RWFromMem failed: %s", SDL_GetError());
800+
free(music);
781801
return NULL;
782802
}
783803

784804
mid = Mix_LoadMUSType_RW(rwops, MUS_MID, SDL_TRUE);
785805
if (!mid) {
786806
rv_log_error("Mix_LoadMUSType_RW failed: %s", Mix_GetError());
807+
free(music);
787808
return NULL;
788809
}
789810

@@ -794,9 +815,11 @@ static void *music_handler(void *arg)
794815

795816
if (Mix_PlayMusic(mid, looping) == -1) {
796817
rv_log_error("Mix_PlayMusic failed: %s", Mix_GetError());
818+
free(music);
797819
return NULL;
798820
}
799821

822+
free(music);
800823
return NULL;
801824
}
802825

@@ -808,21 +831,15 @@ static void play_sfx(riscv_t *rv)
808831
int volume = rv_get_reg(rv, rv_reg_a2);
809832

810833
sfxinfo_t sfxinfo;
811-
uint8_t sfx_data[SFX_SAMPLE_SIZE];
834+
uint32_t sfx_data_size;
835+
812836
#if RV32_HAS(SYSTEM_MMIO)
813837
uint32_t addr = rv->io.mem_translate(rv, sfxinfo_addr, R);
814838
memory_read(attr->mem, (uint8_t *) &sfxinfo, addr, sizeof(sfxinfo_t));
815839

816840
uint32_t sfx_data_offset =
817841
*((uint32_t *) ((uint8_t *) attr->mem->mem_base + addr));
818-
uint32_t sfx_data_size =
819-
*(uint32_t *) ((uint8_t *) attr->mem->mem_base + addr + 4);
820-
uint32_t sfx_data_vaddr = sfx_data_offset;
821-
uint8_t *sfx_data_ptr = &sfx_data[0];
822-
remain_size = sfx_data_size;
823-
curr_offset = 0;
824-
825-
GET_SFX_DATA_FROM_RANDOM_PAGE(sfx_data_vaddr, sfx_data_ptr);
842+
sfx_data_size = *(uint32_t *) ((uint8_t *) attr->mem->mem_base + addr + 4);
826843
#else
827844
memory_read(attr->mem, (uint8_t *) &sfxinfo, sfxinfo_addr,
828845
sizeof(sfxinfo_t));
@@ -832,28 +849,57 @@ static void play_sfx(riscv_t *rv)
832849
* various applications when accessing different sfxinfo_t instances.
833850
*/
834851
uint32_t sfx_data_offset = *((uint32_t *) &sfxinfo);
835-
uint32_t sfx_data_size = *(uint32_t *) ((uint32_t *) &sfxinfo + 1);
836-
memory_read(attr->mem, sfx_data, sfx_data_offset,
837-
sizeof(uint8_t) * sfx_data_size);
852+
sfx_data_size = *(uint32_t *) ((uint32_t *) &sfxinfo + 1);
838853
#endif
839854

840-
sound_t sfx = {
841-
.data = sfx_data,
842-
.size = sfx_data_size,
843-
.volume = volume,
844-
};
845855
#if RV32_HAS(SDL_MIXER)
846-
pthread_create(&sfx_thread, NULL, sfx_handler, &sfx);
847-
sfx_thread_init = true;
856+
/* Validate size to prevent excessive allocation from untrusted guest */
857+
if (sfx_data_size == 0 || sfx_data_size > SFX_SAMPLE_SIZE)
858+
return;
859+
860+
/* Heap allocate sound_t + data buffer together (thread takes ownership) */
861+
sound_t *sfx = malloc(sizeof(sound_t) + sfx_data_size);
862+
if (!sfx)
863+
return;
864+
865+
sfx->data = (uint8_t *) (sfx + 1); /* Data follows the struct */
866+
sfx->size = sfx_data_size;
867+
sfx->volume = volume;
868+
869+
#if RV32_HAS(SYSTEM_MMIO)
870+
uint32_t sfx_data_vaddr = sfx_data_offset;
871+
uint8_t *sfx_data_ptr = sfx->data;
872+
remain_size = sfx_data_size;
873+
curr_offset = 0;
874+
875+
GET_SFX_DATA_FROM_RANDOM_PAGE(sfx_data_vaddr, sfx_data_ptr);
876+
#else
877+
memory_read(attr->mem, sfx->data, sfx_data_offset,
878+
sizeof(uint8_t) * sfx_data_size);
848879
#endif
849-
/* FIXME: In web browser runtime, web workers in thread pool do not reap
850-
* after sfx_handler return, thus we have to join them. sfx_handler does not
851-
* contain infinite loop,so do not worry to be stalled by it */
880+
852881
#ifdef __EMSCRIPTEN__
853-
#if RV32_HAS(SDL_MIXER)
882+
/* Web browser: use joinable thread and wait for completion */
883+
if (pthread_create(&sfx_thread, NULL, sfx_handler, sfx) != 0) {
884+
free(sfx);
885+
return;
886+
}
854887
pthread_join(sfx_thread, NULL);
888+
/* Thread already joined - don't join again in shutdown_audio */
889+
#else
890+
/* Native: use detached thread for non-blocking playback */
891+
pthread_t thread;
892+
pthread_attr_t thread_attr;
893+
pthread_attr_init(&thread_attr);
894+
pthread_attr_setdetachstate(&thread_attr, PTHREAD_CREATE_DETACHED);
895+
if (pthread_create(&thread, &thread_attr, sfx_handler, sfx) != 0) {
896+
pthread_attr_destroy(&thread_attr);
897+
free(sfx);
898+
return;
899+
}
900+
pthread_attr_destroy(&thread_attr);
855901
#endif
856-
#endif
902+
#endif /* RV32_HAS(SDL_MIXER) */
857903
}
858904

859905
static void play_music(riscv_t *rv)
@@ -865,7 +911,8 @@ static void play_music(riscv_t *rv)
865911
int looping = rv_get_reg(rv, rv_reg_a3);
866912

867913
musicinfo_t musicinfo;
868-
uint8_t music_data[MUSIC_MAX_SIZE];
914+
uint32_t music_data_size;
915+
869916
#if RV32_HAS(SYSTEM_MMIO)
870917
uint32_t addr = rv->io.mem_translate(rv, musicinfo_addr, R);
871918
memory_read(attr->mem, (uint8_t *) &musicinfo, addr, sizeof(musicinfo_t));
@@ -876,14 +923,8 @@ static void play_music(riscv_t *rv)
876923
*/
877924
uint32_t music_data_offset =
878925
*((uint32_t *) ((uint8_t *) attr->mem->mem_base + addr));
879-
uint32_t music_data_size =
926+
music_data_size =
880927
*(uint32_t *) ((uint8_t *) attr->mem->mem_base + addr + 4);
881-
uint32_t music_data_vaddr = music_data_offset;
882-
uint8_t *music_data_ptr = &music_data[0];
883-
remain_size = music_data_size;
884-
curr_offset = 0;
885-
886-
GET_MUSIC_DATA_FROM_RANDOM_PAGE(music_data_vaddr, music_data_ptr);
887928
#else
888929
memory_read(attr->mem, (uint8_t *) &musicinfo, musicinfo_addr,
889930
sizeof(musicinfo_t));
@@ -893,28 +934,57 @@ static void play_music(riscv_t *rv)
893934
* various applications when accessing different sfxinfo_t instances.
894935
*/
895936
uint32_t music_data_offset = *((uint32_t *) &musicinfo);
896-
uint32_t music_data_size = *(uint32_t *) ((uint32_t *) &musicinfo + 1);
897-
memory_read(attr->mem, music_data, music_data_offset, music_data_size);
937+
music_data_size = *(uint32_t *) ((uint32_t *) &musicinfo + 1);
898938
#endif
899939

900-
sound_t music = {
901-
.data = music_data,
902-
.size = music_data_size,
903-
.looping = looping,
904-
.volume = volume,
905-
};
906940
#if RV32_HAS(SDL_MIXER)
907-
pthread_create(&music_thread, NULL, music_handler, &music);
908-
music_thread_init = true;
941+
/* Validate size to prevent excessive allocation from untrusted guest */
942+
if (music_data_size == 0 || music_data_size > MUSIC_MAX_SIZE)
943+
return;
944+
945+
/* Heap allocate sound_t + data buffer together (thread takes ownership) */
946+
sound_t *music = malloc(sizeof(sound_t) + music_data_size);
947+
if (!music)
948+
return;
949+
950+
music->data = (uint8_t *) (music + 1); /* Data follows the struct */
951+
music->size = music_data_size;
952+
music->looping = looping;
953+
music->volume = volume;
954+
955+
#if RV32_HAS(SYSTEM_MMIO)
956+
uint32_t music_data_vaddr = music_data_offset;
957+
uint8_t *music_data_ptr = music->data;
958+
remain_size = music_data_size;
959+
curr_offset = 0;
960+
961+
GET_MUSIC_DATA_FROM_RANDOM_PAGE(music_data_vaddr, music_data_ptr);
962+
#else
963+
memory_read(attr->mem, music->data, music_data_offset, music_data_size);
909964
#endif
910-
/* FIXME: In web browser runtime, web workers in thread pool do not reap
911-
* after music_handler return, thus we have to join them. music_handler does
912-
* not contain infinite loop,so do not worry to be stalled by it */
965+
913966
#ifdef __EMSCRIPTEN__
914-
#if RV32_HAS(SDL_MIXER)
967+
/* Web browser: use joinable thread and wait for completion */
968+
if (pthread_create(&music_thread, NULL, music_handler, music) != 0) {
969+
free(music);
970+
return;
971+
}
915972
pthread_join(music_thread, NULL);
973+
/* Thread already joined - don't join again in shutdown_audio */
974+
#else
975+
/* Native: use detached thread for non-blocking playback */
976+
pthread_t thread;
977+
pthread_attr_t thread_attr;
978+
pthread_attr_init(&thread_attr);
979+
pthread_attr_setdetachstate(&thread_attr, PTHREAD_CREATE_DETACHED);
980+
if (pthread_create(&thread, &thread_attr, music_handler, music) != 0) {
981+
pthread_attr_destroy(&thread_attr);
982+
free(music);
983+
return;
984+
}
985+
pthread_attr_destroy(&thread_attr);
916986
#endif
917-
#endif
987+
#endif /* RV32_HAS(SDL_MIXER) */
918988
}
919989

920990
static void stop_music()
@@ -965,27 +1035,34 @@ static void init_audio(void)
9651035

9661036
static void shutdown_audio()
9671037
{
1038+
#if RV32_HAS(SDL_MIXER)
1039+
/* Stop all playback first */
1040+
Mix_HaltMusic();
1041+
Mix_HaltChannel(-1);
1042+
1043+
#ifdef __EMSCRIPTEN__
9681044
/*
969-
* sfx_thread and music_thread might contain invalid identifiers.
970-
* Additionally, there is no built-in method to verify the validity
971-
* of a pthread_t type. Therefore, the sfx_thread_init and music_thread_init
972-
* flags are used for validation, ensuring that pthread_join always operates
973-
* on a valid pthread_t identifier.
1045+
* For EMSCRIPTEN, threads are joinable. Join them if initialized to ensure
1046+
* handlers have completed before freeing resources.
9741047
*/
975-
976-
#if RV32_HAS(SDL_MIXER)
977-
if (music_thread_init) {
978-
stop_music();
1048+
if (music_thread_init)
9791049
pthread_join(music_thread, NULL);
1050+
if (sfx_thread_init)
1051+
pthread_join(sfx_thread, NULL);
1052+
#endif
1053+
1054+
/* Free music resources */
1055+
if (mid) {
9801056
Mix_FreeMusic(mid);
981-
free(music_midi_data);
982-
music_midi_data = NULL;
1057+
mid = NULL;
9831058
}
1059+
free(music_midi_data);
1060+
music_midi_data = NULL;
9841061

985-
if (sfx_thread_init) {
986-
pthread_join(sfx_thread, NULL);
987-
Mix_HaltChannel(-1);
1062+
/* Free sfx resources */
1063+
if (sfx_chunk) {
9881064
Mix_FreeChunk(sfx_chunk);
1065+
sfx_chunk = NULL;
9891066
}
9901067

9911068
Mix_CloseAudio();

0 commit comments

Comments
 (0)