Skip to content

Commit 79ad7b2

Browse files
CopilotAlan-Jowett
andcommitted
Address PR review comments: add overflow checks and improve large program test
- Add overflow checks for calloc in ubpf_create, ubpf_unload_code, and ubpf_set_max_instructions - Update ubpf_test_large_program to actually JIT compile and execute large programs - Initialize r0 to 0 in test program and verify result - Set appropriate JIT buffer size for large programs - Update documentation to reflect actual test behavior Co-authored-by: Alan-Jowett <20480683+Alan-Jowett@users.noreply.github.com>
1 parent 73ff0fa commit 79ad7b2

File tree

4 files changed

+64
-8
lines changed

4 files changed

+64
-8
lines changed
Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,20 @@
11
# Large Program Test
22

3-
This test validates that programs with more than 65,536 instructions can be loaded correctly when `ubpf_set_max_instructions()` is used to increase the limit.
3+
This test validates that programs with more than 65,536 instructions can be loaded, JIT compiled, and executed correctly when `ubpf_set_max_instructions()` is used to increase the limit.
44

55
The test performs the following:
66
1. Creates a VM and sets max_instructions to 100,000
77
2. Generates a program with 66,000 instructions (NOP-like JA instructions with offset 0) plus an EXIT
88
3. Loads the program into the VM
9+
4. JIT compiles the program
10+
5. Executes the JIT-compiled program
911

1012
This validates that:
1113
- The type change from uint16_t to uint32_t for num_insts works correctly
1214
- Programs beyond the old 65,536 limit can be loaded
1315
- The validation path handles large instruction counts correctly
16+
- The JIT compiler can handle large programs (> 65,536 instructions)
17+
- JIT-compiled large programs execute correctly
1418

15-
Note: The test skips interpreter and JIT execution to keep test runtime reasonable.
16-
Large programs with many sequential NOPs would take prohibitively long to execute.
19+
Note: The test skips interpreter execution because executing 66,000 sequential NOP-like
20+
instructions via the interpreter would take prohibitively long for a test.

custom_tests/srcs/ubpf_test_large_program.cc

Lines changed: 40 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,15 @@ main(int argc, char** argv)
2525
const uint32_t num_instructions = 66000;
2626
std::vector<struct ebpf_inst> program(num_instructions);
2727

28-
// Fill with NOP-like JA instructions (jump with offset 0)
29-
for (uint32_t i = 0; i < num_instructions - 1; i++) {
28+
// First instruction: set r0 = 0
29+
program[0].opcode = EBPF_OP_MOV_IMM;
30+
program[0].dst = 0; // r0
31+
program[0].src = 0;
32+
program[0].offset = 0;
33+
program[0].imm = 0; // value 0
34+
35+
// Fill rest with NOP-like JA instructions (jump with offset 0)
36+
for (uint32_t i = 1; i < num_instructions - 1; i++) {
3037
program[i].opcode = EBPF_OP_JA;
3138
program[i].dst = 0;
3239
program[i].src = 0;
@@ -53,6 +60,14 @@ main(int argc, char** argv)
5360
return 1;
5461
}
5562

63+
// Set a larger JIT buffer size for large programs
64+
// Estimate: ~50 bytes per instruction + overhead
65+
size_t jit_buffer_size = num_instructions * 50 + 4096;
66+
if (ubpf_set_jit_code_size(vm.get(), jit_buffer_size) != 0) {
67+
std::cerr << "Failed to set JIT buffer size" << std::endl;
68+
return 1;
69+
}
70+
5671
// Load the program
5772
char* errmsg = nullptr;
5873
int result = ubpf_load(vm.get(), program.data(), num_instructions * sizeof(struct ebpf_inst), &errmsg);
@@ -64,8 +79,29 @@ main(int argc, char** argv)
6479

6580
std::cout << "Successfully loaded program with " << num_instructions << " instructions" << std::endl;
6681

67-
// Skip JIT and interpreter execution for large programs to keep test fast
68-
// The key validation is that we can load programs > 65,536 instructions
82+
// Test JIT compilation with large program
83+
errmsg = nullptr;
84+
ubpf_jit_fn jit_fn = ubpf_compile(vm.get(), &errmsg);
85+
if (!jit_fn) {
86+
std::cerr << "Failed to JIT compile: " << (errmsg ? errmsg : "unknown error") << std::endl;
87+
free(errmsg);
88+
return 1;
89+
}
90+
91+
std::cout << "Successfully JIT compiled program with " << num_instructions << " instructions" << std::endl;
92+
93+
// Execute via JIT - the program sets r0 to 0 and exits
94+
uint64_t jit_result = jit_fn(nullptr, 0);
95+
96+
if (jit_result != 0) {
97+
std::cerr << "JIT execution returned unexpected value: " << jit_result << " (expected 0)" << std::endl;
98+
return 1;
99+
}
100+
101+
std::cout << "JIT execution result: " << jit_result << " (correct)" << std::endl;
102+
103+
// Note: We skip interpreter execution because executing 66,000 sequential NOP-like
104+
// instructions via the interpreter would take too long for a test
69105

70106
std::cout << "Test passed!" << std::endl;
71107
return 0;

vm/ubpf_jit.c

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,11 @@ ubpf_set_max_instructions(struct ubpf_vm* vm, uint32_t max_insts)
115115

116116
// Reallocate local_func_stack_usage if needed
117117
if (max_insts != vm->max_insts) {
118+
// Check for overflow on 32-bit systems where size_t might be 32-bit
119+
if (max_insts > SIZE_MAX / sizeof(struct ubpf_stack_usage)) {
120+
return -1;
121+
}
122+
118123
struct ubpf_stack_usage* new_stack_usage = calloc(max_insts, sizeof(struct ubpf_stack_usage));
119124
if (new_stack_usage == NULL) {
120125
return -1;

vm/ubpf_vm.c

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,12 @@ ubpf_create(void)
115115
// Initialize max_insts to compile-time default
116116
vm->max_insts = UBPF_MAX_INSTS;
117117

118+
// Check for overflow on 32-bit systems where size_t might be 32-bit
119+
if (vm->max_insts > SIZE_MAX / sizeof(struct ubpf_stack_usage)) {
120+
ubpf_destroy(vm);
121+
return NULL;
122+
}
123+
118124
vm->local_func_stack_usage = calloc(vm->max_insts, sizeof(struct ubpf_stack_usage));
119125
if (vm->local_func_stack_usage == NULL) {
120126
ubpf_destroy(vm);
@@ -375,7 +381,12 @@ ubpf_unload_code(struct ubpf_vm* vm)
375381

376382
// Reset the stack usage amounts when code is unloaded.
377383
free(vm->local_func_stack_usage);
378-
vm->local_func_stack_usage = calloc(vm->max_insts, sizeof(struct ubpf_stack_usage));
384+
vm->local_func_stack_usage = NULL;
385+
386+
// Check for overflow on 32-bit systems where size_t might be 32-bit
387+
if (vm->max_insts > 0 && vm->max_insts <= SIZE_MAX / sizeof(struct ubpf_stack_usage)) {
388+
vm->local_func_stack_usage = calloc(vm->max_insts, sizeof(struct ubpf_stack_usage));
389+
}
379390

380391
if (vm->jitted) {
381392
munmap(vm->jitted, vm->jitted_size);

0 commit comments

Comments
 (0)