SUNRPC: Convert unwrap_integ_data() to use xdr_stream
authorChuck Lever <chuck.lever@oracle.com>
Mon, 2 Jan 2023 17:06:54 +0000 (12:06 -0500)
committerChuck Lever <chuck.lever@oracle.com>
Mon, 20 Feb 2023 14:20:16 +0000 (09:20 -0500)
Done as part of hardening the server-side RPC header decoding path.

Reviewed-by: Jeff Layton <jlayton@kernel.org>
Signed-off-by: Chuck Lever <chuck.lever@oracle.com>
include/linux/sunrpc/xdr.h
net/sunrpc/auth_gss/svcauth_gss.c
net/sunrpc/xdr.c

index 8b5c9d0cdcb51e0845d2a40ab472578a1b4b5bc7..accfe8d6e28333a531d47fd45574bee119abe85a 100644 (file)
@@ -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);
index 79222a42845ae8d3d5753164c5a8cf1df541f848..e19974b57683f67a000f0aea73d7f279b09383c8 100644 (file)
@@ -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: */
index 4845ba2113fd1b5f602b72474ad723f0b11b49ff..c7e89921d5110bf999f5b7d7834fa4714ef0b325 100644 (file)
@@ -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