Re: [PATCH v2 1/3] sunrpc: Fix gss_unwrap_resp_integ() again

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 




> On Mar 9, 2020, at 10:06 AM, Chuck Lever <chuck.lever@xxxxxxxxxx> wrote:
> 
> xdr_buf_read_mic() tries to find unused contiguous space in a
> received xdr_buf in order to linearize the checksum for the call
> to gss_verify_mic. However, the corner cases in this code are
> numerous and we seem to keep missing them. I've just hit yet
> another buffer overrun related to it.
> 
> This overrun is at the end of xdr_buf_read_mic():
> 
> 1284         if (buf->tail[0].iov_len != 0)
> 1285                 mic->data = buf->tail[0].iov_base + buf->tail[0].iov_len;
> 1286         else
> 1287                 mic->data = buf->head[0].iov_base + buf->head[0].iov_len;
> 1288         __read_bytes_from_xdr_buf(&subbuf, mic->data, mic->len);
> 1289         return 0;
> 
> This logic assumes the transport has set the length of the tail
> based on the size of the received message. base + len is then
> supposed to be off the end of the message but still within the
> actual buffer.
> 
> In fact, the length of the tail is set by the upper layer when the
> Call is encoded so that the end of the tail is actually the end of
> the allocated buffer itself. This causes the logic above to set
> mic->data to point past the end of the receive buffer.
> 
> The "mic->data = head" arm of this if statement is no less fragile.
> 
> As near as I can tell, this has been a problem forever. I'm not sure
> that minimizing au_rslack recently changed this pathology much.
> 
> So instead, let's use a more straightforward approach: kmalloc a
> separate buffer to linearize the checksum. This is similar to
> how gss_validate() currently works.
> 
> Coming back to this code, I had some trouble understanding what
> was going on. So I've cleaned up the variable naming and added
> a few comments that point back to the XDR definition in RFC 2203
> to help guide future spelunkers, including myself.
> 
> As an added clean up, the functionality that was in
> xdr_buf_read_mic() is folded directly into gss_unwrap_resp_integ(),
> as that is its only caller.
> 
> Signed-off-by: Chuck Lever <chuck.lever@xxxxxxxxxx>
> Reviewed-by: Benjamin Coddington <bcodding@xxxxxxxxxx>
> ---
> net/sunrpc/auth_gss/auth_gss.c |   79 ++++++++++++++++++++++++++++++----------
> 1 file changed, 60 insertions(+), 19 deletions(-)
> 
> diff --git a/net/sunrpc/auth_gss/auth_gss.c b/net/sunrpc/auth_gss/auth_gss.c
> index 24ca861815b1..fa991f4fe53a 100644
> --- a/net/sunrpc/auth_gss/auth_gss.c
> +++ b/net/sunrpc/auth_gss/auth_gss.c
> @@ -1934,35 +1934,71 @@ static int gss_wrap_req(struct rpc_task *task, struct xdr_stream *xdr)
> 	return 0;
> }
> 
> +/*
> + * RFC 2203, Section 5.3.2.2
> + *
> + *	struct rpc_gss_integ_data {
> + *		opaque databody_integ<>;
> + *		opaque checksum<>;
> + *	};
> + *
> + *	struct rpc_gss_data_t {
> + *		unsigned int seq_num;
> + *		proc_req_arg_t arg;
> + *	};
> + */
> static int
> gss_unwrap_resp_integ(struct rpc_task *task, struct rpc_cred *cred,
> 		      struct gss_cl_ctx *ctx, struct rpc_rqst *rqstp,
> 		      struct xdr_stream *xdr)
> {
> -	struct xdr_buf integ_buf, *rcv_buf = &rqstp->rq_rcv_buf;
> -	u32 data_offset, mic_offset, integ_len, maj_stat;
> +	struct xdr_buf gss_data, *rcv_buf = &rqstp->rq_rcv_buf;
> 	struct rpc_auth *auth = cred->cr_auth;
> +	u32 len, offset, seqno, maj_stat;
> 	struct xdr_netobj mic;
> -	__be32 *p;
> +	int ret;
> 
> -	p = xdr_inline_decode(xdr, 2 * sizeof(*p));
> -	if (unlikely(!p))
> +	ret = -EIO;
> +	mic.data = NULL;
> +
> +	/* opaque databody_integ<>; */
> +	if (xdr_stream_decode_u32(xdr, &len))
> 		goto unwrap_failed;
> -	integ_len = be32_to_cpup(p++);
> -	if (integ_len & 3)
> +	if (len & 3)
> 		goto unwrap_failed;
> -	data_offset = (u8 *)(p) - (u8 *)rcv_buf->head[0].iov_base;
> -	mic_offset = integ_len + data_offset;
> -	if (mic_offset > rcv_buf->len)
> +	offset = rcv_buf->len - xdr_stream_remaining(xdr);
> +	if (xdr_stream_decode_u32(xdr, &seqno))
> 		goto unwrap_failed;
> -	if (be32_to_cpup(p) != rqstp->rq_seqno)
> +	if (seqno != rqstp->rq_seqno)
> 		goto bad_seqno;
> +	if (xdr_buf_subsegment(rcv_buf, &gss_data, offset, len))
> +		goto unwrap_failed;
> 
> -	if (xdr_buf_subsegment(rcv_buf, &integ_buf, data_offset, integ_len))
> +	/*
> +	 * The xdr_stream now points to the beginning of the
> +	 * upper layer payload, to be passed below to
> +	 * rpcauth_unwrap_resp_decode(). The checksum, which
> +	 * follows the upper layer payload in @rcv_buf, is
> +	 * located and parsed without updating the xdr_stream.
> +	 */
> +
> +	/* opaque checksum<>; */
> +	offset += len;
> +	if (xdr_decode_word(rcv_buf, offset, &len))
> +		goto unwrap_failed;
> +	offset += sizeof(__be32);
> +	if (len > GSS_VERF_SLACK << 2)
> 		goto unwrap_failed;

Based on the conversation in the other thread about NFSv3 + krb5p,
this would appear to be an arbitrary and over-aggressive sanity check.

I'll post a v3 of this series with this check removed.


> -	if (xdr_buf_read_mic(rcv_buf, &mic, mic_offset))
> +	if (offset + len > rcv_buf->len)
> 		goto unwrap_failed;
> -	maj_stat = gss_verify_mic(ctx->gc_gss_ctx, &integ_buf, &mic);
> +	mic.len = len;
> +	mic.data = kmalloc(len, GFP_NOFS);
> +	if (!mic.data)
> +		goto unwrap_failed;
> +	if (read_bytes_from_xdr_buf(rcv_buf, offset, mic.data, mic.len))
> +		goto unwrap_failed;
> +
> +	maj_stat = gss_verify_mic(ctx->gc_gss_ctx, &gss_data, &mic);
> 	if (maj_stat == GSS_S_CONTEXT_EXPIRED)
> 		clear_bit(RPCAUTH_CRED_UPTODATE, &cred->cr_flags);
> 	if (maj_stat != GSS_S_COMPLETE)
> @@ -1970,16 +2006,21 @@ static int gss_wrap_req(struct rpc_task *task, struct xdr_stream *xdr)
> 
> 	auth->au_rslack = auth->au_verfsize + 2 + 1 + XDR_QUADLEN(mic.len);
> 	auth->au_ralign = auth->au_verfsize + 2;
> -	return 0;
> +	ret = 0;
> +
> +out:
> +	kfree(mic.data);
> +	return ret;
> +
> unwrap_failed:
> 	trace_rpcgss_unwrap_failed(task);
> -	return -EIO;
> +	goto out;
> bad_seqno:
> -	trace_rpcgss_bad_seqno(task, rqstp->rq_seqno, be32_to_cpup(p));
> -	return -EIO;
> +	trace_rpcgss_bad_seqno(task, rqstp->rq_seqno, seqno);
> +	goto out;
> bad_mic:
> 	trace_rpcgss_verify_mic(task, maj_stat);
> -	return -EIO;
> +	goto out;
> }
> 
> static int
> 

--
Chuck Lever







[Index of Archives]     [Linux Filesystem Development]     [Linux USB Development]     [Linux Media Development]     [Video for Linux]     [Linux NILFS]     [Linux Audio Users]     [Yosemite Info]     [Linux SCSI]

  Powered by Linux