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

Reject invalid MPEG puts for certain lib versions #8803

Merged
merged 8 commits into from
Jun 5, 2016
93 changes: 75 additions & 18 deletions Core/HLE/sceMpeg.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -68,8 +68,9 @@ static const int MPEG_DATA_STREAM = 3; // Arbitrary user defined type. Can
static const int MPEG_AUDIO_STREAM = 15;
static const int MPEG_AU_MODE_DECODE = 0;
static const int MPEG_AU_MODE_SKIP = 1;
static const u32 MPEG_MEMSIZE = 0x10000; // 64k.
static const int MPEG_AVC_DECODE_SUCCESS = 1; // Internal value.
static const u32 MPEG_MEMSIZE_0104 = 0x0b3DB;
static const u32 MPEG_MEMSIZE_0105 = 0x10000; // 64k.
static const int MPEG_AVC_DECODE_SUCCESS = 1; // Internal value.

static const int atracDecodeDelayMs = 3000;
static const int avcFirstDelayMs = 3600;
Expand Down Expand Up @@ -269,7 +270,7 @@ static MpegContext *getMpegCtx(u32 mpegAddr) {
static void InitRingbuffer(SceMpegRingBuffer *buf, int packets, int data, int size, int callback_addr, int callback_args) {
buf->packets = packets;
buf->packetsRead = 0;
buf->packetsWritten = 0;
buf->packetsWritePos = 0;
buf->packetsAvail = 0;
buf->packetSize = 2048;
buf->data = data;
Expand Down Expand Up @@ -467,14 +468,21 @@ static u32 sceMpegRingbufferConstruct(u32 ringbufferAddr, u32 numPackets, u32 da
return 0;
}

static u32 MpegRequiredMem() {
if (mpegLibVersion < 0x0105) {
return MPEG_MEMSIZE_0104;
}
return MPEG_MEMSIZE_0105;
}

static u32 sceMpegCreate(u32 mpegAddr, u32 dataPtr, u32 size, u32 ringbufferAddr, u32 frameWidth, u32 mode, u32 ddrTop)
{
if (!Memory::IsValidAddress(mpegAddr)) {
WARN_LOG(ME, "sceMpegCreate(%08x, %08x, %i, %08x, %i, %i, %i): invalid addresses", mpegAddr, dataPtr, size, ringbufferAddr, frameWidth, mode, ddrTop);
return -1;
}

if (size < MPEG_MEMSIZE) {
if (size < MpegRequiredMem()) {
WARN_LOG(ME, "ERROR_MPEG_NO_MEMORY=sceMpegCreate(%08x, %08x, %i, %08x, %i, %i, %i)", mpegAddr, dataPtr, size, ringbufferAddr, frameWidth, mode, ddrTop);
return ERROR_MPEG_NO_MEMORY;
}
Expand Down Expand Up @@ -1070,6 +1078,8 @@ static u32 sceMpegAvcDecode(u32 mpeg, u32 auAddr, u32 frameWidth, u32 bufferAddr
return hleDelayResult(ERROR_MPEG_AVC_DECODE_FATAL, "mpeg buffer empty", avcEmptyDelayMs);
}

s32 beforeAvail = ringbuffer->packets - ctx->mediaengine->getRemainSize() / 2048;

// We stored the video stream id here in sceMpegGetAvcAu().
ctx->mediaengine->setVideoStream(avcAu.esBuffer);

Expand All @@ -1095,7 +1105,14 @@ static u32 sceMpegAvcDecode(u32 mpeg, u32 auAddr, u32 frameWidth, u32 bufferAddr
} else {
ctx->avc.avcFrameStatus = 0;
}
ringbuffer->packetsAvail = ringbuffer->packets - ctx->mediaengine->getRemainSize() / 2048;
s32 afterAvail = ringbuffer->packets - ctx->mediaengine->getRemainSize() / 2048;
// Don't actually reset avail, we only change it by what was decoded.
// Garbage frames can cause this to be incorrect, but some games expect that.
if (mpegLibVersion <= 0x0103) {
ringbuffer->packetsAvail += afterAvail - beforeAvail;
} else {
ringbuffer->packetsAvail = afterAvail;
}

avcAu.pts = ctx->mediaengine->getVideoTimeStamp() + ctx->mpegFirstTimestamp;

Expand Down Expand Up @@ -1239,6 +1256,8 @@ static int sceMpegAvcDecodeYCbCr(u32 mpeg, u32 auAddr, u32 bufferAddr, u32 initA
return hleDelayResult(ERROR_MPEG_AVC_DECODE_FATAL, "mpeg buffer empty", avcEmptyDelayMs);
}

s32 beforeAvail = ringbuffer->packets - ctx->mediaengine->getRemainSize() / 2048;

// We stored the video stream id here in sceMpegGetAvcAu().
ctx->mediaengine->setVideoStream(avcAu.esBuffer);

Expand All @@ -1250,10 +1269,17 @@ static int sceMpegAvcDecodeYCbCr(u32 mpeg, u32 auAddr, u32 bufferAddr, u32 initA
// Don't draw here, we'll draw in the Csc func.
ctx->avc.avcFrameStatus = 1;
ctx->videoFrameCount++;
}else {
} else {
ctx->avc.avcFrameStatus = 0;
}
ringbuffer->packetsAvail = ringbuffer->packets - ctx->mediaengine->getRemainSize() / 2048;
s32 afterAvail = ringbuffer->packets - ctx->mediaengine->getRemainSize() / 2048;
// Don't actually reset avail, we only change it by what was decoded.
// Garbage frames can cause this to be incorrect, but some games expect that.
if (mpegLibVersion <= 0x0103) {
ringbuffer->packetsAvail += afterAvail - beforeAvail;
} else {
ringbuffer->packetsAvail = afterAvail;
}

avcAu.pts = ctx->mediaengine->getVideoTimeStamp() + ctx->mpegFirstTimestamp;

Expand Down Expand Up @@ -1379,8 +1405,40 @@ void PostPutAction::run(MipsCall &call) {
auto ringbuffer = PSPPointer<SceMpegRingBuffer>::Create(ringAddr_);

MpegContext *ctx = getMpegCtx(ringbuffer->mpeg);
int writeOffset = ringbuffer->packetsWritePos % (s32)ringbuffer->packets;
const u8 *data = Memory::GetPointer(ringbuffer->data + writeOffset * 2048);

int packetsAdded = currentMIPS->r[MIPS_REG_V0];

// It seems validation is done only by older mpeg libs.
if (mpegLibVersion < 0x0105 && packetsAdded > 0) {
// TODO: Faster / less wasteful validation.
MpegDemux *demuxer = new MpegDemux(packetsAdded * 2048, 0);
int readOffset = ringbuffer->packetsRead % (s32)ringbuffer->packets;
const u8 *buf = Memory::GetPointer(ringbuffer->data + readOffset * 2048);
bool invalid = false;
for (int i = 0; i < packetsAdded; ++i) {
demuxer->addStreamData(buf, 2048);
buf += 2048;

if (!demuxer->demux(0xFFFF)) {
invalid = true;
}
}
if (invalid) {
// Bail out early - don't accept any of the packets, even the good ones.
ERROR_LOG_REPORT(ME, "sceMpegRingbufferPut(): invalid mpeg data");
call.setReturnValue(ERROR_MPEG_INVALID_VALUE);

if (mpegLibVersion <= 0x0103) {
// Act like they were actually added, but don't increment read pos.
ringbuffer->packetsWritePos += packetsAdded;
ringbuffer->packetsAvail += packetsAdded;
}
return;
}
}

if (ringbuffer->packetsRead == 0 && ctx->mediaengine && packetsAdded > 0) {
// init mediaEngine
AnalyzeMpeg(ctx->mpegheader, ctx);
Expand All @@ -1391,12 +1449,12 @@ void PostPutAction::run(MipsCall &call) {
WARN_LOG(ME, "sceMpegRingbufferPut clamping packetsAdded old=%i new=%i", packetsAdded, ringbuffer->packets - ringbuffer->packetsAvail);
packetsAdded = ringbuffer->packets - ringbuffer->packetsAvail;
}
int actuallyAdded = ctx->mediaengine == NULL ? 8 : ctx->mediaengine->addStreamData(Memory::GetPointer(ringbuffer->data), packetsAdded * 2048) / 2048;
int actuallyAdded = ctx->mediaengine == NULL ? 8 : ctx->mediaengine->addStreamData(data, packetsAdded * 2048) / 2048;
if (actuallyAdded != packetsAdded) {
WARN_LOG_REPORT(ME, "sceMpegRingbufferPut(): unable to enqueue all added packets, going to overwrite some frames.");
}
ringbuffer->packetsRead += packetsAdded;
ringbuffer->packetsWritten += packetsAdded;
ringbuffer->packetsWritePos += packetsAdded;
ringbuffer->packetsAvail += packetsAdded;
}
DEBUG_LOG(ME, "packetAdded: %i packetsRead: %i packetsTotal: %i", packetsAdded, ringbuffer->packetsRead, ringbuffer->packets);
Expand All @@ -1406,7 +1464,7 @@ void PostPutAction::run(MipsCall &call) {


// Program signals that it has written data to the ringbuffer and gets a callback ?
static u32 sceMpegRingbufferPut(u32 ringbufferAddr, u32 numPackets, u32 available)
static u32 sceMpegRingbufferPut(u32 ringbufferAddr, int numPackets, int available)
{
numPackets = std::min(numPackets, available);
if (numPackets <= 0) {
Expand Down Expand Up @@ -1436,8 +1494,9 @@ static u32 sceMpegRingbufferPut(u32 ringbufferAddr, u32 numPackets, u32 availabl
// TODO: Should call this multiple times until we get numPackets.
// Normally this would be if it did not read enough, but also if available > packets.
// Should ultimately return the TOTAL number of returned packets.
u32 packetsThisRound = std::min(numPackets, (u32)ringbuffer->packets);
u32 args[3] = {(u32)ringbuffer->data, packetsThisRound, (u32)ringbuffer->callback_args};
int writeOffset = ringbuffer->packetsWritePos % (s32)ringbuffer->packets;
u32 packetsThisRound = std::min(numPackets, (s32)ringbuffer->packets - writeOffset);
u32 args[3] = {(u32)ringbuffer->data + (u32)writeOffset * 2048, packetsThisRound, (u32)ringbuffer->callback_args};
__KernelDirectMipsCall(ringbuffer->callback_addr, action, args, 3, false);
} else {
ERROR_LOG_REPORT(ME, "sceMpegRingbufferPut: callback_addr zero");
Expand Down Expand Up @@ -1537,10 +1596,8 @@ static u32 sceMpegFinish()
return hleDelayResult(0, "mpeg finish", 250);
}

static u32 sceMpegQueryMemSize()
{
DEBUG_LOG(ME, "%i = sceMpegQueryMemSize()",MPEG_MEMSIZE);
return MPEG_MEMSIZE;
static u32 sceMpegQueryMemSize() {
return hleLogSuccessX(ME, MpegRequiredMem());
}

static int sceMpegGetAtracAu(u32 mpeg, u32 streamId, u32 auAddr, u32 attrAddr)
Expand Down Expand Up @@ -1759,7 +1816,7 @@ static u32 sceMpegFlushAllStream(u32 mpeg)
if (ringbuffer.IsValid()) {
ringbuffer->packetsAvail = 0;
ringbuffer->packetsRead = 0;
ringbuffer->packetsWritten = 0;
ringbuffer->packetsWritePos = 0;
}

return 0;
Expand Down Expand Up @@ -2178,7 +2235,7 @@ const HLEFunction sceMpeg[] =
{0XA11C7026, &WrapI_UU<sceMpegAvcDecodeMode>, "sceMpegAvcDecodeMode", 'i', "xx" },
{0X37295ED8, &WrapU_UUUUUU<sceMpegRingbufferConstruct>, "sceMpegRingbufferConstruct", 'x', "xxxxxx" },
{0X13407F13, &WrapU_U<sceMpegRingbufferDestruct>, "sceMpegRingbufferDestruct", 'x', "x" },
{0XB240A59E, &WrapU_UUU<sceMpegRingbufferPut>, "sceMpegRingbufferPut", 'x', "xxx" },
{0XB240A59E, &WrapU_UII<sceMpegRingbufferPut>, "sceMpegRingbufferPut", 'x', "xxx" },
{0XB5F6DC87, &WrapI_U<sceMpegRingbufferAvailableSize>, "sceMpegRingbufferAvailableSize", 'i', "x" },
{0XD7A29F46, &WrapU_I<sceMpegRingbufferQueryMemSize>, "sceMpegRingbufferQueryMemSize", 'x', "i" },
{0X769BEBB6, &WrapI_U<sceMpegRingbufferQueryPackNum>, "sceMpegRingbufferQueryPackNum", 'i', "x" },
Expand Down
3 changes: 2 additions & 1 deletion Core/HLE/sceMpeg.h
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,9 @@ struct SceMpegAu {
struct SceMpegRingBuffer {
// PSP info
s32_le packets;
// Misused: this is used as total read, but should be read offset (within ring.)
s32_le packetsRead;
s32_le packetsWritten;
s32_le packetsWritePos;
s32_le packetsAvail; // pspsdk: unk2, noxa: iUnk0
s32_le packetSize; // 2048
u32_le data; // address, ring buffer
Expand Down
109 changes: 92 additions & 17 deletions Core/HW/MpegDemux.cpp
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#include "MpegDemux.h"
#include "Core/Reporting.h"

const int PACKET_START_CODE_MASK = 0xffffff00;
const int PACKET_START_CODE_PREFIX = 0x00000100;
Expand Down Expand Up @@ -137,9 +138,8 @@ int MpegDemux::readPesHeader(PesHeader &pesHeader, int length, int startCode) {
return length;
}

int MpegDemux::demuxStream(bool bdemux, int startCode, int channel)
int MpegDemux::demuxStream(bool bdemux, int startCode, int length, int channel)
{
int length = read16();
if (bdemux) {
PesHeader pesHeader(channel);
length = readPesHeader(pesHeader, length, startCode);
Expand All @@ -154,61 +154,136 @@ int MpegDemux::demuxStream(bool bdemux, int startCode, int channel)
return channel;
}

void MpegDemux::demux(int audioChannel)
bool MpegDemux::skipPackHeader() {
// MPEG version / SCR
if ((read8() & 0xC4) != 0x44) {
return false;
}
skip(1);
if ((read8() & 0x04) != 0x04) {
return false;
}
skip(1);
if ((read8() & 0x04) != 0x04) {
return false;
}
// SCR_ext
if ((read8() & 0x01) != 0x01) {
return false;
}

int muxrate = read24();
if ((muxrate & 3) != 3) {
return false;
}
int stuffing = read8() & 7;
while (stuffing > 0) {
if (read8() != 0xFF) {
return false;
}
}
return true;
}

bool MpegDemux::demux(int audioChannel)
{
if (audioChannel >= 0)
m_audioChannel = audioChannel;
while (m_index < m_len)

bool looksValid = false;
bool needMore = false;
while (m_index < m_readSize && !needMore)
{
if (m_index + 2048 > m_readSize)
break;
// Search for start code
int startCode = 0xFF;
while ((startCode & PACKET_START_CODE_MASK) != PACKET_START_CODE_PREFIX && !isEOF()) {
startCode = (startCode << 8) | read8();
}
// Not enough data available yet.
if (m_readSize - m_index < 16) {
m_index -= 4;
break;
}

switch (startCode) {
case PACK_START_CODE:
skip(10);
if (skipPackHeader()) {
looksValid = true;
}
break;
case SYSTEM_HEADER_START_CODE:
skip(14);
case SYSTEM_HEADER_START_CODE: {
looksValid = true;
int length = read16();
if (m_readSize - m_index < length) {
m_index -= 4 + 2;
needMore = true;
break;
}
skip(length);
break;
}
case PADDING_STREAM:
case PRIVATE_STREAM_2:
{
int length = read16();
skip(length);
case PRIVATE_STREAM_2: {
looksValid = true;
int length = read16();
if (m_readSize - m_index < length) {
m_index -= 4 + 2;
needMore = true;
break;
}
skip(length);
break;
}
case PRIVATE_STREAM_1: {
// Audio stream
m_audioChannel = demuxStream(true, startCode, m_audioChannel);
int length = read16();
// Check for PES header marker.
looksValid = (m_buf[m_index] & 0xC0) == 0x80;
if (m_readSize - m_index < length) {
m_index -= 4 + 2;
needMore = true;
break;
}
m_audioChannel = demuxStream(true, startCode, length, m_audioChannel);
looksValid = true;
break;
}
case 0x1E0: case 0x1E1: case 0x1E2: case 0x1E3:
case 0x1E4: case 0x1E5: case 0x1E6: case 0x1E7:
case 0x1E8: case 0x1E9: case 0x1EA: case 0x1EB:
case 0x1EC: case 0x1ED: case 0x1EE: case 0x1EF:
case 0x1EC: case 0x1ED: case 0x1EE: case 0x1EF: {
// Video Stream
demuxStream(false, startCode, -1);
int length = read16();
// Check for PES header marker.
looksValid = (m_buf[m_index] & 0xC0) == 0x80;
if (m_readSize - m_index < length) {
m_index -= 4 + 2;
needMore = true;
break;
}
demuxStream(false, startCode, length, -1);
break;
}
case USER_DATA_START_CODE:
// User data, probably same as queried by sceMpegGetUserdataAu.
// Not sure what exactly to do or how much to read.
// TODO: implement properly.
WARN_LOG_REPORT_ONCE(mpeguserdata, ME, "MPEG user data found");
looksValid = true;
break;
}
}
if (m_index < m_readSize) {
int size = m_readSize - m_index;
memcpy(m_buf, m_buf + m_index, size);
memmove(m_buf, m_buf + m_index, size);
m_index = 0;
m_readSize = size;
} else {
m_index = 0;
m_readSize = 0;
}

return looksValid;
}

static bool isHeader(u8* audioStream, int offset)
Expand Down
Loading