When the security community talks about AI security, the conversation almost always lands on prompt injection, guardrail evasion, and training data extraction. Those discussions stay at the web application layer. Our research looks lower in the stack. We audit the C and C++ inference runtimes as AI workloads shift directly onto edge silicon. That hardware foundation has critical real-world deployment and industry impact across smart cameras, autonomous vehicles, and industrial systems. Think about a residential security camera at 2:00 AM. It has to decide whether a shadow on a driveway is a person, a pet, or a passing car. It runs that inference locally on a dedicated edge AI chip rather than streaming raw video to the cloud.
During our ongoing research into hardware-accelerated machine learning, the Cobalt Offensive Security Research team audited the open-source ax-llm runtime developed by AXERA-TECH. AXERA-TECH is a fabless semiconductor company that raised $379 million in January 2026 ahead of a planned Hong Kong IPO. The company ships millions of AI processors across global supply chains, powering critical real-world deployments in automotive driver-assistance systems, smart security cameras, and industrial infrastructure. Our lab evaluation targeted the Axera AX8850 (LAMBERT) silicon platform on the Radxa AI Core AX-M1 accelerator board, alongside the wider AX650 processor family.
Our audit uncovered seven security defects across the ax-llm inference stack. Five of those bugs corrupt memory: an integer truncation leading to an out-of-bounds DMA write, a stack buffer overflow in model template loading, and an out-of-bounds write in position indices. We also found an integer overflow in image preprocessing and a CLI parser double free. On the execution and service side, we found an OS command injection flaw in audio processing and a NULL pointer dereference in streaming callbacks. We packaged our technical findings, shared them with the AXERA-TECH engineering team through public GitHub issues, and submitted all seven defects to MITRE under coordinated vulnerability disclosure.
Following our technical engagement, the AXERA-TECH engineering team reviewed our findings and deployed remediation commits across active and historical branches (including commit 5ad6f17 addressing Issue #76, commit 47405d2 addressing Issue #80, commit 23faad8 addressing Issues #81 and #82, shell escaping addressing Issue #75, and callback hardening addressing Issue #77). Because upstream repository commits do not automatically update physical devices in the field. These public GitHub issues and fix commits give security teams and device OEMs immediate proof of the bugs. They also provide clear guidance to patch Board Support Packages (BSPs) and deployed firmware before formal MITRE CVE publication on October 31, 2026.
This blog post breaks down why we audited edge AI runtimes, how the AX8850 platform handles physical memory, and why these bugs required formal CVE tracking. We also map out what an attacker can do if a working proof-of-concept exists. We are intentionally withholding our functional exploit proof-of-concept (PoC) code right now. That gives downstream device integrators time to patch deployed firmware before formal MITRE CVE records publish on October 31, 2026.
Unified Vulnerability & Threat Impact Matrix
The table below outlines the complete portfolio of seven CVE Candidate assignments resulting from our research:
|
Criticality |
CAN Identifier |
GitHub Issue |
Internal ZD |
Target Component |
Exploit Potential & Threat Impact |
Vulnerability Class & Root Cause |
Upstream Remediation
|
|---|---|---|---|---|---|---|---|
|
High (CVSS 8.4) |
CAN-2026-2038801 |
ZD-048 / RT-508 |
src/runner/utils/audio_processor.cpp |
Remote Code Execution (RCE): Malicious audio path input executes arbitrary host shell commands with administrative server privileges. |
OS Command Injection (CWE-78): Unsanitized external command execution via subprocess calls. |
Resolved Upstream shell_quote() escaping |
|
|
High (CVSS 7.8) |
CAN-2026-2034987 |
ZD-009 |
src/runner/LLM_init.cpp |
Arbitrary Code Execution / RCE: Smashing stack frames via custom format strings or oversized directory paths overwrites saved instruction pointers. |
Stack Buffer Overflow (CWE-120, CWE-134): Model shard templates formatted via unbounded sprintf into a fixed 1024-byte stack buffer. |
Resolved Upstream snprintf bounds check |
|
|
High (CVSS 7.5) |
CAN-2026-2038778 |
ZD-041 |
src/runner/utils/image_processor.cpp |
Heap Corruption / RCE: Signed integer multiplication wraparound produces undersized allocations; image copy loops corrupt adjacent heap structures. |
Integer Overflow & Heap Overflow (CWE-190, CWE-122): 32-bit stride multiplication wraps negative during image preprocessing. |
Resolved Upstream 64-bit bounds checks |
|
|
High (CVSS 7.1) |
CAN-2026-2038776 |
ZD-006 / ZD-039 |
src/runner/LLM_decode.cpp src/runner/LLM_embed.cpp |
Physical Device Memory Corruption: Corrupts contiguous device memory (MMZ/CMM), leading to host kernel panic, DMA corruption, or NPU bus lockup. |
Out-of-Bounds Memory Write (CWE-787, CWE-190): DMA attention mask copies failed to clamp incoming lengths against device buffer capacity. |
Resolved Upstream Tensor capacity clamp |
|
|
High (CVSS 7.1) |
CAN-2026-2038777 |
ZD-024 |
src/runner/LLM_decode.cpp src/runner/LLM_embed.cpp |
Memory Corruption & Output Tampering: Unchecked writes push integers beyond tensor bounds into physical memory, corrupting adjacent inference data. |
Out-of-Bounds Tensor Write (CWE-787): Position index generation loops lacked capacity bounds checking on indices tensors. |
Resolved Upstream Indices length clamp |
|
|
Medium (CVSS 6.8) |
CAN-2026-2038779 |
N/A (Vendored) |
ZD-002 |
src/cmdline.hpp |
Heap Corruption / Process Hijack: Duplicated deallocation corrupts memory management free-lists, creating an exploit primitive or process termination. |
Double Free (CWE-415): Parser class violates the C++ Rule of Three; shallow copy duplicates raw heap pointers. |
Cataloged with MITRE Rule-of-Three fix (Vendored component) |
|
Medium (CVSS 6.2) |
CAN-2026-2038802 |
ZD-051 |
src/main_api.cpp |
Denial of Service (DoS): Unauthenticated network users can crash the inference daemon remotely by triggering null streaming tokens. |
NULL Pointer Dereference (CWE-476): Streaming callback functions output raw string pointers to fprintf without null pointer validation. |
Resolved in Modern Branch Modern API branch std::string interface |
Why We Audited Edge AI Runtimes: The AX8850 Silicon Context
Cloud-hosted language models run inside heavily isolated infrastructure. They sit behind web application firewalls, API gateways, load balancers, and multi-tenant hypervisors. If a cloud inference worker hits an invalid memory operation, the host OS kills that single container while the surrounding control plane stays online.
Edge AI devices operate under fundamentally different physical and architectural constraints:
1. Critical Real-World Device Deployments Across 3 Commercial Verticals and Industry Impact
A software failure in an edge device does not simply result in an HTTP 500 error on a website. It hits the physical world across AXERA-TECH's three primary commercial deployment verticals:
- Smart vehicles and automotive (assisted driving, smart cockpits): Corrupting physical NPU memory mid-inference can freeze smart cockpit displays or blind assisted-driving lane-keeping and pedestrian detection while a vehicle is in motion.
- Edge AI and on-device computing (home cameras, visual terminals, AR/VR): Command injection and heap overflows let an attacker hijack live home camera and microphone feeds. They also grant root execution on wearable AR/VR devices.
- Smart infrastructure and cities (smart transportation, manufacturing): Triggering a DMA hardware bus lockup knocks out automated factory defect-inspection lines and smart traffic controllers. Recovering the device requires a physical power cycle in the field.
- 2. Direct Silicon and Physical Memory Mapping on the AX8850
- The Axera AX8850 represents a dedicated, high-throughput Neural Processing Unit (NPU) engineered for edge vision and transformer workloads. In our lab deployment on the Radxa AI Core AX-M1 board, the hardware interfaces via PCIe and M.2 buses to deliver over 24 TOPS of INT4 compute.
- Edge inference runtimes cannot afford the performance overhead of virtualized abstraction layers. To achieve high token throughput on low-power devices, runtimes like ax-llm map physical memory pages directly. They interact with specialized kernel drivers (/dev/ax_npu) and allocate continuous physical memory blocks through hardware memory management zones (MMZ) and Contiguous Memory Management (CMM). A memory corruption defect at this layer interacts directly with physical silicon memory rather than a sandboxed virtual page.
- 3. Ambient Administrative Privileges
- Edge AI runtimes frequently execute as root or system-level services. They require administrative privileges to issue low-level ioctl calls, control PCIe accelerator cards, manage hardware clocks, and interface with camera sensors. When a vulnerability exists inside an edge inference daemon, the vulnerable process already possesses administrative operating system control.
4. Upstream Repository Patches vs. The Field Deployment Risk
The AXERA-TECH engineering and ax-llm project maintenance teams fixed these seven vulnerabilities upstream across their GitHub branches. However, a fix in an open-source code repository does not automatically protect hardware in the field.
Unlike cloud environments that deploy continuous CI/CD updates, edge hardware appliances depend on multi-tiered Board Support Packages (BSPs) and downstream firmware updates that are rarely installed by end users. If a vendor does not cut a formal firmware release, or if device operators fail to apply updates, vulnerable code paths remain exposed indefinitely across deployed infrastructure.
5. Historical Precedent and Real-World Repercussions
We have seen this exact pattern play out before in local inference software. In 2023 and early 2024, researchers uncovered GGUF and GGML parser heap buffer overflows in llama.cpp (CVE-2024-25664 through CVE-2024-25668). Those bugs proved that loading an untrusted model file or sending a crafted prompt can smash heap allocations and give an attacker arbitrary code execution on the host.
Unpatched flaws across edge AI runtimes and embedded silicon have fueled poisoned-model supply chain attacks and camera network takeovers. Several campaigns compromised over 10,000 customer devices at once. Auditing low-level edge inference code matters on the ground. The industry is rolling out physical appliances powered by complex C and C++ runtimes that have not seen rigorous binary security reviews.
The Software Target: ax-llm and Upstream Transition State
The ax-llm repository is the core C++ engine that executes quantized transformer models on AXERA silicon. Paired with the vendor pulsar2 compilation toolchain, ax-llm loads compiled .axmodel packages, allocates tensor memory pools, and pushes matrix math directly onto the NPU cores.
Software Versioning and Ongoing Upstream Transition
Open-source inference frameworks often evolve across multiple concurrent branches. In the case of ax-llm, development spans the primary master, axllm, and feature-specific experimental branches.
When our team reported these issues, the AXERA-TECH maintainers moved quickly to evaluate our reproduction logs and test the recommended boundary checks. The upstream team committed fixes directly into active development branches (e.g. commit 5ad6f17 addressing Issue #76) and started coordinated review across all reported issues.
Downstream edge devices pull code from different commit points and vendor BSPs, so patch adoption takes time. We keep version numbers open here on purpose. Engineering teams are actively rolling out patches across their repositories, and maintainers need time to finish regression testing across both AX8850 and AX650 hardware before cutting a universal release.
Downstream device integrators and enterprise consumers should evaluate their specific repository branch, commit hash, and BSP integration against the boundary conditions documented below rather than relying on a static version number.
Why These Defects Warranted CVE Assignment
A common question in open-source research is why certain bugs are submitted for CVE assignment rather than handled as private bug reports.
We started formal CVE tracking for these findings for three concrete reasons:
1. Supply Chain Traceability and Downstream Integrators
The ax-llm repository is not just a standalone application. It is a reference inference engine and dependency embedded into third-party commercial firmware, camera operating systems, and edge development platforms. When maintainers fix a bug quietly in an upstream repo without a CVE ID, downstream OEMs and enterprise buyers never learn that their shipped firmware runs vulnerable code. A CVE provides a globally recognized tracking identifier across the entire hardware supply chain.
2. Software Bill of Materials (SBOM) and Compliance Tracking
Modern enterprise security teams and federal device procurement standards mandate Software Bill of Materials tracking. Automated vulnerability scanners rely on CVE databases to notify administrators when an embedded library requires updating. Without a CVE, security monitoring tools cannot flag vulnerable runtime versions in production environments.
3. Coordinated and Transparent Disclosure
Assigning a CVE establishes a verifiable, standardized timeline. We submitted our vulnerability reports to MITRE and started vendor coordination on July 18, 2026. After the initial review window, we engaged directly with the project engineering team on GitHub by filing technical tracking issues on September 21, 2026. The maintainers responded constructively, merged patches into the codebase, and closed the tracking issues. CVE assignment ensures that the vendor, security practitioners, and downstream integrators operate with identical technical data while tracking remediation across the ecosystem.
Vulnerability 1: Integer Truncation and Out-of-Bounds Tensor Write
Vulnerability Class: CWE-190 (Integer Overflow / Wraparound) chained to CWE-787 (Out-of-Bounds Write)- Secondary Weaknesses: CWE-197 (Numeric Truncation Error) and CWE-681 (Incorrect Conversion between Numeric Types)
- Advisory Reference: Cobalt Research Advisory (Coordinated Disclosure: AXERA-TECH Issue #76)
- Assigned Candidate ID: CAN-2026-2038776
- Internal Tracking IDs: ZD-006 (Integer Overflow) and ZD-039 (Memcpy Out-of-Bounds Write)
- Target Source Locations: src/runner/LLM.cpp (prefill handler) and src/runner/LLM.hpp (input layer preparation)
- CVSS v3.1 Vector: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:H (Base Score: 7.5, High)
- CVSS v4.0 Vector: CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:H/VA:H/SC:N/SI:N/SA:N (Base Score: 8.7, High)
The Architectural Issue Explained
During the prompt prefill phase, the inference engine builds attention masks and token vectors before moving them into physical memory buffers for the NPU.
The breakdown starts in the element capacity calculation. The runtime queries the input tensor size as a 64-bit unsigned size_t, divides by the element width, and casts the quotient directly down into a signed 32-bit int:
|
// src/runner/LLM.cpp line 1894 const auto &t_mask = ref_layer.get_input(gid, "mask"); const int elems = (int)((size_t)t_mask.nSize / sizeof(unsigned short)); |
If an attacker feeds a crafted model configuration or an oversized tensor descriptor, that division exceeds INT_MAX (2,147,483,647). The signed cast truncates the value and wraps it into a negative number or a tiny positive integer, breaking the loop bounds that follow.
From there, the memory copy routine compounds the failure. When transferring attention masks into physical device memory, the code copies bytes based entirely on the size of the incoming prompt buffer:
|
// src/runner/LLM.hpp line 364 auto &input_mask = layer.layer.get_input(prefill_grpid, "mask"); memcpy((void *)input_mask.pVirAddr, (void *)mask_tmp.data(), mask_tmp.size() * sizeof(unsigned short)); |
Notice what is missing: zero destination boundary validation. The destination buffer has a fixed hardware boundary (input_mask.nSize), but the copy routine blindly writes the full volume of incoming prompt data into physical memory.
Step-by-Step Discovery and Proof of Execution
When we audited the prompt prefill and DMA attention mask routines in src/runner/LLM.cpp and src/runner/LLM.hpp, we saw 64-bit tensor sizes downcast into signed 32-bit integers. The code then called memcpy without destination bounds checks, corrupting physical NPU memory directly.
- Step 1 (Source Audit of DMA Prefill Buffers): We inspected the prefill handler and found that memcpy(input_mask.pVirAddr, mask_tmp.data(), mask_tmp.size() * sizeof(unsigned short)) trusted the caller-supplied vector length without checking input_mask.nSize.
- Step 2 (AX8850 Hardware Reproduction): We ran the engine on our Radxa AI Core AX-M1 testbed. We then sent an oversized prompt and attention mask that exceeded the fixed CMM buffer.
- Step 3 (Observed Hardware Failure): The unchecked memcpy overwrote adjacent physical pages in the /dev/ax_npu CMM/MMZ pool. Corrupting nearby tensor descriptors caused the engine to spit out garbled Chinese Mojibake tokens ('赋能生态体系建设烫烫烫锟斤拷...') right before triggering a kernel driver fault and locking the PCIe NPU bus.
Potential Impact If a Working Exploit Existed
With a working exploit targeting this DMA boundary failure, an attacker can trigger four distinct outcomes:
- Unauthenticated Daemon Crash and Service Outage: An unauthenticated client sending an oversized prompt or malformed attention mask triggers a memory violation. The inference daemon crashes on the spot and takes the model offline.
- Physical NPU Bus Lockup and Device Freeze: Because ax-llm maps buffers directly into MMZ and CMM hardware pools, overwriting adjacent driver structures halts the PCIe or M.2 bus. That locks the AX8850 NPU core until someone physically power-cycles the appliance.
- Control Flow Hijacking within Mapped Pages: The unvalidated memory copy writes attacker-controlled token values linearly past the allocated buffer. In embedded environments lacking modern memory layout randomization, overwriting adjacent heap allocations, layer metadata, or function pointers grants execution control under ambient administrative privileges.
- Supply Chain Model Poisoning: Edge deployments often ingest quantized weights from third-party repositories. An attacker publishing a model package with crafted tensor dimensions triggers this truncation during weight initialization, corrupting memory before inference begins.
Vulnerability 2: Stack Buffer Overflow in Model Shard Template Loader
Vulnerability Class: CWE-120 (Buffer Copy without Checking Size of Input)- Secondary Weakness: CWE-134 (Use of Externally-Controlled Format String)
- Advisory Reference: Cobalt Research Advisory (Coordinated Disclosure: AXERA-TECH Issue #80)
- Assigned Candidate ID: CAN-2026-2034987
- Internal Tracking ID: ZD-009 (Stack Buffer Overflow in Model Shard Template Loader)
- Target Source Locations: src/runner/LLM_init.cpp (native AX650 path line 738, AXCL path line 559) and src/runner/LLM.cpp (line 2871)
- CVSS v3.1 Vector: CVSS:3.1/AV:L/AC:L/PR:N/UI:N/S:U/C:L/I:H/A:H (Base Score: 7.3, High)
- CVSS v4.0 Vector: CVSS:4.0/AV:L/AC:L/AT:N/PR:N/UI:N/VC:L/VI:H/VA:H/SC:N/SI:N/SA:N (Base Score: 8.5, High)
The Architectural Issue Explained
When deploying partitioned multi-layer transformer models, the ax-llm runtime synthesizes file paths for individual .axmodel weight shards by formatting layer index numbers into a template filename configured in config.json.
The defect exists in how the initialization routines format these shard path strings:
|
// src/runner/LLM_init.cpp line 738 char axmodel_path[1024]; sprintf(axmodel_path, attr.template_filename_axmodel.c_str(), i); |
The application allocates a fixed 1024-byte buffer on the local stack frame, then formats the template filename into that buffer using sprintf().
Legacy sprintf() never checks the destination size. It keeps writing characters into axmodel_path until the source string ends. If a model package or config.json file includes a long directory path or a wide format specifier like %02048d, the output blows past 1,024 bytes. That write walks straight across the stack frame, overwriting local variables, saved frame pointers, and the return address.
Step-by-Step Discovery and Proof of Execution
While auditing the weight loader in src/runner/LLM_init.cpp, we noticed that filename_post_axmodel from config.json fed straight into an unbounded sprintf() call on a 1024-byte stack buffer.
- Step 1 (Loader Source Audit): We traced config.json parsing in LLM_init.cpp (lines 559 and 738) and LLM.cpp (line 2871), spotting char axmodel_path[1024] filled by sprintf(axmodel_path, attr.filename_post_axmodel.c_str(), i).
- Step 2 (Hardware Reproduction with Crafted Config): On the AX8850 testbed, we supplied a model configuration specifying an expanded format width specifier (%02048d) and elongated shard path template exceeding 1,024 bytes.
- Step 3 (Stack Smashing Proof): During startup, sprintf wrote past axmodel_path into the caller stack frame. That immediately tripped stack smashing protection (*** stack smashing detected ***) and AddressSanitizer (stack-buffer-overflow at LLM_init.cpp:738), killing the process before initialization finished.
Potential Impact If a Working Exploit Existed
Supplying a crafted model package with an oversized template string gives an attacker three direct impact paths:
- Stack Canary Smashing and Daemon Crash: On Linux builds compiled with stack canaries, the overflow corrupts the canary value. That forces __stack_chk_fail() to abort the inference daemon on function return.
- Execution Control Redirection: Many embedded toolchains strip stack protection flags to save flash space or CPU cycles. On those targets, overwriting saved frame pointers and return addresses redirects execution flow directly.
- Denial of Service During Startup: Corrupting stack control data during weight loading kills the daemon at boot, leaving the edge appliance unable to start its AI services.
Vulnerability 3: Out-of-Bounds Tensor Write in Position Indices
Vulnerability Class: CWE-787 (Out-of-Bounds Write)- Secondary Weakness: CWE-190 (Integer Overflow or Wraparound)
- Advisory Reference: Cobalt Research Advisory (Coordinated Disclosure: AXERA-TECH Issue #81)
- Assigned Candidate ID: CAN-2026-2038777
- Internal Tracking ID: ZD-024 (Position Indices Tensor Boundary Overflow)
- Target Source Locations: src/runner/LLM_decode.cpp (lines 405 to 435) and src/runner/LLM_embed.cpp (lines 135 to 170)
- CVSS v3.1 Vector: CVSS:3.1/AV:L/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:H (Base Score: 7.1, High)
- CVSS v4.0 Vector: CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:H/VA:H/SC:N/SI:N/SA:N (Base Score: 8.5, High)
The Architectural Issue Explained
During transformer token decoding and prompt evaluation, the ax-llm engine calculates token sequence positions and writes position indices directly into physical memory buffers allocated for the NPU.
The defect occurs in both LLM_decode.cpp and LLM_embed.cpp when populating the indices tensor buffer:
|
// src/runner/LLM_decode.cpp lines 426-429 for (int j = 0; j < input_num_token; j++) { idx_ptr[r * _attr.prefill_token_num + j] = indices[indices.size() - input_num_token + j]; } |
When calculating row capacity, if the tensor capacity t_idx.nSize is smaller than a single row (_attr.prefill_token_num * sizeof(unsigned int)), integer division rounds down, forcing idx_rows = 1. If incoming prompt tokens exceed the allocated element count idx_elems, the inner loop continues writing up to input_num_token elements into idx_ptr without verifying that (size_t)r * _attr.prefill_token_num + j remains within idx_elems.
The loop writes raw integer indices past the end of the allocated physical memory buffer (idx_ptr), corrupting adjacent device memory allocations in Contiguous Memory Management (CMM) pools.
Step-by-Step Discovery and Proof of Execution
When we audited the position index generator in src/runner/LLM_decode.cpp and src/runner/LLM_embed.cpp, we found that the fill loop never checked whether input_num_token exceeded the allocated indices tensor capacity.
- Step 1 (Tensor Loop Audit): We traced idx_ptr in LLM_decode.cpp (lines 405-435) and LLM_embed.cpp (lines 135-170). The loop wrote input_num_token integers without clamping against idx_elems = t_idx.nSize / sizeof(unsigned int).
- Step 2 (Hardware Reproduction on AX8850): On the Radxa AI Core AX-M1 board, we supplied a prompt sequence where input_num_token exceeded the preallocated position index tensor boundary configured for the model shard.
- Step 3 (Mojibake Output & CMM Corruption Proof): As idx_ptr wrote sequential integers past t_idx.nSize into adjacent CMM pages, rotary position embeddings (RoPE) lost synchronization. Right before the NPU driver faulted, the model output devolved into garbled Chinese Mojibake token loops ('赋能生态体系建设烫烫烫锟斤拷...'), giving clear visual proof of physical tensor corruption.
Potential Impact If a Working Exploit Existed
Pushing out-of-bounds position indices gives an attacker three ways to break the target:
- Kernel Driver and MMZ Memory Corruption: Writing past the bounds of idx_ptr corrupts adjacent physical memory pages managed by the AXERA NPU driver (/dev/ax_npu), destabilizing the hardware memory controller.
- Inference Pipeline Memory Faults: Corrupted position indices scramble attention matrix math on later tokens, producing garbage outputs or crashing the decode worker with a segmentation fault.
- Persistent NPU Hardware Fault State: Overwriting device descriptor tables causes the hardware accelerator to enter an unrecoverable error state, requiring an operating system reboot or hardware power cycle to recover.
Vulnerability 4: Integer Overflow and Invalid Allocation in Image Preprocessor
Vulnerability Class: CWE-190 (Integer Overflow or Wraparound)- Secondary Weakness: CWE-122 (Heap-based Buffer Overflow)
- Advisory Reference: Cobalt Research Advisory (Coordinated Disclosure: AXERA-TECH Issue #82)
- Assigned Candidate ID: CAN-2026-2038778
- Internal Tracking ID: ZD-041 (Multimodal Vision Stride Integer Overflow)
- Target Source Locations: src/runner/utils/image_processor.cpp (lines 142, 164, and 536)
- CVSS v3.1 Vector: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H (Base Score: 7.5, High)
- CVSS v4.0 Vector: CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:H/SC:N/SI:N/SA:N (Base Score: 8.7, High)
The Architectural Issue Explained
Multimodal edge appliances process visual feeds from cameras alongside textual prompts. The ax-llm runtime includes an integrated image preprocessor (image_processor.cpp) that resizes incoming image buffers and slices visual data into patches for vision transformer models.
The defect stems from signed 32-bit arithmetic performed on image dimensions without upper boundary checks:
|
// src/runner/utils/image_processor.cpp line 142 int src_step = src_w * 4; std::vector<uint8_t> src_rgbx((size_t)src_h * (size_t)src_step); |
In resize_pillow(), the stride calculation src_w * 4 uses signed 32-bit integers. When an incoming image specifies a width exceeding 536,870,911 pixels (or negative dimensions), the multiplication overflows INT_MAX and wraps into a negative integer.
A similar integer overflow exists in create_patches() at line 536:
|
// src/runner/utils/image_processor.cpp line 536 patches.resize((size_t)patch_count * patch_size * patch_size * 3); |
Here, the code computes patch_size * patch_size * 3 using signed 32-bit integer math before casting to size_t. If an attacker supplies a large patch_size, the math wraps around. Passing that wrapped value to the allocator either throws an unhandled exception or allocates a tiny heap buffer. The pixel copy loop then writes the full image payload past the end of that undersized heap chunk.
Step-by-Step Discovery and Proof of Execution
When we audited the multimodal vision preprocessor in src/runner/utils/image_processor.cpp, we discovered that 32-bit signed stride and patch multiplications lacked upper-bound validation, allowing extreme image dimensions to wrap negative and corrupt heap allocations.
- Step 1 (Stride Math Audit): We checked resize_pillow() and create_patches() in image_processor.cpp. Both functions computed src_w * 4 and patch_size * patch_size * 3 in 32-bit signed int math before casting to size_t.
- Step 2 (Oversized Dimension Test): On our AX8850 testbed, we fed crafted image headers with width and patch values above the 32-bit signed limit (src_w > 536,870,911 and patch_size > 26,755).
- Step 3 (Crash and Heap Overflow Proof): Negative wraparound values cast to size_t requested petabyte allocations and crashed the daemon immediately with std::bad_alloc. Positive wraparound values allocated tiny heap buffers that tripped AddressSanitizer (heap-buffer-overflow) as pixel copy loops wrote past the chunk boundary.
Potential Impact If a Working Exploit Existed
Sending a crafted image header to a multimodal endpoint lets an attacker trigger three failures:
- Remote Denial of Service via Bad Allocation: Casting a negative signed stride to 64-bit size_t produces a huge unsigned number near 2^64 - 1. The runtime tries to allocate petabytes of RAM, throws std::bad_alloc, and kills the daemon.
- Heap Memory Corruption via Under-allocation: When arithmetic wraparound produces a small positive integer, patches.resize() allocates a buffer smaller than the incoming visual payload. The image slicer writes pixel data past the allocated buffer, corrupting adjacent heap chunks.
- Disruption of Physical Video Analytics: On edge appliances operating as smart cameras, knocking out the image preprocessing module stops all real-time video inference and anomaly detection until the appliance recovers.
Vulnerability 5: Double Free Memory Corruption in CLI Argument Parser
- Vulnerability Class: CWE-415 (Double Free)
- Secondary Weakness: CWE-1350 (Missing Copy-Control Member)
- Advisory Reference: Cobalt Research Advisory (Coordinated Disclosure: Vendored cmdline.hpp)
- Assigned Candidate ID: CAN-2026-2038779
- Internal Tracking ID: ZD-002 (CLI Parser Rule-of-Three Double Free)
- Target Source Location: src/cmdline.hpp (line 298)
- CVSS v3.1 Vector: CVSS:3.1/AV:L/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H (Base Score: 6.2, Medium)
- CVSS v4.0 Vector: CVSS:4.0/AV:L/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:H/SC:N/SI:N/SA:N (Base Score: 6.8, Medium)
The Architectural Issue Explained
The ax-llm runtime bundles a header-only command-line parser (src/cmdline.hpp) to set up model parameters, tokenizer paths, and NPU device IDs across its CLI and server binaries. Inside cmdline::parser, the class stores dynamically allocated option_base* pointers inside std::map<std::string, option_base*> and frees them in ~parser():
// src/cmdline.hpp lines 294-302
~parser() {
for (std::map<std::string, option_base*>::iterator p = options.begin();
p != options.end(); p++) {
delete p->second;
}
}
Because cmdline::parser frees raw heap pointers in its destructor without defining a copy constructor or copy assignment operator, it breaks the C++ Rule of Three. Whenever code passes or assigns a parser instance by value, the compiler generates a shallow copy of the internal std::map. Both parser objects hold raw pointers to the exact same option_base heap allocations. When both objects leave scope, the second destructor runs delete on already-freed heap memory.
Step-by-Step Discovery and Proof of Execution
When we audited the CLI initialization wrapper in src/cmdline.hpp, we discovered that the parser class lacked copy-control operators, allowing shallow object copies to trigger a deterministic heap double-free.
- Step 1 (Static Ownership Audit): We inspected the lifecycle of option_base* allocations in src/cmdline.hpp and verified that ~parser() iterated over options and invoked delete p->second without declaring parser(const parser&) = delete or a deep-cloning copy constructor.
- Step 2 (ASan Harness Reproduction): We compiled a test harness against src/cmdline.hpp on the Radxa AI Core AX-M1 board with -fsanitize=address. We then registered standard CLI flags and copied the parser instance across scopes.
- Step 3 (Double-Free Abort Proof): At scope exit, the second destructor freed the same option_base* pointer again. That immediately tripped AddressSanitizer (attempting double-free) and aborted the process via SIGABRT.
Potential Impact If a Working Exploit Existed
Triggering shallow copies of parser state during initialization or reconfiguration causes two direct impacts:
- Heap Allocator Freelist Corruption: Freeing the same chunk twice corrupts glibc tcache or fastbin freelists, causing later allocations to return overlapping heap chunks.
- Immediate Process Termination: Modern Linux allocators spot duplicate frees on tcache entries and kill the inference daemon immediately via abort().
Vulnerability 6: OS Command Injection in Audio Subprocess Execution
- Vulnerability Class: CWE-78 (Improper Neutralization of Special Elements used in an OS Command)
- Secondary Weakness: CWE-88 (Improper Neutralization of Argument Delimiters in a Command)
- Advisory Reference: Cobalt Research Advisory (Coordinated Disclosure: AXERA-TECH Issue #75)
- Assigned Candidate ID: CAN-2026-2038801
- Internal Tracking IDs: ZD-048 / RT-508 (Audio Subprocess OS Command Injection)
- Target Source Location: src/runner/utils/audio_processor.cpp (historical commits prior to Commit 5dce054)
- CVSS v3.1 Vector: CVSS:3.1/AV:L/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H (Base Score: 8.4, High)
- CVSS v4.0 Vector: CVSS:4.0/AV:L/AC:L/AT:N/PR:N/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N (Base Score: 8.6, High)
The Architectural Issue Explained
Multimodal edge AI deployments process spoken audio commands and microphone streams alongside video and text. To normalize incoming audio files into raw WAV PCM waveforms before feeding them to the NPU, src/runner/utils/audio_processor.cpp invokes an external ffmpeg utility.
In historical commits before upstream refactoring, the audio preprocessor built a shell command string by dropping the caller-supplied audio_path straight into snprintf() and passing that buffer to _popen():
// src/runner/utils/audio_processor.cpp (historical implementation)
char cmd[1024];
snprintf(cmd, sizeof(cmd), "ffmpeg -i \"%s\" -ar 16000 -ac 1 -f wav -", audio_path.c_str());
FILE *pipe = _popen(cmd, "r");
Because _popen() invokes /bin/sh -c on Linux and audio_path had zero metacharacter filtering, a double quote followed by ;, $(), or backticks breaks right out of the filename argument. The shell then executes the injected string under the daemon's user account. After we filed Issue #75, AXERA-TECH confirmed that maintained branches fixed this flaw in Commit 5dce054 using shell_quote() escaping and file extension checks.
Step-by-Step Discovery and Proof of Execution
When we traced the multimodal audio path in src/runner/utils/audio_processor.cpp, we saw user-supplied audio filenames formatted directly into a /bin/sh command string. That gave us immediate OS command execution as the inference daemon user.
- Step 1 (Source Taint Trace): We traced how multimodal API endpoints and CLI runners pass external media paths into audio_processor.cpp and identified the unsanitized snprintf format string feeding _popen().
- Step 2 (Isolated Shell Breakout Test): On our lab testbed, we passed a benign filename containing a closing quote and a shell command separator. That test verified whether /bin/sh executed the injected token before ffmpeg.
- Step 3 (Root Command Execution Proof): The /bin/sh subshell parsed the breakout string and ran our injected command under the UID/GID of ax-llm. We then verified that Commit 5dce054 blocked the breakout with shell_quote().
Potential Impact If a Working Exploit Existed
On deployments running unpatched revisions that expose multimodal audio ingestion to untrusted filenames or network API parameters, an attacker could achieve:
- Unauthenticated Remote Code Execution (RCE) as Root: Because edge inference daemons typically run with root privileges to access /dev/ax_npu, command injection grants immediate administrative control over the physical appliance.
- Camera and Microphone Stream Exfiltration: Running shell commands on a smart camera or voice terminal lets an attacker tap live audio and video nodes. From there, the attacker can drop a reverse shell or pivot across the local network.
Vulnerability 7: NULL Pointer Dereference in Streaming Callback
- Vulnerability Class: CWE-476 (NULL Pointer Dereference)
- Secondary Weakness: CWE-252 (Unchecked Return Value)
- Advisory Reference: Cobalt Research Advisory (Coordinated Disclosure: AXERA-TECH Issue #77)
- Assigned Candidate ID: CAN-2026-2038802
- Internal Tracking ID: ZD-051 (Streaming Callback NULL Pointer Dereference)
- Target Source Location: src/main_api.cpp (llm_running_callback)
- CVSS v3.1 Vector: CVSS:3.1/AV:L/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H (Base Score: 6.2, Medium)
- CVSS v4.0 Vector: CVSS:4.0/AV:L/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:H/SC:N/SI:N/SA:N (Base Score: 6.8, Medium)
The Architectural Issue Explained
To stream generated tokens in real time, legacy API branches of ax-llm register a C callback (llm_running_callback) in src/main_api.cpp. That callback prints decoded token strings as they leave the NPU decode loop:
// src/main_api.cpp
void llm_running_callback(int *p_token, int n_token, const char *p_str, float token_per_sec, void *reserve) {
fprintf(stdout, "%s", p_str);
fflush(stdout);
}
The callback passes the raw const char *p_str pointer straight to fprintf(stdout, "%s", p_str) without a null check. When the tokenizer hits empty control sequences, partial multi-byte UTF-8 characters, or end-of-stream states that emit nullptr, standard C libraries on Linux and Android dereference 0x0 and crash with SIGSEGV. As noted in Issue #77, modern maintained branches fixed this bug by replacing raw C pointers with std::string objects.
Step-by-Step Discovery and Proof of Execution
When we audited the token streaming API in src/main_api.cpp, we discovered that llm_running_callback passed an unchecked const char * pointer directly to fprintf, allowing null token emissions to crash the inference daemon.
- Step 1 (Callback Interface Audit): We inspected src/main_api.cpp and observed that llm_running_callback performed no if (!p_str) guard before invoking fprintf(stdout, "%s", p_str).
- Step 2 (Streaming Boundary Reproduction): We invoked the streaming callback path with a null token pointer representing an unmapped or empty control token during live token generation.
- Step 3 (Segmentation Fault Proof): The runtime tried to read address 0x0000000000000000 inside libc, triggering an immediate SIGSEGV crash and dropping all active client connections.
Potential Impact If a Working Exploit Existed
On legacy deployments still exposing the C-pointer callback interface in src/main_api.cpp, an adversary could achieve:
- Remote Denial of Service (DoS): Submitting prompts that trigger null token emissions during streaming immediately crashes the inference server process, knocking offline all dependent edge AI workflows.
Detailed CWE Classification and Taxonomy Mapping
Understanding these vulnerabilities within the Common Weakness Enumeration (CWE) framework clarifies both the root cause and the downstream failure mechanism:
|
Weakness Identifier |
Classification Title |
Abstraction Level |
Role in Vulnerability Chain
|
|---|---|---|---|
|
CWE-190 |
Integer Overflow or Wraparound |
Base Class |
Primary Root Cause (Vulnerabilities 1 and 4). Calculations produce values exceeding numeric representation boundaries. |
|
CWE-197 |
Numeric Truncation Error |
Variant |
Direct Mechanism (Vulnerability 1). Downcasting a 64-bit integer to a 32-bit type discards upper precision bits. |
|
CWE-681 |
Incorrect Conversion between Numeric Types |
Base Class |
Contributing Factor (Vulnerability 1). Unsigned to signed conversion alters mathematical sign semantics. |
|
CWE-787 |
Out-of-Bounds Write |
Base Class (MITRE Top 25 #1) |
Primary Flaw (Vulnerabilities 1 and 3). Memory writes exceed allocated destination buffer boundaries into hardware memory. |
|
CWE-120 |
Buffer Copy without Checking Size of Input |
Base Class |
Primary Flaw (Vulnerability 2). Unbounded sprintf writing past static stack allocation during template loading. |
|
CWE-134 |
Use of Externally-Controlled Format String |
Class |
Contributing Risk (Vulnerability 2). Model configuration strings parsed directly as format templates. |
|
CWE-122 |
Heap-based Buffer Overflow |
Class |
Physical Memory Manifestation (Vulnerability 4). Wrapped integer calculations lead to invalid heap buffer dimensions. |
|
CWE-415 |
Double Free |
Base Class |
Memory Corruption (Vulnerability 5). Rule-of-Three violation in CLI argument parser duplicating heap pointer ownership. |
|
CWE-78 |
OS Command Injection |
Base Class |
System Boundary Violation (Vulnerability 6). Unsanitized audio filename passed directly to shell execution in _popen. |
|
CWE-476 |
NULL Pointer Dereference |
Base Class |
Availability / Crash (Vulnerability 7). Unchecked token pointer in streaming callback formatted via fprintf. |
MITRE ATT&CK and CAPEC Framework Mappings
To support defensive blue teams, threat hunters, and security architects, we mapped all seven vulnerability chains to the MITRE ATT&CK framework and the Common Attack Pattern Enumeration and Classification (CAPEC):
MITRE ATT&CK Matrix Alignment
|
Attack Phase / Tactic |
ATT&CK Technique ID |
Technique Name |
Operational Context in Edge AI
|
|---|---|---|---|
|
Initial Access |
T1190 |
Exploit Public-Facing Application |
Submitting oversized prompt tokens or malformed image dimensions to network-exposed inference endpoints. |
|
Initial Access |
T1195.002 |
Supply Chain Compromise: Dependencies |
Deploying poisoned model weights or malicious configuration files containing expanded tensor or path values. |
|
Execution |
T1203 |
Exploitation for Client Execution |
Triggering memory corruption routines during local model initialization or prompt token evaluation. |
|
Privilege Escalation |
T1068 |
Exploitation for Privilege Escalation |
Corrupting the memory space of an inference runner executing with ambient administrative or hardware driver permissions. |
|
Defense Evasion |
T1027 |
Obfuscated/Compressed Files |
Packaging corrupted tensor shapes inside compressed model containers to bypass signature inspection. |
|
Impact |
T1499.001 |
Endpoint Denial of Service: OS Exhaustion / Crash |
Forcing physical memory bus lockups, segmentation faults, or stack canary smashing aborts. |
Common Attack Patterns (CAPEC)
- CAPEC-92: Forced Integer Overflow: The adversary supplies an input dimension that forces numeric wraparound during math operations, bypassing later buffer length checks.
- CAPEC-100: Overflow Buffers: The adversary forces a memory management routine to write past the upper boundary of an allocated block, corrupting adjacent heap structures or hardware descriptors.
- CAPEC-24: Filter Failure via Stack Buffer Overflow: The adversary supplies an oversized configuration string that overflows a static stack buffer during unbounded string formatting.
- CAPEC-121: Exploit Buffer Overwrite: The adversary uses out-of-bounds pointer writes in physical device memory pools to corrupt driver memory or hijack execution flow.
- CAPEC-88: OS Command Injection: The adversary constructs an audio file path containing shell metacharacters that execute arbitrary operating system commands when passed to an unsanitized subprocess call.
MITRE CVE Assignment Criteria and CNA Notes
Under the MITRE CVE Program Rules (specifically Section 7 covering assignment guidelines), a security issue qualifies as a distinct CVE when it meets three criteria:
- Independent Fixability: Maintainers can fix each bug independently of any other defect. Vulnerabilities 1 and 3 sit in the LLM_decode.cpp and LLM_embed.cpp tensor and index routines, while Vulnerability 2 sits in the LLM_init.cpp template loader. Vulnerabilities 4, 5, 6, and 7 live in separate modules (image_processor.cpp, cmdline.hpp, audio_processor.cpp, and main_api.cpp), so each patch applies cleanly on its own.
- Negative Security Impact: The issue must violate a defined security policy. Vulnerability 1 and Vulnerability 3 violate memory write boundaries in physical hardware memory pools (MMZ/CMM), leading to process termination or DMA state corruption. Vulnerability 2 violates stack frame integrity, corrupting frame pointers and return addresses. Vulnerability 4 and Vulnerability 5 violate heap allocation and deallocation integrity, triggering heap buffer overflows or double-free corruption. Vulnerability 6 violates operating system execution boundaries via command injection, and Vulnerability 7 violates service availability via a remote NULL pointer dereference.
- Vendor Coordination and Public Verification: Following initial coordination in July and August 2026, our team filed technical reproduction logs, AddressSanitizer traces, and patch designs directly with AXERA-TECH on GitHub in September 2026. The maintainers verified the bugs, pushed fixes across commits 5ad6f17, 47405d2, 23faad8, and 5dce054 (plus modern std::string callback refactoring), and closed the tracking issues.
Industry Data Context: Conventional Flaws Across the AI Stack
These seven ax-llm vulnerabilities come from our direct hardware and C++ runtime research on the AX8850 platform. At the same time, they match a wider pattern across our application and infrastructure pentesting data. In the 2026 State of Pentesting Report, Cobalt platform data shows that Cobalt delivered 2.4x as many AI and LLM pentests in 2025 as in 2024 (+140% growth). Yet LLM-specific flaws dropped from 84.5% to 60.3% of total AI pentest findings (-24.3 percentage points). Roughly 80% of net finding growth (47 of 59 findings) came from conventional vulnerability classes like injection, broken access control, authentication flaws, and SSRF, which quadrupled from 15 to 62 findings (+313%). High-risk findings make up 32% of AI and LLM pentest results compared to 12% across general pentests (2.7x higher). Even so, teams resolve only 38% of high-risk AI and LLM findings, with a median fix time of 36 days.
Cobalt data shows why upstream bugs in embedded AI runtimes survive in production. Across surveyed teams, 45% deploy vendor software without requiring proof of security testing. Another 79% noted that automated scanners rated their environment low risk right before a manual pentest uncovered serious flaws. Meanwhile, only 29% cited IoT and embedded systems as a top attack vector concern, even though edge inference daemons routinely run as root.
Defensive Engineering and Remediation Recommendations for Security and Engineering Teams
We recommend that enterprise security teams, hardware integrators, and runtime developers take the following actions to secure edge AI deployments:
Test the Full Stack Beneath the Model and Verify Firmware Updates: Require proof of security testing from third-party AI vendors. Audit deployed Board Support Packages (BSPs) against upstream commits, and pentest edge inference runtimes alongside application-layer models.
Enforce Strict Input and Hardware Memory Boundaries:
- Enforce Destination Buffer Bounds Checking: Never size a memory copy solely from incoming data length. Compare every requested copy size against the destination buffer capacity (nSize), and clamp or reject any write that exceeds the limit.
- Enforce Range Validation on Integer Conversions: When converting 64-bit unsigned sizes into 32-bit integer representations, the software must perform explicit range checks. If a size value exceeds the upper bound of the target integer type, the system must abort processing rather than allowing silent numeric wraparound.
- Replace Unbounded String Formatting with Safe Alternatives: Remove legacy sprintf calls across all model loaders. Use snprintf with explicit buffer sizes or modern bounds-checked formatting primitives to stop stack overflows.
- Validate Multimodal Image Dimensions and Preprocessing Strides: Multimodal pipelines must enforce explicit upper and lower bounds on input dimensions, pixel width, and patch sizes before performing stride multiplication. Arithmetic operations that compute memory sizes should use 64-bit unsigned integers (size_t) to prevent integer wraparound.
- Apply Strict Boundary Validation on Tensor Indices: Loops that populate position indices or token tables into hardware memory must verify that calculated index offsets do not exceed allocated tensor boundaries (idx_elems).
- Eliminate Shell Interpreter Calls and Enforce Argument Escaping: Media and audio preprocessing pipelines must never pass user-supplied filenames into shell interpreters (_popen or system) via formatted command strings. Invoke external binaries directly using argument-vector primitives (execve or posix_spawn) or enforce strict allowlist validation and shell quoting (shell_quote()).
- Enforce C++ Copy-Control Rules and Non-Null Callback Contracts: Implement the C++ Rule of Three/Five (or delete copy constructors) on any class managing raw heap pointers. Always check streaming callback pointers for nullptr before calling fprintf.
- Run Edge Inference Services with Minimal Privileges: Do not run inference daemons as root. Configure dedicated low-privilege service accounts, drop unnecessary Linux capabilities, and isolate access to /dev/ax_npu.
Summary and Collaborative Disclosure Posture
Security in artificial intelligence cannot focus solely on prompts and model weights. As transformer models move to edge hardware, the low-level C and C++ runtimes that interface with physical silicon become the primary target. Securing edge AI means auditing the code that touches the metal.
Our team discovered and reported these seven ax-llm defects through coordinated disclosure to harden the edge AI ecosystem. By sharing reproduction code with AXERA-TECH and tracking fixes through the CVE process, we give device integrators and security teams the technical data needed to lock down physical edge infrastructure.