From 6e6a4517cc32600c1429d8aef84c7cb30c2a4584 Mon Sep 17 00:00:00 2001 From: Davide Bettio Date: Mon, 22 Jun 2026 10:15:12 +0000 Subject: [PATCH 1/3] Fix out-of-bounds read on avmpack lookup miss The packbeam scanners were unbounded, so a lookup miss (e.g. read_priv for an unpacked file) ran off the pack into erased flash and crashed. Bound and validate every scan against the stored pack size. Signed-off-by: Davide Bettio --- CHANGELOG.md | 1 + src/libAtomVM/avmpack.c | 150 ++++++++++++------- src/libAtomVM/avmpack.h | 41 +++-- src/libAtomVM/globalcontext.c | 3 +- src/libAtomVM/jit_stream_flash.c | 17 ++- src/libAtomVM/nifs.c | 47 +++--- src/platforms/emscripten/src/lib/sys.c | 5 +- src/platforms/emscripten/src/main.c | 3 +- src/platforms/esp32/components/avm_sys/sys.c | 4 +- src/platforms/esp32/main/main.c | 5 +- src/platforms/esp32/test/main/test_main.c | 4 +- src/platforms/generic_unix/lib/sys.c | 2 +- src/platforms/generic_unix/main.c | 6 +- src/platforms/rp2/src/main.c | 10 +- src/platforms/rp2/tests/test_main.c | 3 +- src/platforms/stm32/src/main.c | 6 +- tests/test-jit_stream_flash.c | 2 +- 17 files changed, 207 insertions(+), 102 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 29b81ae4e4..cc19fe6c4e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -64,6 +64,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Fixed several underallocation issues that could trigger data corruption on `binary:replace`, `zlib:compress` and bsd socket recv code. - Fixed a bug where `catch` would raise on regular atom results - Fixed ESP32 socket driver holding the global socket-list lock across blocking TCP connects, leaking the port on connect failure, losing concurrent `accept` waiters, leaking `netbuf` on receive error paths, and a recycled-`netconn` race between socket close and the event handler +- Fixed an out-of-bounds read crashing the VM on an AVM pack section lookup miss (e.g. `atomvm:read_priv/2` for an unpacked file) ## [0.7.0-alpha.1] - 2026-04-06 diff --git a/src/libAtomVM/avmpack.c b/src/libAtomVM/avmpack.c index 624fa82493..826711d443 100644 --- a/src/libAtomVM/avmpack.c +++ b/src/libAtomVM/avmpack.c @@ -27,6 +27,9 @@ #include #define AVMPACK_SIZE 24 +#define AVMPACK_SECTION_HEADER_SIZE 12 +#define AVMPACK_MIN_SECTION_SIZE 16 +#define AVMPACK_END_MARKER_SIZE 16 static inline int pad(int size) { @@ -52,79 +55,122 @@ bool avmpack_is_valid(const void *avmpack_binary, uint32_t size) return memcmp(avmpack_binary, pack_header, AVMPACK_SIZE) == 0; } -int avmpack_find_section_by_flag(const void *avmpack_binary, uint32_t flags_mask, uint32_t flags_val, const void **ptr, uint32_t *size, const char **name) +enum AVMPackSectionKind { - int offset = AVMPACK_SIZE; - const uint32_t *flags; + AVMPackSectionInvalid, + AVMPackSectionRegular, + AVMPackSectionEnd +}; - do { - const uint32_t *sizes = ((const uint32_t *) (avmpack_binary)) + offset / sizeof(uint32_t); - flags = ((const uint32_t *) (avmpack_binary)) + 1 + offset / sizeof(uint32_t); +struct AVMPackSection +{ + uint32_t size; + uint32_t flags; + const char *name; + const void *data; +}; - if ((ENDIAN_SWAP_32(*flags) & flags_mask) == flags_val) { - const char *found_section_name = (const char *) (sizes + 3); - int section_name_len = pad(strlen(found_section_name) + 1); +static enum AVMPackSectionKind read_section(const void *avmpack_binary, uint32_t avmpack_size, + uint32_t offset, struct AVMPackSection *section) +{ + if (offset > avmpack_size || avmpack_size - offset < AVMPACK_MIN_SECTION_SIZE) { + return AVMPackSectionInvalid; + } - *ptr = sizes + 3 + section_name_len / sizeof(uint32_t); - *size = ENDIAN_SWAP_32(*sizes); - *name = (const char *) (sizes + 3); - return 1; + const uint32_t *header = (const uint32_t *) ((const uint8_t *) avmpack_binary + offset); + uint32_t section_size = ENDIAN_SWAP_32(header[0]); + uint32_t flags = ENDIAN_SWAP_32(header[1]); + const char *name = (const char *) (header + 3); + + if (section_size == 0) { + // The min-size guard above keeps the terminator name bytes in bounds for strcmp. + if (flags == 0 && (strcmp(name, "end") == 0 || strcmp(name, "END") == 0)) { + section->size = 0; + section->flags = 0; + section->name = name; + section->data = (const uint8_t *) avmpack_binary + offset + AVMPACK_END_MARKER_SIZE; + return AVMPackSectionEnd; } + return AVMPackSectionInvalid; + } - offset += ENDIAN_SWAP_32(*sizes); + if (section_size < AVMPACK_MIN_SECTION_SIZE || (section_size & 3) != 0 + || section_size > avmpack_size - offset) { + return AVMPackSectionInvalid; + } + + size_t name_region = section_size - AVMPACK_SECTION_HEADER_SIZE; + const void *nul = memchr(name, '\0', name_region); + if (nul == NULL) { + return AVMPackSectionInvalid; + } + size_t padded_name_len = (size_t) pad((int) ((const char *) nul - name + 1)); + if (padded_name_len > name_region) { + return AVMPackSectionInvalid; + } - } while (*flags); + section->size = section_size; + section->flags = flags; + section->name = name; + section->data + = (const uint8_t *) avmpack_binary + offset + AVMPACK_SECTION_HEADER_SIZE + padded_name_len; - return 0; + return AVMPackSectionRegular; } -int avmpack_find_section_by_name(const void *avmpack_binary, const char *name, const void **ptr, uint32_t *size) +int avmpack_find_section_by_flag(const void *avmpack_binary, uint32_t avmpack_size, + uint32_t flags_mask, uint32_t flags_val, const void **ptr, uint32_t *size, const char **name) { - int offset = AVMPACK_SIZE; - const uint32_t *flags; + uint32_t offset = AVMPACK_SIZE; + struct AVMPackSection section; + enum AVMPackSectionKind kind; + + while ((kind = read_section(avmpack_binary, avmpack_size, offset, §ion)) + != AVMPackSectionInvalid) { + if ((section.flags & flags_mask) == flags_val) { + *ptr = section.data; + *size = section.size; + *name = section.name; + return 1; + } + if (kind == AVMPackSectionEnd) { + break; + } + offset += section.size; + } - do { - const uint32_t *sizes = ((const uint32_t *) (avmpack_binary)) + offset / sizeof(uint32_t); - flags = ((const uint32_t *) (avmpack_binary)) + 1 + offset / sizeof(uint32_t); + return 0; +} - const char *found_section_name = (const char *) (sizes + 3); - if (!strcmp(name, found_section_name)) { - int section_name_len = pad(strlen(found_section_name) + 1); +int avmpack_find_section_by_name(const void *avmpack_binary, uint32_t avmpack_size, + const char *name, const void **ptr, uint32_t *size) +{ + uint32_t offset = AVMPACK_SIZE; + struct AVMPackSection section; - *ptr = sizes + 3 + section_name_len / sizeof(uint32_t); - *size = ENDIAN_SWAP_32(*sizes); + while (read_section(avmpack_binary, avmpack_size, offset, §ion) == AVMPackSectionRegular) { + if (!strcmp(name, section.name)) { + *ptr = section.data; + *size = section.size; return 1; } - - offset += ENDIAN_SWAP_32(*sizes); - - } while (*flags); + offset += section.size; + } return 0; } -void *avmpack_fold(void *accum, const void *avmpack_binary, avmpack_fold_fun fold_fun) +void *avmpack_fold( + void *accum, const void *avmpack_binary, uint32_t avmpack_size, avmpack_fold_fun fold_fun) { - int offset = AVMPACK_SIZE; - uint32_t size = 0; - - do { - const uint32_t *size_ptr = ((const uint32_t *) (avmpack_binary)) + offset / sizeof(uint32_t); - size = ENDIAN_SWAP_32(*size_ptr); - if (size > 0) { - const uint32_t *flags_ptr = size_ptr + 1; - uint32_t flags = ENDIAN_SWAP_32(*flags_ptr); - const char *section_name = (const char *) (size_ptr + 3); - int section_name_len = pad(strlen(section_name) + 1); - accum = fold_fun( - accum, - size_ptr, size, - size_ptr + 3 + section_name_len / sizeof(uint32_t), - flags, - section_name); - offset += size; - } - } while (size > 0); + uint32_t offset = AVMPACK_SIZE; + struct AVMPackSection section; + + while (read_section(avmpack_binary, avmpack_size, offset, §ion) == AVMPackSectionRegular) { + accum = fold_fun(accum, (const uint8_t *) avmpack_binary + offset, section.size, + section.data, section.flags, section.name); + offset += section.size; + } return accum; } diff --git a/src/libAtomVM/avmpack.h b/src/libAtomVM/avmpack.h index a68e6e3378..37ef159708 100644 --- a/src/libAtomVM/avmpack.h +++ b/src/libAtomVM/avmpack.h @@ -55,14 +55,17 @@ struct AVMPackData bool in_use; int name_atom_id; const void *data; + uint32_t size; }; -static inline void avmpack_data_init(struct AVMPackData *avm_pack_data, const struct AVMPackInfo *info) +static inline void avmpack_data_init( + struct AVMPackData *avm_pack_data, const struct AVMPackInfo *info, uint32_t size) { avm_pack_data->obj_info = info; avm_pack_data->in_use = false; avm_pack_data->name_atom_id = 0; avm_pack_data->data = NULL; + avm_pack_data->size = size; } static inline void avmpack_data_destroy(struct AVMPackData *avm_pack_data, GlobalContext *global) @@ -102,34 +105,47 @@ typedef void *(*avmpack_fold_fun)(void *accum, const void *section_ptr, uint32_t /** * @brief Finds an AVM Pack section that has certain flags set. * - * @details Finds an AVM Pack section that has certain flags set and returns a pointer to it, its size and its name. + * @details Finds an AVM Pack section that has certain flags set and returns a pointer to it, its + * size and its name. The scan is bounded by \p avmpack_size and validates every section header, so + * a miss (or a truncated/corrupt pack) returns 0 instead of reading past the end of the pack. * @param avmpack_binary a pointer to valid AVM Pack file data. + * @param avmpack_size the size in bytes of the AVM Pack (used to bound the scan). * @param flags_mask that will be matched against file sections. * @param flags_value that will be matched against file sections. * @param ptr will point to the found file section. - * @param size will be set to the file section size that has been found, if the section has not been found it will not be updated. + * @param size will be set to the file section size that has been found, if the section has not been + * found it will not be updated. * @param name the section name, as defined in the module header. * @returns 1 if the file section has been found, 0 otherwise. */ -int avmpack_find_section_by_flag(const void *avmpack_binary, uint32_t flags_mask, uint32_t flags_value, const void **ptr, uint32_t *size, const char **name); +int avmpack_find_section_by_flag(const void *avmpack_binary, uint32_t avmpack_size, + uint32_t flags_mask, uint32_t flags_value, const void **ptr, uint32_t *size, const char **name); /** * @brief Finds an AVM Pack section that has certain name. * * @details Finds an AVM Pack section with a certain name and returns a pointer to it and its size. + * The scan is bounded by \p avmpack_size and validates every section header, so a miss (or a + * truncated/corrupt pack) returns 0 instead of reading past the end of the pack. * @param avmpack_binary a pointer to valid AVM Pack file data. + * @param avmpack_size the size in bytes of the AVM Pack (used to bound the scan). * @param name the file section name that will be searched. - * @param ptr will point to the found file section, if the section has not been found it will not be updated. - * @param size will be set to the file section size that has been found, if the section has not been found it will not be updated. + * @param ptr will point to the found file section, if the section has not been found it will not be + * updated. + * @param size will be set to the file section size that has been found, if the section has not been + * found it will not be updated. * @returns 1 if the file section has been found, 0 otherwise. */ -int avmpack_find_section_by_name(const void *avmpack_binary, const char *name, const void **ptr, uint32_t *size); +int avmpack_find_section_by_name(const void *avmpack_binary, uint32_t avmpack_size, + const char *name, const void **ptr, uint32_t *size); /** - * @brief Returns \c true if the pointed binary is a valid AVM Pack. + * @brief Returns \c true if the pointed binary starts with a valid AVM Pack header. * - * @details Returns if the pointed binary is a valid AVM Pack binary or not. + * @details Performs a cheap check of the 24-byte magic header only. It does not validate the + * section chain or detect truncation; use \c avmpack_check_complete or \c avmpack_compute_size + * for that. * @param avmpack_binary a pointer to an AVM Pack binary. * @param size the size of AVM Pack binary. * @returns \c true if it is a valid AVM Pack binary, \c false otherwise. @@ -140,12 +156,15 @@ bool avmpack_is_valid(const void *avmpack_binary, uint32_t size); * @brief Fold over all the sections in an AVM Pack. * * @details This function will call the callback on each section of the AVM Pack, passing in - * the current section of each module in the AVM binary to the supplied fold function. + * the current section of each module in the AVM binary to the supplied fold function. The scan + * is bounded by \p avmpack_size and stops on the terminator or on the first invalid section header. * @param accum The accumulator supplied by the application. * @param avmpack_binary a pointer to an AVM Pack binary. + * @param avmpack_size the size in bytes of the AVM Pack (used to bound the scan). * @param fold_fun function that will be called for each AVM section. */ -void *avmpack_fold(void *accum, const void *avmpack_binary, avmpack_fold_fun fold_fun); +void *avmpack_fold( + void *accum, const void *avmpack_binary, uint32_t avmpack_size, avmpack_fold_fun fold_fun); #ifdef __cplusplus } diff --git a/src/libAtomVM/globalcontext.c b/src/libAtomVM/globalcontext.c index d00213284c..958f3af809 100644 --- a/src/libAtomVM/globalcontext.c +++ b/src/libAtomVM/globalcontext.c @@ -713,7 +713,8 @@ Module *globalcontext_load_module_from_avm(GlobalContext *global, const char *mo LIST_FOR_EACH (item, avmpack_data) { struct AVMPackData *avmpack_data = GET_LIST_ENTRY(item, struct AVMPackData, avmpack_head); avmpack_data->in_use = true; - if (avmpack_find_section_by_name(avmpack_data->data, module_name, &beam_module, &beam_module_size)) { + if (avmpack_find_section_by_name(avmpack_data->data, avmpack_data->size, module_name, + &beam_module, &beam_module_size)) { break; } } diff --git a/src/libAtomVM/jit_stream_flash.c b/src/libAtomVM/jit_stream_flash.c index d53245dca8..cd3906cdc1 100644 --- a/src/libAtomVM/jit_stream_flash.c +++ b/src/libAtomVM/jit_stream_flash.c @@ -145,7 +145,11 @@ static struct JITEntry *globalcontext_find_first_jit_entry(GlobalContext *global struct ListHead *avmpack_data = synclist_rdlock(&global->avmpack_data); LIST_FOR_EACH (item, avmpack_data) { struct AVMPackData *avmpack_data = GET_LIST_ENTRY(item, struct AVMPackData, avmpack_head); - avmpack_find_section_by_flag(avmpack_data->data, END_OF_FILE_MASK, END_OF_FILE, &end_offset, &end_size, &end_name); + if (!avmpack_find_section_by_flag(avmpack_data->data, avmpack_data->size, END_OF_FILE_MASK, + END_OF_FILE, &end_offset, &end_size, &end_name)) { + valid_cache = false; + continue; + } valid_cache = valid_cache && (strcmp(end_name, "END") == 0); if (end_offset > max_end_offset) { @@ -180,17 +184,26 @@ static void globalcontext_set_cache_valid(GlobalContext *global) do { valid_cache = true; + bool found_end = true; struct ListHead *item; struct ListHead *avmpack_data = synclist_rdlock(&global->avmpack_data); LIST_FOR_EACH (item, avmpack_data) { struct AVMPackData *avmpack_data = GET_LIST_ENTRY(item, struct AVMPackData, avmpack_head); - avmpack_find_section_by_flag(avmpack_data->data, END_OF_FILE_MASK, END_OF_FILE, &end_offset, &end_size, &end_name); + if (!avmpack_find_section_by_flag(avmpack_data->data, avmpack_data->size, + END_OF_FILE_MASK, END_OF_FILE, &end_offset, &end_size, &end_name)) { + found_end = false; + valid_cache = false; + break; + } if (strcmp(end_name, "END")) { valid_cache = false; break; } } synclist_unlock(&global->avmpack_data); + if (!found_end) { + break; + } if (!valid_cache) { // Replace "end" with "END" - this is a 3-byte string replacement const uint8_t end_str[] = "END"; diff --git a/src/libAtomVM/nifs.c b/src/libAtomVM/nifs.c index 2e3f70f7eb..c5e6879c52 100644 --- a/src/libAtomVM/nifs.c +++ b/src/libAtomVM/nifs.c @@ -5334,7 +5334,7 @@ static term nif_atomvm_add_avm_pack_binary(Context *ctx, int argc, term argv[]) size_t bin_size = term_binary_size(binary); - if (UNLIKELY(!avmpack_is_valid(term_binary_data(binary), bin_size))) { + if (UNLIKELY(bin_size > UINT32_MAX || !avmpack_is_valid(term_binary_data(binary), bin_size))) { RAISE_ERROR(BADARG_ATOM); } @@ -5350,7 +5350,7 @@ static term nif_atomvm_add_avm_pack_binary(Context *ctx, int argc, term argv[]) if (IS_NULL_PTR(refc_bin_avm)) { RAISE_ERROR(OUT_OF_MEMORY_ATOM); } - avmpack_data_init(&refc_bin_avm->base, &refc_binary_avm_pack_info); + avmpack_data_init(&refc_bin_avm->base, &refc_binary_avm_pack_info, (uint32_t) bin_size); refc_bin_avm->base.data = (const uint8_t *) term_binary_data(binary); struct RefcBinary *refc_bin = (struct RefcBinary *) term_refc_binary_ptr(binary); refc_binary_increment_refcount(refc_bin); @@ -5363,7 +5363,7 @@ static term nif_atomvm_add_avm_pack_binary(Context *ctx, int argc, term argv[]) if (IS_NULL_PTR(const_avm)) { RAISE_ERROR(OUT_OF_MEMORY_ATOM); } - avmpack_data_init(&const_avm->base, &const_avm_pack_info); + avmpack_data_init(&const_avm->base, &const_avm_pack_info, (uint32_t) bin_size); const_avm->base.data = (const uint8_t *) term_binary_data(binary); avmpack_data = &const_avm->base; @@ -5381,7 +5381,7 @@ static term nif_atomvm_add_avm_pack_binary(Context *ctx, int argc, term argv[]) free(allocated_data); RAISE_ERROR(OUT_OF_MEMORY_ATOM); } - avmpack_data_init(&in_memory_avm->base, &in_memory_avm_pack_info); + avmpack_data_init(&in_memory_avm->base, &in_memory_avm_pack_info, (uint32_t) bin_size); in_memory_avm->base.data = (const uint8_t *) allocated_data; avmpack_data = &in_memory_avm->base; @@ -5521,7 +5521,8 @@ static term nif_atomvm_get_start_beam(Context *ctx, int argc, term argv[]) uint32_t size; const void *beam; const char *module_name; - if (!avmpack_find_section_by_flag(avmpack_data->data, BEAM_START_FLAG, BEAM_START_FLAG, &beam, &size, &module_name)) { + if (!avmpack_find_section_by_flag(avmpack_data->data, avmpack_data->size, + BEAM_START_FLAG, BEAM_START_FLAG, &beam, &size, &module_name)) { synclist_unlock(&ctx->global->avmpack_data); if (UNLIKELY(memory_ensure_free(ctx, TUPLE_SIZE(2)) != MEMORY_GC_OK)) { RAISE_ERROR(OUT_OF_MEMORY_ATOM); @@ -5601,17 +5602,29 @@ static term nif_atomvm_read_priv(Context *ctx, int argc, term argv[]) struct AVMPackData *avmpack_data = GET_LIST_ENTRY(item, struct AVMPackData, avmpack_head); bool prev_in_use = avmpack_data->in_use; avmpack_data->in_use = true; - if (avmpack_find_section_by_name(avmpack_data->data, complete_path, &bin_data, &size)) { - uint32_t file_size = READ_32_ALIGNED((uint32_t *) bin_data); - free(complete_path); - complete_path = NULL; - if (UNLIKELY(memory_ensure_free_opt(ctx, TERM_BOXED_REFC_BINARY_SIZE, MEMORY_CAN_SHRINK) != MEMORY_GC_OK)) { - avmpack_data->in_use = prev_in_use; - synclist_unlock(&glb->avmpack_data); - RAISE_ERROR(OUT_OF_MEMORY_ATOM); + if (avmpack_find_section_by_name( + avmpack_data->data, avmpack_data->size, complete_path, &bin_data, &size)) { + // Bound the length prefix and payload to the pack against a corrupt file size. + size_t content_offset + = (const uint8_t *) bin_data - (const uint8_t *) avmpack_data->data; + if (content_offset + sizeof(uint32_t) <= avmpack_data->size) { + uint32_t file_size = READ_32_ALIGNED((uint32_t *) bin_data); + if (file_size <= avmpack_data->size - content_offset - sizeof(uint32_t)) { + free(complete_path); + complete_path = NULL; + if (UNLIKELY(memory_ensure_free_opt( + ctx, TERM_BOXED_REFC_BINARY_SIZE, MEMORY_CAN_SHRINK) + != MEMORY_GC_OK)) { + avmpack_data->in_use = prev_in_use; + synclist_unlock(&glb->avmpack_data); + RAISE_ERROR(OUT_OF_MEMORY_ATOM); + } + result = term_from_const_binary(((uint8_t *) bin_data) + sizeof(uint32_t), + file_size, &ctx->heap, ctx->global); + break; + } } - result = term_from_const_binary(((uint8_t *) bin_data) + sizeof(uint32_t), file_size, &ctx->heap, ctx->global); - break; + avmpack_data->in_use = prev_in_use; } else { avmpack_data->in_use = prev_in_use; } @@ -6145,7 +6158,7 @@ static term nif_code_all_available(Context *ctx, int argc, term argv[]) LIST_FOR_EACH (item, avmpack_data) { struct AVMPackData *avmpack_data = GET_LIST_ENTRY(item, struct AVMPackData, avmpack_head); acc.avmpack_data = avmpack_data; - avmpack_fold(&acc, avmpack_data->data, nif_code_all_available_fold); + avmpack_fold(&acc, avmpack_data->data, avmpack_data->size, nif_code_all_available_fold); } size_t available_count = acc.acc_count + ctx->global->loaded_modules_count; @@ -6161,7 +6174,7 @@ static term nif_code_all_available(Context *ctx, int argc, term argv[]) LIST_FOR_EACH (item, avmpack_data) { struct AVMPackData *avmpack_data = GET_LIST_ENTRY(item, struct AVMPackData, avmpack_head); acc.avmpack_data = avmpack_data; - avmpack_fold(&acc, avmpack_data->data, nif_code_all_available_fold); + avmpack_fold(&acc, avmpack_data->data, avmpack_data->size, nif_code_all_available_fold); } synclist_unlock(&ctx->global->avmpack_data); diff --git a/src/platforms/emscripten/src/lib/sys.c b/src/platforms/emscripten/src/lib/sys.c index 1a234c9cee..8615e0403c 100644 --- a/src/platforms/emscripten/src/lib/sys.c +++ b/src/platforms/emscripten/src/lib/sys.c @@ -690,7 +690,8 @@ enum OpenAVMResult sys_open_avm_from_file( UNUSED(global); emscripten_fetch_t *fetch = NULL; - void *data = load_or_fetch_file(path, &fetch, NULL); + size_t data_size = 0; + void *data = load_or_fetch_file(path, &fetch, &data_size); if (IS_NULL_PTR(data)) { return AVM_OPEN_CANNOT_OPEN; } @@ -704,7 +705,7 @@ enum OpenAVMResult sys_open_avm_from_file( } return AVM_OPEN_FAILED_ALLOC; } - avmpack_data_init(&const_avm->base, &const_avm_pack_info); + avmpack_data_init(&const_avm->base, &const_avm_pack_info, (uint32_t) data_size); const_avm->base.data = (const uint8_t *) data; *avm_data = &const_avm->base; diff --git a/src/platforms/emscripten/src/main.c b/src/platforms/emscripten/src/main.c index 756bfb43fa..7682a69dc6 100644 --- a/src/platforms/emscripten/src/main.c +++ b/src/platforms/emscripten/src/main.c @@ -59,7 +59,8 @@ static int load_module(const char *path) const void *startup_beam = NULL; uint32_t startup_beam_size; const char *startup_module_name; - avmpack_find_section_by_flag(avmpack_data->data, BEAM_START_FLAG, BEAM_START_FLAG, &startup_beam, &startup_beam_size, &startup_module_name); + avmpack_find_section_by_flag(avmpack_data->data, avmpack_data->size, BEAM_START_FLAG, + BEAM_START_FLAG, &startup_beam, &startup_beam_size, &startup_module_name); if (startup_beam) { avmpack_data->in_use = true; main_module = module_new_from_iff_binary(global, startup_beam, startup_beam_size); diff --git a/src/platforms/esp32/components/avm_sys/sys.c b/src/platforms/esp32/components/avm_sys/sys.c index 42786b6c8e..5041504442 100644 --- a/src/platforms/esp32/components/avm_sys/sys.c +++ b/src/platforms/esp32/components/avm_sys/sys.c @@ -455,7 +455,7 @@ enum OpenAVMResult sys_open_avm_from_file(GlobalContext *global, const char *pat if (IS_NULL_PTR(part_avm)) { return AVM_OPEN_FAILED_ALLOC; } - avmpack_data_init(&part_avm->base, &esp32_part_avm_pack_info); + avmpack_data_init(&part_avm->base, &esp32_part_avm_pack_info, size); part_avm->base.data = part_data; part_avm->part_handle = part_handle; avmpack_data = &part_avm->base; @@ -497,7 +497,7 @@ enum OpenAVMResult sys_open_avm_from_file(GlobalContext *global, const char *pat free(file_data); return AVM_OPEN_FAILED_ALLOC; } - avmpack_data_init(&in_memory_avm->base, &in_memory_avm_pack_info); + avmpack_data_init(&in_memory_avm->base, &in_memory_avm_pack_info, size); in_memory_avm->base.data = file_data; avmpack_data = &in_memory_avm->base; } diff --git a/src/platforms/esp32/main/main.c b/src/platforms/esp32/main/main.c index a91a3755de..8ee2e3c199 100644 --- a/src/platforms/esp32/main/main.c +++ b/src/platforms/esp32/main/main.c @@ -99,7 +99,8 @@ void app_main() ESP_LOGE(TAG, "Invalid startup avmpack. size=%u", size); AVM_ABORT(); } - if (!avmpack_find_section_by_flag(startup_avm, BEAM_START_FLAG, BEAM_START_FLAG, &startup_beam, &startup_beam_size, &startup_module_name)) { + if (!avmpack_find_section_by_flag(startup_avm, size, BEAM_START_FLAG, BEAM_START_FLAG, + &startup_beam, &startup_beam_size, &startup_module_name)) { ESP_LOGE(TAG, "Error: Failed to locate start module in startup partition. (Did you flash a library by mistake?)"); AVM_ABORT(); } @@ -109,7 +110,7 @@ void app_main() ESP_LOGE(TAG, "Memory error: Cannot allocate AVMPackData for main.avm."); AVM_ABORT(); } - avmpack_data_init(&avmpack_data->base, &const_avm_pack_info); + avmpack_data_init(&avmpack_data->base, &const_avm_pack_info, size); avmpack_data->base.in_use = true; avmpack_data->base.data = startup_avm; synclist_append(&glb->avmpack_data, &avmpack_data->base.avmpack_head); diff --git a/src/platforms/esp32/test/main/test_main.c b/src/platforms/esp32/test/main/test_main.c index 480b164695..4aff54ce77 100644 --- a/src/platforms/esp32/test/main/test_main.c +++ b/src/platforms/esp32/test/main/test_main.c @@ -147,7 +147,7 @@ term avm_test_case(const char *test_module) struct ConstAVMPack *avmpack_data = malloc(sizeof(struct ConstAVMPack)); TEST_ASSERT(avmpack_data != NULL); - avmpack_data_init(&avmpack_data->base, &const_avm_pack_info); + avmpack_data_init(&avmpack_data->base, &const_avm_pack_info, size); avmpack_data->base.in_use = true; avmpack_data->base.data = main_avm; synclist_append(&glb->avmpack_data, &avmpack_data->base.avmpack_head); @@ -195,7 +195,7 @@ TEST_CASE("test_jit_compile", "[test_run]") struct ConstAVMPack *avmpack_data = malloc(sizeof(struct ConstAVMPack)); TEST_ASSERT(avmpack_data != NULL); - avmpack_data_init(&avmpack_data->base, &const_avm_pack_info); + avmpack_data_init(&avmpack_data->base, &const_avm_pack_info, size); avmpack_data->base.in_use = true; avmpack_data->base.data = main_avm; synclist_append(&glb->avmpack_data, &avmpack_data->base.avmpack_head); diff --git a/src/platforms/generic_unix/lib/sys.c b/src/platforms/generic_unix/lib/sys.c index ce6a032fc9..5145b756dd 100644 --- a/src/platforms/generic_unix/lib/sys.c +++ b/src/platforms/generic_unix/lib/sys.c @@ -444,7 +444,7 @@ enum OpenAVMResult sys_open_avm_from_file( mapped_file_close(mapped); return AVM_OPEN_FAILED_ALLOC; } - avmpack_data_init(&avmpack_data->base, &mapped_file_avm_pack_info); + avmpack_data_init(&avmpack_data->base, &mapped_file_avm_pack_info, (uint32_t) mapped->size); avmpack_data->base.data = mapped->mapped; avmpack_data->mapped = mapped; diff --git a/src/platforms/generic_unix/main.c b/src/platforms/generic_unix/main.c index 3ede61e821..dce88a673c 100644 --- a/src/platforms/generic_unix/main.c +++ b/src/platforms/generic_unix/main.c @@ -201,7 +201,7 @@ int main(int argc, char **argv) return EXIT_FAILURE; } - avmpack_data_init(avmpack_data, &embedded_avm_pack_info); + avmpack_data_init(avmpack_data, &embedded_avm_pack_info, (uint32_t) embedded_size); avmpack_data->data = embedded_data; // Set the name for the embedded AVM pack so it can be found by atomvm:get_start_beam/1 term escript_atom = globalcontext_make_atom(glb, ATOM_STR("\x7", "escript")); @@ -227,7 +227,9 @@ int main(int argc, char **argv) const void *startup_beam = NULL; const char *startup_module_name; uint32_t startup_beam_size; - avmpack_find_section_by_flag(avmpack_data->data, BEAM_START_FLAG, BEAM_START_FLAG, &startup_beam, &startup_beam_size, &startup_module_name); + avmpack_find_section_by_flag(avmpack_data->data, avmpack_data->size, + BEAM_START_FLAG, BEAM_START_FLAG, &startup_beam, &startup_beam_size, + &startup_module_name); if (startup_beam) { avmpack_data->in_use = true; diff --git a/src/platforms/rp2/src/main.c b/src/platforms/rp2/src/main.c index 5954aac5b3..8f146775a4 100644 --- a/src/platforms/rp2/src/main.c +++ b/src/platforms/rp2/src/main.c @@ -92,7 +92,9 @@ static int app_main() } AVM_ABORT(); } - if (!avmpack_find_section_by_flag(MAIN_AVM, BEAM_START_FLAG, BEAM_START_FLAG, &startup_beam, &startup_beam_size, &startup_module_name)) { + if (!avmpack_find_section_by_flag(MAIN_AVM, (uint32_t) (XIP_SRAM_BASE - (uintptr_t) MAIN_AVM), + BEAM_START_FLAG, BEAM_START_FLAG, &startup_beam, &startup_beam_size, + &startup_module_name)) { sleep_ms(5000); fprintf(stderr, "Fatal error: Failed to locate start module in main.avm packbeam. (Did you flash a library by mistake?)"); AVM_ABORT(); @@ -105,7 +107,8 @@ static int app_main() AVM_ABORT(); } - avmpack_data_init(&avmpack_data->base, &const_avm_pack_info); + avmpack_data_init(&avmpack_data->base, &const_avm_pack_info, + (uint32_t) (XIP_SRAM_BASE - (uintptr_t) MAIN_AVM)); avmpack_data->base.data = MAIN_AVM; avmpack_data->base.in_use = true; @@ -118,7 +121,8 @@ static int app_main() fprintf(stderr, "Memory error: Cannot allocate AVMPackData for lib.avm."); AVM_ABORT(); } - avmpack_data_init(&avmpack_data->base, &const_avm_pack_info); + avmpack_data_init(&avmpack_data->base, &const_avm_pack_info, + (uint32_t) ((uintptr_t) MAIN_AVM - (uintptr_t) LIB_AVM)); avmpack_data->base.data = LIB_AVM; avmpack_data->base.in_use = true; synclist_append(&glb->avmpack_data, &avmpack_data->base.avmpack_head); diff --git a/src/platforms/rp2/tests/test_main.c b/src/platforms/rp2/tests/test_main.c index 8aebf51d06..daaeabd113 100644 --- a/src/platforms/rp2/tests/test_main.c +++ b/src/platforms/rp2/tests/test_main.c @@ -105,7 +105,8 @@ static term avm_test_case(const char *test_module) struct ConstAVMPack *avmpack_data = malloc(sizeof(struct ConstAVMPack)); TEST_ASSERT_NOT_NULL(avmpack_data); - avmpack_data_init(&avmpack_data->base, &const_avm_pack_info); + avmpack_data_init(&avmpack_data->base, &const_avm_pack_info, + (uint32_t) (XIP_SRAM_BASE - (uintptr_t) MAIN_AVM)); avmpack_data->base.data = MAIN_AVM; avmpack_data->base.in_use = true; diff --git a/src/platforms/stm32/src/main.c b/src/platforms/stm32/src/main.c index cda86fc2e3..6decffa063 100644 --- a/src/platforms/stm32/src/main.c +++ b/src/platforms/stm32/src/main.c @@ -333,7 +333,9 @@ int main(void) port_driver_init_all(glb); nif_collection_init_all(glb); - if (!avmpack_is_valid(flashed_avm, size) || !avmpack_find_section_by_flag(flashed_avm, BEAM_START_FLAG, BEAM_START_FLAG, &startup_beam, &startup_beam_size, &startup_module_name)) { + if (!avmpack_is_valid(flashed_avm, size) + || !avmpack_find_section_by_flag(flashed_avm, size, BEAM_START_FLAG, BEAM_START_FLAG, + &startup_beam, &startup_beam_size, &startup_module_name)) { AVM_LOGE(TAG, "Invalid AVM Pack"); AVM_ABORT(); } @@ -345,7 +347,7 @@ int main(void) AVM_LOGE(TAG, "Memory error: Cannot allocate AVMPackData."); AVM_ABORT(); } - avmpack_data_init(&avmpack_data->base, &const_avm_pack_info); + avmpack_data_init(&avmpack_data->base, &const_avm_pack_info, size); avmpack_data->base.data = flashed_avm; avmpack_data->base.in_use = true; synclist_append(&glb->avmpack_data, &avmpack_data->base.avmpack_head); diff --git a/tests/test-jit_stream_flash.c b/tests/test-jit_stream_flash.c index 1dc8d3af07..a0d019984b 100644 --- a/tests/test-jit_stream_flash.c +++ b/tests/test-jit_stream_flash.c @@ -209,7 +209,7 @@ static void register_test_avmpack(GlobalContext *glb) // Create AVMPackData struct ConstAVMPack *pack = malloc(sizeof(struct ConstAVMPack)); - avmpack_data_init(&pack->base, &const_avm_pack_info); + avmpack_data_init(&pack->base, &const_avm_pack_info, sizeof(mock_flash) - 0x100); pack->base.data = mock_flash + 0x100; pack->base.in_use = true; From 38ae415e5343de7e9b3cfc668a993930615743fa Mon Sep 17 00:00:00 2001 From: Davide Bettio Date: Mon, 22 Jun 2026 11:36:17 +0000 Subject: [PATCH 2/3] Add avmpack truncation detection at load Reject truncated or incomplete packs (e.g. an image flashed to a too-small partition) at load instead of failing late, validating by tail check or bounded walk. Signed-off-by: Davide Bettio --- CHANGELOG.md | 1 + doc/src/packbeam-format.md | 2 + src/libAtomVM/avmpack.c | 36 +++++ src/libAtomVM/avmpack.h | 28 ++++ src/libAtomVM/nifs.c | 3 +- src/platforms/emscripten/src/lib/sys.c | 9 ++ src/platforms/esp32/components/avm_sys/sys.c | 7 +- src/platforms/esp32/main/main.c | 9 +- src/platforms/generic_unix/lib/sys.c | 2 +- src/platforms/rp2/src/main.c | 21 +-- src/platforms/stm32/src/main.c | 9 +- tests/CMakeLists.txt | 7 + tests/test-avmpack.c | 160 +++++++++++++++++++ 13 files changed, 271 insertions(+), 23 deletions(-) create mode 100644 tests/test-avmpack.c diff --git a/CHANGELOG.md b/CHANGELOG.md index cc19fe6c4e..6ade4b2012 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -65,6 +65,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Fixed a bug where `catch` would raise on regular atom results - Fixed ESP32 socket driver holding the global socket-list lock across blocking TCP connects, leaking the port on connect failure, losing concurrent `accept` waiters, leaking `netbuf` on receive error paths, and a recycled-`netconn` race between socket close and the event handler - Fixed an out-of-bounds read crashing the VM on an AVM pack section lookup miss (e.g. `atomvm:read_priv/2` for an unpacked file) +- Fixed truncated or oversized AVM packs being accepted; they are now rejected at load instead of failing late ## [0.7.0-alpha.1] - 2026-04-06 diff --git a/doc/src/packbeam-format.md b/doc/src/packbeam-format.md index 532def5c46..b7b5033c80 100644 --- a/doc/src/packbeam-format.md +++ b/doc/src/packbeam-format.md @@ -120,6 +120,8 @@ requirement that the `Path` component of a normal file be a simple file name. Packbeam files end with a special `end` header. The `size` field of the `end` header is 0 bytes. +The `end` header is mandatory: the runtime uses it to confirm a pack is complete, and rejects a pack whose terminator is missing (e.g. a truncated flash image) instead of scanning past its data. + #### Example `end` header The following sequence of bytes encodes the `end` header: diff --git a/src/libAtomVM/avmpack.c b/src/libAtomVM/avmpack.c index 826711d443..4edc7af693 100644 --- a/src/libAtomVM/avmpack.c +++ b/src/libAtomVM/avmpack.c @@ -175,6 +175,42 @@ void *avmpack_fold( return accum; } +bool avmpack_check_complete(const void *avmpack_binary, uint32_t exact_size) +{ + // Require a 4-byte aligned size so the tail read below stays aligned. + if (!avmpack_is_valid(avmpack_binary, exact_size) + || exact_size < AVMPACK_SIZE + AVMPACK_END_MARKER_SIZE || (exact_size & 3) != 0) { + return false; + } + + struct AVMPackSection section; + + return read_section(avmpack_binary, exact_size, exact_size - AVMPACK_END_MARKER_SIZE, §ion) + == AVMPackSectionEnd; +} + +bool avmpack_compute_size(const void *avmpack_binary, uint32_t max_size, uint32_t *real_size) +{ + if (!avmpack_is_valid(avmpack_binary, max_size)) { + return false; + } + + uint32_t offset = AVMPACK_SIZE; + struct AVMPackSection section; + enum AVMPackSectionKind kind; + while ((kind = read_section(avmpack_binary, max_size, offset, §ion)) + == AVMPackSectionRegular) { + offset += section.size; + } + + if (kind == AVMPackSectionEnd) { + *real_size = offset + AVMPACK_END_MARKER_SIZE; + return true; + } + + return false; +} + static void in_memory_avm_pack_destructor(struct AVMPackData *obj, GlobalContext *global); const struct AVMPackInfo in_memory_avm_pack_info = { diff --git a/src/libAtomVM/avmpack.h b/src/libAtomVM/avmpack.h index 37ef159708..21232fc4b3 100644 --- a/src/libAtomVM/avmpack.h +++ b/src/libAtomVM/avmpack.h @@ -152,6 +152,34 @@ int avmpack_find_section_by_name(const void *avmpack_binary, uint32_t avmpack_si */ bool avmpack_is_valid(const void *avmpack_binary, uint32_t size); +/** + * @brief Checks that an AVM Pack of an exactly known size is complete (not truncated). + * + * @details Verifies the magic header and that the pack ends exactly with the canonical \c end + * terminator section at exact_size - 16. Does not traverse the section chain, so it + * preserves lazy loading for memory-mapped files. Use this whenever the exact size of the pack is + * known (a file size, an in-memory binary). Trailing bytes after the terminator are rejected. + * @param avmpack_binary a pointer to an AVM Pack binary. + * @param exact_size the exact size in bytes of the AVM Pack. + * @returns \c true if the pack is a complete AVM Pack, \c false otherwise. + */ +bool avmpack_check_complete(const void *avmpack_binary, uint32_t exact_size); + +/** + * @brief Validates an AVM Pack within an upper-bounded region and computes its real size. + * + * @details Verifies the magic header and walks the section chain (bounded by \p max_size, + * validating every section header) until the canonical \c end terminator is reached. Use this when + * only an upper bound is known (e.g. a flash partition larger than the image); it both validates + * the pack and recovers its real size. If the terminator is not reached within \p max_size (or a + * section header is invalid), the pack is truncated or corrupt. + * @param avmpack_binary a pointer to an AVM Pack binary. + * @param max_size the size in bytes of the region containing the AVM Pack (upper bound). + * @param real_size on success, set to the real size in bytes of the AVM Pack. + * @returns \c true if a complete AVM Pack was found within \p max_size, \c false otherwise. + */ +bool avmpack_compute_size(const void *avmpack_binary, uint32_t max_size, uint32_t *real_size); + /** * @brief Fold over all the sections in an AVM Pack. * diff --git a/src/libAtomVM/nifs.c b/src/libAtomVM/nifs.c index c5e6879c52..400c4245d1 100644 --- a/src/libAtomVM/nifs.c +++ b/src/libAtomVM/nifs.c @@ -5334,7 +5334,8 @@ static term nif_atomvm_add_avm_pack_binary(Context *ctx, int argc, term argv[]) size_t bin_size = term_binary_size(binary); - if (UNLIKELY(bin_size > UINT32_MAX || !avmpack_is_valid(term_binary_data(binary), bin_size))) { + if (UNLIKELY(bin_size > UINT32_MAX + || !avmpack_check_complete(term_binary_data(binary), (uint32_t) bin_size))) { RAISE_ERROR(BADARG_ATOM); } diff --git a/src/platforms/emscripten/src/lib/sys.c b/src/platforms/emscripten/src/lib/sys.c index 8615e0403c..6efbb51090 100644 --- a/src/platforms/emscripten/src/lib/sys.c +++ b/src/platforms/emscripten/src/lib/sys.c @@ -696,6 +696,15 @@ enum OpenAVMResult sys_open_avm_from_file( return AVM_OPEN_CANNOT_OPEN; } + if (UNLIKELY(!avmpack_check_complete(data, (uint32_t) data_size))) { + if (fetch) { + emscripten_fetch_close(fetch); + } else { + free(data); + } + return AVM_OPEN_INVALID; + } + struct ConstAVMPack *const_avm = malloc(sizeof(struct ConstAVMPack)); if (IS_NULL_PTR(const_avm)) { if (fetch) { diff --git a/src/platforms/esp32/components/avm_sys/sys.c b/src/platforms/esp32/components/avm_sys/sys.c index 5041504442..9223546b30 100644 --- a/src/platforms/esp32/components/avm_sys/sys.c +++ b/src/platforms/esp32/components/avm_sys/sys.c @@ -447,7 +447,8 @@ enum OpenAVMResult sys_open_avm_from_file(GlobalContext *global, const char *pat if (IS_NULL_PTR(part_data)) { return AVM_OPEN_CANNOT_OPEN; } - if (UNLIKELY(!avmpack_is_valid(part_data, size))) { + uint32_t avm_size; + if (UNLIKELY(!avmpack_compute_size(part_data, size, &avm_size))) { return AVM_OPEN_INVALID; } @@ -455,7 +456,7 @@ enum OpenAVMResult sys_open_avm_from_file(GlobalContext *global, const char *pat if (IS_NULL_PTR(part_avm)) { return AVM_OPEN_FAILED_ALLOC; } - avmpack_data_init(&part_avm->base, &esp32_part_avm_pack_info, size); + avmpack_data_init(&part_avm->base, &esp32_part_avm_pack_info, avm_size); part_avm->base.data = part_data; part_avm->part_handle = part_handle; avmpack_data = &part_avm->base; @@ -487,7 +488,7 @@ enum OpenAVMResult sys_open_avm_from_file(GlobalContext *global, const char *pat return AVM_OPEN_CANNOT_READ; } - if (UNLIKELY(!avmpack_is_valid(file_data, size))) { + if (UNLIKELY(!avmpack_check_complete(file_data, size))) { free(file_data); return AVM_OPEN_INVALID; } diff --git a/src/platforms/esp32/main/main.c b/src/platforms/esp32/main/main.c index 8ee2e3c199..5f6174c1db 100644 --- a/src/platforms/esp32/main/main.c +++ b/src/platforms/esp32/main/main.c @@ -95,11 +95,12 @@ void app_main() port_driver_init_all(glb); nif_collection_init_all(glb); - if (!avmpack_is_valid(startup_avm, size)) { - ESP_LOGE(TAG, "Invalid startup avmpack. size=%u", size); + uint32_t avm_size; + if (!avmpack_compute_size(startup_avm, size, &avm_size)) { + ESP_LOGE(TAG, "Invalid or truncated startup avmpack. partition size=%u", size); AVM_ABORT(); } - if (!avmpack_find_section_by_flag(startup_avm, size, BEAM_START_FLAG, BEAM_START_FLAG, + if (!avmpack_find_section_by_flag(startup_avm, avm_size, BEAM_START_FLAG, BEAM_START_FLAG, &startup_beam, &startup_beam_size, &startup_module_name)) { ESP_LOGE(TAG, "Error: Failed to locate start module in startup partition. (Did you flash a library by mistake?)"); AVM_ABORT(); @@ -110,7 +111,7 @@ void app_main() ESP_LOGE(TAG, "Memory error: Cannot allocate AVMPackData for main.avm."); AVM_ABORT(); } - avmpack_data_init(&avmpack_data->base, &const_avm_pack_info, size); + avmpack_data_init(&avmpack_data->base, &const_avm_pack_info, avm_size); avmpack_data->base.in_use = true; avmpack_data->base.data = startup_avm; synclist_append(&glb->avmpack_data, &avmpack_data->base.avmpack_head); diff --git a/src/platforms/generic_unix/lib/sys.c b/src/platforms/generic_unix/lib/sys.c index 5145b756dd..f673bd6612 100644 --- a/src/platforms/generic_unix/lib/sys.c +++ b/src/platforms/generic_unix/lib/sys.c @@ -435,7 +435,7 @@ enum OpenAVMResult sys_open_avm_from_file( if (IS_NULL_PTR(mapped)) { return AVM_OPEN_CANNOT_OPEN; } - if (UNLIKELY(!avmpack_is_valid(mapped->mapped, mapped->size))) { + if (UNLIKELY(!avmpack_check_complete(mapped->mapped, (uint32_t) mapped->size))) { return AVM_OPEN_INVALID; } diff --git a/src/platforms/rp2/src/main.c b/src/platforms/rp2/src/main.c index 8f146775a4..5313fa3913 100644 --- a/src/platforms/rp2/src/main.c +++ b/src/platforms/rp2/src/main.c @@ -84,17 +84,18 @@ static int app_main() const void *startup_beam; const char *startup_module_name; - if (!avmpack_is_valid(MAIN_AVM, XIP_SRAM_BASE - (uintptr_t) MAIN_AVM)) { + uint32_t main_avm_size = 0; + if (!avmpack_compute_size( + MAIN_AVM, (uint32_t) (XIP_SRAM_BASE - (uintptr_t) MAIN_AVM), &main_avm_size)) { sleep_ms(5000); - fprintf(stderr, "No application loaded. Please flash your application to get started.\n"); + fprintf(stderr, "main.avm is missing or truncated. Please flash your application.\n"); if (avmpack_is_valid(LIB_AVM, (uintptr_t) MAIN_AVM - (uintptr_t) LIB_AVM)) { fprintf(stderr, "Core libraries are loaded and ready.\n"); } AVM_ABORT(); } - if (!avmpack_find_section_by_flag(MAIN_AVM, (uint32_t) (XIP_SRAM_BASE - (uintptr_t) MAIN_AVM), - BEAM_START_FLAG, BEAM_START_FLAG, &startup_beam, &startup_beam_size, - &startup_module_name)) { + if (!avmpack_find_section_by_flag(MAIN_AVM, main_avm_size, BEAM_START_FLAG, BEAM_START_FLAG, + &startup_beam, &startup_beam_size, &startup_module_name)) { sleep_ms(5000); fprintf(stderr, "Fatal error: Failed to locate start module in main.avm packbeam. (Did you flash a library by mistake?)"); AVM_ABORT(); @@ -107,22 +108,22 @@ static int app_main() AVM_ABORT(); } - avmpack_data_init(&avmpack_data->base, &const_avm_pack_info, - (uint32_t) (XIP_SRAM_BASE - (uintptr_t) MAIN_AVM)); + avmpack_data_init(&avmpack_data->base, &const_avm_pack_info, main_avm_size); avmpack_data->base.data = MAIN_AVM; avmpack_data->base.in_use = true; synclist_append(&glb->avmpack_data, &avmpack_data->base.avmpack_head); - if (avmpack_is_valid(LIB_AVM, (uintptr_t) MAIN_AVM - (uintptr_t) LIB_AVM)) { + uint32_t lib_avm_size = 0; + if (avmpack_compute_size(LIB_AVM, (uint32_t) ((uintptr_t) MAIN_AVM - (uintptr_t) LIB_AVM), + &lib_avm_size)) { avmpack_data = malloc(sizeof(struct ConstAVMPack)); if (IS_NULL_PTR(avmpack_data)) { sleep_ms(5000); fprintf(stderr, "Memory error: Cannot allocate AVMPackData for lib.avm."); AVM_ABORT(); } - avmpack_data_init(&avmpack_data->base, &const_avm_pack_info, - (uint32_t) ((uintptr_t) MAIN_AVM - (uintptr_t) LIB_AVM)); + avmpack_data_init(&avmpack_data->base, &const_avm_pack_info, lib_avm_size); avmpack_data->base.data = LIB_AVM; avmpack_data->base.in_use = true; synclist_append(&glb->avmpack_data, &avmpack_data->base.avmpack_head); diff --git a/src/platforms/stm32/src/main.c b/src/platforms/stm32/src/main.c index 6decffa063..b02a794dae 100644 --- a/src/platforms/stm32/src/main.c +++ b/src/platforms/stm32/src/main.c @@ -333,10 +333,11 @@ int main(void) port_driver_init_all(glb); nif_collection_init_all(glb); - if (!avmpack_is_valid(flashed_avm, size) - || !avmpack_find_section_by_flag(flashed_avm, size, BEAM_START_FLAG, BEAM_START_FLAG, + uint32_t avm_size = 0; + if (!avmpack_compute_size(flashed_avm, size, &avm_size) + || !avmpack_find_section_by_flag(flashed_avm, avm_size, BEAM_START_FLAG, BEAM_START_FLAG, &startup_beam, &startup_beam_size, &startup_module_name)) { - AVM_LOGE(TAG, "Invalid AVM Pack"); + AVM_LOGE(TAG, "Invalid or truncated AVM Pack"); AVM_ABORT(); } AVM_LOGI(TAG, "Booting file mapped at: %p, size: %lu", flashed_avm, startup_beam_size); @@ -347,7 +348,7 @@ int main(void) AVM_LOGE(TAG, "Memory error: Cannot allocate AVMPackData."); AVM_ABORT(); } - avmpack_data_init(&avmpack_data->base, &const_avm_pack_info, size); + avmpack_data_init(&avmpack_data->base, &const_avm_pack_info, avm_size); avmpack_data->base.data = flashed_avm; avmpack_data->base.in_use = true; synclist_append(&glb->avmpack_data, &avmpack_data->base.avmpack_head); diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 3c7f880c49..58784127ca 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -27,6 +27,7 @@ add_executable(test-heap test-heap.c) add_executable(test-jit_stream_flash test-jit_stream_flash.c ../src/libAtomVM/jit_stream_flash.c) add_executable(test-mailbox test-mailbox.c) add_executable(test-structs test-structs.c) +add_executable(test-avmpack test-avmpack.c) target_compile_features(test-erlang PUBLIC c_std_11) target_compile_features(test-enif PUBLIC c_std_11) @@ -34,6 +35,7 @@ target_compile_features(test-heap PUBLIC c_std_11) target_compile_features(test-jit_stream_flash PUBLIC c_std_11) target_compile_features(test-mailbox PUBLIC c_std_11) target_compile_features(test-structs PUBLIC c_std_11) +target_compile_features(test-avmpack PUBLIC c_std_11) if(CMAKE_COMPILER_IS_GNUCC) target_compile_options(test-erlang PUBLIC -Wall -pedantic -Wextra -ggdb) @@ -42,6 +44,7 @@ if(CMAKE_COMPILER_IS_GNUCC) target_compile_options(test-jit_stream_flash PUBLIC -Wall -pedantic -Wextra -ggdb) target_compile_options(test-mailbox PUBLIC -Wall -pedantic -Wextra -ggdb) target_compile_options(test-structs PUBLIC -Wall -pedantic -Wextra -ggdb) + target_compile_options(test-avmpack PUBLIC -Wall -pedantic -Wextra -ggdb) endif() if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux") @@ -56,6 +59,7 @@ if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux") target_link_libraries(test-jit_stream_flash PRIVATE ${LIBRT}) target_link_libraries(test-mailbox PRIVATE ${LIBRT}) target_link_libraries(test-structs PRIVATE ${LIBRT}) + target_link_libraries(test-avmpack PRIVATE ${LIBRT}) else() # might also be in libc check_library_exists(c clock_gettime "" HAVE_CLOCK_GETTIME) @@ -70,6 +74,7 @@ if (MbedTLS_FOUND) target_link_libraries(test-jit_stream_flash PRIVATE MbedTLS::mbedtls) target_link_libraries(test-mailbox PRIVATE MbedTLS::mbedtls) target_link_libraries(test-structs PRIVATE MbedTLS::mbedtls) + target_link_libraries(test-avmpack PRIVATE MbedTLS::mbedtls) endif() set( @@ -97,6 +102,7 @@ target_include_directories(test-heap PRIVATE ../src/libAtomVM) target_include_directories(test-jit_stream_flash PRIVATE ../src/libAtomVM ${CMAKE_CURRENT_SOURCE_DIR}) target_include_directories(test-mailbox PRIVATE ../src/libAtomVM) target_include_directories(test-structs PRIVATE ../src/libAtomVM) +target_include_directories(test-avmpack PRIVATE ../src/libAtomVM) target_link_libraries(test-erlang PRIVATE libAtomVM libAtomVM${PLATFORM_LIB_SUFFIX}) target_link_libraries(test-enif PRIVATE libAtomVM libAtomVM${PLATFORM_LIB_SUFFIX}) target_link_libraries(test-heap PRIVATE libAtomVM libAtomVM${PLATFORM_LIB_SUFFIX}) @@ -108,6 +114,7 @@ endif() target_link_libraries(test-jit_stream_flash PRIVATE libAtomVM libAtomVM${PLATFORM_LIB_SUFFIX}) target_link_libraries(test-mailbox PRIVATE libAtomVM libAtomVM${PLATFORM_LIB_SUFFIX}) target_link_libraries(test-structs PRIVATE libAtomVM libAtomVM${PLATFORM_LIB_SUFFIX}) +target_link_libraries(test-avmpack PRIVATE libAtomVM libAtomVM${PLATFORM_LIB_SUFFIX}) # Except for XCode, also compile beams if (NOT "${CMAKE_GENERATOR}" MATCHES "Xcode") diff --git a/tests/test-avmpack.c b/tests/test-avmpack.c new file mode 100644 index 0000000000..f32f11e3f8 --- /dev/null +++ b/tests/test-avmpack.c @@ -0,0 +1,160 @@ +/* + * This file is part of AtomVM. + * + * Copyright 2026 Davide Bettio + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 OR LGPL-2.1-or-later + */ + +#include +#include +#include +#include + +#include "avmpack.h" + +static int failures = 0; + +#define CHECK(cond) \ + do { \ + if (!(cond)) { \ + fprintf(stderr, "FAIL line %d: %s\n", __LINE__, #cond); \ + failures++; \ + } \ + } while (0) + +static const uint8_t avmpack_header[24] = { 0x23, 0x21, 0x2f, 0x75, 0x73, 0x72, 0x2f, 0x62, 0x69, + 0x6e, 0x2f, 0x65, 0x6e, 0x76, 0x20, 0x41, 0x74, 0x6f, 0x6d, 0x56, 0x4d, 0x0a, 0x00, 0x00 }; + +static void put_u32_be(uint8_t *p, uint32_t v) +{ + p[0] = (uint8_t) (v >> 24); + p[1] = (uint8_t) (v >> 16); + p[2] = (uint8_t) (v >> 8); + p[3] = (uint8_t) v; +} + +static size_t pad4(size_t n) +{ + return (n + 3) & ~(size_t) 3; +} + +static size_t put_section(uint8_t *buf, size_t off, const char *name, uint32_t flags, size_t content) +{ + size_t name_len = strlen(name) + 1; + size_t name_pad = pad4(name_len); + size_t content_pad = pad4(content); + put_u32_be(buf + off, (uint32_t) (12 + name_pad + content_pad)); + put_u32_be(buf + off + 4, flags); + put_u32_be(buf + off + 8, 0); + memcpy(buf + off + 12, name, name_len); + memset(buf + off + 12 + name_len, 0, name_pad - name_len); + memset(buf + off + 12 + name_pad, 0, content_pad); + + return off + 12 + name_pad + content_pad; +} + +static size_t put_end(uint8_t *buf, size_t off) +{ + put_u32_be(buf + off, 0); + put_u32_be(buf + off + 4, 0); + put_u32_be(buf + off + 8, 0); + memcpy(buf + off + 12, "end", 4); + + return off + 16; +} + +int main(void) +{ + uint8_t buf[4096]; + const void *ptr = NULL; + uint32_t sz = 0; + const char *name = NULL; + uint32_t real_size = 0; + + // --- A valid pack: header + one BEAM section + terminator --- + memset(buf, 0, sizeof(buf)); + memcpy(buf, avmpack_header, 24); + size_t off = put_section(buf, 24, "mymod.beam", BEAM_START_FLAG, 16); + uint32_t pack_size = (uint32_t) put_end(buf, off); + + CHECK(avmpack_is_valid(buf, pack_size)); + CHECK(avmpack_check_complete(buf, pack_size)); + CHECK(avmpack_compute_size(buf, sizeof(buf), &real_size) && real_size == pack_size); + CHECK(avmpack_find_section_by_name(buf, pack_size, "mymod.beam", &ptr, &sz) == 1); + CHECK(ptr == buf + 24 + 12 + 12); // header + section header + padded "mymod.beam\0" + CHECK(avmpack_find_section_by_name(buf, pack_size, "missing", &ptr, &sz) == 0); + CHECK(avmpack_find_section_by_flag(buf, pack_size, BEAM_START_FLAG, BEAM_START_FLAG, &ptr, &sz, &name) + == 1); + CHECK(name != NULL && strcmp(name, "mymod.beam") == 0); + // find_by_flag for the terminator still returns it (the JIT cache relies on this) + CHECK(avmpack_find_section_by_flag(buf, pack_size, END_OF_FILE_MASK, END_OF_FILE, &ptr, &sz, &name) + == 1); + CHECK(name != NULL && strcmp(name, "end") == 0); + + // --- Truncated pack: section present, terminator chopped off, 0xFF tail --- + memset(buf, 0xFF, sizeof(buf)); + memcpy(buf, avmpack_header, 24); + put_section(buf, 24, "mymod.beam", BEAM_CODE_FLAG, 16); + CHECK(avmpack_compute_size(buf, sizeof(buf), &real_size) == false); + // a miss must stop at the bound and return 0 without reading past the pack + CHECK(avmpack_find_section_by_name(buf, sizeof(buf), "missing", &ptr, &sz) == 0); + // a hit before the truncation is still reachable + CHECK(avmpack_find_section_by_name(buf, sizeof(buf), "mymod.beam", &ptr, &sz) == 1); + // a file cut exactly at the section end has no tail terminator + CHECK(avmpack_check_complete(buf, 24 + 12 + 12 + 16) == false); + + // --- Oversized section size (the original out-of-bounds scenario) --- + memset(buf, 0xFF, sizeof(buf)); + memcpy(buf, avmpack_header, 24); + put_u32_be(buf + 24, 0x10000); // claims 64 KiB, far past the bounded view + put_u32_be(buf + 24 + 4, 0); + put_u32_be(buf + 24 + 8, 0); + memcpy(buf + 24 + 12, "x", 2); + CHECK(avmpack_find_section_by_name(buf, 64, "anything", &ptr, &sz) == 0); + CHECK(avmpack_compute_size(buf, 64, &real_size) == false); + + // --- 0x00-filled region must not be mistaken for the terminator --- + memset(buf, 0, sizeof(buf)); + memcpy(buf, avmpack_header, 24); + CHECK(avmpack_compute_size(buf, sizeof(buf), &real_size) == false); + CHECK(avmpack_find_section_by_flag(buf, sizeof(buf), END_OF_FILE_MASK, END_OF_FILE, &ptr, &sz, &name) + == 0); + + // --- Section name with no NUL terminator inside the section --- + memset(buf, 'A', sizeof(buf)); + memcpy(buf, avmpack_header, 24); + put_u32_be(buf + 24, 64); // in-range size, but the name region is all 'A' + put_u32_be(buf + 24 + 4, 0); + put_u32_be(buf + 24 + 8, 0); + CHECK(avmpack_find_section_by_name(buf, 24 + 64 + 16, "x", &ptr, &sz) == 0); + + // --- Misaligned section size --- + memset(buf, 0xFF, sizeof(buf)); + memcpy(buf, avmpack_header, 24); + put_u32_be(buf + 24, 17); // not a multiple of 4 + put_u32_be(buf + 24 + 4, 0); + put_u32_be(buf + 24 + 8, 0); + memcpy(buf + 24 + 12, "x", 2); + CHECK(avmpack_compute_size(buf, sizeof(buf), &real_size) == false); + + if (failures == 0) { + fprintf(stderr, "All avmpack tests passed!\n"); + return 0; + } + fprintf(stderr, "%d avmpack test(s) failed.\n", failures); + + return 1; +} From 3dcd5e71720e03af2b00f50216fef8a44346319a Mon Sep 17 00:00:00 2001 From: Davide Bettio Date: Mon, 22 Jun 2026 11:53:56 +0000 Subject: [PATCH 3/3] Make ESP32 build reject an oversized boot.avm Fail at configure time when boot.avm does not fit its partition. Signed-off-by: Davide Bettio --- src/platforms/esp32/CMakeLists.txt | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/platforms/esp32/CMakeLists.txt b/src/platforms/esp32/CMakeLists.txt index 35fdcef0ff..7bd0eefc26 100644 --- a/src/platforms/esp32/CMakeLists.txt +++ b/src/platforms/esp32/CMakeLists.txt @@ -137,6 +137,13 @@ if (NOT ("${BOOT_LIBS}" STREQUAL "NONE")) Consult https://doc.atomvm.org/main/build-instructions.html for build instructions.") else() partition_table_get_partition_info(lib_offset "--partition-name boot.avm" "offset") + partition_table_get_partition_info(lib_size "--partition-name boot.avm" "size") + file(SIZE "${BOOT_LIB_PATH}" boot_avm_size) + math(EXPR boot_part_size "${lib_size}") + if (boot_avm_size GREATER boot_part_size) + message(FATAL_ERROR "${BOOT_LIBS} (${boot_avm_size} bytes) does not fit in the boot.avm \ + partition (${boot_part_size} bytes). Enlarge the boot.avm partition or reduce the boot libraries.") + endif() esptool_py_flash_target_image(flash boot.avm "${lib_offset}" "${BOOT_LIB_PATH}") endif() endif()