Skip to content

Commit db1b7c1

Browse files
committed
Fail read_priv on a corrupt avmpack entry
A priv entry whose length prefix does not fit its own section was skipped, so a corrupt pack was indistinguishable from a missing file. Raise invalid_avm instead. A genuine miss still returns undefined. Signed-off-by: Davide Bettio <davide@uninstall.it>
1 parent 8b3bd17 commit db1b7c1

5 files changed

Lines changed: 89 additions & 21 deletions

File tree

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
5959
reference size, but now emits a compiler warning
6060
- ESP32 builds now fail at configure time when `boot.avm` does not fit its partition, instead of
6161
producing an image that is silently truncated at flash time
62+
- `atomvm:read_priv/2` now raises `invalid_avm` when the requested resource is present but its
63+
recorded size does not fit the AVM pack section holding it, instead of reporting the resource as
64+
missing. A resource that is genuinely absent still returns `undefined`
6265

6366
### Removed
6467
- Removed `ahttp_client` support for obsolete line folding (RFC 9112 §5.2); folded header and

libs/eavmlib/src/atomvm.erl

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -161,8 +161,11 @@ rand_bytes(_Len) ->
161161
%%-----------------------------------------------------------------------------
162162
%% @param App application name.
163163
%% @param Path path to the resource.
164-
%% @returns Binary containing the resource content.
164+
%% @returns Binary containing the resource content, or `undefined' if it is not in any loaded
165+
%% AVM pack.
165166
%% @doc This function allows to fetch priv/ resources content.
167+
%% Raises `invalid_avm' if the resource is present but its recorded size does not fit
168+
%% the AVM pack section holding it.
166169
%% @end
167170
%%-----------------------------------------------------------------------------
168171
-spec read_priv(App :: atom(), Path :: list()) -> binary().

src/libAtomVM/defaultatoms.def

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -230,3 +230,5 @@ X(REPLY_DEMONITOR_ATOM, "\xF", "reply_demonitor")
230230
X(REPLY_ATOM, "\x5", "reply")
231231
X(TAG_ATOM, "\x3", "tag")
232232
X(PRIORITY_ATOM, "\x8", "priority")
233+
234+
X(INVALID_AVM_ATOM, "\xB", "invalid_avm")

src/libAtomVM/nifs.c

Lines changed: 29 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -5764,39 +5764,48 @@ static term nif_atomvm_read_priv(Context *ctx, int argc, term argv[])
57645764
uint32_t size;
57655765
struct ListHead *item;
57665766
term result = UNDEFINED_ATOM;
5767+
bool invalid_pack = false;
57675768
struct ListHead *avmpack_data = synclist_rdlock(&glb->avmpack_data);
57685769
LIST_FOR_EACH (item, avmpack_data) {
57695770
struct AVMPackData *avmpack_data = GET_LIST_ENTRY(item, struct AVMPackData, avmpack_head);
57705771
bool prev_in_use = avmpack_data->in_use;
57715772
avmpack_data->in_use = true;
57725773
if (avmpack_find_section_by_name(
57735774
avmpack_data->data, avmpack_data->size, complete_path, &bin_data, &size)) {
5774-
// Bound the length prefix to the section payload against a corrupt file size.
5775-
if (size >= sizeof(uint32_t)) {
5776-
uint32_t file_size = READ_32_ALIGNED((uint32_t *) bin_data);
5777-
if (file_size <= size - sizeof(uint32_t)) {
5778-
free(complete_path);
5779-
complete_path = NULL;
5780-
if (UNLIKELY(memory_ensure_free_opt(
5781-
ctx, TERM_BOXED_REFC_BINARY_SIZE, MEMORY_CAN_SHRINK)
5782-
!= MEMORY_GC_OK)) {
5783-
avmpack_data->in_use = prev_in_use;
5784-
synclist_unlock(&glb->avmpack_data);
5785-
RAISE_ERROR(OUT_OF_MEMORY_ATOM);
5786-
}
5787-
result = term_from_const_binary(((uint8_t *) bin_data) + sizeof(uint32_t),
5788-
file_size, &ctx->heap, ctx->global);
5789-
break;
5790-
}
5775+
// The length prefix must fit the section payload, otherwise the pack is corrupt.
5776+
if (UNLIKELY(size < sizeof(uint32_t))) {
5777+
avmpack_data->in_use = prev_in_use;
5778+
invalid_pack = true;
5779+
break;
57915780
}
5792-
avmpack_data->in_use = prev_in_use;
5793-
} else {
5794-
avmpack_data->in_use = prev_in_use;
5781+
uint32_t file_size = READ_32_ALIGNED((uint32_t *) bin_data);
5782+
if (UNLIKELY(file_size > size - sizeof(uint32_t))) {
5783+
avmpack_data->in_use = prev_in_use;
5784+
invalid_pack = true;
5785+
break;
5786+
}
5787+
free(complete_path);
5788+
complete_path = NULL;
5789+
if (UNLIKELY(memory_ensure_free_opt(ctx, TERM_BOXED_REFC_BINARY_SIZE, MEMORY_CAN_SHRINK)
5790+
!= MEMORY_GC_OK)) {
5791+
avmpack_data->in_use = prev_in_use;
5792+
synclist_unlock(&glb->avmpack_data);
5793+
RAISE_ERROR(OUT_OF_MEMORY_ATOM);
5794+
}
5795+
result = term_from_const_binary(
5796+
((uint8_t *) bin_data) + sizeof(uint32_t), file_size, &ctx->heap, ctx->global);
5797+
break;
57955798
}
5799+
avmpack_data->in_use = prev_in_use;
57965800
}
57975801
synclist_unlock(&glb->avmpack_data);
57985802

57995803
free(complete_path);
5804+
5805+
if (UNLIKELY(invalid_pack)) {
5806+
RAISE_ERROR(INVALID_AVM_ATOM);
5807+
}
5808+
58005809
return result;
58015810
}
58025811

tests/test-avmpack.c

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,18 @@
2121
#include <stdbool.h>
2222
#include <stdint.h>
2323
#include <stdio.h>
24+
#include <stdlib.h>
2425
#include <string.h>
2526

2627
#include "avmpack.h"
28+
#include "context.h"
29+
#include "defaultatoms.h"
30+
#include "globalcontext.h"
31+
#include "memory.h"
32+
#include "nifs.h"
33+
#include "scheduler.h"
34+
#include "synclist.h"
35+
#include "term.h"
2736

2837
static int failures = 0;
2938

@@ -76,6 +85,46 @@ static size_t put_end(uint8_t *buf, size_t off)
7685
return off + 16;
7786
}
7887

88+
// A priv entry whose length prefix does not fit its section must be reported as a corrupt pack,
89+
// not as a missing file.
90+
static void test_read_priv_rejects_corrupt_entry(void)
91+
{
92+
static uint32_t pack_words[64];
93+
uint8_t *pack = (uint8_t *) pack_words;
94+
95+
memset(pack, 0, sizeof(pack_words));
96+
memcpy(pack, avmpack_header, 24);
97+
size_t off = put_section(pack, 24, "myapp/priv/foo.txt", 4, 8);
98+
uint32_t pack_size = (uint32_t) put_end(pack, off);
99+
100+
// 8 payload bytes hold at most a 4 byte file, claim 100
101+
put_u32_be(pack + 24 + 12 + 20, 100);
102+
103+
GlobalContext *glb = globalcontext_new();
104+
Context *ctx = context_new(glb);
105+
106+
struct ConstAVMPack *avm = malloc(sizeof(struct ConstAVMPack));
107+
avmpack_data_init(&avm->base, &const_avm_pack_info, pack_size);
108+
avm->base.data = pack;
109+
avm->base.in_use = true;
110+
synclist_append(&glb->avmpack_data, &avm->base.avmpack_head);
111+
112+
const struct Nif *nif = nifs_get("atomvm:read_priv/2");
113+
CHECK(nif != NULL);
114+
115+
term argv[2];
116+
argv[0] = globalcontext_make_atom(glb, ATOM_STR("\x5", "myapp"));
117+
CHECK(memory_ensure_free(ctx, term_binary_heap_size(7)) == MEMORY_GC_OK);
118+
argv[1] = term_from_literal_binary((const uint8_t *) "foo.txt", 7, &ctx->heap, glb);
119+
120+
term result = nif->nif_ptr(ctx, 2, argv);
121+
CHECK(term_is_invalid_term(result));
122+
CHECK(ctx->exception_reason == INVALID_AVM_ATOM);
123+
124+
scheduler_terminate(ctx);
125+
globalcontext_destroy(glb);
126+
}
127+
79128
int main(void)
80129
{
81130
uint8_t buf[4096];
@@ -152,6 +201,8 @@ int main(void)
152201
memcpy(buf + 24 + 12, "x", 2);
153202
CHECK(avmpack_compute_size(buf, sizeof(buf), &real_size) == false);
154203

204+
test_read_priv_rejects_corrupt_entry();
205+
155206
if (failures == 0) {
156207
fprintf(stderr, "All avmpack tests passed!\n");
157208
return 0;

0 commit comments

Comments
 (0)