diff --git a/cs.c b/cs.c index 257e70f6c7..c1ca990190 100644 --- a/cs.c +++ b/cs.c @@ -501,7 +501,11 @@ extern void *kern_os_realloc(void *addr, size_t nsize); static void *cs_kern_os_calloc(size_t num, size_t size) { - return kern_os_malloc(num * size); // malloc bzeroes the buffer + size_t alloc = num * size; + if (num && size != alloc / num) { + return NULL; // overflow check + } + return kern_os_malloc(alloc); // malloc bzeroes the buffer } cs_malloc_t cs_mem_malloc = kern_os_malloc; diff --git a/tests/integration/test_poc.c b/tests/integration/test_poc.c index f461de4f47..4539fbf856 100644 --- a/tests/integration/test_poc.c +++ b/tests/integration/test_poc.c @@ -110,6 +110,64 @@ static void test_overflow_set_reg_mem_n(void) return; } +/** + * \brief Clone of cs.c::cs_kern_os_calloc() + * So we can run it in the CI. + */ +static void *clone_cs_kern_os_calloc(size_t num, size_t size) +{ + size_t alloc = num * size; + if (num && size != alloc / num) { + return NULL; // overflow check + } + return malloc(alloc); +} + +#define VALID_N 10 + +static void test_integer_overflow(void) +{ + // Invariant: Multiplication of num * size must not overflow before allocation + // We test this by ensuring the function handles boundary cases safely + + // Test cases: exploit case, boundary values, valid input + struct { + size_t num; + size_t size; + const char *description; + } test_cases[] = { + // Valid normal input + { VALID_N, VALID_N, "valid normal input" }, + // Exploit case: multiplication overflows to small value + { SIZE_MAX, 2, "overflow to small allocation" }, + // Another overflow case + { 1ULL << (sizeof(size_t) * 4), 1ULL << (sizeof(size_t) * 4), + "midpoint overflow" }, + // Zero allocation (edge case) + { 0, SIZE_MAX, "zero num" } + }; + + int num_cases = sizeof(test_cases) / sizeof(test_cases[0]); + + for (int i = 0; i < num_cases; i++) { + printf("num = 0x%zx size = 0x%zx\n", test_cases[i].num, + test_cases[i].size); + // The security property: if multiplication would overflow, + // the function should handle it safely (return NULL or abort) + void *result = clone_cs_kern_os_calloc(test_cases[i].num, + test_cases[i].size); + + // For valid inputs, we expect non-NULL (or NULL is also acceptable + // if memory allocation fails for other reasons) + if (test_cases[i].num == VALID_N || test_cases[i].num == 0) { + assert(result != NULL); + free(result); + } else { + assert(result == NULL); + } + } +} + /// Signed left shift overflow when assembling a little-endian SH-DSP /// parallel instruction word. code[3] is promoted to int and shifted /// left by 24, which is UB whenever its top bit is set (code[3] >= 0x80). @@ -135,6 +193,7 @@ int main() test_overflow_cs_insn_bytes(); test_overflow_cs_insn_bytes_iter(); test_overflow_set_reg_mem_n(); + test_integer_overflow(); test_ub_shift_sh_dsp_p(); return 0;