Bug 1287000 - Ensure ClearKey's CENC PSSH parser can handle PSSH boxes with a 0 size field. r=jwwang

Google's Web Platform EME tests contain a CENC PSSH box with a 0 size field.
Our existing PSSH parser in our ClearKey plugin doesn't handle this well, it
gets stuck in an infinite loop. We should really handle everything that Chrome
handles, so we should handle this input.

We also shouldn't really be using raw pointers in the PSSH parser.

So rewrite the PSSH parser to use a ByteReader, and handle an invalid 0 sized
common SystemID box.

Also add gtests for the parser, and skip over PSSH boxes with unknown SystemIDs
(if they have valid sizes that is).


MozReview-Commit-ID: CdVPpphAJV

--HG--
extra : rebase_source : e9a1b439f8b371653c2c97322a2db64cafef6dd8
This commit is contained in:
Chris Pearce 2016-07-15 09:31:07 +12:00
Родитель 6496bfc972
Коммит b6c7bf750a
7 изменённых файлов: 383 добавлений и 74 удалений

Просмотреть файл

@ -0,0 +1,189 @@
/*
* Copyright 2015, Mozilla Foundation and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "ClearKeyCencParser.h"
#include "mozilla/Assertions.h"
#include "ArrayUtils.h"
#include "BigEndian.h"
#include <memory.h>
#include <algorithm>
#include <assert.h>
#include <limits>
// Stripped down version of mp4_demuxer::ByteReader, stripped down to make it
// easier to link into ClearKey DLL and gtest.
class ByteReader
{
public:
ByteReader(const uint8_t* aData, size_t aSize)
: mPtr(aData), mRemaining(aSize), mLength(aSize)
{
}
size_t Offset() const
{
return mLength - mRemaining;
}
size_t Remaining() const { return mRemaining; }
size_t Length() const { return mLength; }
bool CanRead8() const { return mRemaining >= 1; }
uint8_t ReadU8()
{
auto ptr = Read(1);
if (!ptr) {
MOZ_ASSERT(false);
return 0;
}
return *ptr;
}
bool CanRead32() const { return mRemaining >= 4; }
uint32_t ReadU32()
{
auto ptr = Read(4);
if (!ptr) {
MOZ_ASSERT(false);
return 0;
}
return mozilla::BigEndian::readUint32(ptr);
}
const uint8_t* Read(size_t aCount)
{
if (aCount > mRemaining) {
mRemaining = 0;
return nullptr;
}
mRemaining -= aCount;
const uint8_t* result = mPtr;
mPtr += aCount;
return result;
}
const uint8_t* Seek(size_t aOffset)
{
if (aOffset > mLength) {
MOZ_ASSERT(false);
return nullptr;
}
mPtr = mPtr - Offset() + aOffset;
mRemaining = mLength - aOffset;
return mPtr;
}
private:
const uint8_t* mPtr;
size_t mRemaining;
const size_t mLength;
};
#define FOURCC(a,b,c,d) ((a << 24) + (b << 16) + (c << 8) + d)
// System ID identifying the cenc v2 pssh box format; specified at:
// https://dvcs.w3.org/hg/html-media/raw-file/tip/encrypted-media/cenc-format.html
const uint8_t kSystemID[] = {
0x10, 0x77, 0xef, 0xec, 0xc0, 0xb2, 0x4d, 0x02,
0xac, 0xe3, 0x3c, 0x1e, 0x52, 0xe2, 0xfb, 0x4b
};
void
ParseCENCInitData(const uint8_t* aInitData,
uint32_t aInitDataSize,
std::vector<std::vector<uint8_t>>& aOutKeyIds)
{
ByteReader reader(aInitData, aInitDataSize);
while (reader.CanRead32()) {
// Box size. For the common system Id, ignore this, as some useragents
// handle invalid box sizes.
const size_t start = reader.Offset();
const size_t size = reader.ReadU32();
if (size > std::numeric_limits<size_t>::max() - start) {
// Ensure 'start + size' calculation below can't overflow.
return;
}
const size_t end = std::min<size_t>(start + size, reader.Length());
// PSSH box type.
if (!reader.CanRead32()) {
return;
}
uint32_t box = reader.ReadU32();
if (box != FOURCC('p','s','s','h')) {
reader.Seek(std::max<size_t>(reader.Offset(), end));
continue;
}
// 1 byte version, 3 bytes flags.
if (!reader.CanRead32()) {
return;
}
uint8_t version = reader.ReadU8();
if (version != 1) {
// Ignore pssh boxes with wrong version.
reader.Seek(std::max<size_t>(reader.Offset(), end));
continue;
}
reader.Read(3); // skip flags.
// SystemID
const uint8_t* sid = reader.Read(sizeof(kSystemID));
if (!sid) {
// Insufficinet bytes to read SystemID.
return;
}
if (memcmp(kSystemID, sid, sizeof(kSystemID))) {
// Ignore pssh boxes with wrong system ID.
reader.Seek(std::max<size_t>(reader.Offset(), end));
continue;
}
if (!reader.CanRead32()) {
return;
}
uint32_t kidCount = reader.ReadU32();
for (uint32_t i = 0; i < kidCount; i++) {
if (reader.Remaining() < CLEARKEY_KEY_LEN) {
// Not enough remaining to read key.
return;
}
const uint8_t* kid = reader.Read(CLEARKEY_KEY_LEN);
aOutKeyIds.push_back(std::vector<uint8_t>(kid, kid + CLEARKEY_KEY_LEN));
}
// Size of extra data. EME CENC format spec says datasize should
// always be 0. We explicitly read the datasize, in case the box
// size was 0, so that we get to the end of the box.
if (!reader.CanRead32()) {
return;
}
reader.ReadU32();
// Jump forwards to the end of the box, skipping any padding.
if (size) {
reader.Seek(end);
}
}
}

Просмотреть файл

@ -0,0 +1,30 @@
/*
* Copyright 2015, Mozilla Foundation and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef __ClearKeyCencParser_h__
#define __ClearKeyCencParser_h__
#include <stdint.h>
#include <vector>
#define CLEARKEY_KEY_LEN ((size_t)16)
void
ParseCENCInitData(const uint8_t* aInitData,
uint32_t aInitDataSize,
std::vector<std::vector<uint8_t>>& aOutKeyIds);
#endif

Просмотреть файл

@ -19,6 +19,7 @@
#include "ClearKeySession.h"
#include "ClearKeyUtils.h"
#include "ClearKeyStorage.h"
#include "ClearKeyCencParser.h"
#include "gmp-task-utils.h"
#include "gmp-api/gmp-decryption.h"
#include <assert.h>
@ -60,7 +61,7 @@ ClearKeySession::Init(uint32_t aCreateSessionToken,
CK_LOGD("ClearKeySession::Init");
if (aInitDataType == "cenc") {
ClearKeyUtils::ParseCENCInitData(aInitData, aInitDataSize, mKeyIds);
ParseCENCInitData(aInitData, aInitDataSize, mKeyIds);
} else if (aInitDataType == "keyids") {
std::string sessionType;
ClearKeyUtils::ParseKeyIdsInitData(aInitData, aInitDataSize, mKeyIds, sessionType);

Просмотреть файл

@ -30,15 +30,6 @@
using namespace std;
#define FOURCC(a,b,c,d) ((a << 24) + (b << 16) + (c << 8) + d)
// System ID identifying the cenc v2 pssh box format; specified at:
// https://dvcs.w3.org/hg/html-media/raw-file/tip/encrypted-media/cenc-format.html
const uint8_t kSystemID[] = {
0x10, 0x77, 0xef, 0xec, 0xc0, 0xb2, 0x4d, 0x02,
0xac, 0xe3, 0x3c, 0x1e, 0x52, 0xe2, 0xfb, 0x4b
};
void
CK_Log(const char* aFmt, ...)
{
@ -131,64 +122,6 @@ EncodeBase64Web(vector<uint8_t> aBinary, string& aEncoded)
return true;
}
/* static */ void
ClearKeyUtils::ParseCENCInitData(const uint8_t* aInitData,
uint32_t aInitDataSize,
vector<KeyId>& aOutKeyIds)
{
using mozilla::BigEndian;
uint32_t size = 0;
for (uint32_t offset = 0; offset + sizeof(uint32_t) < aInitDataSize; offset += size) {
const uint8_t* data = aInitData + offset;
size = BigEndian::readUint32(data); data += sizeof(uint32_t);
CK_LOGD("Looking for pssh at offset %u", offset);
if (size + offset > aInitDataSize) {
CK_LOGE("Box size %u overflows init data buffer", size);
return;
}
if (size < 36) {
// Too small to be a cenc2 pssh box
continue;
}
uint32_t box = BigEndian::readUint32(data); data += sizeof(uint32_t);
if (box != FOURCC('p','s','s','h')) {
CK_LOGE("ClearKey CDM passed non-pssh initData");
return;
}
uint32_t head = BigEndian::readUint32(data); data += sizeof(uint32_t);
CK_LOGD("Got version %u pssh box, length %u", head & 0xff, size);
if ((head >> 24) != 1) {
// Ignore pssh boxes with wrong version
CK_LOGD("Ignoring pssh box with wrong version");
continue;
}
if (memcmp(kSystemID, data, sizeof(kSystemID))) {
// Ignore pssh boxes with wrong system ID
continue;
}
data += sizeof(kSystemID);
uint32_t kidCount = BigEndian::readUint32(data); data += sizeof(uint32_t);
if (data + kidCount * CLEARKEY_KEY_LEN > aInitData + aInitDataSize) {
CK_LOGE("pssh key IDs overflow init data buffer");
return;
}
for (uint32_t i = 0; i < kidCount; i++) {
aOutKeyIds.push_back(KeyId(data, data + CLEARKEY_KEY_LEN));
data += CLEARKEY_KEY_LEN;
}
}
}
/* static */ void
ClearKeyUtils::MakeKeyRequest(const vector<KeyId>& aKeyIDs,
string& aOutRequest,

Просмотреть файл

@ -23,8 +23,6 @@
#include <assert.h>
#include "gmp-api/gmp-decryption.h"
#define CLEARKEY_KEY_LEN ((size_t)16)
#if 0
void CK_Log(const char* aFmt, ...);
#define CK_LOGE(...) CK_Log(__VA_ARGS__)
@ -54,10 +52,6 @@ public:
static void DecryptAES(const std::vector<uint8_t>& aKey,
std::vector<uint8_t>& aData, std::vector<uint8_t>& aIV);
static void ParseCENCInitData(const uint8_t* aInitData,
uint32_t aInitDataSize,
std::vector<Key>& aOutKeyIds);
static bool ParseKeyIdsInitData(const uint8_t* aInitData,
uint32_t aInitDataSize,
std::vector<KeyId>& aOutKeyIds,

Просмотреть файл

@ -10,6 +10,7 @@
#include <vector>
#include "../ClearKeyBase64.cpp"
#include "../ClearKeyCencParser.cpp"
#include "../ArrayUtils.h"
@ -73,3 +74,163 @@ TEST(ClearKey, DecodeBase64KeyOrId) {
}
}
}
// This is the CENC initData from Google's web-platform tests.
// https://github.com/w3c/web-platform-tests/blob/master/encrypted-media/Google/encrypted-media-utils.js#L50
const uint8_t gGoogleWPTCencInitData[] = {
0x00, 0x00, 0x00, 0x00, // size = 0
0x70, 0x73, 0x73, 0x68, // 'pssh'
0x01, // version = 1
0x00, 0x00, 0x00, // flags
0x10, 0x77, 0xEF, 0xEC, 0xC0, 0xB2, 0x4D, 0x02, // Common SystemID
0xAC, 0xE3, 0x3C, 0x1E, 0x52, 0xE2, 0xFB, 0x4B,
0x00, 0x00, 0x00, 0x01, // key count
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, // key
0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
0x00, 0x00, 0x00, 0x00 // datasize
};
// Example CENC initData from the EME spec format registry:
// https://w3c.github.io/encrypted-media/format-registry/initdata/cenc.html
const uint8_t gW3SpecExampleCencInitData[] = {
0x00, 0x00, 0x00, 0x4c, 0x70, 0x73, 0x73, 0x68, // BMFF box header (76 bytes, 'pssh')
0x01, 0x00, 0x00, 0x00, // Full box header (version = 1, flags = 0)
0x10, 0x77, 0xef, 0xec, 0xc0, 0xb2, 0x4d, 0x02, // SystemID
0xac, 0xe3, 0x3c, 0x1e, 0x52, 0xe2, 0xfb, 0x4b,
0x00, 0x00, 0x00, 0x02, // KID_count (2)
0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, // First KID ("0123456789012345")
0x38, 0x39, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35,
0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, // Second KID ("ABCDEFGHIJKLMNOP")
0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, 0x50,
0x00, 0x00, 0x00, 0x00 // Size of Data (0)
};
// Invalid box size, would overflow if used.
const uint8_t gOverflowBoxSize[] = {
0xff, 0xff, 0xff, 0xff, // size = UINT32_MAX
};
// Invalid box size, but retrievable data.
const uint8_t gMalformedCencInitData[] = {
0x00, 0x00, 0xff, 0xff, // size = too big a number
0x70, 0x73, 0x73, 0x68, // 'pssh'
0x01, // version = 1
0xff, 0xff, 0xff, // flags
0x10, 0x77, 0xEF, 0xEC, 0xC0, 0xB2, 0x4D, 0x02, // Common SystemID
0xAC, 0xE3, 0x3C, 0x1E, 0x52, 0xE2, 0xFB, 0x4B,
0xff, 0xff, 0xff, 0xff, // key count = UINT32_MAX
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, // key
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff // datasize
};
// Non PSSH box, followed by Non common SystemID PSSH, followed by common SystemID PSSH box.
const uint8_t gLeadingNonCommonCencInitData[] = {
0x00, 0x00, 0x00, 0x09, // size = 9
0xff, 0xff, 0xff, 0xff, // something other than 'pssh'
0xff, // odd number of bytes of garbage to throw off the parser
0x00, 0x00, 0x00, 0x5c, // size = 92
0x70, 0x73, 0x73, 0x68, // 'pssh'
0x01, // version = 1
0x00, 0x00, 0x00, // flags
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, // Invalid SystemID
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, // Some data to pad out the box.
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
// gW3SpecExampleCencInitData
0x00, 0x00, 0x00, 0x4c, 0x70, 0x73, 0x73, 0x68, // BMFF box header (76 bytes, 'pssh')
0x01, 0x00, 0x00, 0x00, // Full box header (version = 1, flags = 0)
0x10, 0x77, 0xef, 0xec, 0xc0, 0xb2, 0x4d, 0x02, // SystemID
0xac, 0xe3, 0x3c, 0x1e, 0x52, 0xe2, 0xfb, 0x4b,
0x00, 0x00, 0x00, 0x02, // KID_count (2)
0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, // First KID ("0123456789012345")
0x38, 0x39, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35,
0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, // Second KID ("ABCDEFGHIJKLMNOP")
0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, 0x50,
0x00, 0x00, 0x00, 0x00 // Size of Data (0)
};
const uint8_t gNonPSSHBoxZeroSize[] = {
0x00, 0x00, 0x00, 0x00, // size = 0
0xff, 0xff, 0xff, 0xff, // something other than 'pssh'
};
// Two lots of the google init data. To ensure we handle
// multiple boxes with size 0.
const uint8_t g2xGoogleWPTCencInitData[] = {
0x00, 0x00, 0x00, 0x00, // size = 0
0x70, 0x73, 0x73, 0x68, // 'pssh'
0x01, // version = 1
0x00, 0x00, 0x00, // flags
0x10, 0x77, 0xEF, 0xEC, 0xC0, 0xB2, 0x4D, 0x02, // Common SystemID
0xAC, 0xE3, 0x3C, 0x1E, 0x52, 0xE2, 0xFB, 0x4B,
0x00, 0x00, 0x00, 0x01, // key count
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, // key
0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
0x00, 0x00, 0x00, 0x00, // datasize
0x00, 0x00, 0x00, 0x00, // size = 0
0x70, 0x73, 0x73, 0x68, // 'pssh'
0x01, // version = 1
0x00, 0x00, 0x00, // flags
0x10, 0x77, 0xEF, 0xEC, 0xC0, 0xB2, 0x4D, 0x02, // Common SystemID
0xAC, 0xE3, 0x3C, 0x1E, 0x52, 0xE2, 0xFB, 0x4B,
0x00, 0x00, 0x00, 0x01, // key count
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, // key
0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
0x00, 0x00, 0x00, 0x00 // datasize
};
TEST(ClearKey, ParseCencInitData) {
std::vector<std::vector<uint8_t>> keyIds;
ParseCENCInitData(gGoogleWPTCencInitData, MOZ_ARRAY_LENGTH(gGoogleWPTCencInitData), keyIds);
EXPECT_EQ(keyIds.size(), 1u);
EXPECT_EQ(keyIds[0].size(), 16u);
EXPECT_EQ(memcmp(&keyIds[0].front(), &gGoogleWPTCencInitData[32], 16), 0);
keyIds.clear();
ParseCENCInitData(gW3SpecExampleCencInitData, MOZ_ARRAY_LENGTH(gW3SpecExampleCencInitData), keyIds);
EXPECT_EQ(keyIds.size(), 2u);
EXPECT_EQ(keyIds[0].size(), 16u);
EXPECT_EQ(memcmp(&keyIds[0].front(), &gW3SpecExampleCencInitData[32], 16), 0);
EXPECT_EQ(memcmp(&keyIds[1].front(), &gW3SpecExampleCencInitData[48], 16), 0);
keyIds.clear();
ParseCENCInitData(gOverflowBoxSize, MOZ_ARRAY_LENGTH(gOverflowBoxSize), keyIds);
EXPECT_EQ(keyIds.size(), 0u);
keyIds.clear();
ParseCENCInitData(gMalformedCencInitData, MOZ_ARRAY_LENGTH(gMalformedCencInitData), keyIds);
EXPECT_EQ(keyIds.size(), 1u);
EXPECT_EQ(keyIds[0].size(), 16u);
EXPECT_EQ(memcmp(&keyIds[0].front(), &gMalformedCencInitData[32], 16), 0);
keyIds.clear();
ParseCENCInitData(gLeadingNonCommonCencInitData, MOZ_ARRAY_LENGTH(gLeadingNonCommonCencInitData), keyIds);
EXPECT_EQ(keyIds.size(), 2u);
EXPECT_EQ(keyIds[0].size(), 16u);
EXPECT_EQ(memcmp(&keyIds[0].front(), &gW3SpecExampleCencInitData[32], 16), 0);
EXPECT_EQ(memcmp(&keyIds[1].front(), &gW3SpecExampleCencInitData[48], 16), 0);
keyIds.clear();
ParseCENCInitData(gNonPSSHBoxZeroSize, MOZ_ARRAY_LENGTH(gNonPSSHBoxZeroSize), keyIds);
EXPECT_EQ(keyIds.size(), 0u);
keyIds.clear();
ParseCENCInitData(g2xGoogleWPTCencInitData, MOZ_ARRAY_LENGTH(g2xGoogleWPTCencInitData), keyIds);
EXPECT_EQ(keyIds.size(), 2u);
EXPECT_EQ(keyIds[0].size(), 16u);
EXPECT_EQ(keyIds[1].size(), 16u);
EXPECT_EQ(memcmp(&keyIds[0].front(), &g2xGoogleWPTCencInitData[32], 16), 0);
EXPECT_EQ(memcmp(&keyIds[1].front(), &g2xGoogleWPTCencInitData[84], 16), 0);
}

Просмотреть файл

@ -13,6 +13,7 @@ FINAL_TARGET_PP_FILES += ['clearkey.info.in']
UNIFIED_SOURCES += [
'ClearKeyAsyncShutdown.cpp',
'ClearKeyBase64.cpp',
'ClearKeyCencParser.cpp',
'ClearKeyDecryptionManager.cpp',
'ClearKeyPersistence.cpp',
'ClearKeySession.cpp',