// SPDX-License-Identifier: Apache-2.0 #include "app/manifest.h" #include #include #include #include #include #include constexpr auto* TAG = "app_metadata_v3"; namespace { // Numeric index out of an "app.." key, or nullopt if it doesn't match that shape. std::optional parse_app_key_index(const std::string& key) { constexpr auto* prefix = "app."; constexpr size_t prefix_len = 4; if (!key.starts_with(prefix)) { return std::nullopt; } auto dot = key.find('.', prefix_len); if (dot == std::string::npos || dot == prefix_len) { return std::nullopt; } size_t index = 0; const auto* first = key.data() + prefix_len; const auto* last = key.data() + dot; auto result = std::from_chars(first, last, index); if (result.ec != std::errc {} || result.ptr != last) { return std::nullopt; } return index; } // Parses one "app..*" block into @a out_binding. error_t parse_app_manifest(const std::map& properties, size_t index, AppManifestBinding& out_binding) { auto prefix = std::format("app.{}.", index); AppManifest& out_manifest = out_binding.manifest; std::string id; if (!app_package_manifest_get_value(properties, prefix + "id", id)) { return ERROR_INVALID_ARGUMENT; } if (!app_manifest_id_is_valid(id.c_str())) { LOG_E(TAG, "Invalid %sid", prefix.c_str()); return ERROR_INVALID_ARGUMENT; } if (!app_package_manifest_copy_bounded(out_manifest.id, sizeof(out_manifest.id), id)) { LOG_E(TAG, "%sid too long", prefix.c_str()); return ERROR_INVALID_ARGUMENT; } std::string binary; if (!app_package_manifest_get_value(properties, prefix + "binary", binary)) { return ERROR_INVALID_ARGUMENT; } if (!app_package_manifest_is_valid_binary_name(binary)) { LOG_E(TAG, "Invalid %sbinary", prefix.c_str()); return ERROR_INVALID_ARGUMENT; } // Filename (without extension) this app installs as under bin// - see // app_package_manifest_parse()'s own doc. if (!app_package_manifest_copy_bounded(out_binding.binary, sizeof(out_binding.binary), binary)) { LOG_E(TAG, "%sbinary too long", prefix.c_str()); return ERROR_INVALID_ARGUMENT; } std::string name; if (!app_package_manifest_get_value(properties, prefix + "name", name)) { return ERROR_INVALID_ARGUMENT; } if (!app_manifest_name_is_valid(name.c_str())) { LOG_E(TAG, "Invalid %sname", prefix.c_str()); return ERROR_INVALID_ARGUMENT; } if (!app_package_manifest_copy_bounded(out_manifest.name, sizeof(out_manifest.name), name)) { LOG_E(TAG, "%sname too long", prefix.c_str()); return ERROR_INVALID_ARGUMENT; } // .stack.depth (optional; if present, must be a valid unsigned decimal fitting uint32_t) auto stack_size_iterator = properties.find(prefix + "stack.depth"); if (stack_size_iterator != properties.end()) { const std::string& stack_size_string = stack_size_iterator->second; if (!app_manifest_stack_size_is_valid(stack_size_string.c_str())) { LOG_E(TAG, "Invalid %sstack.depth", prefix.c_str()); return ERROR_INVALID_ARGUMENT; } uint32_t stack_size = 0; const auto* first = stack_size_string.data(); const auto* last = first + stack_size_string.size(); if (std::from_chars(first, last, stack_size).ec != std::errc {}) { LOG_E(TAG, "%sstack.depth out of range", prefix.c_str()); return ERROR_INVALID_ARGUMENT; } // Reject outright rather than truncating/clamping into AppStackConfig::depth (uint16_t) - // a value like 1073741825 would otherwise silently narrow to 1, handing the app a // catastrophically undersized stack instead of the huge one it declared. if (stack_size > APP_STACK_SIZE_MAX) { LOG_E(TAG, "%sstack.depth %u exceeds APP_STACK_SIZE_MAX(%u)", prefix.c_str(), stack_size, APP_STACK_SIZE_MAX); return ERROR_INVALID_ARGUMENT; } out_manifest.stack.depth = static_cast(stack_size); } // .hidden (optional; defaults to false) auto hidden_iterator = properties.find(prefix + "hidden"); if (hidden_iterator != properties.end()) { if (!app_package_manifest_is_valid_bool(hidden_iterator->second)) { LOG_E(TAG, "Invalid %shidden", prefix.c_str()); return ERROR_INVALID_ARGUMENT; } out_manifest.flags = hidden_iterator->second == "true" ? APP_MANIFEST_FLAG_HIDDEN : 0; } out_manifest.category = APP_CATEGORY_USER; return ERROR_NONE; } } // namespace error_t package_manifest_parse_v3(const std::map& properties, PackageManifest& out_package, AppManifestBinding* out_bindings, size_t bindings_capacity) { // package-level fields: bare keys, no "app." prefix (that's reserved for the app..* // blocks below). std::string id; if (!app_package_manifest_get_value(properties, "id", id)) { return ERROR_INVALID_ARGUMENT; } if (!app_manifest_id_is_valid(id.c_str())) { LOG_E(TAG, "Invalid id"); return ERROR_INVALID_ARGUMENT; } if (!app_package_manifest_copy_bounded(out_package.id, sizeof(out_package.id), id)) { LOG_E(TAG, "id too long"); return ERROR_INVALID_ARGUMENT; } std::string version_name; if (!app_package_manifest_get_value(properties, "version.name", version_name)) { return ERROR_INVALID_ARGUMENT; } if (!app_package_manifest_is_valid_version_name(version_name)) { LOG_E(TAG, "Invalid version.name"); return ERROR_INVALID_ARGUMENT; } if (!app_package_manifest_copy_bounded(out_package.version_name, sizeof(out_package.version_name), version_name)) { LOG_E(TAG, "version.name too long"); return ERROR_INVALID_ARGUMENT; } std::string version_code_string; if (!app_package_manifest_get_value(properties, "version.code", version_code_string)) { return ERROR_INVALID_ARGUMENT; } if (!app_package_manifest_is_valid_version_code(version_code_string)) { LOG_E(TAG, "Invalid version.code"); return ERROR_INVALID_ARGUMENT; } uint64_t version_code = 0; const auto* first = version_code_string.data(); const auto* last = first + version_code_string.size(); if (std::from_chars(first, last, version_code).ec != std::errc {}) { LOG_E(TAG, "version.code out of range"); return ERROR_INVALID_ARGUMENT; } out_package.version_code = version_code; std::string target_sdk; if (!app_package_manifest_get_value(properties, "target.sdk", target_sdk)) { return ERROR_INVALID_ARGUMENT; } if (!app_package_manifest_copy_bounded(out_package.target_sdk, sizeof(out_package.target_sdk), target_sdk)) { LOG_E(TAG, "target.sdk too long"); return ERROR_INVALID_ARGUMENT; } auto device_id_iterator = properties.find("requires.device.id"); if (device_id_iterator != properties.end()) { const std::string& device_id = device_id_iterator->second; if (!app_package_manifest_is_valid_device_id_list(device_id)) { LOG_E(TAG, "Invalid requires.device.id"); return ERROR_INVALID_ARGUMENT; } if (!app_package_manifest_copy_bounded(out_package.requires_device_id, sizeof(out_package.requires_device_id), device_id)) { LOG_E(TAG, "requires.device.id too long"); return ERROR_INVALID_ARGUMENT; } } // app..* blocks: 0-indexed, contiguous - the first missing "app..id" ends the list. size_t count = 0; while (properties.contains(std::format("app.{}.id", count))) { count++; // Bounded here, not just against the caller's bindings_capacity below: a caller that // sizes its own buffer off PackageManifest::app_manifest_count (see // app_package_manifest_parse_into()) would otherwise let a malicious/malformed manifest // demand an unbounded allocation. if (count > PACKAGE_MANIFEST_MAX_APP_MANIFESTS) { LOG_E(TAG, "Manifest declares more than %d apps", PACKAGE_MANIFEST_MAX_APP_MANIFESTS); return ERROR_BUFFER_OVERFLOW; } } out_package.app_manifest_count = static_cast(count); // Catch a gap the count loop above would otherwise silently drop, e.g. app.0.* + app.3.*. for (const auto& [key, value] : properties) { auto index = parse_app_key_index(key); if (index.has_value() && *index >= count) { LOG_E(TAG, "Manifest declares %s but only %zu contiguous app(s) starting at app.0 were found", key.c_str(), count); return ERROR_INVALID_ARGUMENT; } } if (count == 0 || bindings_capacity == 0) { return ERROR_NONE; } if (count > bindings_capacity) { LOG_E(TAG, "Manifest declares %zu apps, capacity is %zu", count, bindings_capacity); return ERROR_BUFFER_OVERFLOW; } for (size_t i = 0; i < count; i++) { error_t result = parse_app_manifest(properties, i, out_bindings[i]); if (result != ERROR_NONE) { return result; } } return ERROR_NONE; }