diff --git a/.github/workflows/build-and-test-macos.yaml b/.github/workflows/build-and-test-macos.yaml index 1d18a21030..db20b0310c 100644 --- a/.github/workflows/build-and-test-macos.yaml +++ b/.github/workflows/build-and-test-macos.yaml @@ -259,6 +259,11 @@ jobs: run: | ./tests/test-term + - name: "Test: test-avmpack" + working-directory: build + run: | + ./tests/test-avmpack + - name: "Test: test_etest.avm" timeout-minutes: 5 working-directory: build diff --git a/.github/workflows/build-and-test-on-freebsd.yaml b/.github/workflows/build-and-test-on-freebsd.yaml index 0e32cd03c4..6ffa31155f 100644 --- a/.github/workflows/build-and-test-on-freebsd.yaml +++ b/.github/workflows/build-and-test-on-freebsd.yaml @@ -164,6 +164,13 @@ jobs: cd build ./tests/test-term + - name: "Test: test-avmpack" + shell: freebsd {0} + run: | + cd $GITHUB_WORKSPACE; + cd build + ./tests/test-avmpack + - name: "Test: test_etest.avm" shell: freebsd {0} run: | diff --git a/.github/workflows/build-and-test.yaml b/.github/workflows/build-and-test.yaml index 48de1aa824..5902fa5664 100644 --- a/.github/workflows/build-and-test.yaml +++ b/.github/workflows/build-and-test.yaml @@ -888,6 +888,21 @@ jobs: ulimit -c unlimited ./tests/test-term + - name: "Test: test-avmpack with valgrind" + if: matrix.library-arch == '' + timeout-minutes: 10 + working-directory: build + run: | + ulimit -c unlimited + valgrind --error-exitcode=1 ./tests/test-avmpack + + - name: "Test: test-avmpack" + timeout-minutes: 10 + working-directory: build + run: | + ulimit -c unlimited + ./tests/test-avmpack + - name: "Test: test_etest.avm with valgrind" if: matrix.library-arch == '' timeout-minutes: 5 diff --git a/.github/workflows/build-linux-artifacts.yaml b/.github/workflows/build-linux-artifacts.yaml index cd65b9cc87..0dfecdc7a4 100644 --- a/.github/workflows/build-linux-artifacts.yaml +++ b/.github/workflows/build-linux-artifacts.yaml @@ -237,6 +237,7 @@ jobs: make test-mailbox && make test-structs && make test-term && + make test-avmpack && file ./tests/test-erlang && ./tests/test-erlang -s prime_smp && file ./tests/test-enif && @@ -249,6 +250,8 @@ jobs: ./tests/test-structs && file ./tests/test-term && ./tests/test-term && + file ./tests/test-avmpack && + ./tests/test-avmpack && file ./src/AtomVM && ./src/AtomVM tests/libs/etest/test_etest.avm && ./src/AtomVM tests/libs/estdlib/test_estdlib.avm && diff --git a/CHANGELOG.md b/CHANGELOG.md index b2594d13b6..cb299fadbb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed - Stopped CI testing on FreeBSD 13, which has reached end of life. +- `.avm` files, binaries and the pack embedded in an escript must now end exactly with the pack + terminator: data after the end of the pack (for example a padded partition dump passed to + `atomvm:add_avm_pack_binary/2`) is rejected when loaded ### Fixed - Fixed maps decoded by `binary_to_term/1,2` and received over distribution keeping the @@ -28,6 +31,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 bytes that the other scheduler then took - Fixed ESP32 SPI pin typespecs to allow `-1` for unused pins. - Fixed ESP32 double free crash when closing SPI ports +- Fixed the VM crashing (`LoadProhibited` on ESP32) when `atomvm:read_priv/2` or a module load + looked up a name that is not in a truncated or corrupt AVM pack, such as an image larger than + its flash partition +- Fixed `code:get_object_code/1` returning extra bytes after the module (the start of the next + pack entry) for modules loaded from an AVM pack +- Fixed truncated AVM packs, such as an image larger than its flash partition, being accepted at + load and failing later in unpredictable ways; they are now rejected when loaded, with an error + at boot or from `atomvm:add_avm_pack_file/2` and `atomvm:add_avm_pack_binary/2` +- Fixed `atomvm:read_priv/2` returning a binary that extends past its pack entry, reading other + entries or memory after the pack, when the entry's recorded size is corrupt; it now raises + `invalid_avm`. A resource that is genuinely absent still returns `undefined` ## [0.7.0-beta.0] - 2026-09-28 diff --git a/doc/src/apidocs/libatomvm/functions.rst b/doc/src/apidocs/libatomvm/functions.rst index e25a540eef..1e35af399a 100644 --- a/doc/src/apidocs/libatomvm/functions.rst +++ b/doc/src/apidocs/libatomvm/functions.rst @@ -21,9 +21,11 @@ Functions .. doxygenfunction:: atom_string_len .. doxygenfunction:: atom_string_to_c .. doxygenfunction:: atom_write_mfa +.. doxygenfunction:: avmpack_compute_size .. doxygenfunction:: avmpack_find_section_by_flag .. doxygenfunction:: avmpack_find_section_by_name .. doxygenfunction:: avmpack_fold +.. doxygenfunction:: avmpack_is_complete .. doxygenfunction:: avmpack_is_valid .. doxygenfunction:: bitstring_copy_bits .. doxygenfunction:: bitstring_copy_bits_incomplete_bytes 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/libs/eavmlib/src/atomvm.erl b/libs/eavmlib/src/atomvm.erl index e3be29b6a6..12a13a417d 100644 --- a/libs/eavmlib/src/atomvm.erl +++ b/libs/eavmlib/src/atomvm.erl @@ -184,11 +184,14 @@ rand_bytes(_Len) -> %%----------------------------------------------------------------------------- %% @param App application name. %% @param Path path to the resource. -%% @returns Binary containing the resource content. +%% @returns Binary containing the resource content, or `undefined' if it is not in any loaded +%% AVM pack. %% @doc This function allows to fetch priv/ resources content. +%% Raises `invalid_avm' if the resource is present but its recorded size does not fit +%% the AVM pack section holding it. %% @end %%----------------------------------------------------------------------------- --spec read_priv(App :: atom(), Path :: list()) -> binary(). +-spec read_priv(App :: atom(), Path :: list()) -> binary() | undefined. read_priv(_App, _Path) -> erlang:nif_error(undefined). diff --git a/src/libAtomVM/avmpack.c b/src/libAtomVM/avmpack.c index 624fa82493..317261f107 100644 --- a/src/libAtomVM/avmpack.c +++ b/src/libAtomVM/avmpack.c @@ -27,8 +27,11 @@ #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) +static inline size_t pad(size_t size) { return ((size + 4 - 1) >> 2) << 2; } @@ -52,83 +55,173 @@ 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 +{ + const char *name; + const void *data; + uint32_t size; + uint32_t data_size; + uint32_t flags; +}; - 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 bool is_end_marker_name(const char *name) +{ + return (name[0] == 'e' || name[0] == 'E') && (name[1] == 'n' || name[1] == 'N') + && (name[2] == 'd' || name[2] == 'D') && name[3] == '\0'; +} - *ptr = sizes + 3 + section_name_len / sizeof(uint32_t); - *size = ENDIAN_SWAP_32(*sizes); - *name = (const char *) (sizes + 3); - return 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; + } + + const uint8_t *header = (const uint8_t *) avmpack_binary + offset; + uint32_t section_size = READ_32_UNALIGNED(header); + uint32_t flags = READ_32_UNALIGNED(header + 4); + const char *name = (const char *) (header + AVMPACK_SECTION_HEADER_SIZE); + + if (section_size == 0) { + if (flags == 0 && is_end_marker_name(name)) { + section->size = 0; + section->data_size = 0; + section->flags = 0; + section->name = name; + section->data = header + AVMPACK_END_MARKER_SIZE; + return AVMPackSectionEnd; } + return AVMPackSectionInvalid; + } + + if (section_size < AVMPACK_MIN_SECTION_SIZE || (section_size & 3) != 0 + || section_size > avmpack_size - offset) { + return AVMPackSectionInvalid; + } - offset += ENDIAN_SWAP_32(*sizes); + size_t name_region = section_size - AVMPACK_SECTION_HEADER_SIZE; + const char *nul = memchr(name, '\0', name_region); + if (nul == NULL) { + return AVMPackSectionInvalid; + } + size_t padded_name_len = pad(nul - name + 1); - } while (*flags); + section->size = section_size; + section->data_size = section_size - AVMPACK_SECTION_HEADER_SIZE - padded_name_len; + section->flags = flags; + section->name = name; + section->data = header + 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_value, 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_value) { + *ptr = section.data; + *size = section.data_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.data_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; } +bool avmpack_is_complete(const void *avmpack_binary, size_t exact_size) +{ +#if SIZE_MAX > UINT32_MAX + if (exact_size > UINT32_MAX) { + return false; + } +#endif + uint32_t size = exact_size; + // The header and every section are multiples of 4, so a complete pack's size must be too. + if (!avmpack_is_valid(avmpack_binary, size) + || size < AVMPACK_SIZE + AVMPACK_END_MARKER_SIZE || (size & 3) != 0) { + return false; + } + + struct AVMPackSection section; + + return read_section(avmpack_binary, size, 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 a68e6e3378..d8099ba26c 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,50 +105,86 @@ 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. * @param avmpack_binary a pointer to valid AVM Pack file data. + * @param avmpack_size the size in bytes of the AVM Pack. * @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 name the section name, as defined in the module header. + * @param[out] ptr will point to the found file section. + * @param[out] size the payload size at \p ptr, without the section header and the padded name; not + * updated if the section has not been found. + * @param[out] 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. * @param avmpack_binary a pointer to valid AVM Pack file data. + * @param avmpack_size the size in bytes of the AVM Pack. * @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[out] ptr will point to the found file section; not updated if the section has not been + * found. + * @param[out] size the payload size at \p ptr, without the section header and the padded name; not + * updated if the section has not been found. * @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 Checks the 24-byte magic header only; see \c avmpack_is_complete and + * \c avmpack_compute_size for truncation. * @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. */ bool avmpack_is_valid(const void *avmpack_binary, uint32_t size); +/** + * @brief Returns \c true if an AVM Pack of exactly known size ends with its \c end terminator. + * + * @details Reads the header and the last 16 bytes only, so a memory-mapped pack stays lazily + * paged. Trailing bytes after the terminator and sizes above \c UINT32_MAX 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 complete, \c false otherwise. + */ +bool avmpack_is_complete(const void *avmpack_binary, size_t exact_size); + +/** + * @brief Finds the end of an AVM Pack within a region and returns its real size. + * + * @details Walks the section chain, validating every header, until the \c end terminator or + * \p max_size is reached. + * @param avmpack_binary a pointer to an AVM Pack binary. + * @param max_size the size in bytes of the region containing the AVM Pack. + * @param[out] real_size set to the size in bytes of the AVM Pack on success. + * @returns \c true if the terminator 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. * * @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. * @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/defaultatoms.def b/src/libAtomVM/defaultatoms.def index 8911bc90a0..bfa4b968a4 100644 --- a/src/libAtomVM/defaultatoms.def +++ b/src/libAtomVM/defaultatoms.def @@ -230,3 +230,5 @@ X(REPLY_DEMONITOR_ATOM, "\xF", "reply_demonitor") X(REPLY_ATOM, "\x5", "reply") X(TAG_ATOM, "\x3", "tag") X(PRIORITY_ATOM, "\x8", "priority") + +X(INVALID_AVM_ATOM, "\xB", "invalid_avm") diff --git a/src/libAtomVM/globalcontext.c b/src/libAtomVM/globalcontext.c index 971ab5a6b6..3ac1df286a 100644 --- a/src/libAtomVM/globalcontext.c +++ b/src/libAtomVM/globalcontext.c @@ -753,7 +753,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..137040115c 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) { @@ -154,6 +158,12 @@ static struct JITEntry *globalcontext_find_first_jit_entry(GlobalContext *global } synclist_unlock(&global->avmpack_data); + if (IS_NULL_PTR(max_end_offset)) { + // No pack has a terminator, so there is no place to append + *is_valid = false; + return NULL; + } + uintptr_t max_end_offset_page = ((((uintptr_t) max_end_offset) - 1) & ~(FLASH_SECTOR_SIZE - 1)); *is_valid = valid_cache; @@ -180,17 +190,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"; @@ -398,6 +417,10 @@ static term nif_jit_stream_flash_new(Context *ctx, int argc, term argv[]) // No valid entries, get the first position bool is_valid; new_entry = globalcontext_find_first_jit_entry(ctx->global, &is_valid); + if (IS_NULL_PTR(new_entry)) { + fprintf(stderr, "No valid position to append JIT code to\n"); + RAISE_ERROR(BADARG_ATOM); + } } else { // Get position after last valid entry new_entry = jit_entry_next(last_valid_entry); diff --git a/src/libAtomVM/nifs.c b/src/libAtomVM/nifs.c index 94677bd36a..d9916f948d 100644 --- a/src/libAtomVM/nifs.c +++ b/src/libAtomVM/nifs.c @@ -5670,7 +5670,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(!avmpack_is_complete(term_binary_data(binary), bin_size))) { RAISE_ERROR(BADARG_ATOM); } @@ -5686,7 +5686,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, 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); @@ -5699,7 +5699,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, bin_size); const_avm->base.data = (const uint8_t *) term_binary_data(binary); avmpack_data = &const_avm->base; @@ -5717,7 +5717,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, bin_size); in_memory_avm->base.data = (const uint8_t *) allocated_data; avmpack_data = &in_memory_avm->base; @@ -5740,7 +5740,7 @@ static term open_avm_error_tuple(Context *ctx, enum OpenAVMResult result) RAISE_ERROR(OUT_OF_MEMORY_ATOM); break; case AVM_OPEN_INVALID: - reason = globalcontext_make_atom(ctx->global, ATOM_STR("\xB", "invalid_avm")); + reason = INVALID_AVM_ATOM; break; case AVM_OPEN_CANNOT_OPEN: reason = globalcontext_make_atom(ctx->global, ATOM_STR("\xB", "cannot_open")); @@ -5857,7 +5857,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); @@ -5932,29 +5933,48 @@ static term nif_atomvm_read_priv(Context *ctx, int argc, term argv[]) uint32_t size; struct ListHead *item; term result = UNDEFINED_ATOM; + bool invalid_pack = false; struct ListHead *avmpack_data = synclist_rdlock(&glb->avmpack_data); LIST_FOR_EACH (item, avmpack_data) { 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); + if (avmpack_find_section_by_name( + avmpack_data->data, avmpack_data->size, complete_path, &bin_data, &size)) { + // A normal file is <> + if (UNLIKELY(size < sizeof(uint32_t))) { + avmpack_data->in_use = prev_in_use; + invalid_pack = true; + break; + } + uint32_t file_size = READ_32_UNALIGNED(bin_data); + if (UNLIKELY(file_size > size - sizeof(uint32_t))) { + avmpack_data->in_use = prev_in_use; + invalid_pack = true; + break; + } free(complete_path); complete_path = NULL; - if (UNLIKELY(memory_ensure_free_opt(ctx, TERM_BOXED_REFC_BINARY_SIZE, MEMORY_CAN_SHRINK) != MEMORY_GC_OK)) { + 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); + result = term_from_const_binary( + (const uint8_t *) bin_data + sizeof(uint32_t), file_size, &ctx->heap, ctx->global); break; - } else { - avmpack_data->in_use = prev_in_use; } + avmpack_data->in_use = prev_in_use; } synclist_unlock(&glb->avmpack_data); free(complete_path); + + if (UNLIKELY(invalid_pack)) { + RAISE_ERROR(INVALID_AVM_ATOM); + } + return result; } @@ -6495,7 +6515,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; @@ -6511,7 +6531,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 3263f1f8f9..45bd2d923a 100644 --- a/src/platforms/emscripten/src/lib/sys.c +++ b/src/platforms/emscripten/src/lib/sys.c @@ -790,11 +790,21 @@ 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; } + if (UNLIKELY(!avmpack_is_complete(data, 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) { @@ -804,7 +814,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, 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 b3a96428e5..981710f07c 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 679405634a..15cecfcb67 100644 --- a/src/platforms/esp32/components/avm_sys/sys.c +++ b/src/platforms/esp32/components/avm_sys/sys.c @@ -455,7 +455,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; } @@ -463,7 +464,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, avm_size); part_avm->base.data = part_data; part_avm->part_handle = part_handle; avmpack_data = &part_avm->base; @@ -495,7 +496,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_is_complete(file_data, size))) { free(file_data); return AVM_OPEN_INVALID; } @@ -505,7 +506,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..5f6174c1db 100644 --- a/src/platforms/esp32/main/main.c +++ b/src/platforms/esp32/main/main.c @@ -95,11 +95,13 @@ 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, BEAM_START_FLAG, BEAM_START_FLAG, &startup_beam, &startup_beam_size, &startup_module_name)) { + 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(); } @@ -109,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); + 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/esp32/test/main/test_main.c b/src/platforms/esp32/test/main/test_main.c index 9fbd7c8d6c..e1c5675e12 100644 --- a/src/platforms/esp32/test/main/test_main.c +++ b/src/platforms/esp32/test/main/test_main.c @@ -160,12 +160,13 @@ term avm_test_case(const char *test_module) port_driver_init_all(glb); nif_collection_init_all(glb); - TEST_ASSERT(avmpack_is_valid(main_avm, size) != 0); + uint32_t main_avm_size; + TEST_ASSERT(avmpack_compute_size(main_avm, size, &main_avm_size)); 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, main_avm_size); avmpack_data->base.in_use = true; avmpack_data->base.data = main_avm; synclist_append(&glb->avmpack_data, &avmpack_data->base.avmpack_head); @@ -209,11 +210,12 @@ TEST_CASE("test_jit_compile", "[test_run]") port_driver_init_all(glb); nif_collection_init_all(glb); - TEST_ASSERT(avmpack_is_valid(main_avm, size) != 0); + uint32_t main_avm_size; + TEST_ASSERT(avmpack_compute_size(main_avm, size, &main_avm_size)); 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, main_avm_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 617c4f353d..2f7f0dcfbb 100644 --- a/src/platforms/generic_unix/lib/sys.c +++ b/src/platforms/generic_unix/lib/sys.c @@ -439,7 +439,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_is_complete(mapped->mapped, mapped->size))) { return AVM_OPEN_INVALID; } @@ -448,7 +448,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, 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..0aeeca723b 100644 --- a/src/platforms/generic_unix/main.c +++ b/src/platforms/generic_unix/main.c @@ -194,6 +194,12 @@ int main(int argc, char **argv) Module *startup_module = NULL; if (has_embedded_avm) { + if (UNLIKELY(!avmpack_is_complete(embedded_data, embedded_size))) { + fprintf(stderr, "Embedded AVM pack is invalid or truncated.\n"); + globalcontext_destroy(glb); + return EXIT_FAILURE; + } + struct AVMPackData *avmpack_data = malloc(sizeof(struct AVMPackData)); if (!avmpack_data) { fprintf(stderr, "Failed to allocate memory for embedded AVM pack.\n"); @@ -201,7 +207,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, 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 +233,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/lib/rp2_sys.h b/src/platforms/rp2/src/lib/rp2_sys.h index 77b2918c88..b225e981bd 100644 --- a/src/platforms/rp2/src/lib/rp2_sys.h +++ b/src/platforms/rp2/src/lib/rp2_sys.h @@ -27,6 +27,7 @@ #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wpedantic" +#include #include #include @@ -43,6 +44,10 @@ #define EVENT_QUEUE_LEN 16 +// 16 MiB XIP window of flash chip select 0, always mapped on RP2040 and RP2350 (a smaller chip +// mirrors), unlike PICO_FLASH_SIZE_BYTES, which a board file can understate +#define RP2_XIP_CS0_END (XIP_BASE + 16 * 1024 * 1024) + typedef queue_t *listener_event_t; struct EventListener diff --git a/src/platforms/rp2/src/main.c b/src/platforms/rp2/src/main.c index 5954aac5b3..98131f6d45 100644 --- a/src/platforms/rp2/src/main.c +++ b/src/platforms/rp2/src/main.c @@ -84,15 +84,24 @@ 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; + uint32_t lib_avm_size = 0; + if (!avmpack_is_valid(MAIN_AVM, RP2_XIP_CS0_END - (uintptr_t) MAIN_AVM)) { sleep_ms(5000); fprintf(stderr, "No application loaded. Please flash your application to get started.\n"); - if (avmpack_is_valid(LIB_AVM, (uintptr_t) MAIN_AVM - (uintptr_t) LIB_AVM)) { + if (avmpack_compute_size(LIB_AVM, (uintptr_t) MAIN_AVM - (uintptr_t) LIB_AVM, + &lib_avm_size)) { fprintf(stderr, "Core libraries are loaded and ready.\n"); } 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_compute_size(MAIN_AVM, RP2_XIP_CS0_END - (uintptr_t) MAIN_AVM, &main_avm_size)) { + sleep_ms(5000); + fprintf(stderr, "main.avm is truncated or corrupt. Please flash your application.\n"); + AVM_ABORT(); + } + 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(); @@ -105,23 +114,28 @@ 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, 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)) { + if (avmpack_compute_size(LIB_AVM, (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); + 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); + } else if (avmpack_is_valid(LIB_AVM, (uintptr_t) MAIN_AVM - (uintptr_t) LIB_AVM)) { + sleep_ms(5000); + fprintf(stderr, "lib.avm is truncated or corrupt. Please flash the core libraries.\n"); + AVM_ABORT(); } else { fprintf(stderr, "Warning: invalid lib.avm packbeam\n"); } diff --git a/src/platforms/rp2/tests/test_main.c b/src/platforms/rp2/tests/test_main.c index 8aebf51d06..e725954083 100644 --- a/src/platforms/rp2/tests/test_main.c +++ b/src/platforms/rp2/tests/test_main.c @@ -98,14 +98,15 @@ static term avm_test_case(const char *test_module) nif_collection_init_all(glb); - if (!avmpack_is_valid(MAIN_AVM, XIP_SRAM_BASE - (uintptr_t) MAIN_AVM)) { - TEST_FAIL_MESSAGE("Fatal error: invalid main.avm packbeam"); + uint32_t main_avm_size = 0; + if (!avmpack_compute_size(MAIN_AVM, RP2_XIP_CS0_END - (uintptr_t) MAIN_AVM, &main_avm_size)) { + TEST_FAIL_MESSAGE("Fatal error: invalid or truncated main.avm packbeam"); return term_invalid_term(); } 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, main_avm_size); 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..b02a794dae 100644 --- a/src/platforms/stm32/src/main.c +++ b/src/platforms/stm32/src/main.c @@ -333,8 +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, BEAM_START_FLAG, BEAM_START_FLAG, &startup_beam, &startup_beam_size, &startup_module_name)) { - AVM_LOGE(TAG, "Invalid AVM Pack"); + 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 or truncated AVM Pack"); AVM_ABORT(); } AVM_LOGI(TAG, "Booting file mapped at: %p, size: %lu", flashed_avm, startup_beam_size); @@ -345,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); + 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 2ba0944bab..f724770f4a 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -28,6 +28,7 @@ add_executable(test-jit_stream_flash test-jit_stream_flash.c ../src/libAtomVM/ji add_executable(test-mailbox test-mailbox.c) add_executable(test-structs test-structs.c) add_executable(test-term test-term.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) @@ -36,6 +37,7 @@ 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-term 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) @@ -45,6 +47,7 @@ if(CMAKE_COMPILER_IS_GNUCC) target_compile_options(test-mailbox PUBLIC -Wall -pedantic -Wextra -ggdb) target_compile_options(test-structs PUBLIC -Wall -pedantic -Wextra -ggdb) target_compile_options(test-term PUBLIC -Wall -pedantic -Wextra -ggdb) + target_compile_options(test-avmpack PUBLIC -Wall -pedantic -Wextra -ggdb) endif() if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux") @@ -60,6 +63,7 @@ if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux") target_link_libraries(test-mailbox PRIVATE ${LIBRT}) target_link_libraries(test-structs PRIVATE ${LIBRT}) target_link_libraries(test-term PRIVATE ${LIBRT}) + target_link_libraries(test-avmpack PRIVATE ${LIBRT}) else() # might also be in libc check_library_exists(c clock_gettime "" HAVE_CLOCK_GETTIME) @@ -75,6 +79,7 @@ if (MbedTLS_FOUND) target_link_libraries(test-mailbox PRIVATE MbedTLS::mbedtls) target_link_libraries(test-structs PRIVATE MbedTLS::mbedtls) target_link_libraries(test-term PRIVATE MbedTLS::mbedtls) + target_link_libraries(test-avmpack PRIVATE MbedTLS::mbedtls) endif() set( @@ -104,6 +109,7 @@ target_include_directories(test-jit_stream_flash PRIVATE ../src/libAtomVM ${CMAK target_include_directories(test-mailbox PRIVATE ../src/libAtomVM) target_include_directories(test-structs PRIVATE ../src/libAtomVM) target_include_directories(test-term 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}) @@ -116,6 +122,7 @@ target_link_libraries(test-jit_stream_flash PRIVATE libAtomVM libAtomVM${PLATFOR 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-term 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") @@ -142,6 +149,7 @@ if (COVERAGE) append_coverage_compiler_flags_to_target(test-mailbox) append_coverage_compiler_flags_to_target(test-structs) append_coverage_compiler_flags_to_target(test-term) + append_coverage_compiler_flags_to_target(test-avmpack) append_coverage_linker_flags_to_target(test-erlang) append_coverage_linker_flags_to_target(test-enif) append_coverage_linker_flags_to_target(test-heap) @@ -149,6 +157,7 @@ if (COVERAGE) append_coverage_linker_flags_to_target(test-mailbox) append_coverage_linker_flags_to_target(test-structs) append_coverage_linker_flags_to_target(test-term) + append_coverage_linker_flags_to_target(test-avmpack) if (CMAKE_COMPILER_IS_GNUCC) setup_target_for_coverage_lcov(NAME coverage EXECUTABLE test-erlang DEPENDENCIES test-erlang) endif() diff --git a/tests/test-avmpack.c b/tests/test-avmpack.c new file mode 100644 index 0000000000..f279a98b7b --- /dev/null +++ b/tests/test-avmpack.c @@ -0,0 +1,383 @@ +/* + * 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 + +#include "avmpack.h" +#include "context.h" +#include "defaultatoms.h" +#include "globalcontext.h" +#include "memory.h" +#include "nifs.h" +#include "scheduler.h" +#include "synclist.h" +#include "term.h" +#include "utils.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] = v >> 24; + p[1] = v >> 16; + p[2] = v >> 8; + p[3] = v; +} + +static size_t pad4(size_t n) +{ + return (n + 3) & ~3; +} + +static size_t put_section( + uint8_t *buf, size_t off, const char *name, uint32_t flags, size_t content_size) +{ + size_t name_len = strlen(name) + 1; + size_t name_pad = pad4(name_len); + size_t content_pad = pad4(content_size); + put_u32_be(buf + off, 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 void *count_sections(void *accum, const void *section_ptr, uint32_t section_size, + const void *beam_ptr, uint32_t flags, const char *section_name) +{ + UNUSED(section_ptr); + UNUSED(section_size); + UNUSED(beam_ptr); + UNUSED(flags); + UNUSED(section_name); + + return (void *) ((uintptr_t) accum + 1); +} + +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; +} + +static void register_pack(GlobalContext *glb, const uint8_t *pack, uint32_t pack_size) +{ + struct ConstAVMPack *avm = malloc(sizeof(struct ConstAVMPack)); + if (avm == NULL) { + fprintf(stderr, "malloc failed\n"); + exit(1); + } + avmpack_data_init(&avm->base, &const_avm_pack_info, pack_size); + avm->base.data = pack; + avm->base.in_use = true; + synclist_append(&glb->avmpack_data, &avm->base.avmpack_head); +} + +static term call_read_priv(Context *ctx, const struct Nif *nif, const char *path) +{ + size_t path_len = strlen(path); + if (memory_ensure_free(ctx, term_binary_heap_size(path_len)) != MEMORY_GC_OK) { + fprintf(stderr, "memory_ensure_free failed\n"); + exit(1); + } + term argv[2]; + argv[0] = globalcontext_make_atom(ctx->global, ATOM_STR("\x5", "myapp")); + argv[1] = term_from_literal_binary(path, path_len, &ctx->heap, ctx->global); + + return nif->nif_ptr(ctx, 2, argv); +} + +static void test_read_priv(void) +{ + // an aligned pack with a valid entry: <<4:32, "abcd">> in an 8 byte payload + static uint32_t aligned_words[64]; + uint8_t *aligned = (uint8_t *) aligned_words; + memset(aligned, 0, sizeof(aligned_words)); + memcpy(aligned, avmpack_header, 24); + size_t off = put_section(aligned, 24, "myapp/priv/foo.txt", 4, 8); + uint32_t aligned_size = put_end(aligned, off); + put_u32_be(aligned + 24 + 12 + 20, 4); + memcpy(aligned + 24 + 12 + 20 + 4, "abcd", 4); + + // a pack at an odd address with a valid entry and a corrupt one + static uint8_t odd_storage[256 + 1]; + uint8_t *odd = odd_storage + 1; + memset(odd_storage, 0, sizeof(odd_storage)); + memcpy(odd, avmpack_header, 24); + off = put_section(odd, 24, "myapp/priv/odd.txt", 4, 8); + size_t bad_off = off; + off = put_section(odd, off, "myapp/priv/bad.txt", 4, 8); + uint32_t odd_size = put_end(odd, off); + put_u32_be(odd + 24 + 12 + 20, 4); + memcpy(odd + 24 + 12 + 20 + 4, "wxyz", 4); + put_u32_be(odd + bad_off + 12 + 20, 100); // 8 payload bytes hold at most a 4 byte file + + GlobalContext *glb = globalcontext_new(); + Context *ctx = context_new(glb); + register_pack(glb, aligned, aligned_size); + register_pack(glb, odd, odd_size); + + const struct Nif *nif = nifs_get("atomvm:read_priv/2"); + CHECK(nif != NULL); + + term result = call_read_priv(ctx, nif, "foo.txt"); + CHECK(term_is_binary(result) && term_binary_size(result) == 4 + && memcmp(term_binary_data(result), "abcd", 4) == 0); + result = call_read_priv(ctx, nif, "odd.txt"); + CHECK(term_is_binary(result) && term_binary_size(result) == 4 + && memcmp(term_binary_data(result), "wxyz", 4) == 0); + result = call_read_priv(ctx, nif, "missing.txt"); + CHECK(result == UNDEFINED_ATOM); + result = call_read_priv(ctx, nif, "bad.txt"); + CHECK(term_is_invalid_term(result)); + CHECK(ctx->exception_reason == INVALID_AVM_ATOM); + + scheduler_terminate(ctx); + globalcontext_destroy(glb); +} + +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 = put_end(buf, off); + + CHECK(avmpack_is_valid(buf, 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(sz == 16); // the payload size, not the whole section size + 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); + CHECK(sz == 16); + // find_by_flag for flags 0 returns the terminator + 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); + CHECK(ptr == buf + pack_size); + + // --- Sections after a section with flags 0 stay reachable --- + memset(buf, 0, sizeof(buf)); + memcpy(buf, avmpack_header, 24); + off = put_section(buf, 24, "app.beam", BEAM_START_FLAG, 16); + off = put_section(buf, off, "app/priv/a.txt", 0, 8); + off = put_section(buf, off, "app/priv/b.txt", 0, 8); + pack_size = put_end(buf, off); + CHECK(avmpack_find_section_by_name(buf, pack_size, "app/priv/b.txt", &ptr, &sz) == 1); + CHECK(sz == 8); + CHECK(avmpack_fold(NULL, buf, pack_size, count_sections) == (void *) (uintptr_t) 3); + + // --- 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_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); + + // --- Oversized section size --- + 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); + + // --- 0x00-filled region must not be mistaken for the terminator --- + memset(buf, 0, sizeof(buf)); + memcpy(buf, avmpack_header, 24); + CHECK(avmpack_find_section_by_flag( + buf, sizeof(buf), END_OF_FILE_MASK, END_OF_FILE, &ptr, &sz, &name) + == 0); + + // --- Misaligned pack base (an in-place const binary may sit at any byte offset) --- + _Alignas(uint32_t) uint8_t storage[512 + 1]; + uint8_t *pack = storage + 1; + memset(storage, 0xFF, sizeof(storage)); + memcpy(pack, avmpack_header, 24); + off = put_section(pack, 24, "mymod.beam", BEAM_START_FLAG, 16); + pack_size = put_end(pack, off); + CHECK(avmpack_find_section_by_name(pack, pack_size, "mymod.beam", &ptr, &sz) == 1 && sz == 16); + CHECK(avmpack_find_section_by_flag( + pack, pack_size, END_OF_FILE_MASK, END_OF_FILE, &ptr, &sz, &name) + == 1); + CHECK(avmpack_find_section_by_name(pack, pack_size, "missing", &ptr, &sz) == 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); + 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, sizeof(buf), "x", &ptr, &sz) == 0); + + // --- Terminator name in any mix of case --- + memset(buf, 0, sizeof(buf)); + memcpy(buf, avmpack_header, 24); + off = put_section(buf, 24, "mymod.beam", BEAM_START_FLAG, 16); + pack_size = put_end(buf, off); + memcpy(buf + off + 12, "eNd", 4); + 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 && ptr == buf + pack_size); + memcpy(buf + off + 12, "enx", 4); + CHECK(avmpack_find_section_by_flag( + buf, pack_size, END_OF_FILE_MASK, END_OF_FILE, &ptr, &sz, &name) + == 0); + + // --- Completeness of a pack of exactly known size, and size recovery in a bounded region --- + memset(buf, 0, sizeof(buf)); + memcpy(buf, avmpack_header, 24); + off = put_section(buf, 24, "mymod.beam", BEAM_START_FLAG, 16); + pack_size = put_end(buf, off); + CHECK(avmpack_is_complete(buf, pack_size)); + CHECK(avmpack_compute_size(buf, sizeof(buf), &real_size) && real_size == pack_size); + // trailing bytes after the terminator are rejected + CHECK(avmpack_is_complete(buf, pack_size + 4) == false); + // a file cut exactly at the section end has no tail terminator + CHECK(avmpack_is_complete(buf, pack_size - 16) == false); + // the same pack at a misaligned base + memset(storage, 0xFF, sizeof(storage)); + memcpy(pack, avmpack_header, 24); + off = put_section(pack, 24, "mymod.beam", BEAM_START_FLAG, 16); + pack_size = put_end(pack, off); + CHECK(avmpack_is_complete(pack, pack_size)); + CHECK(avmpack_compute_size(pack, sizeof(storage) - 1, &real_size) && real_size == pack_size); + + // --- 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); + + // --- Oversized, misaligned and zero section sizes --- + 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_compute_size(buf, 64, &real_size) == false); + put_u32_be(buf + 24, 17); + CHECK(avmpack_compute_size(buf, sizeof(buf), &real_size) == false); + memset(buf, 0, sizeof(buf)); + memcpy(buf, avmpack_header, 24); + CHECK(avmpack_compute_size(buf, sizeof(buf), &real_size) == false); + + // --- Terminator name with no NUL in its 4 bytes, at the exact end of the pack --- + memset(buf, 0, sizeof(buf)); + memcpy(buf, avmpack_header, 24); + off = put_section(buf, 24, "mymod.beam", BEAM_START_FLAG, 16); + put_u32_be(buf + off, 0); + put_u32_be(buf + off + 4, 0); + put_u32_be(buf + off + 8, 0); + memcpy(buf + off + 12, "endX", 4); + pack_size = off + 16; + uint8_t *exact = malloc(pack_size); + if (exact == NULL) { + fprintf(stderr, "malloc failed\n"); + return 1; + } + memcpy(exact, buf, pack_size); + CHECK(avmpack_is_complete(exact, pack_size) == false); + CHECK(avmpack_compute_size(exact, pack_size, &real_size) == false); + memcpy(exact + off + 12, "ENDX", 4); + CHECK(avmpack_is_complete(exact, pack_size) == false); + memcpy(exact + off + 12, "END", 4); + CHECK(avmpack_is_complete(exact, pack_size)); + CHECK(avmpack_compute_size(exact, pack_size, &real_size) && real_size == pack_size); + memcpy(exact + off + 12, "eNd", 4); + CHECK(avmpack_is_complete(exact, pack_size)); + CHECK(avmpack_compute_size(exact, pack_size, &real_size) && real_size == pack_size); + memcpy(exact + off + 12, "enx", 4); + CHECK(avmpack_is_complete(exact, pack_size) == false); + free(exact); + + // --- Corrupt chain with a well-formed terminator as the last 16 bytes --- + memset(buf, 0xFF, sizeof(buf)); + memcpy(buf, avmpack_header, 24); + put_u32_be(buf + 24, 0x10000); // first section claims 64 KiB + put_u32_be(buf + 24 + 4, 0); + put_u32_be(buf + 24 + 8, 0); + memcpy(buf + 24 + 12, "x", 2); + pack_size = put_end(buf, 256); + CHECK(avmpack_is_complete(buf, pack_size)); + CHECK(avmpack_compute_size(buf, pack_size, &real_size) == false); + CHECK(avmpack_find_section_by_name(buf, pack_size, "missing", &ptr, &sz) == 0); + +#if SIZE_MAX > UINT32_MAX + // --- A size that 32-bit pack offsets cannot describe --- + CHECK(avmpack_is_complete(buf, (size_t) UINT32_MAX + 1) == false); +#endif + + test_read_priv(); + + if (failures == 0) { + fprintf(stderr, "All avmpack tests passed!\n"); + return 0; + } + fprintf(stderr, "%d avmpack test(s) failed.\n", failures); + + return 1; +} diff --git a/tests/test-jit_stream_flash.c b/tests/test-jit_stream_flash.c index 1dc8d3af07..c3118a0438 100644 --- a/tests/test-jit_stream_flash.c +++ b/tests/test-jit_stream_flash.c @@ -27,6 +27,7 @@ #include "avmpack.h" #include "context.h" +#include "defaultatoms.h" #include "globalcontext.h" #include "jit_stream_flash.h" #include "jit_stream_flash_platform.h" @@ -185,8 +186,8 @@ static uint8_t create_minimal_avmpack(void) uint32_t *sec_header = (uint32_t *) section; // Section format: size (4) + flags (4) + reserved (4) + name (null-terminated) - // Write size in big-endian (total section size including header) - uint32_t section_size = 4 + 4 + 4 + 4; // size + flags + reserved + "end\0" + // The terminator declares size 0 even though it occupies 16 bytes + uint32_t section_size = 0; sec_header[0] = __builtin_bswap32(section_size); // Write flags in big-endian @@ -202,6 +203,15 @@ static uint8_t create_minimal_avmpack(void) return 0; } +static void create_terminatorless_avmpack(void) +{ + uint8_t *pack = mock_flash + 0x100; + + const char header_str[] = "#!/usr/bin/env AtomVM\n"; + memcpy(pack, header_str, 23); + pack[23] = 0; +} + // Register AVM pack with global context static void register_test_avmpack(GlobalContext *glb) { @@ -209,7 +219,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; @@ -217,6 +227,18 @@ static void register_test_avmpack(GlobalContext *glb) synclist_append(&glb->avmpack_data, &pack->base.avmpack_head); } +static void register_terminatorless_avmpack(GlobalContext *glb) +{ + create_terminatorless_avmpack(); + + struct ConstAVMPack *pack = malloc(sizeof(struct ConstAVMPack)); + avmpack_data_init(&pack->base, &const_avm_pack_info, sizeof(mock_flash) - 0x100); + pack->base.data = mock_flash + 0x100; + pack->base.in_use = true; + + synclist_append(&glb->avmpack_data, &pack->base.avmpack_head); +} + // Test helper: create binary term with proper GC rooting static term make_binary_rooted(Context *ctx, const uint8_t *data, size_t len, term *roots, int num_roots) { @@ -836,6 +858,42 @@ static void test_stale_data_cleanup(void) fprintf(stderr, "PASS: Stale data cleanup test\n"); } +static void test_no_terminator_bug(void) +{ + fprintf(stderr, "\n=== Test: Pack Without Terminator ===\n"); + + memset(mock_flash, 0x00, MOCK_FLASH_SIZE); + memset(&mock_flash[0], 0xFF, FLASH_SECTOR_SIZE); // first page with AVM + + GlobalContext *glb = globalcontext_new(); + Context *ctx = context_new(glb); + + // Only the broken pack is registered, so no pack provides an append position + register_terminatorless_avmpack(glb); + jit_stream_flash_init(glb); + + nif_function new_nif = get_nif("jit_stream_flash:new/1"); + assert(new_nif != NULL); + + term argv[1]; + argv[0] = term_from_int(10); // label count + term stream = new_nif(ctx, 1, argv); + + if (!term_is_invalid_term(stream)) { + fprintf(stderr, "FAIL: expected jit_stream_flash:new/1 to raise, got a stream\n"); + exit(1); + } + if (ctx->exception_reason != BADARG_ATOM) { + fprintf(stderr, "FAIL: expected badarg, got a different exception reason\n"); + exit(1); + } + + scheduler_terminate(ctx); + globalcontext_destroy(glb); + + fprintf(stderr, "PASS: Pack without terminator\n"); +} + int main(int argc, char **argv) { UNUSED(argc); @@ -852,6 +910,7 @@ int main(int argc, char **argv) test_esp32_crash_bug(); test_tail_corruption_bug(); test_stale_data_cleanup(); + test_no_terminator_bug(); fprintf(stderr, "\nAll tests passed!\n"); return EXIT_SUCCESS;