Skip to content

rpc : harden GRAPH_COMPUTE against crafted tensor descriptors - #31017

Closed
las7 wants to merge 1 commit into
ggml-org:masterfrom
las7:rpc-hardening-validate-inputs
Closed

las7 wants to merge 1 commit into
ggml-org:masterfrom
las7:rpc-hardening-validate-inputs

Conversation

@las7

@las7 las7 commented Oct 23, 2026

Copy link
Copy Markdown
Contributor

Summary

Harden the RPC server's `GRAPH_COMPUTE` path against crafted `rpc_tensor` descriptors sent by an untrusted client. Three issues, all reachable pre-auth by anyone who can connect:

  1. Arbitrary read/write via `view_offs`. A view node with `buffer=NULL`, `data=NULL` and a valid `view_src` passes the relaxed `create_node` check, and `ggml_backend_view_init` then computes `data = view_src->data + view_offs` with `view_offs` never validated anywhere.
  2. Remotely triggered abort (DoS). `ne[]` whose product overflows int64 hits `GGML_ASSERT` in `ggml_new_tensor_impl`; `nb[]` can wrap `ggml_nbytes`' size_t arithmetic. Both abort the server process.
  3. NULL data dereference. A bufferless non-view node with a compute op reaches the backend with `data=NULL`.

Changes (ggml-rpc.cpp only)

  • `create_node`: when `buffer==NULL`, reject `data!=0` (kept from RPC GRAPH_COMPUTE Arbitrary RCE patch #20908) and reject `view_src==0 && op != GGML_OP_NONE`. `GGML_OP_NONE` stays legal — `serialize_tensor` zeroes `buffer`/`data` for tensors whose buffer lives on a different server, and the cross-server graph in `test-rpc-multi-server` depends on it; its data is never touched.
  • `create_node`: validate views — `view_src` must have a buffer, and `[view_src->data + view_offs, +nbytes(view))` must lie inside that buffer.
  • `deserialize_tensor`: cumulative-product check on `ne[]` mirroring the assertion in `ggml_new_tensor_impl`, plus a sanity bound on `nb[]`, both before `ggml_new_tensor_4d` so no abort path remains reachable.

Testing

PoC harness (raw sockets, untrusted client) against the patched server:

input result
view with `view_offs=0xdeadbeef000` rejected, server stays up
`ne` product overflow `(2^32-1)^2` rejected, no abort
bufferless node with compute op rejected
bufferless node + forged `data` ptr rejected
valid buffered F32 graph accepted
in-bounds view (`view_offs=64`) accepted
`op=NONE` cross-server passthrough accepted

Usual-case regression:

  • `test-rpc-multi-server.sh`: PASS (cross-server graphs, strided `nb` variants)
  • `llama-cli` over RPC, CPU and Metal devices, TinyLlama Q8_0, temp 0 / fixed seed: token-identical output vs unpatched server

🤖 Generated with Claude Code

The RPC server trusts client-supplied tensor descriptors more than it
should. Three attack paths exist on a server reachable by an
untrusted client, all via GRAPH_COMPUTE:

1. Arbitrary read/write. A view node with buffer=NULL, data=NULL and a
   valid view_src passes the relaxed create_node check, and
   ggml_backend_view_init then computes data = view_src->data +
   view_offs with view_offs never validated anywhere.

2. Remotely triggered server abort. ne[] whose product overflows
   int64 hits GGML_ASSERT in ggml_new_tensor_impl, and nb[] can wrap
   ggml_nbytes' size_t arithmetic; both are remotely triggerable
   aborts (DoS).

3. NULL data dereference. A bufferless non-view node with a compute
   op passes deserialization and reaches the backend with data=NULL.

Fixes:

- create_node: when buffer==NULL, reject data!=0 (kept from ggml-org#20908)
  and also reject view_src==0 with op != GGML_OP_NONE. GGML_OP_NONE
  stays legal: serialize_tensor zeroes buffer/data for tensors whose
  buffer lives on a different server, and the cross-server graph in
  test-rpc-multi-server depends on it. Its data is never touched.
- create_node: validate views. view_src must have a buffer, and
  [view_src->data + view_offs, +nbytes(view)) must lie inside that
  buffer.
- deserialize_tensor: check ne[] with the same cumulative-product
  rule ggml_new_tensor_impl asserts on, and sanity-check nb[],
  before ggml_new_tensor_4d so no abort path remains reachable.

Verified against an untrusted-client PoC harness (raw sockets):

  OOB view_offs (0xdeadbeef000)        rejected, server stays up
  ne product overflow (2^32-1)^2       rejected, no abort
  bufferless compute-op node           rejected
  bufferless node + forged data ptr    rejected
  valid buffered graph                 accepted
  in-bounds view (view_offs=64)        accepted
  op=NONE cross-server passthrough     accepted

And against real client workloads:

  test-rpc-multi-server.sh: PASS (cross-server graphs, strided nb)
  llama-cli over RPC (CPU and Metal devices, TinyLlama Q8_0,
  temp 0, fixed seed): token-identical output vs unpatched server

Co-Authored-By: Claude Code <noreply@anthropic.com>
@las7
las7 requested a review from a team as a code owner October 23, 2026 05:00
@las7 las7 closed this Oct 23, 2026
@github-actions github-actions Bot added the ggml changes relating to the ggml tensor library for machine learning label Oct 23, 2026
@ggml-gh-bot

ggml-gh-bot Bot commented Oct 23, 2026

Copy link
Copy Markdown

Hi @las7, thanks for your contribution!

Per our contribution guidelines, the automated PR checker found the following issue(s) that need your attention:

  • PR Template not respected: Please respect the template when creating a new pull request. Make sure to fill out all required sections.

  • Maintainers cannot push to this PR: Please enable Allow edits by maintainers. If this PR comes from an organization-owned fork, that option is not available on GitHub; please re-open the PR from a fork owned by your personal account.

  • AI-generated content: While code is allowed to be generated by AI, please write the PR description and commit messages on your own without the help of AI.


Please note that maintainers reserve the right to make final decisions on PRs. If you believe there is a mistake, please comment below.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ggml changes relating to the ggml tensor library for machine learning

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant