Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Don't destroy voice when it is output to other voices #331

Merged
merged 2 commits into from
Feb 27, 2024

Conversation

gofman
Copy link

@gofman gofman commented Feb 27, 2024

Cosmoteer (Steam game id 799600) sometimes destroys a submix voice which is an output for another voice. That leads to use after free access in audio client thread performing mixing which leads to obscure random crashes.

MS docs [1] suggest that IXAudio2Voice::DestroyVoice fails in such a case ("If any other voice is currently sending audio to this voice, the method fails.").

I made a test on top of Wine xaudio2 tests which indirectly confirms that (attaching a patch). I don't immediately see a direct way to test if DestroyVoice actually failed or not as it doesn't return any status. However, I found that after successful voice destruction IXAudio2SubmixVoice_GetVoiceDetails() for that voice crashes on Windows and using this fact to probe for actual voice destruction (the first one in the test doesn't crash as, the second one guarded with if (0) crashes for me on Win10).

  1. https://learn.microsoft.com/en-us/windows/win32/api/xaudio2/nf-xaudio2-ixaudio2voice-destroyvoice

0001-xaudio2-tests-Test-destroying-a-voice-which-is-an-ou.txt

Copy link
Member

@flibitijibibo flibitijibibo left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

At a high level this makes sense to me, we may have to tweak things slightly though - I think this would actually fix #326 as well, so bringing in @thatcosmonaut for review.

include/FAudio.h Outdated
@@ -1062,7 +1062,7 @@ FAUDIOAPI void FAudioVoice_GetOutputMatrix(
);

/* Removes this voice from the audio graph and frees memory. */
FAUDIOAPI void FAudioVoice_DestroyVoice(FAudioVoice *voice);
FAUDIOAPI uint32_t FAudioVoice_DestroyVoice(FAudioVoice *voice);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this counts as an ABI break, so we may have to export a new function that includes the return value :(

src/FAudio.c Outdated
voice
)
LOG_API_EXIT(voice->audio)
return 0;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be E_FAIL and the one below be 0?

@flibitijibibo
Copy link
Member

Actually it turns out the signature is correct...

https://learn.microsoft.com/en-us/windows/win32/api/xaudio2/nf-xaudio2-ixaudio2voice-destroyvoice

... but honestly I'd be up for exporting a new function since being able to see this failure seems important. DestroyVoiceSafeEXT maybe?

@gofman
Copy link
Author

gofman commented Feb 27, 2024

Yes, sure. I did it this way because technically it doesn't break the ABI, if old client uses void version and doesn't check value it is not broken with newer version returning the value. But it is looks cleaner to have a different function. I think both old and new one will check if the voice is in use and the difference will be only in the presence return value. Or do you want the old version work like before?

@flibitijibibo
Copy link
Member

What we can probably do is have this in the header...

FAUDIOAPI void FAudioVoice_DestroyVoice(...);

FAUDIOAPI uint32_t FAudioVoice_DestroyVoiceSafeEXT(...);

... and then we'll rename the implementation we have now to DestroyVoiceSafeEXT, and DestroyVoice can call DestroyVoiceSafeEXT and discard the return value entirely. It's a bit roundabout, but we've done similar things with other extensions like voice filters and it's been easy enough to keep stable!

@flibitijibibo flibitijibibo merged commit c00b01c into FNA-XNA:master Feb 27, 2024
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants