blob: 397f542992fe8beeaa4770c64de318be7854c293 [file] [log] [blame]
#!/usr/bin/env python3
"""This program will take trusted application's manifest config JSON file as
input. Processes the JSON config file and creates packed data
mapping to C structures and dumps in binary format.
USAGE:
manifest_compiler.py --input <input_filename> --output <output_filename> \
--constants <config_constants_file_1> \
--constants <config_constants_file_2> \
--header-dir <header_file_path>
Arguments:
input_filename - Trusted app manifest config file in JSON format.
output_filename - Binary file containing packed manifest config data mapped
to C structres.
config_constant_file - This is optional
Config file with constants in JSON format
Corresponding header file will be
created with its constants defined in it
header_file_path - Directory in which header files to be generated.
example:
manifest_compiler.py --input manifest.json --output output.bin \
--constants manifest_constants.json \
--header-dir \
<build_dir>/user_tasks/trusty/user/app/sample/hwcrypto/include
If the output filename is omitted, the compiler will only generate constants
headers for the given constants files.
Input sample JSON Manifest config file content -
{
"uuid": "SECURE_STORAGE_SERVER_APP_UUID",
"min_heap": 4096,
"min_stack": 4096,
"mem_map": [
{
"id": 1,
"addr": "0x70000000",
"size": "0x1000"
},
{
"id": 2,
"addr": "0x70010000",
"size": "0x100"
},
{
"id": 3,
"addr": "0x70020000",
"size": "0x4",
"type": "uncached_device",
"non_secure": false
}
],
"mgmt_flags": {
"restart_on_exit": true,
"deferred_start": false,
"non_critical_app": false
},
"start_ports": [
{
"name": "LOADABLE_START_PORT",
"flags": {
"allow_ta_connect": true,
"allow_ns_connect": false
}
}
],
"pinned_cpu": 3,
"priority" : 10,
"version": 1,
"min_version": 1,
"apploader_flags": {
"requires_encryption": false
}
}
JSON manifest constant config -
{
"header": "storage_constants.h",
"constants": [
{
"name": "LOADABLE_START_PORT",
"value": "com.android.trusty.appmgmt.loadable.start",
"type": "port"
},
{
"name": "SECURE_STORAGE_SERVER_APP_UUID",
"value": "eca48f94-00aa-560e-8f8c-d94b50d484f3",
"type": "uuid"
}
]
}
"""
import argparse
import io
import json
import os.path
import struct
import sys
assert (sys.version_info.major, sys.version_info.minor) >= (3, 6), (
# pylint: disable-next=consider-using-f-string
"Python 3.6 or newer is required; found {}. Did you forget to set PY3?"
.format(sys.version))
# Manifest properties
UUID = "uuid"
MIN_HEAP = "min_heap"
MIN_STACK = "min_stack"
MIN_SHADOW_STACK = "min_shadow_stack"
MEM_MAP = "mem_map"
MEM_MAP_ID = "id"
MEM_MAP_ADDR = "addr"
MEM_MAP_SIZE = "size"
MEM_MAP_TYPE = "type"
MEM_MAP_TYPE_CACHED = "cached"
MEM_MAP_TYPE_UNCACHED = "uncached"
MEM_MAP_TYPE_UNCACHED_DEVICE = "uncached_device"
MEM_MAP_NON_SECURE = "non_secure"
MGMT_FLAGS = "mgmt_flags"
MGMT_FLAG_RESTART_ON_EXIT = "restart_on_exit"
MGMT_FLAG_DEFERRED_START = "deferred_start"
MGMT_FLAG_NON_CRITICAL_APP = "non_critical_app"
START_PORTS = "start_ports"
START_PORT_FLAGS = "flags"
START_PORT_NAME = "name"
START_PORT_ALLOW_TA_CONNECT = "allow_ta_connect"
START_PORT_ALLOW_NS_CONNECT = "allow_ns_connect"
APP_NAME = "app_name"
PINNED_CPU = "pinned_cpu"
PRIORITY = "priority"
VERSION = "version"
MIN_VERSION = "min_version"
APPLOADER_FLAGS = "apploader_flags"
APPLOADER_FLAGS_REQUIRES_ENCRYPTION = "requires_encryption"
# constants configs
CONSTANTS = "constants"
HEADER = "header"
CONST_NAME = "name"
CONST_VALUE = "value"
CONST_TYPE = "type"
CONST_UNSIGNED = "unsigned"
CONST_PORT = "port"
CONST_UUID = "uuid"
CONST_INT = "int"
CONST_BOOL = "bool"
# CONFIG TAGS
# These values need to be kept in sync with lib/app_manifest/app_manifest.h
TRUSTY_APP_CONFIG_KEY_MIN_STACK_SIZE = 1
TRUSTY_APP_CONFIG_KEY_MIN_HEAP_SIZE = 2
TRUSTY_APP_CONFIG_KEY_MAP_MEM = 3
TRUSTY_APP_CONFIG_KEY_MGMT_FLAGS = 4
TRUSTY_APP_CONFIG_KEY_START_PORT = 5
TRUSTY_APP_CONFIG_KEY_PINNED_CPU = 6
TRUSTY_APP_CONFIG_KEY_VERSION = 7
TRUSTY_APP_CONFIG_KEY_MIN_SHADOW_STACK_SIZE = 8
TRUSTY_APP_CONFIG_KEY_APPLOADER_FLAGS = 9
TRUSTY_APP_CONFIG_KEY_PRIORITY = 10
TRUSTY_APP_CONFIG_KEY_MIN_VERSION = 11
# MEM_MAP ARCH_MMU_FLAGS
# These values need to be kept in sync with external/lk/include/arch/mmu.h
ARCH_MMU_FLAG_CACHED = 0 << 0
ARCH_MMU_FLAG_UNCACHED = 1 << 0
ARCH_MMU_FLAG_UNCACHED_DEVICE = 2 << 0
ARCH_MMU_FLAG_CACHE_MASK = 3 << 0
ARCH_MMU_FLAG_NS = 1 << 5
# MGMT FLAGS
# These values need to be kept in sync with lib/app_manifest/app_manifest.h
TRUSTY_APP_MGMT_FLAGS_NONE = 0
TRUSTY_APP_MGMT_FLAGS_RESTART_ON_EXIT = 1 << 0
TRUSTY_APP_MGMT_FLAGS_DEFERRED_START = 1 << 1
TRUSTY_APP_MGMT_FLAGS_NON_CRITICAL_APP = 1 << 2
# APPLOADER FLAGS
# These values need to be kept in sync with lib/app_manifest/app_manifest.h
TRUSTY_APP_APPLOADER_FLAGS_NONE = 0
TRUSTY_APP_APPLOADER_FLAGS_REQUIRES_ENCRYPTION = 1 << 0
# START_PORT flags
# These values need to be kept in sync with user/base/include/user/trusty_ipc.h
IPC_PORT_ALLOW_TA_CONNECT = 0x1
IPC_PORT_ALLOW_NS_CONNECT = 0x2
IPC_PORT_PATH_MAX = 64
class Constant(object):
def __init__(self, name, value, type_, unsigned=False, hex_num=False):
self.name = name
self.value = value
self.type = type_
self.unsigned = unsigned
self.hex_num = hex_num
class ConfigConstants(object):
def __init__(self, constants, header):
self.constants = constants
self.header = header
class StartPortFlags(object):
def __init__(self, allow_ta_connect, allow_ns_connect):
self.allow_ta_connect = allow_ta_connect
self.allow_ns_connect = allow_ns_connect
class StartPort(object):
def __init__(self, name, name_size, start_port_flags):
self.name = name
self.name_size = name_size
self.start_port_flags = start_port_flags
class MemIOMap(object):
def __init__(self, id_, addr, size, type_, non_secure):
self.id = id_
self.addr = addr
self.size = size
self.type = type_
self.non_secure = non_secure
class MgmtFlags(object):
def __init__(self, restart_on_exit, deferred_start, non_critical_app):
self.restart_on_exit = restart_on_exit
self.deferred_start = deferred_start
self.non_critical_app = non_critical_app
class ApploaderFlags(object):
def __init__(self, requires_encryption):
self.requires_encryption = requires_encryption
class Manifest(object):
"""Holds Manifest data to be used for packing"""
def __init__(
self,
uuid,
app_name,
min_heap,
min_stack,
min_shadow_stack,
mem_io_maps,
mgmt_flags,
start_ports,
pinned_cpu,
priority,
version,
min_version,
apploader_flags,
):
self.uuid = uuid
self.app_name = app_name
self.min_heap = min_heap
self.min_stack = min_stack
self.min_shadow_stack = min_shadow_stack
self.mem_io_maps = mem_io_maps
self.mgmt_flags = mgmt_flags
self.start_ports = start_ports
self.pinned_cpu = pinned_cpu
self.priority = priority
self.version = version
self.min_version = min_version
self.apploader_flags = apploader_flags
class Log(object):
"""Tracks errors during manifest compilation"""
def __init__(self):
self.error_count = 0
def error(self, msg):
sys.stderr.write(f"Error: {msg}\n")
self.error_count += 1
def error_occurred(self):
return self.error_count > 0
def get_string_sub_type(field):
"""For the given manifest JSON field it returns its literal value type
mapped.
"""
if field == UUID:
return CONST_UUID
if field == START_PORT_NAME:
return CONST_PORT
# field with string value but doesn't support a constant
return None
def get_constant(constants, key, type_, log):
const = constants.get(key)
if const is None:
return None
if const.type != type_:
log.error(f"{key} constant type mismatch, expected type is {type_}")
return None
return const.value
def get_string(manifest_dict, key, constants, log, optional=False,
default=None):
"""Determines whether the value for the given key in dictionary is of type
string and if it is a string then returns the value.
"""
if key not in manifest_dict:
if not optional:
log.error(f"Manifest is missing required attribute - {key}")
return default
value = manifest_dict.pop(key)
# try to check is this field holding a constant
type_ = get_string_sub_type(key)
if type_:
const_value = get_constant(constants, value, type_, log)
if const_value is not None:
return const_value
return coerce_to_string(value, key, log)
def coerce_to_string(value, key, log):
if not isinstance(value, str):
log.error(
"Invalid value for" +
f" {key} - \"{value}\", Valid string value is expected")
return None
return value
def get_int(manifest_dict, key, constants, log, optional=False,
default=None):
"""Determines whether the value for the given key in dictionary is of type
integer and if it is int then returns the value
"""
if key not in manifest_dict:
if not optional:
log.error(f"Manifest is missing required attribute - {key}")
return default
value = manifest_dict.pop(key)
const_value = get_constant(constants, value, CONST_INT, log)
if const_value is not None:
return const_value
return coerce_to_int(value, key, log)
def coerce_to_int(value, key, log):
if isinstance(value, int) and not isinstance(value, bool):
return value
if isinstance(value, str):
try:
return int(value, 0)
except ValueError:
log.error(f"Invalid value for {key} - \"{value}\", " +
"valid integer or hex string is expected")
return None
else:
log.error("Invalid value for" +
f" {key} - \"{value}\", valid integer value is expected")
return None
def get_list(manifest_dict, key, log, optional=False, default=None):
"""Determines whether the value for the given key in dictionary is of type
List and if it is List then returns the value
"""
if key not in manifest_dict:
if not optional:
log.error(f"Manifest is missing required attribute - {key}")
return default
return coerce_to_list(manifest_dict.pop(key), key, log)
def coerce_to_list(value, key, log):
if not isinstance(value, list):
log.error("Invalid value for" +
f" {key} - \"{value}\", valid list is expected")
return None
return value
def get_dict(manifest_dict, key, log, optional=False, default=None):
"""Determines whether the value for the given key in dictionary is of type
Dictionary and if it is Dictionary then returns the value
"""
if key not in manifest_dict:
if not optional:
log.error(f"Manifest is missing required attribute - {key}")
return default
return coerce_to_dict(manifest_dict.pop(key), key, log)
def coerce_to_dict(value, key, log):
if not isinstance(value, dict):
log.error("Invalid value for" +
f" {key} - \"{value}\", valid dict is expected")
return None
return value
def get_boolean(manifest_dict, key, constants, log, optional=False,
default=None):
"""Determines whether the value for the given key in dictionary is of type
boolean and if it is boolean then returns the value
"""
if key not in manifest_dict:
if not optional:
log.error(f"Manifest is missing required attribute - {key}")
return default
value = manifest_dict.pop(key)
const_value = get_constant(constants, value, CONST_BOOL, log)
if const_value is not None:
return const_value
return coerce_to_boolean(value, key, log)
def coerce_to_boolean(value, key, log):
if not isinstance(value, bool):
log.error(
"Invalid value for" +
f" {key} - \"{value}\", Valid boolean value is expected")
return None
return value
def get_uuid(manifest_dict, key, constants, log, optional=False, default=None):
if key not in manifest_dict:
if not optional:
log.error(f"Manifest is missing required attribute - {key}")
return default
uuid = get_string(manifest_dict, key, {}, log, optional, default)
const_value = get_constant(constants, uuid, CONST_UUID, log)
if const_value is not None:
return const_value
return parse_uuid(uuid, log)
def get_port(port, key, constants, log, optional=False, default=None):
return get_string(port, key, constants, log, optional, default)
def parse_uuid(uuid, log):
"""Validate and arrange UUID byte order. If it is valid UUID then return 16
byte UUID
"""
if uuid is None:
return None
# Example UUID: "5f902ace-5e5c-4cd8-ae54-87b88c22ddaf"
if len(uuid) != 36:
log.error(f"Invalid UUID {uuid}. UUID should be 16 bytes long")
return None
uuid_data = uuid.split("-")
if len(uuid_data) != 5:
log.error(
f"Invalid UUID {uuid}. UUID should be 16 hexadecimal numbers"
" divided into 5 groups by hyphens (-)"
)
return None
try:
uuid_data = [bytearray.fromhex(part) for part in uuid_data]
except ValueError:
log.error(
f"Invalid UUID {uuid}. UUID should only contain hexadecimal"
" numbers (separated by hyphens)"
)
return None
if len(uuid_data[0]) != 4 or \
len(uuid_data[1]) != 2 or \
len(uuid_data[2]) != 2 or \
len(uuid_data[3]) != 2 or \
len(uuid_data[4]) != 6:
log.error(f"Wrong grouping of UUID {uuid}")
return None
return b"".join(uuid_data)
def parse_memory_size(memory_size, memory_kind, log, zero_is_ok=True):
"""Validate memory size value. If valid, return memory size value else
return None
"""
if memory_size is None:
return None
if memory_size == 0 and not zero_is_ok:
log.error(f"{memory_kind}: Minimum memory size cannot be zero.")
return None
if memory_size < 0 or memory_size % 4096 != 0:
log.error(f"{memory_kind}: {memory_size}, Minimum memory size should " +
"be a non-negative multiple of 4096")
return None
return memory_size
def parse_shadow_stack_size(stack_size, log):
"""Validate the shadow stack size
:returns: validated shadow stack size or None
"""
if stack_size is None:
return None
# shadow call stack is only supported on arm64 where pointers are 8 bytes
ptr_size = 8
if stack_size < 0 or stack_size % ptr_size != 0:
log.error(f"{MIN_SHADOW_STACK}: {stack_size}, Minimum shadow stack " +
"size should be a non-negative multiple of the native " +
"pointer size")
return None
return stack_size
def parse_mem_map_type(mem_map_type, log):
if mem_map_type not in {MEM_MAP_TYPE_CACHED,
MEM_MAP_TYPE_UNCACHED,
MEM_MAP_TYPE_UNCACHED_DEVICE}:
log.error(f"Unknown mem_map.type entry in manifest: {mem_map_type}")
return mem_map_type
def parse_mem_map(mem_maps, key, constants, log):
if mem_maps is None:
return None
mem_io_maps = []
for mem_map_entry in mem_maps:
mem_map_entry = coerce_to_dict(mem_map_entry, key, log)
if mem_map_entry is None:
continue
mem_map = MemIOMap(
get_int(mem_map_entry, MEM_MAP_ID, constants, log),
get_int(mem_map_entry, MEM_MAP_ADDR, constants, log),
get_int(mem_map_entry, MEM_MAP_SIZE, constants, log),
parse_mem_map_type(
get_string(mem_map_entry, MEM_MAP_TYPE, constants, log,
optional=True,
default=MEM_MAP_TYPE_UNCACHED_DEVICE), log),
get_boolean(mem_map_entry, MEM_MAP_NON_SECURE, constants, log,
optional=True)
)
if mem_map_entry:
log.error("Unknown attributes in mem_map entries in "
f"manifest: {mem_map_entry}")
mem_io_maps.append(mem_map)
return mem_io_maps
def parse_mgmt_flags(flags, constants, log):
if flags is None:
return None
mgmt_flags = MgmtFlags(
get_boolean(flags, MGMT_FLAG_RESTART_ON_EXIT, constants, log,
optional=True),
get_boolean(flags, MGMT_FLAG_DEFERRED_START, constants, log,
optional=True),
get_boolean(flags, MGMT_FLAG_NON_CRITICAL_APP, constants, log,
optional=True)
)
if flags:
log.error("Unknown attributes in mgmt_flags entries in " +
f"manifest: {flags}")
return mgmt_flags
def parse_apploader_flags(flags, constants, log):
if flags is None:
return None
apploader_flags = ApploaderFlags(
get_boolean(flags, APPLOADER_FLAGS_REQUIRES_ENCRYPTION, constants, log,
optional=True)
)
if flags:
log.error("Unknown attributes in apploader_flags entries in " +
f"manifest: {flags}")
return apploader_flags
def parse_app_start_ports(start_port_list, key, constants, log):
start_ports = []
for port_entry in start_port_list:
port_entry = coerce_to_dict(port_entry, key, log)
if port_entry is None:
continue
name = get_port(port_entry, START_PORT_NAME, constants, log)
if len(name) >= IPC_PORT_PATH_MAX:
log.error("Length of start port name should be less than " +
str(IPC_PORT_PATH_MAX))
flags = get_dict(port_entry, START_PORT_FLAGS, log)
start_ports_flag = None
if flags:
start_ports_flag = StartPortFlags(
get_boolean(flags, START_PORT_ALLOW_TA_CONNECT, constants,
log),
get_boolean(flags, START_PORT_ALLOW_NS_CONNECT, constants,
log))
if port_entry:
log.error("Unknown attributes in start_ports entries" +
f" in manifest: {port_entry}")
if flags:
log.error("Unknown attributes in start_ports.flags entries" +
f" in manifest: {flags}")
start_ports.append(StartPort(name, len(name), start_ports_flag))
return start_ports
def parse_app_name(app_name, log):
if app_name is None:
return None
if not app_name:
log.error("empty app-name is not allowed in manifest")
return None
return app_name.strip()
def parse_manifest_config(manifest_dict, constants, default_app_name, log):
"""validate the manifest config and extract key, values"""
# UUID
uuid = get_uuid(manifest_dict, UUID, constants, log)
# MIN_HEAP
min_heap = parse_memory_size(get_int(manifest_dict, MIN_HEAP, constants,
log), MIN_HEAP, log)
# MIN_STACK
min_stack = parse_memory_size(get_int(manifest_dict, MIN_STACK, constants,
log), MIN_STACK, log, False)
# MIN_SHADOW_STACK
min_shadow_stack = parse_shadow_stack_size(get_int(manifest_dict,
MIN_SHADOW_STACK,
constants, log,
optional=True), log)
# MEM_MAP
mem_io_maps = parse_mem_map(
get_list(manifest_dict, MEM_MAP, log, optional=True, default=[]),
MEM_MAP,
constants, log)
# MGMT_FLAGS
mgmt_flags = parse_mgmt_flags(
get_dict(manifest_dict, MGMT_FLAGS, log, optional=True,
default={
MGMT_FLAG_RESTART_ON_EXIT: False,
MGMT_FLAG_DEFERRED_START: False,
MGMT_FLAG_NON_CRITICAL_APP: False}),
constants, log)
# START_PORTS
start_ports = parse_app_start_ports(
get_list(manifest_dict, START_PORTS, log,
optional=True, default=[]),
START_PORTS,
constants,
log)
# APP_NAME
app_name = parse_app_name(
get_string(manifest_dict, APP_NAME, constants, log,
optional=True, default=default_app_name), log)
# PINNED_CPU
pinned_cpu = get_int(manifest_dict, PINNED_CPU, constants, log,
optional=True)
# PRIORITY
priority = get_int(manifest_dict, PRIORITY, constants, log, optional=True)
# VERSION
version = get_int(manifest_dict, VERSION, constants, log, optional=True)
# MIN_VERSION
min_version = get_int(manifest_dict, MIN_VERSION, constants, log, optional=True)
if min_version is not None:
if version is None:
log.error("'min_version' cannot be specified without 'version'")
elif version < min_version:
log.error("'version' cannot be less than 'min_version'")
# APPLOADER_FLAGS
apploader_flags = parse_apploader_flags(
get_dict(manifest_dict, APPLOADER_FLAGS, log, optional=True,
default={APPLOADER_FLAGS_REQUIRES_ENCRYPTION: False}),
constants, log)
# look for any extra attributes
if manifest_dict:
log.error(f"Unknown attributes in manifest: {manifest_dict} ")
if log.error_occurred():
return None
return Manifest(uuid, app_name, min_heap, min_stack, min_shadow_stack,
mem_io_maps, mgmt_flags, start_ports, pinned_cpu,
priority, version, min_version, apploader_flags)
def swap_uuid_bytes(uuid):
"""This script represents UUIDs in a purely big endian order.
Trusty stores the first three components of the UUID in little endian order.
Rearrange the byte order accordingly by doing inverse
on first three components of UUID
"""
return uuid[3::-1] + uuid[5:3:-1] + uuid[7:5:-1] + uuid[8:]
def pack_mem_map_arch_mmu_flags(mem_map):
arch_mmu_flags = 0
if mem_map.type == MEM_MAP_TYPE_CACHED:
arch_mmu_flags |= ARCH_MMU_FLAG_CACHED
elif mem_map.type == MEM_MAP_TYPE_UNCACHED:
arch_mmu_flags |= ARCH_MMU_FLAG_UNCACHED
elif mem_map.type == MEM_MAP_TYPE_UNCACHED_DEVICE:
arch_mmu_flags |= ARCH_MMU_FLAG_UNCACHED_DEVICE
if mem_map.non_secure:
arch_mmu_flags |= ARCH_MMU_FLAG_NS
return arch_mmu_flags
def pack_mgmt_flags(mgmt_flags):
flags = TRUSTY_APP_MGMT_FLAGS_NONE
if mgmt_flags.restart_on_exit:
flags |= TRUSTY_APP_MGMT_FLAGS_RESTART_ON_EXIT
if mgmt_flags.deferred_start:
flags |= TRUSTY_APP_MGMT_FLAGS_DEFERRED_START
if mgmt_flags.non_critical_app:
flags |= TRUSTY_APP_MGMT_FLAGS_NON_CRITICAL_APP
return flags
def pack_apploader_flags(apploader_flags):
flags = TRUSTY_APP_APPLOADER_FLAGS_NONE
if apploader_flags.requires_encryption:
flags |= TRUSTY_APP_APPLOADER_FLAGS_REQUIRES_ENCRYPTION
return flags
def pack_start_port_flags(flags):
start_port_flags = TRUSTY_APP_MGMT_FLAGS_NONE
if flags.allow_ta_connect:
start_port_flags |= IPC_PORT_ALLOW_TA_CONNECT
if flags.allow_ns_connect:
start_port_flags |= IPC_PORT_ALLOW_NS_CONNECT
return start_port_flags
def pack_inline_string(value):
"""Pack a given string with null padding to make its size
multiple of 4.
packed data includes length + string + null + padding
"""
size = len(value) + 1
pad_len = 3 - (size + 3) % 4
packed = struct.pack("I", size) + value.encode() + b"\0" + pad_len * b"\0"
assert len(packed) % 4 == 0
return packed
def pack_manifest_data(manifest):
"""Creates Packed data from extracted manifest data.
Writes the packed data to binary file
"""
# PACK {
# uuid, app_name_size, app_name,
# TRUSTY_APP_CONFIG_KEY_MIN_HEAP_SIZE, min_heap,
# TRUSTY_APP_CONFIG_KEY_MIN_STACK_SIZE, min_stack,
# TRUSTY_APP_CONFIG_KEY_MAP_MEM, id, addr, size,
# TRUSTY_APP_CONFIG_KEY_MGMT_FLAGS, mgmt_flags
# TRUSTY_APP_CONFIG_KEY_START_PORT, flag, name_size, name
# TRUSTY_APP_CONFIG_KEY_PINNED_CPU, pinned_cpu
# TRUSTY_APP_CONFIG_KEY_PRIORITY, priority
# TRUSTY_APP_CONFIG_KEY_VERSION, version
# TRUSTY_APP_CONFIG_KEY_MIN_VERSION, min_version
# TRUSTY_APP_CONFIG_KEY_MIN_SHADOW_STACK_SIZE, min_shadow_stack,
# TRUSTY_APP_CONFIG_KEY_APPLOADER_FLAGS, apploader_flags,
# }
out = io.BytesIO()
uuid = swap_uuid_bytes(manifest.uuid)
out.write(uuid)
out.write(pack_inline_string(manifest.app_name))
if manifest.min_heap is not None:
out.write(struct.pack("II", TRUSTY_APP_CONFIG_KEY_MIN_HEAP_SIZE,
manifest.min_heap))
if manifest.min_stack is not None:
out.write(struct.pack("II", TRUSTY_APP_CONFIG_KEY_MIN_STACK_SIZE,
manifest.min_stack))
for memio_map in manifest.mem_io_maps:
out.write(struct.pack("IIQQI",
TRUSTY_APP_CONFIG_KEY_MAP_MEM,
memio_map.id,
memio_map.addr,
memio_map.size,
pack_mem_map_arch_mmu_flags(memio_map)))
if manifest.mgmt_flags is not None:
out.write(struct.pack("II",
TRUSTY_APP_CONFIG_KEY_MGMT_FLAGS,
pack_mgmt_flags(manifest.mgmt_flags)))
for port_entry in manifest.start_ports:
out.write(struct.pack("II",
TRUSTY_APP_CONFIG_KEY_START_PORT,
pack_start_port_flags(
port_entry.start_port_flags)))
out.write(pack_inline_string(port_entry.name))
if manifest.pinned_cpu is not None:
out.write(struct.pack("II",
TRUSTY_APP_CONFIG_KEY_PINNED_CPU,
manifest.pinned_cpu))
if manifest.priority is not None:
out.write(struct.pack("II",
TRUSTY_APP_CONFIG_KEY_PRIORITY,
manifest.priority))
if manifest.version is not None:
out.write(struct.pack("II",
TRUSTY_APP_CONFIG_KEY_VERSION,
manifest.version))
if manifest.min_version is not None:
out.write(struct.pack("II",
TRUSTY_APP_CONFIG_KEY_MIN_VERSION,
manifest.min_version))
if manifest.min_shadow_stack is not None:
out.write(struct.pack("II",
TRUSTY_APP_CONFIG_KEY_MIN_SHADOW_STACK_SIZE,
manifest.min_shadow_stack))
if manifest.apploader_flags is not None:
out.write(struct.pack("II",
TRUSTY_APP_CONFIG_KEY_APPLOADER_FLAGS,
pack_apploader_flags(manifest.apploader_flags)))
return out.getvalue()
def unpack_binary_manifest_to_json(packed_data):
"""Creates manifest JSON string from packed manifest data"""
return manifest_data_to_json(unpack_binary_manifest_to_data(packed_data))
def manifest_data_to_json(manifest):
return json.dumps(manifest, sort_keys=True, indent=4)
def unpack_binary_manifest_to_data(packed_data):
"""This method can be used for extracting manifest data from packed binary.
UUID should be present in packed data.
"""
manifest = {}
# Extract UUID
uuid, packed_data = packed_data[:16], packed_data[16:]
uuid = swap_uuid_bytes(uuid)
uuid = uuid.hex()
uuid = uuid[:8] + "-" \
+ uuid[8:12] + "-" \
+ uuid[12:16] + "-" \
+ uuid[16:20] + "-" \
+ uuid[20:]
manifest[UUID] = uuid
# Extract APP_NAME
# read size of the name, this includes a null character
(name_size,), packed_data = struct.unpack(
"I", packed_data[:4]), packed_data[4:]
# read the name without a trailing null character
manifest[APP_NAME], packed_data = \
packed_data[:name_size - 1].decode(), packed_data[name_size - 1:]
# discard trailing null characters
# it includes trailing null character of a string and null padding
pad_len = 1 + 3 - (name_size + 3) % 4
packed_data = packed_data[pad_len:]
# Extract remaining app configurations
while len(packed_data) > 0:
(tag,), packed_data = struct.unpack(
"I", packed_data[:4]), packed_data[4:]
if tag == TRUSTY_APP_CONFIG_KEY_MIN_HEAP_SIZE:
assert MIN_HEAP not in manifest
(manifest[MIN_HEAP],), packed_data = struct.unpack(
"I", packed_data[:4]), packed_data[4:]
elif tag == TRUSTY_APP_CONFIG_KEY_MIN_STACK_SIZE:
assert MIN_STACK not in manifest
(manifest[MIN_STACK],), packed_data = struct.unpack(
"I", packed_data[:4]), packed_data[4:]
elif tag == TRUSTY_APP_CONFIG_KEY_MIN_SHADOW_STACK_SIZE:
assert MIN_SHADOW_STACK not in manifest
(manifest[MIN_SHADOW_STACK],), packed_data = struct.unpack(
"I", packed_data[:4]), packed_data[4:]
elif tag == TRUSTY_APP_CONFIG_KEY_MAP_MEM:
if MEM_MAP not in manifest:
manifest[MEM_MAP] = []
mem_map_entry = {}
(id_,), packed_data = struct.unpack(
"I", packed_data[:4]), packed_data[4:]
(addr,), packed_data = struct.unpack(
"Q", packed_data[:8]), packed_data[8:]
(size,), packed_data = struct.unpack(
"Q", packed_data[:8]), packed_data[8:]
(arch_mmu_flags,), packed_data = struct.unpack(
"I", packed_data[:4]), packed_data[4:]
mem_map_entry[MEM_MAP_ID] = id_
mem_map_entry[MEM_MAP_ADDR] = hex(addr)
mem_map_entry[MEM_MAP_SIZE] = hex(size)
mem_map_entry[MEM_MAP_TYPE] = {
ARCH_MMU_FLAG_CACHED: MEM_MAP_TYPE_CACHED,
ARCH_MMU_FLAG_UNCACHED: MEM_MAP_TYPE_UNCACHED,
ARCH_MMU_FLAG_UNCACHED_DEVICE: MEM_MAP_TYPE_UNCACHED_DEVICE,
}[arch_mmu_flags & ARCH_MMU_FLAG_CACHE_MASK]
mem_map_entry[MEM_MAP_NON_SECURE] = bool(arch_mmu_flags &
ARCH_MMU_FLAG_NS)
manifest[MEM_MAP].append(mem_map_entry)
elif tag == TRUSTY_APP_CONFIG_KEY_MGMT_FLAGS:
(flag,), packed_data = struct.unpack(
"I", packed_data[:4]), packed_data[4:]
mgmt_flag = {
MGMT_FLAG_RESTART_ON_EXIT: False,
MGMT_FLAG_DEFERRED_START: False,
MGMT_FLAG_NON_CRITICAL_APP: False
}
if flag & TRUSTY_APP_MGMT_FLAGS_RESTART_ON_EXIT:
mgmt_flag[MGMT_FLAG_RESTART_ON_EXIT] = True
if flag & TRUSTY_APP_MGMT_FLAGS_DEFERRED_START:
mgmt_flag[MGMT_FLAG_DEFERRED_START] = True
if flag & TRUSTY_APP_MGMT_FLAGS_NON_CRITICAL_APP:
mgmt_flag[MGMT_FLAG_NON_CRITICAL_APP] = True
manifest[MGMT_FLAGS] = mgmt_flag
elif tag == TRUSTY_APP_CONFIG_KEY_START_PORT:
if START_PORTS not in manifest:
manifest[START_PORTS] = []
start_port_entry = {}
(flag,), packed_data = struct.unpack(
"I", packed_data[:4]), packed_data[4:]
# read size of the name, this includes a null character
(name_size,), packed_data = struct.unpack(
"I", packed_data[:4]), packed_data[4:]
# read the name without a trailing null character
start_port_entry[START_PORT_NAME], packed_data = (
packed_data[:name_size - 1].decode(),
packed_data[name_size - 1:]
)
# discard trailing null characters
# it includes trailing null character of a string and null padding
pad_len = 1 + 3 - (name_size + 3) % 4
packed_data = packed_data[pad_len:]
start_port_flags = {
START_PORT_ALLOW_TA_CONNECT: False,
START_PORT_ALLOW_NS_CONNECT: False
}
if flag & IPC_PORT_ALLOW_TA_CONNECT:
start_port_flags[START_PORT_ALLOW_TA_CONNECT] = True
if flag & IPC_PORT_ALLOW_NS_CONNECT:
start_port_flags[IPC_PORT_ALLOW_NS_CONNECT] = True
start_port_entry[START_PORT_FLAGS] = start_port_flags
manifest[START_PORTS].append(start_port_entry)
elif tag == TRUSTY_APP_CONFIG_KEY_PINNED_CPU:
assert PINNED_CPU not in manifest
(pinned_cpu,), packed_data = struct.unpack(
"I", packed_data[:4]), packed_data[4:]
manifest[PINNED_CPU] = pinned_cpu
elif tag == TRUSTY_APP_CONFIG_KEY_PRIORITY:
assert PRIORITY not in manifest
(priority,), packed_data = struct.unpack(
"I", packed_data[:4]), packed_data[4:]
manifest[PRIORITY] = priority
elif tag == TRUSTY_APP_CONFIG_KEY_VERSION:
assert VERSION not in manifest
(version,), packed_data = struct.unpack(
"I", packed_data[:4]), packed_data[4:]
manifest[VERSION] = version
elif tag == TRUSTY_APP_CONFIG_KEY_MIN_VERSION:
assert MIN_VERSION not in manifest
(min_version,), packed_data = struct.unpack(
"I", packed_data[:4]), packed_data[4:]
manifest[MIN_VERSION] = min_version
elif tag == TRUSTY_APP_CONFIG_KEY_APPLOADER_FLAGS:
assert APPLOADER_FLAGS not in manifest
(flag,), packed_data = struct.unpack(
"I", packed_data[:4]), packed_data[4:]
apploader_flag = {
APPLOADER_FLAGS_REQUIRES_ENCRYPTION: False,
}
if flag & TRUSTY_APP_APPLOADER_FLAGS_REQUIRES_ENCRYPTION:
apploader_flag[APPLOADER_FLAGS_REQUIRES_ENCRYPTION] = True
manifest[APPLOADER_FLAGS] = apploader_flag
else:
raise Exception(f"Unknown tag: {tag}")
return manifest
def write_packed_data_to_bin_file(packed_data, output_file, log):
"""Write packed data to binary file"""
try:
with open(output_file, "wb") as out_file:
out_file.write(packed_data)
out_file.close()
except IOError as ex:
log.error(f"Unable to write to output file: {output_file}\n" + str(ex))
def read_json_config_file(input_file, log):
try:
with open(input_file, "r", encoding="utf-8") as read_file:
manifest_dict = json.load(read_file)
return manifest_dict
except IOError as ex:
log.error(f"{input_file}: unable to open input file: {ex}")
return None
except json.JSONDecodeError as jde:
location = f"{input_file}:{jde.lineno}:{jde.colno}"
log.error(f"{location}: Unable to parse config JSON: {jde.msg}")
return None
except ValueError as ex:
log.error(f"{input_file}: Unexpected error: {ex}")
return None
def read_config_constants(const_config_files, log):
const_configs_list = []
for const_file in const_config_files:
const_configs_list.append(read_json_config_file(const_file, log))
return const_configs_list
def define_integer_const_entry(const):
text = hex(const.value) if const.hex_num else str(const.value)
if const.unsigned:
text += "U"
return f"#define {const.name} ({text})\n"
def define_string_const_entry(const):
return f"#define {const.name} {json.dumps(const.value)}\n"
def define_bool_const_entry(const):
return f"#define {const.name} ({json.dumps(const.value)})\n"
def define_uuid_const_entry(const):
uuid = const.value.hex()
part = ", ".join(
["0x" + uuid[index:index + 2] for index in range(16, len(uuid), 2)])
value = f"{{0x{uuid[:8]}, 0x{uuid[8:12]}, 0x{uuid[12:16]}, {{ {part} }}}}\n"
return f"#define {const.name} {value}"
def create_header_entry(constant):
if constant.type == CONST_PORT:
return define_string_const_entry(constant)
if constant.type == CONST_UUID:
return define_uuid_const_entry(constant)
if constant.type == CONST_INT:
return define_integer_const_entry(constant)
if constant.type == CONST_BOOL:
return define_bool_const_entry(constant)
raise Exception(f"Unknown tag: {constant.type}")
def write_consts_to_header_file(const_config, header_dir, log):
"""Writes given constants to header file in given header directory."""
# Construct header file path
header_file = os.path.join(header_dir, const_config.header)
# Check whether the output directory of header file exist
# If it not exists create it.
dir_name = os.path.dirname(header_file)
if dir_name and not os.path.exists(dir_name):
os.makedirs(dir_name)
try:
with open(header_file, "w", encoding="utf-8") as out_file:
out_file.write("#pragma once\n")
out_file.write("#include <stdbool.h>\n\n")
for const in const_config.constants:
header_entries = create_header_entry(const)
out_file.write(header_entries)
except IOError as ex:
log.error(f"Unable to write to header file: {header_file}\n" + str(ex))
def parse_constant(constant, log):
"""Parse a give JSON constant data structure"""
const_type = get_string(constant, CONST_TYPE, {}, log)
if const_type is None:
return None
name = get_string(constant, CONST_NAME, {}, log)
if const_type == CONST_PORT:
value = get_string(constant, CONST_VALUE, {}, log)
return Constant(name, value, const_type)
if const_type == CONST_UUID:
value = get_string(constant, CONST_VALUE, {}, log)
return Constant(name, parse_uuid(value, log), const_type)
if const_type == CONST_INT:
unsigned = get_boolean(constant, CONST_UNSIGNED, {}, log)
text_value = constant.get(CONST_VALUE)
hex_num = isinstance(text_value, str) and text_value.startswith("0x")
value = get_int(constant, CONST_VALUE, {}, log)
return Constant(name, value, const_type, unsigned, hex_num)
if const_type == CONST_BOOL:
value = get_boolean(constant, CONST_VALUE, {}, log)
return Constant(name, value, const_type)
log.error(f"Unknown constant type: {const_type}")
return None
def parse_config_constant(const_config, log):
"""Parse a given JSON constant-config data structure containing a header and
list of constants
"""
header_file = get_string(const_config, HEADER, {}, log)
const_list = get_list(const_config, CONSTANTS, log, optional=False,
default=[])
constants = []
for item in const_list:
item = coerce_to_dict(item, CONSTANTS, log)
if item is None:
continue
constants.append(parse_constant(item, log))
if item:
log.error("Unknown attributes in constant: {item}")
if const_config:
log.error(f"Unknown attributes in constants config: {const_config}")
return ConfigConstants(constants, header_file)
def extract_config_constants(config_consts_list, log):
"""Collects ConfigConstant(s) from list of JSON config constants data"""
config_constants = []
for config_const in config_consts_list:
config_constants.append(parse_config_constant(config_const, log))
return config_constants
def process_config_constants(const_config_files, header_dir, log):
"""Parse JSON config constants and creates separate header files with
constants for each JSON config
"""
if const_config_files is None:
return []
config_consts_list = read_config_constants(const_config_files, log)
if log.error_occurred():
return []
config_constants = extract_config_constants(config_consts_list, log)
if log.error_occurred():
return []
# generate header files
for const_config in config_constants:
write_consts_to_header_file(const_config, header_dir, log)
return config_constants
def index_constants(config_constants):
constants = {}
for const_config in config_constants:
for const in const_config.constants:
constants[const.name] = const
return constants
def main():
"""Handles the command line arguments. Parses the given manifest input file
and creates packed data. Writes the packed data to binary output file.
"""
parser = argparse.ArgumentParser()
parser.add_argument(
"-i", "--input",
dest="input_filename",
required=False,
type=str,
help="It should be trusty app manifest config JSON file"
)
parser.add_argument(
"-o", "--output",
dest="output_filename",
required=False,
type=str,
help="It will be binary file with packed manifest data"
)
parser.add_argument(
"-c", "--constants",
dest="constants",
required=False,
action="append",
help="JSON file with manifest config constants"
)
parser.add_argument(
"--header-dir",
dest="header_dir",
required=False,
type=str,
help="Directory path for generating headers"
)
parser.add_argument(
"--enable-shadow-call-stack",
dest="shadow_call_stack",
required=False,
action="store_true", # implies default := False
help="Allow apps to opt into having a shadow call stack. "
"Without this flag, apps will not have shadow stacks "
"even if their manifests define \"min_shadow_stack\"."
)
parser.add_argument(
"--default-shadow-call-stack-size",
dest="default_shadow_call_stack_size",
required=False,
default=4096,
type=int,
metavar="DEFAULT_SIZE",
help="Controls the size of the default shadow call stack."
"This option has no effect unless shadow call stacks "
"are enabled via the --enable-shadow-call-stack flag."
)
# Parse the command line arguments
args = parser.parse_args()
if args.constants and not args.header_dir:
parser.error("--header-dir is required if --constants are specified")
if args.input_filename and not args.output_filename:
parser.error("Input file provided with no manifest output file.")
if args.output_filename and not args.input_filename:
parser.error("Building a manifest output file requires an input file.")
if args.default_shadow_call_stack_size <= 0:
parser.error(
"--default-shadow-call-stack-size expects a positive integer")
log = Log()
# collect config constants and create header files for each const config
config_constants = process_config_constants(args.constants,
args.header_dir, log)
if log.error_occurred():
return 1
if not args.output_filename:
return 0
constants = index_constants(config_constants)
if not os.path.exists(args.input_filename):
log.error(
f"Manifest config JSON file doesn't exist: {args.input_filename}")
return 1
manifest_dict = read_json_config_file(args.input_filename, log)
if log.error_occurred():
return 1
# By default, app directory name will be used as app-name
default_app_name = os.path.basename(os.path.dirname(args.input_filename))
# parse the manifest config
manifest = parse_manifest_config(manifest_dict, constants,
default_app_name, log)
if log.error_occurred():
return 1
# Optionally adjust min_shadow_stack based on command line arguments
if args.shadow_call_stack:
# If shadow callstack is enabled but the size is not specified in the
# manifest, set it to the default value.
if manifest.min_shadow_stack is None:
manifest.min_shadow_stack = args.default_shadow_call_stack_size
else:
# If shadow call stack is not enabled, make sure the size is set to
# zero in the binary manifest. In the future, "not present" may
# indicate the binary does not use a shadow callstack, but for now
# we're making sure a value is always present.
manifest.min_shadow_stack = 0
assert (args.shadow_call_stack and manifest.min_shadow_stack > 0) != \
(manifest.min_shadow_stack == 0)
# Pack the data as per C structures
packed_data = pack_manifest_data(manifest)
if log.error_occurred():
return 1
# Write to file.
write_packed_data_to_bin_file(packed_data, args.output_filename, log)
if log.error_occurred():
return 1
return 0
if __name__ == "__main__":
sys.exit(main())