Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,8 @@ 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)
- 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

Expand Down
2 changes: 2 additions & 0 deletions doc/src/packbeam-format.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
186 changes: 134 additions & 52 deletions src/libAtomVM/avmpack.c
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@
#include <stdio.h>

#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)
{
Expand All @@ -52,83 +55,162 @@ 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;
}

} while (*flags);
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;
}

return 0;
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 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, &section))
!= 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, &section) == 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, &section) == 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_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, &section)
== 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, &section))
== 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 = {
Expand Down
69 changes: 58 additions & 11 deletions src/libAtomVM/avmpack.h
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -102,50 +105,94 @@ 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.
*/
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 <tt>exact_size - 16</tt>. 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.
*
* @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
}
Expand Down
3 changes: 2 additions & 1 deletion src/libAtomVM/globalcontext.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
}
Expand Down
Loading
Loading