SUNRPC: Copy the deferred RPC Call from the head buffer
Subsystems (3)
Chuck Lever
maintainerPATCH 1/1 SUNRPC: Copy the deferred RPC Call from the head buffer
svc_defer() snapshots the RPC Call for replay once a pending cache upcall resolves. It copies the Call starting (rq_arg.len - head[0].iov_len) bytes before head[0].iov_base, a back-up that once covered a transport header. No transport has had one since commit 983084b2672c ("SUNRPC: Remove svc_rqst::rq_xprt_hlen"), so the two lengths are meant to be equal.
An RPCSEC_GSS Call breaks that assumption. Unwrapping the integrity service truncates rq_arg.len by the length of the trailing checksum, and unwrapping the privacy service leaves rq_arg.len short by an amount the client sets with trailing bytes after the wrap token. Neither adjusts head[0].iov_len. Both lengths are size_t, so the subtraction underflows and the copy starts past head[0].iov_base. Every deferred integrity Call replays a shifted snapshot.
A deferred replay skips GSS verification, so a shifted snapshot runs as an authenticated Call. With the privacy service the offset is client-chosen. A forged Call placed in that window, naming another user's GSS context handle, runs under that identity.
Copy the Call from head[0].iov_base. Refuse to defer a Call whose length runs past the head buffer, so the copy cannot read out of bounds.
--- net/sunrpc/svc_xprt.c | 22 +++++++--------------- 1 file changed, 7 insertions(+), 15 deletions(-)
net/sunrpc/svc_xprt.c +7 -15
@@ -1278,13 +1278,8 @@ static void svc_revisit(struct cache_deferred_req *dreq, int too_many) }/* -* Save the request off for later processing. The request buffer looks -* like this: -* -* <xprt-header><rpc-header><rpc-pagelist><rpc-tail> -* -* This code can only handle requests that consist of an xprt-header -* and rpc-header. +* Save the request off for later processing. Only a Call that fits +* entirely in rq_arg.head[0] can be deferred. */ static struct cache_deferred_req *svc_defer (struct cache_req *req) {
@@ -1297,8 +1292,11 @@ static struct cache_deferred_req *svc_defer(struct cache_req *req) dr = rqstp->rq_deferred; rqstp->rq_deferred =NULL ; }else { -size_t skip;size_t size; + +if (rqstp->rq_arg.len > rqstp->rq_arg.head[0 ].iov_len) +return NULL ; +/* FIXME maybe discard if size too large */ size =sizeof (struct svc_deferred_req ) + rqstp->rq_arg.len; dr =kmalloc (size,GFP_KERNEL );
@@ -1312,9 +1310,7 @@ static struct cache_deferred_req *svc_defer(struct cache_req *req) dr->daddr = rqstp->rq_daddr; dr->argslen = rqstp->rq_arg.len >>2 ; -/* back up head to the start of the buffer and copy */ - skip = rqstp->rq_arg.len - rqstp->rq_arg.head[0 ].iov_len; -memcpy (dr->args, rqstp->rq_arg.head[0 ].iov_base - skip, +memcpy (dr->args, rqstp->rq_arg.head[0 ].iov_base, dr->argslen <<2 ); } dr->xprt_ctxt = rqstp->rq_xprt_ctxt;
@@ -1337,17 +1333,13 @@ static noinline int svc_deferred_recv(struct svc_rqst *rqstp)trace_svc_defer_recv (dr); -/* setup iov_base past transport header */ rqstp->rq_arg.head[0 ].iov_base = dr->args; -/* The iov_len does not include the transport header bytes */ rqstp->rq_arg.head[0 ].iov_len = dr->argslen <<2 ; rqstp->rq_arg.page_len =0 ; -/* The rq_arg.len includes the transport header bytes */ rqstp->rq_arg.len = dr->argslen <<2 ; rqstp->rq_prot = dr->prot;memcpy (&rqstp->rq_addr, &dr->addr, dr->addrlen); rqstp->rq_addrlen = dr->addrlen; -/* Save off transport header len in case we get deferred again */ rqstp->rq_daddr = dr->daddr; rqstp->rq_xprt_ctxt = dr->xprt_ctxt;
Signature · 1 line
2.55.0