ryansecuritytest-fanpierlabs commited on
Commit
6964854
·
verified ·
1 Parent(s): a104adb

Upload 23-executorch-freecall-oob.md with huggingface_hub

Browse files
Files changed (1) hide show
  1. 23-executorch-freecall-oob.md +156 -0
23-executorch-freecall-oob.md ADDED
@@ -0,0 +1,156 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Out-of-Bounds Array Access via Unvalidated FreeCall and MoveCall Indices in ExecuTorch PTE Loading
2
+
3
+ ## Target
4
+ pytorch/executorch
5
+
6
+ ## Vulnerability Type
7
+ Out-of-Bounds Read/Write (CWE-125, CWE-787)
8
+
9
+ ## Severity
10
+ **HIGH** (CVSS 3.1: 8.6 -- AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H)
11
+
12
+ A crafted .pte model file can contain FreeCall or MoveCall instructions with attacker-controlled indices that are never validated, causing out-of-bounds access on the runtime values array during program execution.
13
+
14
+ ## Summary
15
+
16
+ During ExecuTorch method initialization (`Method::init()`), each instruction in the program is validated at load time. KernelCall, DelegateCall, and JumpFalseCall instructions all have their index fields bounds-checked against the values array. However, FreeCall and MoveCall instructions fall through to the `default` case in the init switch statement, which performs **no validation at all**. At execution time, FreeCall directly indexes `values_[]` without bounds checking, and MoveCall passes its indices to `get_value()`/`mutable_value()` which use `ET_CHECK_MSG` (an assertion that aborts the process on debug builds but may be compiled out in release builds).
17
+
18
+ ## Root Cause
19
+
20
+ ### File: `runtime/executor/method.cpp`, lines 1063-1065 (init-time)
21
+
22
+ During `Method::init()`, the instruction setup loop validates indices for KernelCall (line 997-1027), DelegateCall (line 1028-1047), and JumpFalseCall (line 1048-1062). But MoveCall and FreeCall are handled by:
23
+
24
+ ```cpp
25
+ default: {
26
+ chain_instruction_arg_lists[instr_idx] = InstructionArgs();
27
+ } break;
28
+ ```
29
+
30
+ No validation of the indices (`move_from`, `move_to`, `value_index`) is performed.
31
+
32
+ ### File: `runtime/executor/method.cpp`, line 1504 (execution-time, FreeCall)
33
+
34
+ ```cpp
35
+ case executorch_flatbuffer::InstructionArguments::FreeCall: {
36
+ auto free_call = instruction->instr_args_as_FreeCall();
37
+ auto t = values_[free_call->value_index()].toTensor();
38
+ internal::reset_data_ptr(t);
39
+ } break;
40
+ ```
41
+
42
+ `values_[free_call->value_index()]` directly indexes the `values_` array using an attacker-controlled `value_index` from the FlatBuffer with **no bounds check**. If `value_index` exceeds `n_value_`, this reads out-of-bounds heap memory, interprets it as an EValue, calls `.toTensor()` on it (type confusion), and then calls `reset_data_ptr()` which writes to whatever the corrupted Tensor pointer points to.
43
+
44
+ ### File: `runtime/executor/method.cpp`, line 1495 (execution-time, MoveCall)
45
+
46
+ ```cpp
47
+ case executorch_flatbuffer::InstructionArguments::MoveCall: {
48
+ auto move_call = instruction->instr_args_as_MoveCall();
49
+ mutable_value(move_call->move_to()) = get_value(move_call->move_from());
50
+ } break;
51
+ ```
52
+
53
+ `mutable_value()` and `get_value()` do have `ET_CHECK_MSG` assertions, but these are:
54
+ 1. Not present in production/release builds when `ET_CHECK_MSG` is compiled as a no-op
55
+ 2. Even when present, they call `abort()` rather than returning an error, causing a denial-of-service
56
+
57
+ ### Contrast with properly validated instructions
58
+
59
+ JumpFalseCall is properly validated at init time (lines 1048-1062):
60
+ ```cpp
61
+ case executorch_flatbuffer::InstructionArguments::JumpFalseCall: {
62
+ auto index = ...->cond_value_index();
63
+ ET_CHECK_OR_RETURN_ERROR(
64
+ index >= 0 && static_cast<size_t>(index) < n_value_,
65
+ InvalidProgram,
66
+ "Index %zd negative or >= %" ET_PRIsize_t,
67
+ static_cast<ssize_t>(index),
68
+ n_value_);
69
+ ```
70
+
71
+ Neither FreeCall's `value_index` nor MoveCall's `move_from`/`move_to` receive equivalent validation.
72
+
73
+ ### Validation gap in program_validation.cpp
74
+
75
+ The `validate_program()` function in `runtime/executor/program_validation.cpp` validates Tensor values and TensorList indices, but does NOT validate any instruction indices (FreeCall, MoveCall, KernelCall op_index, DelegateCall delegate_index, etc.). The init-time validation in `method.cpp` is the only line of defense, and it has this gap for FreeCall and MoveCall.
76
+
77
+ ## Exploitation Flow
78
+
79
+ 1. **Attacker crafts a .pte file** containing a FreeCall instruction with `value_index` set to a large value (e.g., 0xFFFFFFFF or any value >= the values array size).
80
+
81
+ 2. **The program loads successfully** -- FreeCall indices are never checked during init.
82
+
83
+ 3. **During execution**, `values_[free_call->value_index()]` reads out of bounds:
84
+ - The read accesses heap memory beyond the `values_` array
85
+ - The bytes are reinterpreted as an `EValue` struct
86
+ - `.toTensor()` extracts a `TensorImpl*` pointer from the corrupted EValue
87
+ - `reset_data_ptr()` writes to the address pointed to by the corrupted TensorImpl's `data_` pointer
88
+
89
+ 4. **Result**: An attacker who controls both the PTE file and can predict/influence heap layout achieves arbitrary memory write. Even without heap control, this is a reliable crash (denial of service).
90
+
91
+ ### MoveCall variant
92
+
93
+ A MoveCall with `move_to` set to a large value would:
94
+ - In debug builds: trigger `ET_CHECK_MSG` -> `abort()` (DoS)
95
+ - In release builds (if ET_CHECK_MSG is compiled out): write an EValue to an out-of-bounds location in the values array -> heap corruption
96
+
97
+ ## Impact
98
+
99
+ - **Heap Out-of-Bounds Read**: Reading arbitrary heap memory via crafted FreeCall/MoveCall indices
100
+ - **Heap Corruption / Arbitrary Write**: Writing to attacker-influenced memory locations
101
+ - **Denial of Service**: Process abort via assertion failure or segfault
102
+ - **Particularly dangerous on embedded targets**: ExecuTorch runs on mobile/embedded devices with limited memory protections
103
+
104
+ ## Affected Code Path
105
+
106
+ ```
107
+ Program::load() -> Method::load() -> Method::init()
108
+ -> [instruction loop, default case -- NO VALIDATION for FreeCall/MoveCall]
109
+
110
+ Method::execute() -> Method::step() -> Method::execute_instruction()
111
+ -> FreeCall: values_[free_call->value_index()] -- OUT-OF-BOUNDS
112
+ -> MoveCall: mutable_value(move_call->move_to()) -- ASSERT-ONLY CHECK
113
+ ```
114
+
115
+ ## Remediation
116
+
117
+ Add init-time bounds validation for FreeCall and MoveCall, matching the pattern used for JumpFalseCall:
118
+
119
+ ```cpp
120
+ case executorch_flatbuffer::InstructionArguments::FreeCall: {
121
+ auto index = static_cast<const executorch_flatbuffer::FreeCall*>(
122
+ instr_args)->value_index();
123
+ ET_CHECK_OR_RETURN_ERROR(
124
+ index >= 0 && static_cast<size_t>(index) < n_value_,
125
+ InvalidProgram,
126
+ "FreeCall value_index %d out of range",
127
+ index);
128
+ chain_instruction_arg_lists[instr_idx] = InstructionArgs();
129
+ } break;
130
+
131
+ case executorch_flatbuffer::InstructionArguments::MoveCall: {
132
+ auto mc = static_cast<const executorch_flatbuffer::MoveCall*>(instr_args);
133
+ ET_CHECK_OR_RETURN_ERROR(
134
+ mc->move_from() >= 0 && static_cast<size_t>(mc->move_from()) < n_value_ &&
135
+ mc->move_to() >= 0 && static_cast<size_t>(mc->move_to()) < n_value_,
136
+ InvalidProgram,
137
+ "MoveCall indices out of range");
138
+ chain_instruction_arg_lists[instr_idx] = InstructionArgs();
139
+ } break;
140
+ ```
141
+
142
+ Additionally, add a runtime bounds check at the FreeCall execution site (line 1504) as defense-in-depth:
143
+ ```cpp
144
+ ET_CHECK_OR_RETURN_ERROR(
145
+ static_cast<size_t>(free_call->value_index()) < n_value_,
146
+ Internal,
147
+ "FreeCall value_index out of bounds");
148
+ ```
149
+
150
+ ## References
151
+
152
+ - `runtime/executor/method.cpp:1063-1065` -- Missing init-time validation (default case)
153
+ - `runtime/executor/method.cpp:1504` -- FreeCall unchecked array access
154
+ - `runtime/executor/method.cpp:1495` -- MoveCall assert-only check
155
+ - `runtime/executor/method.cpp:1048-1062` -- JumpFalseCall proper validation (for comparison)
156
+ - `runtime/executor/program_validation.cpp` -- No instruction index validation