|
| 1 | +# UAR Bug Analysis in async_context_threadsafe_background |
| 2 | + |
| 3 | +## Memory Layout Context |
| 4 | + |
| 5 | +### RP2040 Stack Configuration |
| 6 | +- Each Cortex M0+ core has its own stack |
| 7 | +- Stacks grow downward from high to low addresses |
| 8 | +- Default configuration: |
| 9 | + - Single core: 8KB stack for core 0 |
| 10 | + - Dual core: 4KB per core (shared from same 8KB) |
| 11 | + - Stacks placed in separate scratch banks (X/Y) for performance |
| 12 | +- In our case: `bool core1_separate_stack = true;` gives each core 8KB |
| 13 | + |
| 14 | +### Stack Usage Analysis |
| 15 | +- Worker struct allocated at 0x20041f74 |
| 16 | +- Printf buffer pushing against worker memory |
| 17 | +- Critical 48-byte alignment causing corruption |
| 18 | +- Separate stack configuration prevents core0/core1 stack collision |
| 19 | + |
| 20 | +## Debugging Setup |
| 21 | + |
| 22 | +### 1. Timestamp Collection |
| 23 | +```cpp |
| 24 | +timestamp_enter = to_us_since_boot(get_absolute_time()); |
| 25 | +const auto rc = async_context_execute_sync(&async_ctx.core, do_some_work, &ref_counter); |
| 26 | +timestamp_exit = to_us_since_boot(get_absolute_time()); |
| 27 | +``` |
| 28 | + |
| 29 | +### 2. GDB Instrumentation |
| 30 | +```gdb |
| 31 | +break async_context_base_execute_once |
| 32 | +commands |
| 33 | + printf "[INFO] Enter: %llu; Exit: %llu\n", timestamp_enter, timestamp_exit |
| 34 | + x/4wx $worker |
| 35 | + p *$worker |
| 36 | +end |
| 37 | +
|
| 38 | +break async_context_base_remove_when_pending_worker |
| 39 | +commands |
| 40 | + printf "[INFO] Enter: %llu; Exit: %llu\n", timestamp_enter, timestamp_exit |
| 41 | + x/4wx worker |
| 42 | + p *$worker |
| 43 | +end |
| 44 | +``` |
| 45 | + |
| 46 | +## Findings |
| 47 | + |
| 48 | +### 1. Stack Memory Pattern |
| 49 | +``` |
| 50 | +0x20041f74 (Worker struct location): |
| 51 | +|-------------------| |
| 52 | +| next (4 bytes) | <-- Corrupted to 0xa in cycle 11 |
| 53 | +| do_work (4 bytes) | <-- Remains valid at 0x1000a529 |
| 54 | +| work_pending (4) | <-- Corrupted to 232 from cycle 1 |
| 55 | +| user_data (4) | |
| 56 | +|-------------------| |
| 57 | + ↑ |
| 58 | +Printf buffer (grows down) |
| 59 | +- 47 chars (safe) |
| 60 | +- 48 chars (aligns with worker) |
| 61 | +``` |
| 62 | + |
| 63 | +### 2. Serial Output and Memory Impact |
| 64 | +``` |
| 65 | +// String lengths affecting stack layout |
| 66 | +Cycle 9: "Counter 9; ..." // 47 chars, stack safe |
| 67 | +Cycle 10: "[Counter 10]..." // 47 chars, stack safe |
| 68 | +Cycle 11: "[Counter 11]..." // 48 chars, fatal alignment |
| 69 | +``` |
| 70 | + |
| 71 | +### 3. Memory Corruption Pattern |
| 72 | +``` |
| 73 | +Initial state (cycle 1): |
| 74 | +- work_pending corrupted (1->232) |
| 75 | +- Stack overlap present but non-fatal |
| 76 | +- Core fields (next/do_work) protected by alignment |
| 77 | +
|
| 78 | +Fatal state (cycle 11): |
| 79 | +- String reaches 48 chars |
| 80 | +- Perfect stack alignment |
| 81 | +- next pointer corrupted |
| 82 | +- Stack corruption becomes fatal |
| 83 | +``` |
| 84 | + |
| 85 | +### 4. Stack Layout Analysis |
| 86 | +- Worker struct (16 bytes) at 0x20041f74 |
| 87 | +- Printf buffer allocated above worker |
| 88 | +- 48-byte boundary critical because: |
| 89 | + 1. Stack is 8-byte aligned |
| 90 | + 2. Printf buffer rounded up for alignment |
| 91 | + 3. Counter digits affect buffer size |
| 92 | + 4. At 48 bytes, buffer end perfectly aligns with worker start |
| 93 | + |
| 94 | +### 5. Crash Analysis |
| 95 | +```asm |
| 96 | +// Fatal access in async_context_base_execute_once during list traversal: |
| 97 | +0x1000a7be: ldr r3, [r5, #4] // Try to read next field from corrupted worker (0xa) |
| 98 | +0x1000a7c0: blx r3 // Never reached |
| 99 | +
|
| 100 | +Code context: |
| 101 | +for(async_when_pending_worker_t *when_pending_worker = self->when_pending_list; |
| 102 | + when_pending_worker; // r5 = 0xa here (corrupted next pointer) |
| 103 | + when_pending_worker = when_pending_worker->next) // Crash when trying to read next |
| 104 | +
|
| 105 | +Registers at crash: |
| 106 | +r5 = 0xa // This is the corrupted 'next' pointer we're trying to traverse |
| 107 | +r0 = 0x20001560 // async_context pointer |
| 108 | +``` |
| 109 | + |
| 110 | +## Key Insights |
| 111 | +1. Stack Layout Significance: |
| 112 | + - 8KB per core (due to core1_separate_stack = true) |
| 113 | + - Stack alignment requirements affect corruption pattern |
| 114 | + - Worker struct position relative to printf buffer critical |
| 115 | + |
| 116 | +2. UAR Manifestation: |
| 117 | + - work_pending corrupted to 232 immediately (early warning) |
| 118 | + - next field corrupted to 0xa when string hits 48 bytes |
| 119 | + - Crash happens when trying to traverse corrupted linked list |
| 120 | + - Fatal because of invalid memory access (0xa is not a valid address) |
| 121 | + |
| 122 | +3. Corruption Chain: |
| 123 | + - Printf buffer (48 bytes) overwrites worker struct |
| 124 | + - Worker still in linked list but memory reused |
| 125 | + - Next traversal attempts to dereference 0xa |
| 126 | + - System crashes trying to read from invalid address |
| 127 | + |
| 128 | +This analysis shows how a stack-based UAR vulnerability manifests through linked list corruption, where the crash occurs during list traversal rather than during worker execution. |
0 commit comments