| From 3ad200d387ad56a4d70c038be2951cf502642853 Mon Sep 17 00:00:00 2001 |
| From: Chuck Lever <chuck.lever@oracle.com> |
| Date: Wed, 11 Mar 2020 11:21:07 -0400 |
| Subject: [PATCH] sunrpc: Fix gss_unwrap_resp_integ() again |
| |
| commit 4047aa909c4a40fceebc36fff708d465a4d3c6e2 upstream. |
| |
| 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@oracle.com> |
| Reviewed-by: Benjamin Coddington <bcodding@redhat.com> |
| Signed-off-by: Trond Myklebust <trond.myklebust@hammerspace.com> |
| Signed-off-by: Paul Gortmaker <paul.gortmaker@windriver.com> |
| |
| diff --git a/net/sunrpc/auth_gss/auth_gss.c b/net/sunrpc/auth_gss/auth_gss.c |
| index f9e1a7e61eda..ff5fcb3e1208 100644 |
| --- a/net/sunrpc/auth_gss/auth_gss.c |
| +++ b/net/sunrpc/auth_gss/auth_gss.c |
| @@ -1935,35 +1935,69 @@ gss_unwrap_resp_auth(struct rpc_cred *cred) |
| 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 (offset + len > rcv_buf->len) |
| goto unwrap_failed; |
| - if (xdr_buf_read_mic(rcv_buf, &mic, mic_offset)) |
| + 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, &integ_buf, &mic); |
| + |
| + 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) |
| @@ -1971,16 +2005,21 @@ gss_unwrap_resp_integ(struct rpc_task *task, struct rpc_cred *cred, |
| |
| 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 |
| -- |
| 2.7.4 |
| |