Prevent out of memory errors when the game passes in an improper length value

HACK

In Luigi's Mansion Dark Moon in HLE audio, the game mysteriously passes
in an extremely large value for length, which without any checks, causes
HLE audio to allocate an extremely large buffer.

This value seemingly is caused by some other HLE audio feature is missing,
and Luigi's Mansion subtracts two values to get a length, without
checking for overflow first. This appears to be caused by an incorrect
HLE audio emulation, as its fixed entirely by only changing to LLE. As
such, further investigation is required, but in the meantime, completely
eating up our users RAM is unacceptable.
This commit is contained in:
James Rowe 2019-12-14 18:09:45 -07:00
parent 8b1738aeac
commit 87facaa2e2

View file

@ -171,6 +171,20 @@ void Source::ParseConfig(SourceConfiguration::Configuration& config,
if (config.embedded_buffer_dirty) { if (config.embedded_buffer_dirty) {
config.embedded_buffer_dirty.Assign(0); config.embedded_buffer_dirty.Assign(0);
// HACK
// Luigi's Mansion Dark Moon configures the embedded buffer with an extremely large value
// for length, causing the Dequeue method to allocate a buffer of that size, eating up all
// of the users RAM. It appears that the game is calculating the length of the sample by
// using some value from the DSP and subtracting another value, which causes it to
// underflow. We need to investigate further into what value the game is reading from and
// fix that, but as a stop gap, we can just prevent these underflowed values from playing in
// the mean time
if (static_cast<s32>(config.length) < 0) {
LOG_ERROR(Audio_DSP,
"Skipping embedded buffer sample! Game passed in improper value for length. "
"addr {:X} length {:X}",
config.physical_address, config.length);
} else {
state.input_queue.emplace(Buffer{ state.input_queue.emplace(Buffer{
config.physical_address, config.physical_address,
config.length, config.length,
@ -185,6 +199,7 @@ void Source::ParseConfig(SourceConfiguration::Configuration& config,
play_position, play_position,
false, false,
}); });
}
LOG_TRACE(Audio_DSP, "enqueuing embedded addr={:#010x} len={} id={} start={}", LOG_TRACE(Audio_DSP, "enqueuing embedded addr={:#010x} len={} id={} start={}",
config.physical_address, config.length, config.buffer_id, config.physical_address, config.length, config.buffer_id,
static_cast<u32>(config.play_position)); static_cast<u32>(config.play_position));
@ -201,6 +216,12 @@ void Source::ParseConfig(SourceConfiguration::Configuration& config,
for (std::size_t i = 0; i < 4; i++) { for (std::size_t i = 0; i < 4; i++) {
if (config.buffers_dirty & (1 << i)) { if (config.buffers_dirty & (1 << i)) {
const auto& b = config.buffers[i]; const auto& b = config.buffers[i];
if (static_cast<s32>(b.length) < 0) {
LOG_ERROR(Audio_DSP,
"Skipping buffer queue sample! Game passed in improper value for "
"length. addr {:X} length {:X}",
b.physical_address, b.length);
} else {
state.input_queue.emplace(Buffer{ state.input_queue.emplace(Buffer{
b.physical_address, b.physical_address,
b.length, b.length,
@ -215,6 +236,7 @@ void Source::ParseConfig(SourceConfiguration::Configuration& config,
{}, // 0 in u32_dsp {}, // 0 in u32_dsp
false, false,
}); });
}
LOG_TRACE(Audio_DSP, "enqueuing queued {} addr={:#010x} len={} id={}", i, LOG_TRACE(Audio_DSP, "enqueuing queued {} addr={:#010x} len={} id={}", i,
b.physical_address, b.length, b.buffer_id); b.physical_address, b.length, b.buffer_id);
} }