Merge of http://go/wvgerrit/95666 Mostly fixing coding styles and a few vulnerability check. Updating tests according to the fix. Bug: 150614088 Bug: 150881959 Test: Ran cdm and odk unit tests Change-Id: I109a96ee8ded089d59ab49c2f94b6833c932fd1e
327 lines
13 KiB
C++
327 lines
13 KiB
C++
/* Copyright 2019 Google LLC. All rights reserved. This file and proprietary
|
|
* source code may only be used and distributed under the Widevine Master
|
|
* License Agreement.
|
|
*/
|
|
|
|
#include <cassert>
|
|
#include <cstddef>
|
|
#include <cstdint>
|
|
#include <cstring>
|
|
#include <functional>
|
|
#include <memory>
|
|
#include <vector>
|
|
|
|
#include "OEMCryptoCENCCommon.h"
|
|
#include "core_message_deserialize.h"
|
|
#include "core_message_serialize.h"
|
|
#include "core_message_types.h"
|
|
#include "odk.h"
|
|
#include "odk_serialize.h"
|
|
#include "odk_structs.h"
|
|
#include "odk_structs_priv.h"
|
|
|
|
typedef std::function<void(const uint8_t*, uint8_t*, size_t, size_t)>
|
|
roundtrip_fun;
|
|
|
|
using oemcrypto_core_message::ODK_LicenseRequest;
|
|
using oemcrypto_core_message::ODK_ProvisioningRequest;
|
|
using oemcrypto_core_message::ODK_RenewalRequest;
|
|
|
|
using oemcrypto_core_message::deserialize::CoreLicenseRequestFromMessage;
|
|
using oemcrypto_core_message::deserialize::CoreProvisioningRequestFromMessage;
|
|
using oemcrypto_core_message::deserialize::CoreRenewalRequestFromMessage;
|
|
|
|
using oemcrypto_core_message::serialize::CreateCoreLicenseResponse;
|
|
using oemcrypto_core_message::serialize::CreateCoreProvisioningResponse;
|
|
using oemcrypto_core_message::serialize::CreateCoreRenewalResponse;
|
|
|
|
// @ kdo deserialize; odk serialize
|
|
static OEMCryptoResult odk_serialize_LicenseRequest(
|
|
const void* in, uint8_t* out, size_t* size,
|
|
const ODK_LicenseRequest& core_license_request,
|
|
const ODK_NonceValues* nonce_values) {
|
|
return ODK_PrepareCoreLicenseRequest(out, SIZE_MAX, size, nonce_values);
|
|
}
|
|
|
|
static OEMCryptoResult odk_serialize_RenewalRequest(
|
|
const void* in, uint8_t* out, size_t* size,
|
|
const ODK_RenewalRequest& core_renewal, ODK_NonceValues* nonce_values) {
|
|
ODK_ClockValues clock{};
|
|
memcpy(&clock, in, sizeof(ODK_ClockValues));
|
|
uint64_t system_time_seconds = core_renewal.playback_time_seconds;
|
|
return ODK_PrepareCoreRenewalRequest(out, SIZE_MAX, size, nonce_values,
|
|
&clock, system_time_seconds);
|
|
}
|
|
|
|
static OEMCryptoResult odk_serialize_ProvisioningRequest(
|
|
const void* in, uint8_t* out, size_t* size,
|
|
const ODK_ProvisioningRequest& core_provisioning,
|
|
const ODK_NonceValues* nonce_values) {
|
|
const std::string& device_id = core_provisioning.device_id;
|
|
return ODK_PrepareCoreProvisioningRequest(
|
|
out, SIZE_MAX, size, nonce_values,
|
|
reinterpret_cast<const uint8_t*>(device_id.data()), device_id.size());
|
|
}
|
|
|
|
/**
|
|
* Template arguments:
|
|
* T: kdo deserialize output/odk serialize input structure
|
|
* F: kdo deserialize function
|
|
* G: odk serialize function
|
|
*
|
|
* raw bytes -> F deserialize -> struct T -> G serialize -> raw bytes
|
|
*/
|
|
template <typename T, typename F, typename G>
|
|
static roundtrip_fun kdo_odk(const F& kdo_fun, const G& odk_fun) {
|
|
auto roundtrip = [&](const uint8_t* in, uint8_t* out, size_t size,
|
|
size_t clock_value_size) -> void {
|
|
if (size <= clock_value_size) {
|
|
return;
|
|
}
|
|
// Input byte array format: [Clock Values][data to parse]
|
|
std::string input(reinterpret_cast<const char*>(in) + clock_value_size,
|
|
size - clock_value_size);
|
|
T t = {};
|
|
if (!kdo_fun(input, &t)) {
|
|
return;
|
|
}
|
|
ODK_NonceValues nonce_values = {t.api_minor_version, t.api_major_version,
|
|
t.nonce, t.session_id};
|
|
OEMCryptoResult err = odk_fun(in, out, &size, t, &nonce_values);
|
|
if (OEMCrypto_SUCCESS != err) {
|
|
return;
|
|
}
|
|
assert(0 == memcmp(in + clock_value_size, out, size));
|
|
};
|
|
return roundtrip;
|
|
}
|
|
|
|
// @ odk deserialize; kdo serialize
|
|
namespace {
|
|
struct ODK_ParseLicense_Args {
|
|
ODK_NonceValues nonce_values;
|
|
uint8_t initial_license_load;
|
|
uint8_t usage_entry_present;
|
|
uint8_t request_hash[32];
|
|
ODK_TimerLimits timer_limits;
|
|
ODK_ClockValues clock_values;
|
|
};
|
|
struct ODK_ParseRenewal_Args {
|
|
ODK_NonceValues nonce_values;
|
|
uint64_t system_time;
|
|
ODK_TimerLimits timer_limits;
|
|
ODK_ClockValues clock_values;
|
|
};
|
|
struct ODK_ParseProvisioning_Args {
|
|
ODK_NonceValues nonce_values;
|
|
size_t device_id_length;
|
|
uint8_t device_id[64];
|
|
};
|
|
} // namespace
|
|
|
|
bool convert_byte_to_valid_boolean(const bool* in) {
|
|
const int value = *reinterpret_cast<const int*>(in);
|
|
return value != 0;
|
|
}
|
|
|
|
static OEMCryptoResult odk_deserialize_LicenseResponse(
|
|
const uint8_t* message, size_t core_message_length,
|
|
ODK_ParseLicense_Args* a, ODK_NonceValues* nonce_values,
|
|
ODK_ParsedLicense* parsed_lic) {
|
|
return ODK_ParseLicense(message, SIZE_MAX, core_message_length,
|
|
static_cast<bool>(a->initial_license_load),
|
|
static_cast<bool>(a->usage_entry_present),
|
|
a->request_hash, &a->timer_limits, &a->clock_values,
|
|
nonce_values, parsed_lic);
|
|
}
|
|
|
|
static bool kdo_serialize_LicenseResponse(const ODK_ParseLicense_Args* args,
|
|
const ODK_ParsedLicense& parsed_lic,
|
|
std::string* oemcrypto_core_message) {
|
|
const auto& nonce_values = args->nonce_values;
|
|
ODK_LicenseRequest core_request{nonce_values.api_minor_version,
|
|
nonce_values.api_major_version,
|
|
nonce_values.nonce, nonce_values.session_id};
|
|
std::string core_request_sha_256(
|
|
reinterpret_cast<const char*>(args->request_hash), 32);
|
|
return CreateCoreLicenseResponse(
|
|
parsed_lic, core_request, core_request_sha_256, oemcrypto_core_message);
|
|
}
|
|
|
|
static OEMCryptoResult odk_deserialize_RenewalResponse(
|
|
const uint8_t* buf, size_t len, ODK_ParseRenewal_Args* a,
|
|
ODK_NonceValues* nonce_values, ODK_PreparedRenewalRequest* renewal_msg) {
|
|
/* Address Sanitizer doesn't like values other than 0 OR 1 for boolean
|
|
* variables. Input from fuzzer can be parsed and any random bytes can be
|
|
* assigned to boolean variables. Using the workaround to mitigate sanitizer
|
|
* errors in fuzzer code and converting random bytes to 0 OR 1.
|
|
* This has no negative security impact*/
|
|
a->timer_limits.soft_enforce_playback_duration =
|
|
convert_byte_to_valid_boolean(
|
|
&a->timer_limits.soft_enforce_playback_duration);
|
|
a->timer_limits.soft_enforce_rental_duration = convert_byte_to_valid_boolean(
|
|
&a->timer_limits.soft_enforce_rental_duration);
|
|
uint64_t timer_value = 0;
|
|
OEMCryptoResult err =
|
|
ODK_ParseRenewal(buf, SIZE_MAX, len, nonce_values, a->system_time,
|
|
&a->timer_limits, &a->clock_values, &timer_value);
|
|
if (OEMCrypto_SUCCESS == err) {
|
|
Message* msg = nullptr;
|
|
AllocateMessage(&msg, message_block);
|
|
InitMessage(msg, const_cast<uint8_t*>(buf), len);
|
|
SetSize(msg, len);
|
|
Unpack_ODK_PreparedRenewalRequest(msg, renewal_msg);
|
|
assert(ValidMessage(msg));
|
|
}
|
|
return err;
|
|
}
|
|
|
|
static bool kdo_serialize_RenewalResponse(
|
|
const ODK_ParseRenewal_Args* args,
|
|
const ODK_PreparedRenewalRequest& renewal_msg,
|
|
std::string* oemcrypto_core_message) {
|
|
const auto& nonce_values = args->nonce_values;
|
|
ODK_RenewalRequest core_request{
|
|
nonce_values.api_minor_version, nonce_values.api_major_version,
|
|
nonce_values.nonce, nonce_values.session_id, renewal_msg.playback_time};
|
|
return CreateCoreRenewalResponse(
|
|
core_request, args->timer_limits.initial_renewal_duration_seconds,
|
|
oemcrypto_core_message);
|
|
}
|
|
|
|
static OEMCryptoResult odk_deserialize_ProvisioningResponse(
|
|
const uint8_t* buf, size_t len, ODK_ParseProvisioning_Args* a,
|
|
ODK_NonceValues* nonce_values, ODK_ParsedProvisioning* parsed_prov) {
|
|
return ODK_ParseProvisioning(buf, SIZE_MAX, len, nonce_values, a->device_id,
|
|
a->device_id_length, parsed_prov);
|
|
}
|
|
|
|
static bool kdo_serialize_ProvisioningResponse(
|
|
const ODK_ParseProvisioning_Args* args,
|
|
const ODK_ParsedProvisioning& parsed_prov,
|
|
std::string* oemcrypto_core_message) {
|
|
const auto& nonce_values = args->nonce_values;
|
|
if (args->device_id_length > sizeof(args->device_id)) {
|
|
return false;
|
|
}
|
|
ODK_ProvisioningRequest core_request{
|
|
nonce_values.api_minor_version, nonce_values.api_major_version,
|
|
nonce_values.nonce, nonce_values.session_id,
|
|
std::string(reinterpret_cast<const char*>(args->device_id),
|
|
args->device_id_length)};
|
|
return CreateCoreProvisioningResponse(parsed_prov, core_request,
|
|
oemcrypto_core_message);
|
|
}
|
|
|
|
/**
|
|
* Template arguments:
|
|
* A: struct holding function arguments
|
|
* T: odk deserialize output/kdo serialize input structure
|
|
* F: odk deserialize function
|
|
* G: kdo serialize function
|
|
*
|
|
* raw bytes -> F deserialize -> struct T -> G serialize -> raw bytes
|
|
*/
|
|
template <typename A, typename T, typename F, typename G>
|
|
static roundtrip_fun odk_kdo(const F& odk_fun, const G& kdo_fun) {
|
|
auto roundtrip = [&](const uint8_t* in, uint8_t* out, size_t size,
|
|
size_t args_size) -> void {
|
|
// Input byte array format: [function arguments][data to parse]
|
|
if (args_size > size) {
|
|
return;
|
|
}
|
|
T t = {};
|
|
const uint8_t* buf = in + args_size;
|
|
std::shared_ptr<A> _args(new A());
|
|
A* args = _args.get();
|
|
memcpy(args, in, args_size);
|
|
args->nonce_values.api_major_version = ODK_MAJOR_VERSION;
|
|
args->nonce_values.api_minor_version = ODK_MINOR_VERSION;
|
|
/*
|
|
* Input random bytes from autofuzz are interpreted by this script as
|
|
* [function args][data to parse]. Odk deserialize functions
|
|
* expect the nonce values in function args to match with those
|
|
* in data to parse which is not possible with random bytes.
|
|
* We follow two pass approach.
|
|
*
|
|
* 1st pass - We copy random bytes into struct t and call kdo serialize
|
|
* with function args which will create oemcrypto core message using nonce
|
|
* from function args. Now we have a valid oemcrypto core message which is
|
|
* formed using nonce_values from function args which acts as input bytes
|
|
* for 2nd pass
|
|
*
|
|
* 2nd pass - oemcrypto core message from 1st pass guarantees that
|
|
* nonce_values in [function args] and core message match. we call
|
|
* odk_deserialize using nonce from function args and oemcrypto core message
|
|
* from 1st pass. Then we call kdo function which generates oemcrypto core
|
|
* message2, which should be equal to oemcrypto_core_message which was input
|
|
* to 2nd pass
|
|
*/
|
|
// TODO(ellurubharath): Use structure aware fuzzing
|
|
// 1st pass
|
|
memcpy(&t, buf, sizeof(t));
|
|
std::string oemcrypto_core_message;
|
|
if (!kdo_fun(args, t, &oemcrypto_core_message)) {
|
|
return;
|
|
}
|
|
assert(oemcrypto_core_message.size() <= size);
|
|
|
|
// 2nd pass
|
|
ODK_NonceValues nonce_values = args->nonce_values;
|
|
OEMCryptoResult result =
|
|
odk_fun(reinterpret_cast<const uint8_t*>(oemcrypto_core_message.data()),
|
|
oemcrypto_core_message.size(), args, &nonce_values, &t);
|
|
if (result != OEMCrypto_SUCCESS) {
|
|
return;
|
|
}
|
|
std::string oemcrypto_core_message2;
|
|
if (!kdo_fun(args, t, &oemcrypto_core_message2)) {
|
|
return;
|
|
}
|
|
assert(oemcrypto_core_message == oemcrypto_core_message2);
|
|
};
|
|
return roundtrip;
|
|
}
|
|
|
|
// @ fuzz raw -> parsed -> raw
|
|
static void verify_roundtrip(const uint8_t* in, size_t size,
|
|
roundtrip_fun roundtrip, size_t args_size) {
|
|
std::vector<uint8_t> _out(size);
|
|
auto out = _out.data();
|
|
roundtrip(in, out, size, args_size);
|
|
}
|
|
|
|
// Entry point for fuzzer, data is random bytes program gets from autofuzzer
|
|
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
|
|
verify_roundtrip(data, size,
|
|
kdo_odk<ODK_LicenseRequest>(CoreLicenseRequestFromMessage,
|
|
odk_serialize_LicenseRequest),
|
|
0);
|
|
verify_roundtrip(data, size,
|
|
kdo_odk<ODK_RenewalRequest>(CoreRenewalRequestFromMessage,
|
|
odk_serialize_RenewalRequest),
|
|
sizeof(ODK_ClockValues));
|
|
verify_roundtrip(
|
|
data, size,
|
|
kdo_odk<ODK_ProvisioningRequest>(CoreProvisioningRequestFromMessage,
|
|
odk_serialize_ProvisioningRequest),
|
|
0);
|
|
verify_roundtrip(
|
|
data, size,
|
|
odk_kdo<ODK_ParseLicense_Args, ODK_ParsedLicense>(
|
|
odk_deserialize_LicenseResponse, kdo_serialize_LicenseResponse),
|
|
sizeof(ODK_ParseLicense_Args));
|
|
verify_roundtrip(
|
|
data, size,
|
|
odk_kdo<ODK_ParseRenewal_Args, ODK_PreparedRenewalRequest>(
|
|
odk_deserialize_RenewalResponse, kdo_serialize_RenewalResponse),
|
|
sizeof(ODK_ParseRenewal_Args));
|
|
verify_roundtrip(data, size,
|
|
odk_kdo<ODK_ParseProvisioning_Args, ODK_ParsedProvisioning>(
|
|
odk_deserialize_ProvisioningResponse,
|
|
kdo_serialize_ProvisioningResponse),
|
|
sizeof(ODK_ParseProvisioning_Args));
|
|
|
|
return 0;
|
|
}
|