SUNRPC: Fix a re-entrancy bug in xs_tcp_read_calldir()

If the attempt to read the calldir fails, then instead of storing the read
bytes, we currently discard them. This leads to a garbage final result when
upon re-entry to the same routine, we read the remaining bytes.

Fixes the regression in bugzilla number 16213. Please see
    https://bugzilla.kernel.org/show_bug.cgi?id=16213

Signed-off-by: Trond Myklebust <Trond.Myklebust@netapp.com>
Cc: stable@kernel.org
This commit is contained in:
Trond Myklebust 2010-06-16 13:57:32 -04:00
parent f799bdb355
commit b76ce56192

View file

@ -210,7 +210,8 @@ struct sock_xprt {
* State of TCP reply receive * State of TCP reply receive
*/ */
__be32 tcp_fraghdr, __be32 tcp_fraghdr,
tcp_xid; tcp_xid,
tcp_calldir;
u32 tcp_offset, u32 tcp_offset,
tcp_reclen; tcp_reclen;
@ -927,7 +928,7 @@ static inline void xs_tcp_read_calldir(struct sock_xprt *transport,
{ {
size_t len, used; size_t len, used;
u32 offset; u32 offset;
__be32 calldir; char *p;
/* /*
* We want transport->tcp_offset to be 8 at the end of this routine * We want transport->tcp_offset to be 8 at the end of this routine
@ -936,26 +937,33 @@ static inline void xs_tcp_read_calldir(struct sock_xprt *transport,
* transport->tcp_offset is 4 (after having already read the xid). * transport->tcp_offset is 4 (after having already read the xid).
*/ */
offset = transport->tcp_offset - sizeof(transport->tcp_xid); offset = transport->tcp_offset - sizeof(transport->tcp_xid);
len = sizeof(calldir) - offset; len = sizeof(transport->tcp_calldir) - offset;
dprintk("RPC: reading CALL/REPLY flag (%Zu bytes)\n", len); dprintk("RPC: reading CALL/REPLY flag (%Zu bytes)\n", len);
used = xdr_skb_read_bits(desc, &calldir, len); p = ((char *) &transport->tcp_calldir) + offset;
used = xdr_skb_read_bits(desc, p, len);
transport->tcp_offset += used; transport->tcp_offset += used;
if (used != len) if (used != len)
return; return;
transport->tcp_flags &= ~TCP_RCV_READ_CALLDIR; transport->tcp_flags &= ~TCP_RCV_READ_CALLDIR;
transport->tcp_flags |= TCP_RCV_COPY_CALLDIR;
transport->tcp_flags |= TCP_RCV_COPY_DATA;
/* /*
* We don't yet have the XDR buffer, so we will write the calldir * We don't yet have the XDR buffer, so we will write the calldir
* out after we get the buffer from the 'struct rpc_rqst' * out after we get the buffer from the 'struct rpc_rqst'
*/ */
if (ntohl(calldir) == RPC_REPLY) switch (ntohl(transport->tcp_calldir)) {
case RPC_REPLY:
transport->tcp_flags |= TCP_RCV_COPY_CALLDIR;
transport->tcp_flags |= TCP_RCV_COPY_DATA;
transport->tcp_flags |= TCP_RPC_REPLY; transport->tcp_flags |= TCP_RPC_REPLY;
else break;
case RPC_CALL:
transport->tcp_flags |= TCP_RCV_COPY_CALLDIR;
transport->tcp_flags |= TCP_RCV_COPY_DATA;
transport->tcp_flags &= ~TCP_RPC_REPLY; transport->tcp_flags &= ~TCP_RPC_REPLY;
dprintk("RPC: reading %s CALL/REPLY flag %08x\n", break;
(transport->tcp_flags & TCP_RPC_REPLY) ? default:
"reply for" : "request with", calldir); dprintk("RPC: invalid request message type\n");
xprt_force_disconnect(&transport->xprt);
}
xs_tcp_check_fraghdr(transport); xs_tcp_check_fraghdr(transport);
} }
@ -975,12 +983,10 @@ static inline void xs_tcp_read_common(struct rpc_xprt *xprt,
/* /*
* Save the RPC direction in the XDR buffer * Save the RPC direction in the XDR buffer
*/ */
__be32 calldir = transport->tcp_flags & TCP_RPC_REPLY ?
htonl(RPC_REPLY) : 0;
memcpy(rcvbuf->head[0].iov_base + transport->tcp_copied, memcpy(rcvbuf->head[0].iov_base + transport->tcp_copied,
&calldir, sizeof(calldir)); &transport->tcp_calldir,
transport->tcp_copied += sizeof(calldir); sizeof(transport->tcp_calldir));
transport->tcp_copied += sizeof(transport->tcp_calldir);
transport->tcp_flags &= ~TCP_RCV_COPY_CALLDIR; transport->tcp_flags &= ~TCP_RCV_COPY_CALLDIR;
} }