diff mbox series

[v1,14/25] SUNRPC: Convert unwrap_integ_data() to use xdr_stream

Message ID 167267921434.112521.15582369756118768001.stgit@manet.1015granger.net (mailing list archive)
State New, archived
Headers show
Series Server-side RPC call header parsing overhaul | expand

Commit Message

Chuck Lever Jan. 2, 2023, 5:06 p.m. UTC
From: Chuck Lever <chuck.lever@oracle.com>

Done as part of hardening the server-side RPC header decoding path.

Signed-off-by: Chuck Lever <chuck.lever@oracle.com>
---
 include/linux/sunrpc/xdr.h        |    1 +
 net/sunrpc/auth_gss/svcauth_gss.c |   47 ++++++++++++++++++++++++-------------
 net/sunrpc/xdr.c                  |   15 ++++++++++++
 3 files changed, 47 insertions(+), 16 deletions(-)
diff mbox series

Patch

diff --git a/include/linux/sunrpc/xdr.h b/include/linux/sunrpc/xdr.h
index 8b5c9d0cdcb5..accfe8d6e283 100644
--- a/include/linux/sunrpc/xdr.h
+++ b/include/linux/sunrpc/xdr.h
@@ -247,6 +247,7 @@  extern int xdr_reserve_space_vec(struct xdr_stream *xdr, struct kvec *vec,
 		size_t nbytes);
 extern void __xdr_commit_encode(struct xdr_stream *xdr);
 extern void xdr_truncate_encode(struct xdr_stream *xdr, size_t len);
+extern void xdr_truncate_decode(struct xdr_stream *xdr, size_t len);
 extern int xdr_restrict_buflen(struct xdr_stream *xdr, int newbuflen);
 extern void xdr_write_pages(struct xdr_stream *xdr, struct page **pages,
 		unsigned int base, unsigned int len);
diff --git a/net/sunrpc/auth_gss/svcauth_gss.c b/net/sunrpc/auth_gss/svcauth_gss.c
index 33fe307372d0..d049db997ab7 100644
--- a/net/sunrpc/auth_gss/svcauth_gss.c
+++ b/net/sunrpc/auth_gss/svcauth_gss.c
@@ -904,13 +904,14 @@  EXPORT_SYMBOL_GPL(svcauth_gss_register_pseudoflavor);
  *		proc_req_arg_t arg;
  *	};
  */
-static int
-svcauth_gss_unwrap_integ(struct svc_rqst *rqstp, struct xdr_buf *buf, u32 seq,
-			 struct gss_ctx *ctx)
+static noinline_for_stack int
+svcauth_gss_unwrap_integ(struct svc_rqst *rqstp, u32 seq, struct gss_ctx *ctx)
 {
 	struct gss_svc_data *gsd = rqstp->rq_auth_data;
+	struct xdr_stream *xdr = &rqstp->rq_arg_stream;
+	u32 len, offset, seq_num, maj_stat;
+	struct xdr_buf *buf = xdr->buf;
 	struct xdr_buf databody_integ;
-	u32 len, seq_num, maj_stat;
 	struct xdr_netobj checksum;
 
 	/* NFS READ normally uses splice to send data in-place. However
@@ -925,29 +926,43 @@  svcauth_gss_unwrap_integ(struct svc_rqst *rqstp, struct xdr_buf *buf, u32 seq,
 	if (rqstp->rq_deferred)
 		return 0;
 
-	len = svc_getnl(&buf->head[0]);
-	if (len & 3)
+	if (xdr_stream_decode_u32(xdr, &len) < 0)
 		goto unwrap_failed;
-	if (len > buf->len)
+	if (len & 3)
 		goto unwrap_failed;
-	if (xdr_buf_subsegment(buf, &databody_integ, 0, len))
+	offset = xdr_stream_pos(xdr);
+	if (xdr_buf_subsegment(buf, &databody_integ, offset, len))
 		goto unwrap_failed;
 
-	if (xdr_decode_word(buf, len, &checksum.len))
+	/*
+	 * The xdr_stream now points to the @seq_num field. The next
+	 * XDR data item is the @arg field, which contains the clear
+	 * text RPC program payload. The checksum, which follows the
+	 * @arg field, is located and decoded without updating the
+	 * xdr_stream.
+	 */
+
+	offset += len;
+	if (xdr_decode_word(buf, offset, &checksum.len))
 		goto unwrap_failed;
 	if (checksum.len > sizeof(gsd->gsd_scratch))
 		goto unwrap_failed;
 	checksum.data = gsd->gsd_scratch;
-	if (read_bytes_from_xdr_buf(buf, len + 4, checksum.data, checksum.len))
+	if (read_bytes_from_xdr_buf(buf, offset + XDR_UNIT, checksum.data,
+				    checksum.len))
 		goto unwrap_failed;
+
 	maj_stat = gss_verify_mic(ctx, &databody_integ, &checksum);
 	if (maj_stat != GSS_S_COMPLETE)
 		goto bad_mic;
-	seq_num = svc_getnl(&buf->head[0]);
+
+	/* The received seqno is protected by the checksum. */
+	if (xdr_stream_decode_u32(xdr, &seq_num) < 0)
+		goto unwrap_failed;
 	if (seq_num != seq)
 		goto bad_seqno;
-	/* trim off the mic and padding at the end before returning */
-	xdr_buf_trim(buf, round_up_to_quad(checksum.len) + 4);
+
+	xdr_truncate_decode(xdr, XDR_UNIT + checksum.len);
 	return 0;
 
 unwrap_failed:
@@ -1652,11 +1667,11 @@  svcauth_gss_accept(struct svc_rqst *rqstp)
 			/* placeholders for length and seq. number: */
 			svc_putnl(resv, 0);
 			svc_putnl(resv, 0);
-			if (svcauth_gss_unwrap_integ(rqstp, &rqstp->rq_arg,
-						     gc->gc_seq, rsci->mechctx))
+			svcxdr_init_decode(rqstp);
+			if (svcauth_gss_unwrap_integ(rqstp, gc->gc_seq,
+						     rsci->mechctx))
 				goto garbage_args;
 			rqstp->rq_auth_slack = RPC_MAX_AUTH_SIZE;
-			svcxdr_init_decode(rqstp);
 			break;
 		case RPC_GSS_SVC_PRIVACY:
 			/* placeholders for length and seq. number: */
diff --git a/net/sunrpc/xdr.c b/net/sunrpc/xdr.c
index 4845ba2113fd..c7e89921d511 100644
--- a/net/sunrpc/xdr.c
+++ b/net/sunrpc/xdr.c
@@ -1192,6 +1192,21 @@  void xdr_truncate_encode(struct xdr_stream *xdr, size_t len)
 }
 EXPORT_SYMBOL(xdr_truncate_encode);
 
+/**
+ * xdr_truncate_decode - Truncate a decoding stream
+ * @xdr: pointer to struct xdr_stream
+ * @len: Number of bytes to remove
+ *
+ */
+void xdr_truncate_decode(struct xdr_stream *xdr, size_t len)
+{
+	unsigned int nbytes = xdr_align_size(len);
+
+	xdr->buf->len -= nbytes;
+	xdr->nwords -= XDR_QUADLEN(nbytes);
+}
+EXPORT_SYMBOL_GPL(xdr_truncate_decode);
+
 /**
  * xdr_restrict_buflen - decrease available buffer space
  * @xdr: pointer to xdr_stream