#!/bin/sh """:"# Shell script (in docstring to appease pylint) # Find and invoke hermetic python3 interpreter
. "`dirname $0`/../../../vendor/google/aosp/scripts/envsetup.sh"
exec "$PY3""$0""$@" # Shell script end
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.
Arguments:
input_filename - Trusted app manifest config file in JSON format.
At least one manifest config should be provided.
Options from additional configs override scalars and merge to lists of the main manifest config,
processing manifests in the order specified on the
command line.
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.
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))
# 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
TRUSTY_APP_CONFIG_KEY_AUXVAL = 12
# MEM_MAP ARCH_MMU_FLAGS # These values need to be kept in sync with $LKROOT/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
# AUXVALS # These values need to be kept in sync with external/trusty/musl/include/elf.h # and also their internal kernel definitions in # trusty/kernel/lib/trusty/trusty_app.c
TRUSTY_AT_INSTANCE_ID_PTR = 1000001
TRUSTY_AT_SELF_PEER_IDS = 1000003
# 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
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 returnNone
if const.type != type_:
log.error(f"{key} constant type mismatch, expected type is {type_}") returnNone
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 andif it is a string then returns the value. """ if key notin manifest_dict: ifnot 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 isnotNone: return const_value
return coerce_to_string(value, key, log)
def coerce_to_string(value, key, log): ifnot isinstance(value, str):
log.error( "Invalid value for" +
f" {key} - \"{value}\", Valid string value is expected") returnNone
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 andif it is int then returns the value """ if key notin manifest_dict: ifnot 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 isnotNone: return const_value
return coerce_to_int(value, key, log)
def coerce_to_int(value, key, log): if isinstance(value, int) andnot 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") returnNone else:
log.error("Invalid value for" +
f" {key} - \"{value}\", valid integer value is expected") returnNone
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 andif it is List then returns the value """ if key notin manifest_dict: ifnot optional:
log.error(f"Manifest is missing required attribute - {key}") return default
def coerce_to_list(value, key, log): ifnot isinstance(value, list):
log.error("Invalid value for" +
f" {key} - \"{value}\", valid list is expected") returnNone
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 andif it is Dictionary then returns the value """ if key notin manifest_dict: ifnot optional:
log.error(f"Manifest is missing required attribute - {key}") return default
def coerce_to_dict(value, key, log): ifnot isinstance(value, dict):
log.error("Invalid value for" +
f" {key} - \"{value}\", valid dict is expected") returnNone
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 andif it is boolean then returns the value """ if key notin manifest_dict: ifnot 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 isnotNone: return const_value
return coerce_to_boolean(value, key, log)
def coerce_to_boolean(value, key, log): ifnot isinstance(value, bool):
log.error( "Invalid value for" +
f" {key} - \"{value}\", Valid boolean value is expected") returnNone
def parse_uuid(uuid, log): """Validate and arrange UUID byte order. If it is valid UUID then return 16
byte UUID """ if uuid isNone: returnNone
# Example UUID: "5f902ace-5e5c-4cd8-ae54-87b88c22ddaf" if len(uuid) != 36:
log.error(f"Invalid UUID {uuid}. UUID should be 16 bytes long") returnNone
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 (-)"
) returnNone
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)"
) returnNone
# shadow call stack is only supported on arm64 where pointers are 8 bytes
ptr_size = 8 if stack_size < 0or 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") returnNone
return stack_size
def parse_mem_map_type(mem_map_type, log): if mem_map_type notin {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 isNone: returnNone
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 isNone: 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}") if any(item.id == mem_map.id for item in mem_io_maps):
log.error(f"Duplicate mem_map ID found: {mem_map.id}")
mem_io_maps.append(mem_map)
return mem_io_maps
def parse_mgmt_flags(flags, constants, log): if flags isNone: returnNone
if flags:
log.error("Unknown attributes in mgmt_flags entries in " +
f"manifest: {flags}")
return mgmt_flags
def parse_auxvals(lst, constants, log): if lst isNone: returnNone
auxvals = [] for x in lst: if x == AUXVALS_INSTANCE_ID:
auxvals.append(TRUSTY_AT_INSTANCE_ID_PTR) elif x == AUXVALS_SELF_PEER_IDS:
auxvals.append(TRUSTY_AT_SELF_PEER_IDS) else:
log.error(f"Unknown auxval entry in manifest: {x}")
return auxvals
def parse_apploader_flags(flags, constants, log): if flags isNone: returnNone
for port_entry in start_port_list:
port_entry = coerce_to_dict(port_entry, key, log) if port_entry isNone: 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))
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}")
if min_version isnotNone: if version isNone:
log.error("'min_version' cannot be specified without 'version'") elif version < min_version:
log.error("'version' cannot be less than 'min_version'")
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:]
if manifest.min_heap isnotNone:
out.write(struct.pack("II", TRUSTY_APP_CONFIG_KEY_MIN_HEAP_SIZE,
manifest.min_heap))
if manifest.min_stack isnotNone:
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 isnotNone:
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 isnotNone:
out.write(struct.pack("II",
TRUSTY_APP_CONFIG_KEY_PINNED_CPU,
manifest.pinned_cpu))
if manifest.priority isnotNone:
out.write(struct.pack("II",
TRUSTY_APP_CONFIG_KEY_PRIORITY,
manifest.priority))
if manifest.version isnotNone:
out.write(struct.pack("II",
TRUSTY_APP_CONFIG_KEY_VERSION,
manifest.version))
if manifest.min_version isnotNone:
out.write(struct.pack("II",
TRUSTY_APP_CONFIG_KEY_MIN_VERSION,
manifest.min_version))
if manifest.min_shadow_stack isnotNone:
out.write(struct.pack("II",
TRUSTY_APP_CONFIG_KEY_MIN_SHADOW_STACK_SIZE,
manifest.min_shadow_stack))
if manifest.apploader_flags isnotNone:
out.write(struct.pack("II",
TRUSTY_APP_CONFIG_KEY_APPLOADER_FLAGS,
pack_apploader_flags(manifest.apploader_flags)))
if manifest.auxvals isnotNone: for auxval in manifest.auxvals:
out.write(struct.pack("II",
TRUSTY_APP_CONFIG_KEY_AUXVAL,
auxval))
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 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:]
# 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
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}") returnNone except json.JSONDecodeError as jde:
location = f"{input_file}:{jde.lineno}:{jde.colno}"
log.error(f"{location}: Unable to parse config JSON: {jde.msg}") returnNone except ValueError as ex:
log.error(f"{input_file}: Unexpected error: {ex}") returnNone
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"
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) if constant.type == CONST_ID: return define_identifier_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 andnot 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 isNone: returnNone
name = get_string(constant, CONST_NAME, {}, log) if const_type == CONST_PORT or const_type == CONST_ID:
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)
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)
constants = [] for item in const_list:
item = coerce_to_dict(item, CONSTANTS, log) if item isNone: 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 isNone: 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 merge_manifest_dicts(manifests: list, log): """Merges multiple manifests """ def merge(base, overlay, log):
match base, overlay:
case dict(), dict():
common_keys = base.keys() & overlay.keys()
res = {k: merge(base[k], overlay[k], log) for k in common_keys}
res |= {k: base[k] for k in base.keys() - common_keys}
res |= {k: overlay[k] for k in overlay.keys() - common_keys} return res
case list(), list():
res = base.copy()
res.extend(i for i in overlay if i notin base) return res
case int() | float() | str(), int() | float() | str(): return overlay # overlay overrides base for scalars
case _:
log.error(
f"Unhandled type pair: {type(base)} and {type(overlay)}") return base
manifest_dict_merged : dict = {}
for manifest in manifests:
manifest_dict_merged = merge(manifest_dict_merged, manifest, log)
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_filenames",
required=False,
action="append",
type=str,
help="Trusty app manifest in JSON format. " "If the flag is used to provide multiple input files, " "subsequent files overwrite the values provided in " "the first manifest 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 andnot args.header_dir:
parser.error("--header-dir is required if --constants are specified")
if args.input_filenames andnot args.output_filename:
parser.error("Input file provided with no manifest output file.")
if args.output_filename andnot args.input_filenames:
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(): return1
# 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 isNone:
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
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.