Skip to content

Commit adab7d7

Browse files
committed
Fix critical memory safety issues
This adds explicit NULL checks for all malloc/calloc/mpool_alloc calls that previously relied solely on assert(), which compiles out with '-DNDEBUG'. It prevents NULL pointer dereferences in production builds. - Add bounds checking in memory_write/memory_fill to prevent guest code from accessing arbitrary host memory - Fix memcpy/memset handlers to validate addresses and raise traps on violations (STORE_MISALIGNED/LOAD_MISALIGNED) - Add overflow-safe arithmetic for all size calculations - Implement proper error handling with goto-based cleanup chains - Fix memory leaks in error paths (mpool_extend, syscall_open)
1 parent 1a06240 commit adab7d7

File tree

11 files changed

+297
-87
lines changed

11 files changed

+297
-87
lines changed

src/cache.c

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,10 @@ static inline void hlist_del_init(struct hlist_node *n)
147147

148148
cache_t *cache_create(uint32_t size_bits)
149149
{
150+
/* Prevent integer overflow in 1 << size_bits */
151+
if (size_bits >= 32)
152+
return NULL;
153+
150154
cache_t *cache = malloc(sizeof(cache_t));
151155
if (!cache)
152156
return NULL;
@@ -160,16 +164,23 @@ cache_t *cache_create(uint32_t size_bits)
160164
cache->ghost_list_size = 0;
161165
cache->capacity = cache_size;
162166

163-
cache->map.ht_list_head = malloc(cache_size * sizeof(struct hlist_head));
164-
if (!cache->map.ht_list_head) {
165-
free(cache);
166-
return NULL;
167-
}
167+
/* Check for overflow in size calculation */
168+
size_t alloc_size = cache_size * sizeof(struct hlist_head);
169+
if (alloc_size / sizeof(struct hlist_head) != cache_size)
170+
goto fail_cache;
171+
172+
cache->map.ht_list_head = malloc(alloc_size);
173+
if (!cache->map.ht_list_head)
174+
goto fail_cache;
168175

169176
for (uint32_t i = 0; i < cache_size; i++)
170177
INIT_HLIST_HEAD(&cache->map.ht_list_head[i]);
171178

172179
return cache;
180+
181+
fail_cache:
182+
free(cache);
183+
return NULL;
173184
}
174185

175186
void *cache_get(const cache_t *cache, uint32_t key, bool update)
@@ -285,6 +296,17 @@ void *cache_put(cache_t *cache, uint32_t key, void *value)
285296
}
286297

287298
cache_entry_t *new_entry = calloc(1, sizeof(cache_entry_t));
299+
if (unlikely(!new_entry)) {
300+
/* Allocation failed - restore replaced entry if exists */
301+
if (replaced) {
302+
replaced->alive = true;
303+
list_del_init(&replaced->list);
304+
list_add(&replaced->list, &cache->list);
305+
cache->size++;
306+
cache->ghost_list_size--;
307+
}
308+
return NULL;
309+
}
288310
assert(new_entry);
289311

290312
INIT_LIST_HEAD(&new_entry->list);

src/elf.c

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -274,14 +274,14 @@ bool elf_load(elf_t *e, memory_t *mem)
274274

275275
/* memcpy required range */
276276
const int to_copy = min(phdr->p_memsz, phdr->p_filesz);
277-
if (to_copy)
278-
memory_write(mem, phdr->p_vaddr, e->raw_data + phdr->p_offset,
279-
to_copy);
277+
if (to_copy && !memory_write(mem, phdr->p_vaddr,
278+
e->raw_data + phdr->p_offset, to_copy))
279+
return false;
280280

281281
/* zero fill required range */
282282
const int to_zero = max(phdr->p_memsz, phdr->p_filesz) - to_copy;
283-
if (to_zero)
284-
memory_fill(mem, phdr->p_vaddr + to_copy, to_zero, 0);
283+
if (to_zero && !memory_fill(mem, phdr->p_vaddr + to_copy, to_zero, 0))
284+
return false;
285285
}
286286

287287
return true;

src/emulate.c

Lines changed: 103 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -273,6 +273,8 @@ HASH_FUNC_IMPL(map_hash, BLOCK_MAP_CAPACITY_BITS, 1 << BLOCK_MAP_CAPACITY_BITS)
273273
static block_t *block_alloc(riscv_t *rv)
274274
{
275275
block_t *block = mpool_alloc(rv->block_mp);
276+
if (unlikely(!block))
277+
return NULL;
276278
assert(block);
277279
block->n_insn = 0;
278280
#if RV32_HAS(JIT)
@@ -619,13 +621,15 @@ FORCE_INLINE bool insn_is_indirect_branch(uint8_t opcode)
619621
}
620622
}
621623

622-
static void block_translate(riscv_t *rv, block_t *block)
624+
static bool block_translate(riscv_t *rv, block_t *block)
623625
{
624626
retranslate:
625627
block->pc_start = block->pc_end = rv->PC;
626628

627629
rv_insn_t *prev_ir = NULL;
628630
rv_insn_t *ir = mpool_calloc(rv->block_ir_mp);
631+
if (unlikely(!ir))
632+
return false;
629633
block->ir_head = ir;
630634

631635
/* translate the basic block */
@@ -665,6 +669,8 @@ static void block_translate(riscv_t *rv, block_t *block)
665669
if (insn_is_branch(ir->opcode)) {
666670
if (insn_is_indirect_branch(ir->opcode)) {
667671
ir->branch_table = calloc(1, sizeof(branch_history_table_t));
672+
if (unlikely(!ir->branch_table))
673+
return false;
668674
assert(ir->branch_table);
669675
memset(ir->branch_table->PC, -1,
670676
sizeof(uint32_t) * HISTORY_SIZE);
@@ -673,36 +679,44 @@ static void block_translate(riscv_t *rv, block_t *block)
673679
}
674680

675681
ir = mpool_calloc(rv->block_ir_mp);
682+
if (unlikely(!ir))
683+
return false;
676684
}
677685

678686
assert(prev_ir);
679687
block->ir_tail = prev_ir;
680688
block->ir_tail->next = NULL;
689+
return true;
681690
}
682691

683692
#if RV32_HAS(MOP_FUSION)
684-
#define COMBINE_MEM_OPS(RW) \
685-
next_ir = ir->next; \
686-
count = 1; \
687-
while (1) { \
688-
if (next_ir->opcode != IIF(RW)(rv_insn_lw, rv_insn_sw)) \
689-
break; \
690-
count++; \
691-
if (!next_ir->next) \
692-
break; \
693-
next_ir = next_ir->next; \
694-
} \
695-
if (count > 1) { \
696-
ir->opcode = IIF(RW)(rv_insn_fuse4, rv_insn_fuse3); \
697-
ir->fuse = malloc(count * sizeof(opcode_fuse_t)); \
698-
assert(ir->fuse); \
699-
ir->imm2 = count; \
700-
memcpy(ir->fuse, ir, sizeof(opcode_fuse_t)); \
701-
ir->impl = dispatch_table[ir->opcode]; \
702-
next_ir = ir->next; \
703-
for (int j = 1; j < count; j++, next_ir = next_ir->next) \
704-
memcpy(ir->fuse + j, next_ir, sizeof(opcode_fuse_t)); \
705-
remove_next_nth_ir(rv, ir, block, count - 1); \
693+
#define COMBINE_MEM_OPS(RW) \
694+
next_ir = ir->next; \
695+
count = 1; \
696+
while (1) { \
697+
if (next_ir->opcode != IIF(RW)(rv_insn_lw, rv_insn_sw)) \
698+
break; \
699+
count++; \
700+
if (!next_ir->next) \
701+
break; \
702+
next_ir = next_ir->next; \
703+
} \
704+
if (count > 1) { \
705+
ir->opcode = IIF(RW)(rv_insn_fuse4, rv_insn_fuse3); \
706+
ir->fuse = malloc(count * sizeof(opcode_fuse_t)); \
707+
if (unlikely(!ir->fuse)) { \
708+
ir->opcode = IIF(RW)(rv_insn_lw, rv_insn_sw); \
709+
count = 1; /* Degrade to non-fused operation */ \
710+
} else { \
711+
assert(ir->fuse); \
712+
ir->imm2 = count; \
713+
memcpy(ir->fuse, ir, sizeof(opcode_fuse_t)); \
714+
ir->impl = dispatch_table[ir->opcode]; \
715+
next_ir = ir->next; \
716+
for (int j = 1; j < count; j++, next_ir = next_ir->next) \
717+
memcpy(ir->fuse + j, next_ir, sizeof(opcode_fuse_t)); \
718+
remove_next_nth_ir(rv, ir, block, count - 1); \
719+
} \
706720
}
707721

708722
static inline void remove_next_nth_ir(const riscv_t *rv,
@@ -762,16 +776,20 @@ static void match_pattern(riscv_t *rv, block_t *block)
762776
next_ir = next_ir->next;
763777
}
764778
if (count > 1) {
765-
ir->opcode = rv_insn_fuse1;
766779
ir->fuse = malloc(count * sizeof(opcode_fuse_t));
767-
assert(ir->fuse);
768-
ir->imm2 = count;
769-
memcpy(ir->fuse, ir, sizeof(opcode_fuse_t));
770-
ir->impl = dispatch_table[ir->opcode];
771-
next_ir = ir->next;
772-
for (int j = 1; j < count; j++, next_ir = next_ir->next)
773-
memcpy(ir->fuse + j, next_ir, sizeof(opcode_fuse_t));
774-
remove_next_nth_ir(rv, ir, block, count - 1);
780+
if (likely(ir->fuse)) {
781+
ir->opcode = rv_insn_fuse1;
782+
assert(ir->fuse);
783+
ir->imm2 = count;
784+
memcpy(ir->fuse, ir, sizeof(opcode_fuse_t));
785+
ir->impl = dispatch_table[ir->opcode];
786+
next_ir = ir->next;
787+
for (int j = 1; j < count; j++, next_ir = next_ir->next)
788+
memcpy(ir->fuse + j, next_ir,
789+
sizeof(opcode_fuse_t));
790+
remove_next_nth_ir(rv, ir, block, count - 1);
791+
}
792+
/* If malloc failed, degrade gracefully to non-fused ops */
775793
}
776794
break;
777795
}
@@ -803,15 +821,18 @@ static void match_pattern(riscv_t *rv, block_t *block)
803821
}
804822
if (count > 1) {
805823
ir->fuse = malloc(count * sizeof(opcode_fuse_t));
806-
assert(ir->fuse);
807-
memcpy(ir->fuse, ir, sizeof(opcode_fuse_t));
808-
ir->opcode = rv_insn_fuse5;
809-
ir->imm2 = count;
810-
ir->impl = dispatch_table[ir->opcode];
811-
next_ir = ir->next;
812-
for (int j = 1; j < count; j++, next_ir = next_ir->next)
813-
memcpy(ir->fuse + j, next_ir, sizeof(opcode_fuse_t));
814-
remove_next_nth_ir(rv, ir, block, count - 1);
824+
if (likely(ir->fuse)) {
825+
assert(ir->fuse);
826+
memcpy(ir->fuse, ir, sizeof(opcode_fuse_t));
827+
ir->opcode = rv_insn_fuse5;
828+
ir->imm2 = count;
829+
ir->impl = dispatch_table[ir->opcode];
830+
next_ir = ir->next;
831+
for (int j = 1; j < count; j++, next_ir = next_ir->next)
832+
memcpy(ir->fuse + j, next_ir, sizeof(opcode_fuse_t));
833+
remove_next_nth_ir(rv, ir, block, count - 1);
834+
}
835+
/* If malloc failed, degrade gracefully to non-fused ops */
815836
}
816837
break;
817838
}
@@ -881,8 +902,11 @@ static block_t *block_find_or_translate(riscv_t *rv)
881902
#endif
882903
/* allocate a new block */
883904
next_blk = block_alloc(rv);
905+
if (unlikely(!next_blk))
906+
return NULL;
884907

885-
block_translate(rv, next_blk);
908+
if (unlikely(!block_translate(rv, next_blk)))
909+
return NULL;
886910

887911
#if RV32_HAS(JIT) && RV32_HAS(SYSTEM)
888912
/*
@@ -1078,6 +1102,12 @@ void rv_step(void *arg)
10781102
*/
10791103
block_t *block = block_find_or_translate(rv);
10801104
/* by now, a block should be available */
1105+
if (unlikely(!block)) {
1106+
rv_log_fatal("Failed to allocate or translate block at PC=0x%08x",
1107+
rv->PC);
1108+
rv->halt = true;
1109+
return;
1110+
}
10811111
assert(block);
10821112

10831113
#if RV32_HAS(JIT) && RV32_HAS(SYSTEM)
@@ -1129,6 +1159,11 @@ void rv_step(void *arg)
11291159
else if (!block->compiled && block->n_invoke >= THRESHOLD) {
11301160
block->compiled = true;
11311161
queue_entry_t *entry = malloc(sizeof(queue_entry_t));
1162+
if (unlikely(!entry)) {
1163+
/* Malloc failed - reset compiled flag to allow retry later */
1164+
block->compiled = false;
1165+
continue;
1166+
}
11321167
entry->block = block;
11331168
pthread_mutex_lock(&rv->wait_queue_lock);
11341169
list_add(&entry->list, &rv->wait_queue);
@@ -1378,16 +1413,38 @@ void ecall_handler(riscv_t *rv)
13781413
void memset_handler(riscv_t *rv)
13791414
{
13801415
memory_t *m = PRIV(rv)->mem;
1381-
memset((char *) m->mem_base + rv->X[rv_reg_a0], rv->X[rv_reg_a1],
1382-
rv->X[rv_reg_a2]);
1416+
uint32_t dest = rv->X[rv_reg_a0];
1417+
uint32_t value = rv->X[rv_reg_a1];
1418+
uint32_t count = rv->X[rv_reg_a2];
1419+
1420+
/* Bounds checking to prevent buffer overflow */
1421+
if (dest >= m->mem_size || count > m->mem_size - dest) {
1422+
SET_CAUSE_AND_TVAL_THEN_TRAP(rv, STORE_MISALIGNED, dest);
1423+
return;
1424+
}
1425+
1426+
memset((char *) m->mem_base + dest, value, count);
13831427
rv->PC = rv->X[rv_reg_ra] & ~1U;
13841428
}
13851429

13861430
void memcpy_handler(riscv_t *rv)
13871431
{
13881432
memory_t *m = PRIV(rv)->mem;
1389-
memcpy((char *) m->mem_base + rv->X[rv_reg_a0],
1390-
(char *) m->mem_base + rv->X[rv_reg_a1], rv->X[rv_reg_a2]);
1433+
uint32_t dest = rv->X[rv_reg_a0];
1434+
uint32_t src = rv->X[rv_reg_a1];
1435+
uint32_t count = rv->X[rv_reg_a2];
1436+
1437+
/* Bounds checking to prevent buffer overflow */
1438+
if (dest >= m->mem_size || count > m->mem_size - dest) {
1439+
SET_CAUSE_AND_TVAL_THEN_TRAP(rv, STORE_MISALIGNED, dest);
1440+
return;
1441+
}
1442+
if (src >= m->mem_size || count > m->mem_size - src) {
1443+
SET_CAUSE_AND_TVAL_THEN_TRAP(rv, LOAD_MISALIGNED, src);
1444+
return;
1445+
}
1446+
1447+
memcpy((char *) m->mem_base + dest, (char *) m->mem_base + src, count);
13911448
rv->PC = rv->X[rv_reg_ra] & ~1U;
13921449
}
13931450

src/io.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ memory_t *memory_new(uint32_t size)
2323
return NULL;
2424

2525
memory_t *mem = malloc(sizeof(memory_t));
26+
if (!mem)
27+
return NULL;
2628
assert(mem);
2729
#if HAVE_MMAP
2830
data_memory_base = mmap(NULL, size, PROT_READ | PROT_WRITE,

src/io.h

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,12 +36,16 @@ uint8_t memory_read_b(uint32_t addr);
3636
void memory_read(const memory_t *m, uint8_t *dst, uint32_t addr, uint32_t size);
3737

3838
/* write a length of data to memory */
39-
static inline void memory_write(memory_t *m,
39+
static inline bool memory_write(memory_t *m,
4040
uint32_t addr,
4141
const uint8_t *src,
4242
uint32_t size)
4343
{
44+
/* Bounds checking to prevent buffer overflow */
45+
if (addr >= m->mem_size || size > m->mem_size - addr)
46+
return false;
4447
memcpy(m->mem_base + addr, src, size);
48+
return true;
4549
}
4650

4751
/* write a word to memory */
@@ -54,10 +58,14 @@ void memory_write_s(uint32_t addr, const uint8_t *src);
5458
void memory_write_b(uint32_t addr, const uint8_t *src);
5559

5660
/* write a length of certain value to memory */
57-
static inline void memory_fill(memory_t *m,
61+
static inline bool memory_fill(memory_t *m,
5862
uint32_t addr,
5963
uint32_t size,
6064
uint8_t val)
6165
{
66+
/* Bounds checking to prevent buffer overflow */
67+
if (addr >= m->mem_size || size > m->mem_size - addr)
68+
return false;
6269
memset(m->mem_base + addr, val, size);
70+
return true;
6371
}

0 commit comments

Comments
 (0)