Skip to content

Commit 0e7fd1b

Browse files
committed
StandalonePluginHolder: Fix out-of-bounds read when audio callbacks use larger-than-expected buffers
1 parent 0e47da1 commit 0e7fd1b

1 file changed

Lines changed: 87 additions & 2 deletions

File tree

modules/juce_audio_plugin_client/Standalone/juce_StandaloneFilterWindow.h

Lines changed: 87 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -408,6 +408,91 @@ class StandalonePluginHolder : private AudioIODeviceCallback,
408408
Array<MidiDeviceInfo> lastMidiDevices;
409409

410410
private:
411+
/* This class can be used to ensure that audio callbacks use buffers with a
412+
predictable maximum size.
413+
414+
On some platforms (such as iOS 10), the expected buffer size reported in
415+
audioDeviceAboutToStart may be smaller than the blocks passed to
416+
audioDeviceIOCallback. This can lead to out-of-bounds reads if the render
417+
callback depends on additional buffers which were initialised using the
418+
smaller size.
419+
420+
As a workaround, this class will ensure that the render callback will
421+
only ever be called with a block with a length less than or equal to the
422+
expected block size.
423+
*/
424+
class CallbackMaxSizeEnforcer : public AudioIODeviceCallback
425+
{
426+
public:
427+
explicit CallbackMaxSizeEnforcer (AudioIODeviceCallback& callbackIn)
428+
: inner (callbackIn) {}
429+
430+
void audioDeviceAboutToStart (AudioIODevice* device) override
431+
{
432+
maximumSize = device->getCurrentBufferSizeSamples();
433+
storedInputChannels .resize ((size_t) device->getActiveInputChannels() .countNumberOfSetBits());
434+
storedOutputChannels.resize ((size_t) device->getActiveOutputChannels().countNumberOfSetBits());
435+
436+
inner.audioDeviceAboutToStart (device);
437+
}
438+
439+
void audioDeviceIOCallback (const float** inputChannelData,
440+
int numInputChannels,
441+
float** outputChannelData,
442+
int numOutputChannels,
443+
int numSamples) override
444+
{
445+
jassert ((int) storedInputChannels.size() == numInputChannels);
446+
jassert ((int) storedOutputChannels.size() == numOutputChannels);
447+
ignoreUnused (numInputChannels, numOutputChannels);
448+
449+
int position = 0;
450+
451+
while (position < numSamples)
452+
{
453+
const auto blockLength = jmin (maximumSize, numSamples - position);
454+
455+
initChannelPointers (inputChannelData, storedInputChannels, position);
456+
initChannelPointers (outputChannelData, storedOutputChannels, position);
457+
458+
inner.audioDeviceIOCallback (storedInputChannels.data(),
459+
(int) storedInputChannels.size(),
460+
storedOutputChannels.data(),
461+
(int) storedOutputChannels.size(),
462+
blockLength);
463+
464+
position += blockLength;
465+
}
466+
}
467+
468+
void audioDeviceStopped() override
469+
{
470+
inner.audioDeviceStopped();
471+
}
472+
473+
private:
474+
struct GetChannelWithOffset
475+
{
476+
int offset;
477+
478+
template <typename Ptr>
479+
auto operator() (Ptr ptr) const noexcept -> Ptr { return ptr + offset; }
480+
};
481+
482+
template <typename Ptr, typename Vector>
483+
void initChannelPointers (Ptr&& source, Vector&& target, int offset)
484+
{
485+
std::transform (source, source + target.size(), target.begin(), GetChannelWithOffset { offset });
486+
}
487+
488+
AudioIODeviceCallback& inner;
489+
int maximumSize = 0;
490+
std::vector<const float*> storedInputChannels;
491+
std::vector<float*> storedOutputChannels;
492+
};
493+
494+
CallbackMaxSizeEnforcer maxSizeEnforcer { *this };
495+
411496
//==============================================================================
412497
class SettingsComponent : public Component
413498
{
@@ -533,7 +618,7 @@ class StandalonePluginHolder : private AudioIODeviceCallback,
533618
const String& preferredDefaultDeviceName,
534619
const AudioDeviceManager::AudioDeviceSetup* preferredSetupOptions)
535620
{
536-
deviceManager.addAudioCallback (this);
621+
deviceManager.addAudioCallback (&maxSizeEnforcer);
537622
deviceManager.addMidiInputDeviceCallback ({}, &player);
538623

539624
reloadAudioDeviceState (enableAudioInput, preferredDefaultDeviceName, preferredSetupOptions);
@@ -544,7 +629,7 @@ class StandalonePluginHolder : private AudioIODeviceCallback,
544629
saveAudioDeviceState();
545630

546631
deviceManager.removeMidiInputDeviceCallback ({}, &player);
547-
deviceManager.removeAudioCallback (this);
632+
deviceManager.removeAudioCallback (&maxSizeEnforcer);
548633
}
549634

550635
void timerCallback() override

0 commit comments

Comments
 (0)