Skip to content

Commit 8de292b

Browse files
committed
docs: Add detailed UAR bug analysis for async_context
Add in-depth analysis of Use-After-Remove (UAR) bug in async_context_threadsafe_background, documenting how stack layout and string buffer size trigger worker corruption. Key findings: - work_pending corrupted to 232 from cycle 1 (early warning) - Fatal corruption at 48-byte string boundary - Counter reaching "11" pushes string length to critical size - Crash in linked list traversal, not worker execution - Demonstrates perfect stack alignment causing UAR Analysis includes: - RP2040 dual-core stack architecture details - GDB and timestamp collection methodology - Memory corruption progression patterns - Assembly-level crash analysis - Stack frame alignment effects This documentation helps understand how stack allocation patterns affect UAR vulnerabilities in multi-core environments.
1 parent 4178b78 commit 8de292b

File tree

1 file changed

+128
-0
lines changed

1 file changed

+128
-0
lines changed

docs/uar-2433-log-analysis.md

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
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

Comments
 (0)