diff --git a/examples/htool.c b/examples/htool.c index 8053d13..c2399df 100644 --- a/examples/htool.c +++ b/examples/htool.c @@ -996,6 +996,23 @@ static const struct htool_cmd CMDS[] = { {}}, .func = htool_payload_read, }, + { + .verbs = (const char*[]){"payload", "info", "all", NULL}, + .desc = "Display detailed payload info for a Titan image, " + "including region details.", + .params = + (const struct htool_param[]){ + {HTOOL_POSITIONAL, .name = "source-file"}, {}}, + .func = htool_payload_info_all, + }, + { + .verbs = (const char*[]){"payload", "info", "nonstatic", NULL}, + .desc = "Print non-static regions in a Titan image.", + .params = + (const struct htool_param[]){ + {HTOOL_POSITIONAL, .name = "source-file"}, {}}, + .func = htool_payload_info_nonstatic, + }, { .verbs = (const char*[]){"payload", "info", NULL}, .desc = "Display payload info for a Titan image.", diff --git a/examples/htool_payload.c b/examples/htool_payload.c index 684dc96..3a853c3 100644 --- a/examples/htool_payload.c +++ b/examples/htool_payload.c @@ -16,12 +16,14 @@ #include #include +#include #include #include #include #include #include #include +#include #include #include "host_commands.h" @@ -29,6 +31,117 @@ #include "protocol/payload_info.h" #include "protocol/payload_status.h" +static void print_region_attributes(uint16_t attributes) { + static const struct { + uint16_t mask; + const char* name; + } flags[] = { + {IMAGE_REGION_STATIC, "STATIC"}, + {IMAGE_REGION_COMPRESSED, "COMPRESSED"}, + {IMAGE_REGION_WRITE_PROTECTED, "WRITE_PROTECTED"}, + {IMAGE_REGION_PERSISTENT, "PERSISTENT"}, + {IMAGE_REGION_PERSISTENT_RELOCATABLE, "PERSISTENT_RELOCATABLE"}, + {IMAGE_REGION_PERSISTENT_EXPANDABLE, "PERSISTENT_EXPANDABLE"}, + {IMAGE_REGION_OVERRIDE, "OVERRIDE"}, + {IMAGE_REGION_OVERRIDE_ON_TRANSITION, "OVERRIDE_ON_TRANSITION"}, + {IMAGE_REGION_MAILBOX, "MAILBOX"}, + {IMAGE_REGION_SKIP_BOOT_VALIDATION, "SKIP_BOOT_VALIDATION"}, + {IMAGE_REGION_EMPTY, "EMPTY"}, + }; + + printf("0x%04x", attributes); + bool first = true; + for (size_t i = 0; i < sizeof(flags) / sizeof(flags[0]); i++) { + if (attributes & flags[i].mask) { + printf("%s%s", first ? " (" : " | ", flags[i].name); + first = false; + } + } + if (!first) { + printf(")"); + } + printf("\n"); +} + +static void print_regions(const struct payload_info_all* info_all, + uint16_t skip_mask) { + for (uint8_t i = 0; i < info_all->region_count; i++) { + const struct payload_region_info* r = &info_all->regions[i]; + if (r->region_attributes & skip_mask) { + continue; + } + printf(" Region %u:\n", i); + printf(" name: %s\n", r->region_name); + printf(" offset: 0x%08x\n", r->region_offset); + printf(" size: 0x%08x\n", r->region_size); + printf(" version: %u\n", r->region_version); + printf(" attributes: "); + print_region_attributes(r->region_attributes); + } +} + +static const char* hash_type_string(uint8_t hash_type) { + switch (hash_type) { + case HASH_NONE: + return "None"; + case HASH_SHA2_256: + return "SHA2-256"; + default: + return "Unknown"; + } +} + +struct htool_payload_image { + uint8_t* image; + size_t size; + int fd; +}; + +static int htool_payload_image_open(const struct htool_invocation* inv, + struct htool_payload_image* img) { + const char* image_file; + if (htool_get_param_string(inv, "source-file", &image_file) != 0) { + return -1; + } + img->fd = open(image_file, O_RDONLY, 0); + if (img->fd == -1) { + fprintf(stderr, "Error opening file %s: %s\n", image_file, strerror(errno)); + return -1; + } + struct stat statbuf; + if (fstat(img->fd, &statbuf)) { + fprintf(stderr, "fstat error: %s\n", strerror(errno)); + close(img->fd); + return -1; + } + if (statbuf.st_size > SIZE_MAX) { + fprintf(stderr, "file too large\n"); + close(img->fd); + return -1; + } + img->size = (size_t)statbuf.st_size; + img->image = mmap(NULL, img->size, PROT_READ, MAP_PRIVATE, img->fd, 0); + if (img->image == MAP_FAILED) { + fprintf(stderr, "mmap error: %s\n", strerror(errno)); + close(img->fd); + return -1; + } + return 0; +} + +static int htool_payload_image_close(struct htool_payload_image* img) { + int rv = 0; + if (munmap(img->image, img->size) != 0) { + fprintf(stderr, "munmap error: %s\n", strerror(errno)); + rv = -1; + } + if (close(img->fd) != 0) { + fprintf(stderr, "close error: %s\n", strerror(errno)); + rv = -1; + } + return rv; +} + int htool_payload_status(const struct htool_invocation* inv) { (void)inv; struct libhoth_device* dev = htool_libhoth_device(); @@ -79,36 +192,15 @@ int htool_payload_status(const struct htool_invocation* inv) { } int htool_payload_info(const struct htool_invocation* inv) { - const char* image_file; - if (htool_get_param_string(inv, "source-file", &image_file) != 0) { - return -1; - } - - int fd = open(image_file, O_RDONLY, 0); - if (fd == -1) { - fprintf(stderr, "Error opening file %s: %s\n", image_file, strerror(errno)); + struct htool_payload_image img; + if (htool_payload_image_open(inv, &img) != 0) { return -1; } - struct stat statbuf; - if (fstat(fd, &statbuf)) { - fprintf(stderr, "fstat error: %s\n", strerror(errno)); - goto cleanup2; - } - if (statbuf.st_size > SIZE_MAX) { - fprintf(stderr, "file too large\n"); - goto cleanup2; - } - - uint8_t* image = mmap(NULL, statbuf.st_size, PROT_READ, MAP_PRIVATE, fd, 0); - if (image == MAP_FAILED) { - fprintf(stderr, "mmap error: %s\n", strerror(errno)); - goto cleanup2; - } struct payload_info info; - if (!libhoth_payload_info(image, statbuf.st_size, &info)) { + if (!libhoth_payload_info(img.image, img.size, &info)) { fprintf(stderr, "Failed to parse payload image. Is this a titan image?\n"); - goto cleanup; + return htool_payload_image_close(&img); } printf("Payload Info:\n"); @@ -119,22 +211,80 @@ int htool_payload_info(const struct htool_invocation* inv) { info.image_version.subpoint); printf(" type: %u\n", info.image_type); printf(" hash: "); - for (int i = 0; i < sizeof(info.image_hash); i++) { + for (size_t i = 0; i < sizeof(info.image_hash); i++) { printf("%02x", info.image_hash[i]); } printf("\n"); -cleanup: - if (munmap(image, statbuf.st_size) != 0) { - fprintf(stderr, "munmap error: %s\n", strerror(errno)); + return htool_payload_image_close(&img); +} + +int htool_payload_info_all(const struct htool_invocation* inv) { + struct htool_payload_image img; + if (htool_payload_image_open(inv, &img) != 0) { return -1; } -cleanup2: - if (close(fd) != 0) { - fprintf(stderr, "close error: %s\n", strerror(errno)); - return -1; + struct payload_info_all info_all; + if (!libhoth_payload_info_all(img.image, img.size, &info_all)) { + fprintf(stderr, "Failed to parse payload image. Is this a titan image?\n"); + return htool_payload_image_close(&img); } - return 0; + printf("Payload Info All:\n"); + printf(" name: %-32s\n", info_all.info.image_name); + printf(" family: %u\n", info_all.info.image_family); + printf(" version: %u.%u.%u.%u\n", info_all.info.image_version.major, + info_all.info.image_version.minor, info_all.info.image_version.point, + info_all.info.image_version.subpoint); + printf(" type: %u (%s)\n", info_all.info.image_type, + libhoth_image_type_string(info_all.info.image_type)); + printf(" hash_type: %u (%s)\n", info_all.hash_type, + hash_type_string(info_all.hash_type)); + printf(" hash: "); + for (size_t i = 0; i < sizeof(info_all.info.image_hash); i++) { + printf("%02x", info_all.info.image_hash[i]); + } + printf("\n"); + printf(" descriptor_version: %u.%u\n", info_all.descriptor_major, + info_all.descriptor_minor); + printf(" build_timestamp: %" PRIu64, info_all.build_timestamp); +#if __SIZEOF_POINTER__ >= 8 + if (info_all.build_timestamp != 0) { + time_t t = (time_t)info_all.build_timestamp; + struct tm tm; + if (gmtime_r(&t, &tm)) { + char buf[64]; + if (strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S UTC", &tm) > 0) { + printf(" (%s)", buf); + } + } + } +#endif + printf("\n"); + printf(" image_size: 0x%08x\n", info_all.image_size); + printf(" blob_size: %u\n", info_all.blob_size); + printf(" region_count: %u\n", info_all.region_count); + + print_regions(&info_all, /*skip_mask=*/0); + + return htool_payload_image_close(&img); } + +int htool_payload_info_nonstatic(const struct htool_invocation* inv) { + struct htool_payload_image img; + if (htool_payload_image_open(inv, &img) != 0) { + return -1; + } + + struct payload_info_all info_all; + if (!libhoth_payload_info_all(img.image, img.size, &info_all)) { + fprintf(stderr, "Failed to parse payload image. Is this a titan image?\n"); + return htool_payload_image_close(&img); + } + + printf("Non-static regions:\n"); + print_regions(&info_all, /*skip_mask=*/IMAGE_REGION_STATIC); + + return htool_payload_image_close(&img); +} \ No newline at end of file diff --git a/examples/htool_payload.h b/examples/htool_payload.h index 82c77ac..359bbc3 100644 --- a/examples/htool_payload.h +++ b/examples/htool_payload.h @@ -26,6 +26,8 @@ extern "C" { int htool_payload_status(const struct htool_invocation* inv); int htool_payload_info(const struct htool_invocation* inv); +int htool_payload_info_all(const struct htool_invocation* inv); +int htool_payload_info_nonstatic(const struct htool_invocation* inv); #ifdef __cplusplus } diff --git a/protocol/payload_info.c b/protocol/payload_info.c index 182a333..905b403 100644 --- a/protocol/payload_info.c +++ b/protocol/payload_info.c @@ -83,3 +83,50 @@ bool libhoth_payload_info(const uint8_t* image, size_t len, return true; } + +bool libhoth_payload_info_all(const uint8_t* image, size_t len, + struct payload_info_all* info_all) { + if (!libhoth_payload_info(image, len, &info_all->info)) { + return false; + } + + const struct image_descriptor* descr = + libhoth_find_image_descriptor(image, len); + if (descr == NULL) { + return false; + } + + // Reject images with more regions than PAYLOAD_INFO_ALL_MAX_REGIONS + uint8_t count = descr->region_count; + if (count > PAYLOAD_INFO_ALL_MAX_REGIONS) { + return false; + } + + // Validate that the claimed region_count fits within descriptor_area_size. + uint32_t regions_end = + sizeof(struct image_descriptor) + count * sizeof(struct image_region); + if (regions_end > descr->descriptor_area_size) { + return false; + } + + info_all->descriptor_major = descr->descriptor_major; + info_all->descriptor_minor = descr->descriptor_minor; + info_all->build_timestamp = descr->build_timestamp; + info_all->hash_type = descr->hash_type; + info_all->region_count = descr->region_count; + info_all->image_size = descr->image_size; + info_all->blob_size = descr->blob_size; + + for (uint8_t i = 0; i < count; i++) { + const struct image_region* src = &descr->image_regions[i]; + struct payload_region_info* dst = &info_all->regions[i]; + memcpy(dst->region_name, src->region_name, sizeof(dst->region_name)); + dst->region_name[sizeof(dst->region_name) - 1] = 0; + dst->region_offset = src->region_offset; + dst->region_size = src->region_size; + dst->region_version = src->region_version; + dst->region_attributes = src->region_attributes; + } + + return true; +} diff --git a/protocol/payload_info.h b/protocol/payload_info.h index 5e02bef..a5774fb 100644 --- a/protocol/payload_info.h +++ b/protocol/payload_info.h @@ -154,12 +154,37 @@ struct payload_info { uint8_t image_hash[HASH_SHA256_BYTES]; }; +struct payload_region_info { + char region_name[32]; + uint32_t region_offset; + uint32_t region_size; + uint16_t region_version; + uint16_t region_attributes; +}; + +// Avoid oversized stack allocations in struct payload_info_all. +#define PAYLOAD_INFO_ALL_MAX_REGIONS 32 + +struct payload_info_all { + struct payload_info info; + uint8_t descriptor_major; + uint8_t descriptor_minor; + uint64_t build_timestamp; + uint8_t hash_type; + uint8_t region_count; + uint32_t image_size; + uint32_t blob_size; + struct payload_region_info regions[PAYLOAD_INFO_ALL_MAX_REGIONS]; +}; + // Returns a pointer to a valid image_descriptor if found inside the image, // otherwise returns NULL const struct image_descriptor* libhoth_find_image_descriptor( const uint8_t* image, size_t len); bool libhoth_payload_info(const uint8_t* image, size_t len, struct payload_info* payload_info); +bool libhoth_payload_info_all(const uint8_t* image, size_t len, + struct payload_info_all* info_all); #ifdef __cplusplus } diff --git a/protocol/payload_info_test.cc b/protocol/payload_info_test.cc index e5ba7a2..07cdcbb 100644 --- a/protocol/payload_info_test.cc +++ b/protocol/payload_info_test.cc @@ -163,3 +163,71 @@ TEST(PayloadInfoTest, PayloadInfoFuzzRegression) { EXPECT_FALSE(libhoth_payload_info( reinterpret_cast(data.data()), data.size(), &info)); } + +TEST(PayloadInfotest, payload_info_all) { + int fd = open(kTestData, O_RDONLY, 0); + ASSERT_NE(fd, -1); + + struct stat statbuf; + ASSERT_EQ(fstat(fd, &statbuf), 0); + + uint8_t* image = reinterpret_cast( + mmap(NULL, statbuf.st_size, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0)); + ASSERT_NE(image, nullptr); + + struct image_descriptor* descr = const_cast( + libhoth_find_image_descriptor(image, statbuf.st_size)); + ASSERT_NE(descr, nullptr); + + struct payload_info_all info_all; + EXPECT_TRUE(libhoth_payload_info_all(image, statbuf.st_size, &info_all)); + + // Verify basic info fields match the existing payload_info test + EXPECT_STREQ(info_all.info.image_name, "test layout"); + EXPECT_EQ(info_all.info.image_family, 2); + EXPECT_EQ(info_all.info.image_version.major, 1); + EXPECT_EQ(info_all.info.image_version.minor, 0); + EXPECT_EQ(info_all.info.image_version.point, 0); + EXPECT_EQ(info_all.info.image_version.subpoint, 0); + EXPECT_EQ(info_all.info.image_type, 0); + + // Verify hash matches + std::stringstream stream; + stream << std::hex; + for (const auto c : info_all.info.image_hash) { + stream << std::setw(2) << std::setfill('0') << (int)c; + } + EXPECT_EQ(kTestHash, stream.str()); + + // Verify extended fields + EXPECT_EQ(info_all.hash_type, HASH_SHA2_256); + ASSERT_EQ(info_all.region_count, 8); + EXPECT_EQ(info_all.image_size, 0x400000u); + + // Verify region offsets and sizes against the known test image layout. + EXPECT_EQ(info_all.regions[0].region_offset, 0x0u); + EXPECT_EQ(info_all.regions[0].region_size, 0x1000u); + EXPECT_EQ(info_all.regions[1].region_offset, 0x1000u); + EXPECT_EQ(info_all.regions[1].region_size, 0xf000u); + EXPECT_EQ(info_all.regions[2].region_offset, 0x10000u); + EXPECT_EQ(info_all.regions[2].region_size, 0x10000u); + EXPECT_EQ(info_all.regions[3].region_offset, 0x20000u); + EXPECT_EQ(info_all.regions[3].region_size, 0x20000u); + EXPECT_EQ(info_all.regions[4].region_offset, 0x40000u); + EXPECT_EQ(info_all.regions[4].region_size, 0x10000u); + EXPECT_EQ(info_all.regions[5].region_offset, 0x50000u); + EXPECT_EQ(info_all.regions[5].region_size, 0x10000u); + EXPECT_EQ(info_all.regions[6].region_offset, 0x60000u); + EXPECT_EQ(info_all.regions[6].region_size, 0x20000u); + EXPECT_EQ(info_all.regions[7].region_offset, 0x80000u); + EXPECT_EQ(info_all.regions[7].region_size, 0x380000u); + + // Set region_count beyond PAYLOAD_INFO_ALL_MAX_REGIONS (32) to verify + // that libhoth_payload_info_all() rejects it. + descr->region_count = 64; + + EXPECT_FALSE(libhoth_payload_info_all(image, statbuf.st_size, &info_all)); + + (void)munmap(image, statbuf.st_size); + close(fd); +}