Files
odkitee/serialization/serialization_base.c
2020-07-24 12:03:58 -07:00

643 lines
20 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 "serialization_base.h"
#include <assert.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include "OEMCryptoCENC.h"
#include "bump_allocator.h"
#include "marshaller_base.h"
#include "shared_memory_allocator.h"
#include "shared_memory_interface.h"
struct _Message {
uint8_t* base;
size_t capacity;
size_t size;
size_t read_offset;
MessageStatus status;
};
typedef enum {
TAG_INVALID,
TAG_BOOL,
TAG_SIZE_T,
TAG_UINT8,
TAG_UINT16,
TAG_UINT32,
TAG_UINT64,
TAG_MEMORY,
TAG_SHARED_MEMORY,
TAG_EOM
} TagType;
static bool ValidMessage(Message* message) {
if (message == NULL) {
return false;
}
if (message->status != MESSAGE_STATUS_OK) {
return false;
}
if (message->base == NULL) {
message->status = MESSAGE_STATUS_NULL_POINTER_ERROR;
return false;
}
if (message->read_offset > message->capacity ||
message->size > message->capacity ||
message->read_offset > message->size) {
message->status = MESSAGE_STATUS_OVERFLOW_ERROR;
return false;
}
return true;
}
static bool NullCheck(Message* message, const void* ptr) {
if (!ptr) {
message->status = MESSAGE_STATUS_NULL_POINTER_ERROR;
return true;
}
return false;
}
/* Callers are expected to have validated the message already */
static void PackBytes(Message* message, const uint8_t* ptr, size_t count) {
if (!NullCheck(message, ptr)) {
if (count <= message->capacity - message->size) {
memcpy(message->base + message->size, ptr, count);
message->size += count;
} else {
message->status = MESSAGE_STATUS_OVERFLOW_ERROR;
}
}
}
static void PackTag(Message* message, TagType tag) {
if (!ValidMessage(message)) return;
uint8_t byte = (uint8_t)tag;
PackBytes(message, &byte, sizeof(byte));
}
LengthType LengthFromUint32T(uint32_t length) {
LengthType length_type = {.tag = LENGTH_TYPE_UINT32_T,
.type.uint32_t_value = length};
return length_type;
}
LengthType LengthFromSizeT(size_t length) {
LengthType length_type = {.tag = LENGTH_TYPE_SIZE_T,
.type.size_t_value = length};
return length_type;
}
LengthType LengthFromUint32TPointer(const uint32_t* length) {
LengthType length_type = {.tag = LENGTH_TYPE_UINT32_T_POINTER,
.type.uint32_t_pointer = length};
return length_type;
}
LengthType LengthFromSizeTPointer(const size_t* length) {
LengthType length_type = {.tag = LENGTH_TYPE_SIZE_T_POINTER,
.type.size_t_pointer = length};
return length_type;
}
LengthType LengthFromUint32TDoublePointer(uint32_t** length) {
LengthType length_type = {.tag = LENGTH_TYPE_UINT32_T_DOUBLE_POINTER,
.type.uint32_t_double_pointer = length};
return length_type;
}
LengthType LengthFromSizeTDoublePointer(size_t** length) {
LengthType length_type = {.tag = LENGTH_TYPE_SIZE_T_DOUBLE_POINTER,
.type.size_t_double_pointer = length};
return length_type;
}
size_t LengthAsSizeT(LengthType length) {
size_t result = 0;
switch(length.tag) {
case LENGTH_TYPE_SIZE_T:
result = length.type.size_t_value;
break;
case LENGTH_TYPE_UINT32_T:
result = (size_t)length.type.uint32_t_value;
break;
case LENGTH_TYPE_SIZE_T_POINTER:
if (length.type.size_t_pointer) {
result = *length.type.size_t_pointer;
}
break;
case LENGTH_TYPE_UINT32_T_POINTER:
if (length.type.uint32_t_pointer) {
result = (size_t)*length.type.uint32_t_pointer;
}
break;
case LENGTH_TYPE_SIZE_T_DOUBLE_POINTER:
if (length.type.size_t_double_pointer && *length.type.size_t_double_pointer) {
result = **length.type.size_t_double_pointer;
}
break;
case LENGTH_TYPE_UINT32_T_DOUBLE_POINTER:
if (length.type.uint32_t_double_pointer && *length.type.uint32_t_double_pointer) {
result = (size_t)**length.type.uint32_t_double_pointer;
}
break;
}
return result;
}
bool LengthIsNull(LengthType length) {
switch(length.tag) {
case LENGTH_TYPE_SIZE_T:
case LENGTH_TYPE_UINT32_T:
return false;
case LENGTH_TYPE_SIZE_T_POINTER:
return length.type.size_t_pointer == NULL;
case LENGTH_TYPE_UINT32_T_POINTER:
return length.type.uint32_t_pointer == NULL;
case LENGTH_TYPE_SIZE_T_DOUBLE_POINTER:
return length.type.size_t_double_pointer == NULL ||
*length.type.size_t_double_pointer == NULL;
case LENGTH_TYPE_UINT32_T_DOUBLE_POINTER:
return length.type.uint32_t_double_pointer == NULL ||
*length.type.uint32_t_double_pointer == NULL;
}
assert(false);
return true;
}
void ODK_Pack_bool(Message* message, const bool* value) {
if (!ValidMessage(message)) return;
PackTag(message, TAG_BOOL);
uint8_t b = (*value) ? 1 : 0;
PackBytes(message, &b, sizeof(b));
}
void ODK_Pack_size_t(Message* message, const size_t* value) {
if (!ValidMessage(message)) return;
PackTag(message, TAG_SIZE_T);
uint64_t u64 = *value;
uint8_t buf[sizeof(uint64_t)];
buf[0] = (uint8_t)u64;
buf[1] = (uint8_t)(u64 >> 8);
buf[2] = (uint8_t)(u64 >> 16);
buf[3] = (uint8_t)(u64 >> 24);
buf[4] = (uint8_t)(u64 >> 32);
buf[5] = (uint8_t)(u64 >> 40);
buf[6] = (uint8_t)(u64 >> 48);
buf[7] = (uint8_t)(u64 >> 56);
PackBytes(message, buf, sizeof(buf));
}
void ODK_Pack_c_str(Message* message, const char* value) {
if (!ValidMessage(message) || NullCheck(message, value)) return;
size_t length = strlen(value) + 1;
ODK_PackMemory(message, (const uint8_t*)value, LengthFromSizeT(length));
}
void ODK_Pack_uint8_t(Message* message, const uint8_t* value) {
if (!ValidMessage(message)) return;
PackTag(message, TAG_UINT8);
PackBytes(message, (const uint8_t*)value, sizeof(*value));
}
void ODK_Pack_uint16_t(Message* message, const uint16_t* value) {
if (!ValidMessage(message)) return;
PackTag(message, TAG_UINT16);
uint8_t buf[sizeof(uint16_t)];
buf[0] = (uint8_t)(*value);
buf[1] = (uint8_t)(*value >> 8);
PackBytes(message, buf, sizeof(buf));
}
void ODK_Pack_uint32_t(Message* message, const uint32_t* value) {
if (!ValidMessage(message)) return;
PackTag(message, TAG_UINT32);
uint8_t buf[sizeof(uint32_t)];
buf[0] = (uint8_t)(*value);
buf[1] = (uint8_t)(*value >> 8);
buf[2] = (uint8_t)(*value >> 16);
buf[3] = (uint8_t)(*value >> 24);
PackBytes(message, buf, sizeof(buf));
}
void ODK_Pack_uint64_t(Message* message, const uint64_t* value) {
if (!ValidMessage(message)) return;
PackTag(message, TAG_UINT64);
uint8_t buf[sizeof(uint64_t)];
buf[0] = (uint8_t)(*value);
buf[1] = (uint8_t)(*value >> 8);
buf[2] = (uint8_t)(*value >> 16);
buf[3] = (uint8_t)(*value >> 24);
buf[4] = (uint8_t)(*value >> 32);
buf[5] = (uint8_t)(*value >> 40);
buf[6] = (uint8_t)(*value >> 48);
buf[7] = (uint8_t)(*value >> 56);
PackBytes(message, buf, sizeof(buf));
}
/*
* Convenience function used by the serializer to
* pack a bool without having to allocate a local
* variable.
*/
bool ODK_PackBoolValue(Message* message, bool value) {
ODK_Pack_bool(message, (const bool*)&value);
return value;
}
/*
* Pack a boolean indicating if the pointer value is NULL
*/
bool ODK_PackIsNull(Message* message, const void* pointer) {
return ODK_PackBoolValue(message, pointer == NULL);
}
/*
* Pack a boolean indicating if the address value is NULL. It's
* effectivey the same as PackIsNull but provided for symmetry with
* UnpackAlloc.
*/
void ODK_PackAlloc(Message* message, const void* addr) {
ODK_PackIsNull(message, addr);
}
/*
* Pack a range of memory given by address and length. First pack a
* bool indicating if the address is NULL. If the address is non-null
* and length is non-null then pack the length and |length| bytes from
* memory beginning at |address|.
*/
void ODK_PackMemory(Message* message, const uint8_t* address,
LengthType length) {
if (!ValidMessage(message)) return;
PackTag(message, TAG_MEMORY);
ODK_PackBoolValue(message, address == NULL || LengthIsNull(length));
if (address && !LengthIsNull(length)) {
size_t count = LengthAsSizeT(length);
ODK_Pack_size_t(message, &count);
if (LengthAsSizeT(length) > 0) {
PackBytes(message, address, LengthAsSizeT(length));
}
}
}
/*
* Pack memory representing an array of fundamental types, given by
* address and length.
*/
void ODK_PackArray(Message* message, const uint8_t* address, size_t length) {
ODK_PackMemory(message, address, LengthFromSizeT(length));
}
/*
* Pack a variable length array of objects at the base address given
* by |address|, with |length| elements of size |size|. The ObjPacker is
* a pack function able to pack elements of the array.
*/
void ODK_PackObjArray(Message* message, const uint8_t* objs, LengthType length,
size_t size, ObjPacker packer) {
bool is_null = objs == NULL || LengthIsNull(length);
if (!ODK_PackBoolValue(message, is_null)) {
for (size_t i = 0; i < LengthAsSizeT(length); i++) {
(*packer)(message, objs + i * size);
}
}
}
/*
* On the REE side, if address and length are not null, map the shared
* memory segment specified by index into our address space using the
* shared memory bump allocator and pack fields into the message so
* that the receiver can map the corresponding segment if needed. Copy
* the input data into the shared segment.
*
* Parameters:
* message - The message to pack into
* index - the index that identifies which segment to map
* address - base address of the local memory buffer. If address is
* null, shared memory will not be allocated, which is indicated
* by a packed bool value so the receiver will also know not to
* allocate the segment.
* length - the length of the segment. A segment of this size will
* be mapped if length is non-null and *length is non-zero.
* otherwise the segment will not be mapped.
*/
void ODK_PackSharedInputBuffer(Message* message, uint16_t index,
const uint8_t* address, LengthType length) {
if (!ValidMessage(message)) return;
PackTag(message, TAG_SHARED_MEMORY);
ODK_PackBoolValue(message, address == NULL || LengthIsNull(length));
if (address && !LengthIsNull(length)) {
uint8_t* shared_address = SharedMemory_Allocate(index, LengthAsSizeT(length));
if (shared_address) {
memcpy(shared_address, address, LengthAsSizeT(length));
}
}
}
/*
* Pass information to the receiver indicating whether shared memory
* needs to be allocated for the buffer indicated by |address| and |length|.
*
* Parameters:
* message - The message to pack into
* address - base address of the local memory buffer. If null, a shared
* memory segment will not need to be mapped by the receiver.
* length - the length of the segment. If null, or if the length is 0,
* a shared memory segment will not need to be mapped by the receiver.
*
* Returns:
* void
*/
void ODK_PackSharedOutputBuffer(Message* message, const uint8_t* address,
LengthType length) {
if (!ValidMessage(message)) return;
PackTag(message, TAG_SHARED_MEMORY);
ODK_PackBoolValue(message, address == NULL || LengthIsNull(length));
}
void ODK_PackEOM(Message* message) {
if (!ValidMessage(message)) return;
PackTag(message, TAG_EOM);
}
/**************************** Unpack Functions *******************************/
static void UnpackBytes(Message* message, uint8_t* ptr, size_t count) {
if (!ValidMessage(message)) return;
if (count <= message->size - message->read_offset) {
if (ptr) {
memcpy(ptr, message->base + message->read_offset, count);
}
message->read_offset += count;
} else {
message->status = MESSAGE_STATUS_UNDERFLOW_ERROR;
}
}
static bool CheckTag(Message* message, TagType tag) {
uint8_t byte = TAG_INVALID;
UnpackBytes(message, &byte, sizeof(byte));
if (tag != (TagType)byte) {
message->status = MESSAGE_STATUS_INVALID_TAG_ERROR;
return false;
}
return true;
}
void ODK_Unpack_bool(Message* message, bool* value) {
if (!ValidMessage(message)) return;
if (!CheckTag(message, TAG_BOOL)) return;
uint8_t b = 0;
UnpackBytes(message, &b, sizeof(b));
*value = b ? true : false;
}
void ODK_Unpack_size_t(Message* message, size_t* value) {
if (!ValidMessage(message)) return;
if (!CheckTag(message, TAG_SIZE_T)) return;
uint8_t buf[sizeof(uint64_t)];
UnpackBytes(message, buf, sizeof(buf));
uint64_t u64 = *value;
u64 = buf[0];
u64 |= (uint64_t)buf[1] << 8;
u64 |= (uint64_t)buf[2] << 16;
u64 |= (uint64_t)buf[3] << 24;
u64 |= (uint64_t)buf[4] << 32;
u64 |= (uint64_t)buf[5] << 40;
u64 |= (uint64_t)buf[6] << 48;
u64 |= (uint64_t)buf[7] << 56;
*value = u64;
}
void ODK_Unpack_c_str(Message* message, char** value) {
if (!ValidMessage(message) || NullCheck(message, value)) return;
*value = NULL;
uint8_t* ptr = NULL;
ODK_UnpackPointerToMemory(message, &ptr);
if (ptr) {
/* calculate length, be careful to stay within length of message */
size_t length = 0;
uint8_t* p = ptr;
while (*p && p < message->base + message->size) {
length++;
p++;
}
void* buffer = BumpAllocate(length + 1);
if (buffer) {
memcpy(buffer, ptr, length);
*value = buffer;
}
}
}
void ODK_Unpack_uint8_t(Message* message, uint8_t* value) {
if (!ValidMessage(message)) return;
if (!CheckTag(message, TAG_UINT8)) return;
UnpackBytes(message, (uint8_t*)value, sizeof(*value));
}
void ODK_Unpack_uint16_t(Message* message, uint16_t* value) {
if (!ValidMessage(message)) return;
if (!CheckTag(message, TAG_UINT16)) return;
uint8_t buf[sizeof(uint16_t)];
UnpackBytes(message, buf, sizeof(buf));
*value = buf[0];
*value |= (uint16_t)buf[1] << 8;
}
void ODK_Unpack_uint32_t(Message* message, uint32_t* value) {
if (!ValidMessage(message)) return;
if (!CheckTag(message, TAG_UINT32)) return;
uint8_t buf[sizeof(uint32_t)];
UnpackBytes(message, buf, sizeof(buf));
*value = buf[0];
*value |= (uint32_t)buf[1] << 8;
*value |= (uint32_t)buf[2] << 16;
*value |= (uint32_t)buf[3] << 24;
}
void ODK_Unpack_uint64_t(Message* message, uint64_t* value) {
if (!ValidMessage(message)) return;
if (!CheckTag(message, TAG_UINT64)) return;
uint8_t buf[sizeof(uint64_t)];
UnpackBytes(message, buf, sizeof(buf));
*value = buf[0];
*value |= (uint64_t)buf[1] << 8;
*value |= (uint64_t)buf[2] << 16;
*value |= (uint64_t)buf[3] << 24;
*value |= (uint64_t)buf[4] << 32;
*value |= (uint64_t)buf[5] << 40;
*value |= (uint64_t)buf[6] << 48;
*value |= (uint64_t)buf[7] << 56;
}
bool ODK_UnpackBoolValue(Message* message) {
bool value = false;
ODK_Unpack_bool(message, &value);
return value;
}
/* Return true if the pointer was packed as NULL */
bool ODK_UnpackIsNull(Message* message) { return ODK_UnpackBoolValue(message); }
/*
* If the pointer was packed as NULL, return NULL. Otherwise
* return the number of bytes of memory from the bump allocator
* requested by |size|
*/
uint8_t* ODK_UnpackAlloc(Message* message, size_t size) {
return ODK_UnpackIsNull(message) ? NULL : BumpAllocate(size);
}
/*
* If the pointer was packed as NULL, return NULL. Otherwise
* return the number of bytes of memory from the bump allocator
* requested by |size|
*/
uint8_t* ODK_UnpackAllocBuffer(Message* message, LengthType length, size_t size) {
uint8_t* buffer = NULL;
if (!ODK_UnpackIsNull(message) && !LengthIsNull(length)) {
buffer = BumpAllocate(LengthAsSizeT(length) * size);
}
return buffer;
}
/*
* If the pointer was packed as NULL, return NULL. Otherwise unpack
* the array of objects into memory allocated from the bump allocator.
*/
void ODK_UnpackObjArray(Message* message, uint8_t** address, LengthType count,
size_t size, ObjUnpacker unpacker) {
if (address) {
*address = NULL;
}
if (!ODK_UnpackIsNull(message)) {
if (address && !LengthIsNull(count)) {
*address = BumpAllocate(LengthAsSizeT(count) * size);
if (*address) {
for (size_t i = 0; i < LengthAsSizeT(count); i++) {
(*unpacker)(message, (*address) + size * i);
}
}
}
}
}
/*
* Unpack a range of memory representing an array of fundamental types
* given a pointer to a buffer and a length. The caller is responsible
* for ensuring that the buffer is large enough to handle the number
* of bytes specified by |length|. First unpack a bool that indicates
* if the pointer to the memory is NULL and if not unpack the
* requested number of bytes into the address specified by |address|.
*/
void ODK_UnpackArray(Message* message, uint8_t* address, size_t length) {
if (!ValidMessage(message) || NullCheck(message, address)) return;
uint8_t* array = NULL;
ODK_UnpackPointerToMemory(message, &array);
if (array) {
memcpy(address, array, length);
}
}
/*
* Unpack a pointer to memory within the received message and return
* it. First unpack a bool that indicates if the address was passed in
* as NULL. If so set *address to NULL and return. Otherwise set
* *|address| to the current message read offset and unpack the length
* of the memory range. Then increment the message read offset by that
* amount.
*/
void ODK_UnpackPointerToMemory(Message* message, uint8_t** address) {
if (!ValidMessage(message) || NullCheck(message, address)) return;
if (!CheckTag(message, TAG_MEMORY)) return;
bool is_null = true;
ODK_Unpack_bool(message, &is_null);
if (is_null) {
*address = NULL;
return;
} else {
size_t length;
ODK_Unpack_size_t(message, &length);
size_t new_offset;
if (__builtin_add_overflow(message->read_offset, length, &new_offset) ||
new_offset > message->size) {
message->status = MESSAGE_STATUS_OVERFLOW_ERROR;
} else {
*address = message->base + message->read_offset;
message->read_offset = new_offset;
}
}
}
/*
* Unpack fields from |message| and use them to map a shared memory
* segment using the shared memory bump allocator. If the packed bool
* indicates that the output buffer is non-null, allocate a shared
* memory segment identified by |index| of the size indicated by
* |length|.
*
* Parameters:
* message - the message to unpack from
* index - the index that identifies which segment to map
* length - the length of the segment
* Returns:
* The address of the mapped segment
*/
uint8_t* ODK_UnpackSharedBuffer(Message* message, uint16_t index,
LengthType length) {
if (!ValidMessage(message)) return NULL;
if (!CheckTag(message, TAG_SHARED_MEMORY)) return NULL;
if (!ODK_UnpackIsNull(message)) {
return SharedMemory_Allocate(index, LengthAsSizeT(length));
}
return NULL;
}
/*
* In the REE, unpack fields from |message| and use them to map a
* shared memory segment using the shared memory bump allocator. If
* the packed bool indicates that the output buffer is non-null,
* allocate a shared memory segment identified by |index| of the size
* indicated by |length|. Copy the buffer from shared memory to
* the OEMCrypto API parameter.
*
* Parameters:
* message - the message to unpack from
* address - the OEMCrypto API parameter for the output buffer
* index - the index that identifies which segment to map
* length - the length of the segment
*/
void ODK_UnpackSharedOutputBuffer(Message* message, uint16_t index,
uint8_t** address, LengthType length) {
if (address && !LengthIsNull(length)) {
uint8_t* shared_address = ODK_UnpackSharedBuffer(message, index, length);
if (*address && LengthAsSizeT(length) > 0) {
memcpy(*address, shared_address, LengthAsSizeT(length));
}
}
}
/* end of message */
void ODK_UnpackEOM(Message* message) {
if (!ValidMessage(message)) return;
if (!CheckTag(message, TAG_EOM)) return;
if (message->read_offset != message->size) {
message->status = MESSAGE_STATUS_END_OF_MESSAGE_ERROR;
}
}