Merge pull request #8803 from unknownbrackets/mpeg

Reject invalid MPEG puts for certain lib versions
This commit is contained in:
Henrik Rydgård 2016-06-05 13:44:38 +02:00
commit ce8d2bef57
4 changed files with 178 additions and 41 deletions

View file

@ -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;
@ -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;
@ -467,6 +468,13 @@ 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)) {
@ -474,7 +482,7 @@ static u32 sceMpegCreate(u32 mpegAddr, u32 dataPtr, u32 size, u32 ringbufferAddr
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;
}
@ -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);
@ -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;
@ -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);
@ -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;
@ -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);
@ -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);
@ -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) {
@ -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");
@ -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)
@ -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;
@ -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" },

View file

@ -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

View file

@ -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;
@ -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);
@ -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);
break;
case PADDING_STREAM:
case PRIVATE_STREAM_2:
{
int length = read16();
skip(length);
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: {
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)

View file

@ -15,7 +15,7 @@ public:
~MpegDemux();
bool addStreamData(const u8 *buf, int addSize);
void demux(int audioChannel);
bool demux(int audioChannel);
// return its framesize
int getNextAudioFrame(u8 **buf, int *headerCode1, int *headerCode2, s64 *pts = NULL);
@ -46,6 +46,9 @@ private:
int read16() {
return (read8() << 8) | read8();
}
int read24() {
return (read8() << 16) | (read8() << 8) | read8();
}
s64 readPts() {
return readPts(read8());
}
@ -53,7 +56,7 @@ private:
return (((s64) (c & 0x0E)) << 29) | ((read16() >> 1) << 15) | (read16() >> 1);
}
bool isEOF() const {
return m_index >= m_len;
return m_index >= m_readSize;
}
void skip(int n) {
if (n > 0) {
@ -61,7 +64,8 @@ private:
}
}
int readPesHeader(PesHeader &pesHeader, int length, int startCode);
int demuxStream(bool bdemux, int startCode, int channel);
int demuxStream(bool bdemux, int startCode, int length, int channel);
bool skipPackHeader();
int m_index;
int m_len;