| // Copyright 2018 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include <libzbi/zbi.h> |
| #include <stdbool.h> |
| #include <string.h> |
| |
| struct check_state { |
| zbi_header_t** err; |
| bool seen_bootfs; |
| }; |
| |
| static zbi_result_t for_each_check_entry(zbi_header_t* hdr, void* payload, |
| void* cookie) { |
| struct check_state* const state = cookie; |
| |
| zbi_result_t result = ZBI_RESULT_OK; |
| |
| if (hdr->magic != ZBI_ITEM_MAGIC) { |
| result = ZBI_RESULT_BAD_MAGIC; |
| } else if ((hdr->flags & ZBI_FLAG_VERSION) == 0) { |
| result = ZBI_RESULT_BAD_VERSION; |
| } else if ((hdr->flags & ZBI_FLAG_CRC32) == 0 && |
| hdr->crc32 != ZBI_ITEM_NO_CRC32) { |
| result = ZBI_RESULT_BAD_CRC; |
| } |
| |
| // If we found a problem, try to report it back up to the caller. |
| if (state->err != NULL && result != ZBI_RESULT_OK) { |
| *state->err = hdr; |
| } |
| |
| if (hdr->type == ZBI_TYPE_STORAGE_BOOTFS) { |
| state->seen_bootfs = true; |
| } |
| |
| return result; |
| } |
| |
| |
| static zbi_result_t zbi_check_internal(const void* base, |
| uint32_t check_complete, |
| zbi_header_t** err) { |
| zbi_result_t res = ZBI_RESULT_OK; |
| const zbi_header_t* header = base; |
| |
| if (header->type != ZBI_TYPE_CONTAINER) { |
| res = ZBI_RESULT_BAD_TYPE; |
| } else if (header->extra != ZBI_CONTAINER_MAGIC) { |
| res = ZBI_RESULT_BAD_MAGIC; |
| } else if ((header->flags & ZBI_FLAG_VERSION) == 0) { |
| res = ZBI_RESULT_BAD_VERSION; |
| } else if ((header->flags & ZBI_FLAG_CRC32) == 0 && |
| header->crc32 != ZBI_ITEM_NO_CRC32) { |
| res = ZBI_RESULT_BAD_CRC; |
| } |
| |
| // Something was wrong with the container. Don't even attempt to process |
| // the rest of the image. Return diagnostic information back to the caller |
| // if they requested it. |
| if (res != ZBI_RESULT_OK) { |
| if (err) { *err = (zbi_header_t*)header; } |
| return res; |
| } |
| |
| struct check_state state = { .err = err }; |
| res = zbi_for_each(base, for_each_check_entry, &state); |
| |
| if (res == ZBI_RESULT_OK && check_complete != 0) { |
| if (header->length == 0) { |
| res = ZBI_RESULT_ERR_TRUNCATED; |
| } else if (header[1].type != check_complete) { |
| res = ZBI_RESULT_INCOMPLETE_KERNEL; |
| if (err) { |
| *err = (zbi_header_t*)(header + 1); |
| } |
| } else if (!state.seen_bootfs) { |
| res = ZBI_RESULT_INCOMPLETE_BOOTFS; |
| if (err) { |
| *err = (zbi_header_t*)header; |
| } |
| } |
| } |
| |
| if (err && res == ZBI_RESULT_ERR_TRUNCATED) { |
| // A truncated image perhaps indicates a problem with the container? |
| *err = (zbi_header_t*)header; |
| } |
| |
| return res; |
| } |
| |
| zbi_result_t zbi_check(const void* base, zbi_header_t** err) { |
| return zbi_check_internal(base, 0, err); |
| } |
| |
| zbi_result_t zbi_check_complete(const void* base, zbi_header_t** err) { |
| return zbi_check_internal(base, |
| #ifdef __aarch64__ |
| ZBI_TYPE_KERNEL_ARM64, |
| #elif defined(__x86_64__) || defined(__i386__) |
| ZBI_TYPE_KERNEL_X64, |
| #else |
| #error "what architecture?" |
| #endif |
| err); |
| } |
| |
| zbi_result_t zbi_for_each(const void* base, const zbi_foreach_cb_t cb, |
| void* cookie) { |
| zbi_header_t* header = (zbi_header_t*)(base); |
| |
| // Skip container header. |
| const uint32_t totalSize = (uint32_t)sizeof(zbi_header_t) + header->length; |
| uint32_t offset = sizeof(zbi_header_t); |
| while (offset < totalSize) { |
| zbi_header_t* entryHeader = |
| (zbi_header_t*)(base + offset); |
| |
| zbi_result_t result = cb(entryHeader, entryHeader + 1, cookie); |
| |
| if (result != ZBI_RESULT_OK) return result; |
| |
| if ((offset + entryHeader->length + sizeof(zbi_header_t)) > totalSize) |
| return ZBI_RESULT_ERR_TRUNCATED; |
| |
| offset = ZBI_ALIGN(offset + entryHeader->length + sizeof(zbi_header_t)); |
| } |
| |
| return ZBI_RESULT_OK; |
| } |
| |
| zbi_result_t zbi_append_section(void* base, const size_t capacity, |
| uint32_t section_length, uint32_t type, |
| uint32_t extra, uint32_t flags, |
| const void* payload) { |
| |
| uint8_t* new_section; |
| zbi_result_t result = zbi_create_section(base, capacity, section_length, |
| type, extra, flags, |
| (void**)&new_section); |
| |
| if (result != ZBI_RESULT_OK) return result; |
| |
| // Copy in the payload. |
| memcpy(new_section, payload, section_length); |
| return ZBI_RESULT_OK; |
| } |
| |
| zbi_result_t zbi_create_section(void* base, size_t capacity, |
| uint32_t section_length, uint32_t type, |
| uint32_t extra, uint32_t flags, |
| void** payload) { |
| // We don't support CRC computation (yet?) |
| if (flags & ZBI_FLAG_CRC32) return ZBI_RESULT_ERROR; |
| |
| zbi_header_t* hdr = (zbi_header_t*)base; |
| |
| // Make sure we were actually passed a bootdata container. |
| if ((hdr->type != ZBI_TYPE_CONTAINER) || |
| (hdr->magic != ZBI_ITEM_MAGIC) || |
| (hdr->extra != ZBI_CONTAINER_MAGIC)) { |
| return ZBI_RESULT_BAD_TYPE; |
| } |
| |
| // Make sure we have enough space in the buffer to append the new section. |
| if (capacity - sizeof(*hdr) < hdr->length) { |
| return ZBI_RESULT_TOO_BIG; |
| } |
| const size_t available = capacity - sizeof(*hdr) - hdr->length; |
| if (available < sizeof(*hdr) || |
| available - sizeof(*hdr) < ZBI_ALIGN(section_length)) { |
| return ZBI_RESULT_TOO_BIG; |
| } |
| |
| // Fill in the new section header. |
| zbi_header_t* new_header = (void*)((uint8_t*)(hdr + 1) + hdr->length); |
| *new_header = (zbi_header_t) { |
| .type = type, |
| .length = section_length, |
| .extra = extra, |
| .flags = flags | ZBI_FLAG_VERSION, |
| .magic = ZBI_ITEM_MAGIC, |
| .crc32 = ZBI_ITEM_NO_CRC32, |
| }; |
| |
| // Tell the caller where to fill in the payload. |
| *payload = new_header + 1; |
| |
| // Update the container header, always keeping the length aligned. |
| hdr->length += sizeof(*new_header) + new_header->length; |
| if (hdr->length % ZBI_ALIGNMENT != 0) { |
| uint32_t aligned_length = ZBI_ALIGN(hdr->length); |
| if (capacity - sizeof(*hdr) < aligned_length) { |
| return ZBI_RESULT_TOO_BIG; |
| } |
| memset((uint8_t*)(hdr + 1) + hdr->length, 0, |
| aligned_length - hdr->length); |
| hdr->length = aligned_length; |
| } |
| |
| return ZBI_RESULT_OK; |
| } |