SUNRPC: Use struct xdr_stream when decoding RPC Reply header
authorChuck Lever <chuck.lever@oracle.com>
Mon, 11 Feb 2019 16:24:58 +0000 (11:24 -0500)
committerAnna Schumaker <Anna.Schumaker@Netapp.com>
Thu, 14 Feb 2019 14:11:18 +0000 (09:11 -0500)
Modernize and harden the code path that parses an RPC Reply
message.

Signed-off-by: Chuck Lever <chuck.lever@oracle.com>
Signed-off-by: Anna Schumaker <Anna.Schumaker@Netapp.com>
include/linux/sunrpc/auth.h
include/linux/sunrpc/xdr.h
net/sunrpc/auth.c
net/sunrpc/auth_gss/auth_gss.c
net/sunrpc/auth_null.c
net/sunrpc/auth_unix.c
net/sunrpc/clnt.c

index 96e237f..c51e189 100644 (file)
@@ -134,11 +134,12 @@ struct rpc_credops {
        int                     (*crmarshal)(struct rpc_task *task,
                                             struct xdr_stream *xdr);
        int                     (*crrefresh)(struct rpc_task *);
-       __be32 *                (*crvalidate)(struct rpc_task *, __be32 *);
+       int                     (*crvalidate)(struct rpc_task *task,
+                                             struct xdr_stream *xdr);
        int                     (*crwrap_req)(struct rpc_task *task,
                                              struct xdr_stream *xdr);
-       int                     (*crunwrap_resp)(struct rpc_task *, kxdrdproc_t,
-                                               void *, __be32 *, void *);
+       int                     (*crunwrap_resp)(struct rpc_task *task,
+                                                struct xdr_stream *xdr);
        int                     (*crkey_timeout)(struct rpc_cred *);
        char *                  (*crstringify_acceptor)(struct rpc_cred *);
        bool                    (*crneed_reencode)(struct rpc_task *);
@@ -168,12 +169,16 @@ struct rpc_cred * rpcauth_lookupcred(struct rpc_auth *, int);
 void                   put_rpccred(struct rpc_cred *);
 int                    rpcauth_marshcred(struct rpc_task *task,
                                          struct xdr_stream *xdr);
-__be32 *               rpcauth_checkverf(struct rpc_task *, __be32 *);
+int                    rpcauth_checkverf(struct rpc_task *task,
+                                         struct xdr_stream *xdr);
 int                    rpcauth_wrap_req_encode(struct rpc_task *task,
                                                struct xdr_stream *xdr);
 int                    rpcauth_wrap_req(struct rpc_task *task,
                                         struct xdr_stream *xdr);
-int                    rpcauth_unwrap_resp(struct rpc_task *task, kxdrdproc_t decode, void *rqstp, __be32 *data, void *obj);
+int                    rpcauth_unwrap_resp_decode(struct rpc_task *task,
+                                                  struct xdr_stream *xdr);
+int                    rpcauth_unwrap_resp(struct rpc_task *task,
+                                           struct xdr_stream *xdr);
 bool                   rpcauth_xmit_need_reencode(struct rpc_task *task);
 int                    rpcauth_refreshcred(struct rpc_task *);
 void                   rpcauth_invalcred(struct rpc_task *);
index c540419..65af6a2 100644 (file)
@@ -89,6 +89,7 @@ xdr_buf_init(struct xdr_buf *buf, void *start, size_t len)
 
 #define        rpc_auth_null   cpu_to_be32(RPC_AUTH_NULL)
 #define        rpc_auth_unix   cpu_to_be32(RPC_AUTH_UNIX)
+#define        rpc_auth_short  cpu_to_be32(RPC_AUTH_SHORT)
 #define        rpc_auth_gss    cpu_to_be32(RPC_AUTH_GSS)
 
 #define        rpc_call        cpu_to_be32(RPC_CALL)
index add2135..e786102 100644 (file)
@@ -17,6 +17,8 @@
 #include <linux/sunrpc/gss_api.h>
 #include <linux/spinlock.h>
 
+#include <trace/events/sunrpc.h>
+
 #define RPC_CREDCACHE_DEFAULT_HASHBITS (4)
 struct rpc_cred_cache {
        struct hlist_head       *hashtable;
@@ -773,14 +775,6 @@ int rpcauth_marshcred(struct rpc_task *task, struct xdr_stream *xdr)
        return ops->crmarshal(task, xdr);
 }
 
-__be32 *
-rpcauth_checkverf(struct rpc_task *task, __be32 *p)
-{
-       struct rpc_cred *cred = task->tk_rqstp->rq_cred;
-
-       return cred->cr_ops->crvalidate(task, p);
-}
-
 /**
  * rpcauth_wrap_req_encode - XDR encode the RPC procedure
  * @task: controlling RPC task
@@ -814,27 +808,52 @@ int rpcauth_wrap_req(struct rpc_task *task, struct xdr_stream *xdr)
        return ops->crwrap_req(task, xdr);
 }
 
-static int
-rpcauth_unwrap_req_decode(kxdrdproc_t decode, struct rpc_rqst *rqstp,
-                         __be32 *data, void *obj)
+/**
+ * rpcauth_checkverf - Validate verifier in RPC Reply header
+ * @task: controlling RPC task
+ * @xdr: xdr_stream containing RPC Reply header
+ *
+ * On success, @xdr is updated to point past the verifier and
+ * zero is returned. Otherwise, @xdr is in an undefined state
+ * and a negative errno is returned.
+ */
+int
+rpcauth_checkverf(struct rpc_task *task, struct xdr_stream *xdr)
 {
-       struct xdr_stream xdr;
+       const struct rpc_credops *ops = task->tk_rqstp->rq_cred->cr_ops;
 
-       xdr_init_decode(&xdr, &rqstp->rq_rcv_buf, data, rqstp);
-       return decode(rqstp, &xdr, obj);
+       return ops->crvalidate(task, xdr);
 }
 
+/**
+ * rpcauth_unwrap_resp_decode - Invoke XDR decode function
+ * @task: controlling RPC task
+ * @xdr: stream where the Reply message resides
+ *
+ * Returns zero on success; otherwise a negative errno is returned.
+ */
 int
-rpcauth_unwrap_resp(struct rpc_task *task, kxdrdproc_t decode, void *rqstp,
-               __be32 *data, void *obj)
+rpcauth_unwrap_resp_decode(struct rpc_task *task, struct xdr_stream *xdr)
 {
-       struct rpc_cred *cred = task->tk_rqstp->rq_cred;
+       kxdrdproc_t decode = task->tk_msg.rpc_proc->p_decode;
+
+       return decode(task->tk_rqstp, xdr, task->tk_msg.rpc_resp);
+}
+EXPORT_SYMBOL_GPL(rpcauth_unwrap_resp_decode);
+
+/**
+ * rpcauth_unwrap_resp - Invoke unwrap and decode function for the cred
+ * @task: controlling RPC task
+ * @xdr: stream where the Reply message resides
+ *
+ * Returns zero on success; otherwise a negative errno is returned.
+ */
+int
+rpcauth_unwrap_resp(struct rpc_task *task, struct xdr_stream *xdr)
+{
+       const struct rpc_credops *ops = task->tk_rqstp->rq_cred->cr_ops;
 
-       if (cred->cr_ops->crunwrap_resp)
-               return cred->cr_ops->crunwrap_resp(task, decode, rqstp,
-                                                  data, obj);
-       /* By default, we decode the arguments normally. */
-       return rpcauth_unwrap_req_decode(decode, rqstp, data, obj);
+       return ops->crunwrap_resp(task, xdr);
 }
 
 bool
index b333b1b..206788e 100644 (file)
@@ -1671,59 +1671,62 @@ gss_refresh_null(struct rpc_task *task)
        return 0;
 }
 
-static __be32 *
-gss_validate(struct rpc_task *task, __be32 *p)
+static int
+gss_validate(struct rpc_task *task, struct xdr_stream *xdr)
 {
        struct rpc_cred *cred = task->tk_rqstp->rq_cred;
        struct gss_cl_ctx *ctx = gss_cred_get_ctx(cred);
-       __be32          *seq = NULL;
+       __be32          *p, *seq = NULL;
        struct kvec     iov;
        struct xdr_buf  verf_buf;
        struct xdr_netobj mic;
-       u32             flav,len;
-       u32             maj_stat;
-       __be32          *ret = ERR_PTR(-EIO);
+       u32             len, maj_stat;
+       int             status;
 
-       dprintk("RPC: %5u %s\n", task->tk_pid, __func__);
+       p = xdr_inline_decode(xdr, 2 * sizeof(*p));
+       if (!p)
+               goto validate_failed;
+       if (*p++ != rpc_auth_gss)
+               goto validate_failed;
+       len = be32_to_cpup(p);
+       if (len > RPC_MAX_AUTH_SIZE)
+               goto validate_failed;
+       p = xdr_inline_decode(xdr, len);
+       if (!p)
+               goto validate_failed;
 
-       flav = ntohl(*p++);
-       if ((len = ntohl(*p++)) > RPC_MAX_AUTH_SIZE)
-               goto out_bad;
-       if (flav != RPC_AUTH_GSS)
-               goto out_bad;
        seq = kmalloc(4, GFP_NOFS);
        if (!seq)
-               goto out_bad;
-       *seq = htonl(task->tk_rqstp->rq_seqno);
+               goto validate_failed;
+       *seq = cpu_to_be32(task->tk_rqstp->rq_seqno);
        iov.iov_base = seq;
        iov.iov_len = 4;
        xdr_buf_from_iov(&iov, &verf_buf);
        mic.data = (u8 *)p;
        mic.len = len;
-
-       ret = ERR_PTR(-EACCES);
        maj_stat = gss_verify_mic(ctx->gc_gss_ctx, &verf_buf, &mic);
        if (maj_stat == GSS_S_CONTEXT_EXPIRED)
                clear_bit(RPCAUTH_CRED_UPTODATE, &cred->cr_flags);
-       if (maj_stat) {
-               dprintk("RPC: %5u %s: gss_verify_mic returned error 0x%08x\n",
-                       task->tk_pid, __func__, maj_stat);
-               goto out_bad;
-       }
+       if (maj_stat)
+               goto bad_mic;
+
        /* We leave it to unwrap to calculate au_rslack. For now we just
         * calculate the length of the verifier: */
        cred->cr_auth->au_verfsize = XDR_QUADLEN(len) + 2;
+       status = 0;
+out:
        gss_put_ctx(ctx);
-       dprintk("RPC: %5u %s: gss_verify_mic succeeded.\n",
-                       task->tk_pid, __func__);
-       kfree(seq);
-       return p + XDR_QUADLEN(len);
-out_bad:
-       gss_put_ctx(ctx);
-       dprintk("RPC: %5u %s failed ret %ld.\n", task->tk_pid, __func__,
-               PTR_ERR(ret));
        kfree(seq);
-       return ret;
+       return status;
+
+validate_failed:
+       status = -EIO;
+       goto out;
+bad_mic:
+       dprintk("RPC: %5u %s: gss_verify_mic returned error 0x%08x\n",
+               task->tk_pid, __func__, maj_stat);
+       status = -EACCES;
+       goto out;
 }
 
 static int gss_wrap_req_integ(struct rpc_cred *cred, struct gss_cl_ctx *ctx,
@@ -1921,79 +1924,98 @@ out:
        return status;
 }
 
-static inline int
+static int
+gss_unwrap_resp_auth(struct rpc_cred *cred)
+{
+       cred->cr_auth->au_rslack = cred->cr_auth->au_verfsize;
+       return 0;
+}
+
+static int
 gss_unwrap_resp_integ(struct rpc_cred *cred, struct gss_cl_ctx *ctx,
-               struct rpc_rqst *rqstp, __be32 **p)
+                     struct rpc_rqst *rqstp, struct xdr_stream *xdr)
 {
-       struct xdr_buf  *rcv_buf = &rqstp->rq_rcv_buf;
-       struct xdr_buf integ_buf;
+       struct xdr_buf integ_buf, *rcv_buf = &rqstp->rq_rcv_buf;
+       u32 data_offset, mic_offset, integ_len, maj_stat;
        struct xdr_netobj mic;
-       u32 data_offset, mic_offset;
-       u32 integ_len;
-       u32 maj_stat;
-       int status = -EIO;
+       __be32 *p;
 
-       integ_len = ntohl(*(*p)++);
+       p = xdr_inline_decode(xdr, 2 * sizeof(*p));
+       if (unlikely(!p))
+               goto unwrap_failed;
+       integ_len = be32_to_cpup(p++);
        if (integ_len & 3)
-               return status;
-       data_offset = (u8 *)(*p) - (u8 *)rcv_buf->head[0].iov_base;
+               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)
-               return status;
-       if (ntohl(*(*p)++) != rqstp->rq_seqno)
-               return status;
-
-       if (xdr_buf_subsegment(rcv_buf, &integ_buf, data_offset,
-                               mic_offset - data_offset))
-               return status;
+               goto unwrap_failed;
+       if (be32_to_cpup(p) != rqstp->rq_seqno)
+               goto unwrap_failed;
 
+       if (xdr_buf_subsegment(rcv_buf, &integ_buf, data_offset, integ_len))
+               goto unwrap_failed;
        if (xdr_buf_read_netobj(rcv_buf, &mic, mic_offset))
-               return status;
-
+               goto unwrap_failed;
        maj_stat = gss_verify_mic(ctx->gc_gss_ctx, &integ_buf, &mic);
        if (maj_stat == GSS_S_CONTEXT_EXPIRED)
                clear_bit(RPCAUTH_CRED_UPTODATE, &cred->cr_flags);
        if (maj_stat != GSS_S_COMPLETE)
-               return status;
+               goto bad_mic;
+
+       cred->cr_auth->au_rslack = cred->cr_auth->au_verfsize + 2 +
+                                  1 + XDR_QUADLEN(mic.len);
        return 0;
+unwrap_failed:
+       return -EIO;
+bad_mic:
+       dprintk("RPC:       %s: gss_verify_mic returned error 0x%08x\n",
+               __func__, maj_stat);
+       return -EIO;
 }
 
-static inline int
+static int
 gss_unwrap_resp_priv(struct rpc_cred *cred, struct gss_cl_ctx *ctx,
-               struct rpc_rqst *rqstp, __be32 **p)
-{
-       struct xdr_buf  *rcv_buf = &rqstp->rq_rcv_buf;
-       u32 offset;
-       u32 opaque_len;
-       u32 maj_stat;
-       int status = -EIO;
-
-       opaque_len = ntohl(*(*p)++);
-       offset = (u8 *)(*p) - (u8 *)rcv_buf->head[0].iov_base;
+                    struct rpc_rqst *rqstp, struct xdr_stream *xdr)
+{
+       struct xdr_buf *rcv_buf = &rqstp->rq_rcv_buf;
+       struct kvec *head = rqstp->rq_rcv_buf.head;
+       unsigned int savedlen = rcv_buf->len;
+       u32 offset, opaque_len, maj_stat;
+       __be32 *p;
+
+       p = xdr_inline_decode(xdr, 2 * sizeof(*p));
+       if (unlikely(!p))
+               goto unwrap_failed;
+       opaque_len = be32_to_cpup(p++);
+       offset = (u8 *)(p) - (u8 *)head->iov_base;
        if (offset + opaque_len > rcv_buf->len)
-               return status;
-       /* remove padding: */
+               goto unwrap_failed;
        rcv_buf->len = offset + opaque_len;
 
        maj_stat = gss_unwrap(ctx->gc_gss_ctx, offset, rcv_buf);
        if (maj_stat == GSS_S_CONTEXT_EXPIRED)
                clear_bit(RPCAUTH_CRED_UPTODATE, &cred->cr_flags);
        if (maj_stat != GSS_S_COMPLETE)
-               return status;
-       if (ntohl(*(*p)++) != rqstp->rq_seqno)
-               return status;
-
-       return 0;
-}
+               goto bad_unwrap;
+       /* gss_unwrap decrypted the sequence number */
+       if (be32_to_cpup(p++) != rqstp->rq_seqno)
+               goto unwrap_failed;
 
-static int
-gss_unwrap_req_decode(kxdrdproc_t decode, struct rpc_rqst *rqstp,
-                     __be32 *p, void *obj)
-{
-       struct xdr_stream xdr;
+       /* gss_unwrap redacts the opaque blob from the head iovec.
+        * rcv_buf has changed, thus the stream needs to be reset.
+        */
+       xdr_init_decode(xdr, rcv_buf, p, rqstp);
 
-       xdr_init_decode(&xdr, &rqstp->rq_rcv_buf, p, rqstp);
-       return decode(rqstp, &xdr, obj);
+       cred->cr_auth->au_rslack = cred->cr_auth->au_verfsize + 2 +
+                                  XDR_QUADLEN(savedlen - rcv_buf->len);
+       return 0;
+unwrap_failed:
+       return -EIO;
+bad_unwrap:
+       dprintk("RPC:       %s: gss_unwrap returned error 0x%08x\n",
+               __func__, maj_stat);
+       return -EIO;
 }
 
 static bool
@@ -2037,39 +2059,33 @@ out:
 }
 
 static int
-gss_unwrap_resp(struct rpc_task *task,
-               kxdrdproc_t decode, void *rqstp, __be32 *p, void *obj)
+gss_unwrap_resp(struct rpc_task *task, struct xdr_stream *xdr)
 {
-       struct rpc_cred *cred = task->tk_rqstp->rq_cred;
+       struct rpc_rqst *rqstp = task->tk_rqstp;
+       struct rpc_cred *cred = rqstp->rq_cred;
        struct gss_cred *gss_cred = container_of(cred, struct gss_cred,
                        gc_base);
        struct gss_cl_ctx *ctx = gss_cred_get_ctx(cred);
-       __be32          *savedp = p;
-       struct kvec     *head = ((struct rpc_rqst *)rqstp)->rq_rcv_buf.head;
-       int             savedlen = head->iov_len;
-       int             status = -EIO;
+       int status = -EIO;
 
        if (ctx->gc_proc != RPC_GSS_PROC_DATA)
                goto out_decode;
        switch (gss_cred->gc_service) {
        case RPC_GSS_SVC_NONE:
+               status = gss_unwrap_resp_auth(cred);
                break;
        case RPC_GSS_SVC_INTEGRITY:
-               status = gss_unwrap_resp_integ(cred, ctx, rqstp, &p);
-               if (status)
-                       goto out;
+               status = gss_unwrap_resp_integ(cred, ctx, rqstp, xdr);
                break;
        case RPC_GSS_SVC_PRIVACY:
-               status = gss_unwrap_resp_priv(cred, ctx, rqstp, &p);
-               if (status)
-                       goto out;
+               status = gss_unwrap_resp_priv(cred, ctx, rqstp, xdr);
                break;
        }
-       /* take into account extra slack for integrity and privacy cases: */
-       cred->cr_auth->au_rslack = cred->cr_auth->au_verfsize + (p - savedp)
-                                               + (savedlen - head->iov_len);
+       if (status)
+               goto out;
+
 out_decode:
-       status = gss_unwrap_req_decode(decode, rqstp, p, obj);
+       status = rpcauth_unwrap_resp_decode(task, xdr);
 out:
        gss_put_ctx(ctx);
        dprintk("RPC: %5u %s returning %d\n",
index 797f847..bf96975 100644 (file)
@@ -86,25 +86,19 @@ nul_refresh(struct rpc_task *task)
        return 0;
 }
 
-static __be32 *
-nul_validate(struct rpc_task *task, __be32 *p)
+static int
+nul_validate(struct rpc_task *task, struct xdr_stream *xdr)
 {
-       rpc_authflavor_t        flavor;
-       u32                     size;
-
-       flavor = ntohl(*p++);
-       if (flavor != RPC_AUTH_NULL) {
-               printk("RPC: bad verf flavor: %u\n", flavor);
-               return ERR_PTR(-EIO);
-       }
-
-       size = ntohl(*p++);
-       if (size != 0) {
-               printk("RPC: bad verf size: %u\n", size);
-               return ERR_PTR(-EIO);
-       }
-
-       return p;
+       __be32 *p;
+
+       p = xdr_inline_decode(xdr, 2 * sizeof(*p));
+       if (!p)
+               return -EIO;
+       if (*p++ != rpc_auth_null)
+               return -EIO;
+       if (*p != xdr_zero)
+               return -EIO;
+       return 0;
 }
 
 const struct rpc_authops authnull_ops = {
@@ -134,6 +128,7 @@ const struct rpc_credops null_credops = {
        .crwrap_req     = rpcauth_wrap_req_encode,
        .crrefresh      = nul_refresh,
        .crvalidate     = nul_validate,
+       .crunwrap_resp  = rpcauth_unwrap_resp_decode,
 };
 
 static
index 1d5b7ed..5ea84a9 100644 (file)
@@ -160,29 +160,32 @@ unx_refresh(struct rpc_task *task)
        return 0;
 }
 
-static __be32 *
-unx_validate(struct rpc_task *task, __be32 *p)
+static int
+unx_validate(struct rpc_task *task, struct xdr_stream *xdr)
 {
-       rpc_authflavor_t        flavor;
-       u32                     size;
-
-       flavor = ntohl(*p++);
-       if (flavor != RPC_AUTH_NULL &&
-           flavor != RPC_AUTH_UNIX &&
-           flavor != RPC_AUTH_SHORT) {
-               printk("RPC: bad verf flavor: %u\n", flavor);
-               return ERR_PTR(-EIO);
-       }
+       __be32 *p;
+       u32 size;
 
-       size = ntohl(*p++);
-       if (size > RPC_MAX_AUTH_SIZE) {
-               printk("RPC: giant verf size: %u\n", size);
-               return ERR_PTR(-EIO);
+       p = xdr_inline_decode(xdr, 2 * sizeof(*p));
+       if (!p)
+               return -EIO;
+       switch (*p++) {
+       case rpc_auth_null:
+       case rpc_auth_unix:
+       case rpc_auth_short:
+               break;
+       default:
+               return -EIO;
        }
-       task->tk_rqstp->rq_cred->cr_auth->au_rslack = (size >> 2) + 2;
-       p += (size >> 2);
+       size = be32_to_cpup(p);
+       if (size > RPC_MAX_AUTH_SIZE)
+               return -EIO;
+       p = xdr_inline_decode(xdr, size);
+       if (!p)
+               return -EIO;
 
-       return p;
+       task->tk_rqstp->rq_cred->cr_auth->au_rslack = (size >> 2) + 2;
+       return 0;
 }
 
 int __init rpc_init_authunix(void)
@@ -223,4 +226,5 @@ const struct rpc_credops unix_credops = {
        .crwrap_req     = rpcauth_wrap_req_encode,
        .crrefresh      = unx_refresh,
        .crvalidate     = unx_validate,
+       .crunwrap_resp  = rpcauth_unwrap_resp_decode,
 };
index e973508..803e931 100644 (file)
@@ -79,7 +79,8 @@ static void   call_connect_status(struct rpc_task *task);
 
 static int     rpc_encode_header(struct rpc_task *task,
                                  struct xdr_stream *xdr);
-static __be32  *rpc_decode_header(struct rpc_task *task);
+static int     rpc_decode_header(struct rpc_task *task,
+                                 struct xdr_stream *xdr);
 static int     rpc_ping(struct rpc_clnt *clnt);
 
 static void rpc_register_client(struct rpc_clnt *clnt)
@@ -2251,12 +2252,11 @@ call_decode(struct rpc_task *task)
 {
        struct rpc_clnt *clnt = task->tk_client;
        struct rpc_rqst *req = task->tk_rqstp;
-       kxdrdproc_t     decode = task->tk_msg.rpc_proc->p_decode;
-       __be32          *p;
+       struct xdr_stream xdr;
 
        dprint_status(task);
 
-       if (!decode) {
+       if (!task->tk_msg.rpc_proc->p_decode) {
                task->tk_action = rpc_exit_task;
                return;
        }
@@ -2292,29 +2292,27 @@ call_decode(struct rpc_task *task)
                goto out_retry;
        }
 
-       p = rpc_decode_header(task);
-       if (IS_ERR(p)) {
-               if (p == ERR_PTR(-EAGAIN))
-                       goto out_retry;
+       xdr_init_decode(&xdr, &req->rq_rcv_buf,
+                       req->rq_rcv_buf.head[0].iov_base, req);
+       switch (rpc_decode_header(task, &xdr)) {
+       case 0:
+               task->tk_action = rpc_exit_task;
+               task->tk_status = rpcauth_unwrap_resp(task, &xdr);
+               dprintk("RPC: %5u %s result %d\n",
+                       task->tk_pid, __func__, task->tk_status);
                return;
-       }
-       task->tk_action = rpc_exit_task;
-
-       task->tk_status = rpcauth_unwrap_resp(task, decode, req, p,
-                                             task->tk_msg.rpc_resp);
-
-       dprintk("RPC: %5u call_decode result %d\n", task->tk_pid,
-                       task->tk_status);
-       return;
+       case -EAGAIN:
 out_retry:
-       task->tk_status = 0;
-       /* Note: rpc_decode_header() may have freed the RPC slot */
-       if (task->tk_rqstp == req) {
-               xdr_free_bvec(&req->rq_rcv_buf);
-               req->rq_reply_bytes_recvd = req->rq_rcv_buf.len = 0;
-               if (task->tk_client->cl_discrtry)
-                       xprt_conditional_disconnect(req->rq_xprt,
-                                       req->rq_connect_cookie);
+               task->tk_status = 0;
+               /* Note: rpc_decode_header() may have freed the RPC slot */
+               if (task->tk_rqstp == req) {
+                       xdr_free_bvec(&req->rq_rcv_buf);
+                       req->rq_reply_bytes_recvd = 0;
+                       req->rq_rcv_buf.len = 0;
+                       if (task->tk_client->cl_discrtry)
+                               xprt_conditional_disconnect(req->rq_xprt,
+                                                           req->rq_connect_cookie);
+               }
        }
 }
 
@@ -2347,14 +2345,12 @@ out_fail:
        return error;
 }
 
-static noinline __be32 *
-rpc_decode_header(struct rpc_task *task)
+static noinline int
+rpc_decode_header(struct rpc_task *task, struct xdr_stream *xdr)
 {
        struct rpc_clnt *clnt = task->tk_client;
-       struct kvec *iov = &task->tk_rqstp->rq_rcv_buf.head[0];
-       int len = task->tk_rqstp->rq_rcv_buf.len >> 2;
-       __be32  *p = iov->iov_base;
        int error = -EACCES;
+       __be32 *p;
 
        /* RFC-1014 says that the representation of XDR data must be a
         * multiple of four bytes
@@ -2363,25 +2359,26 @@ rpc_decode_header(struct rpc_task *task)
         */
        if (task->tk_rqstp->rq_rcv_buf.len & 3)
                goto out_badlen;
-       if ((len -= 3) < 0)
-               goto out_unparsable;
 
+       p = xdr_inline_decode(xdr, 3 * sizeof(*p));
+       if (!p)
+               goto out_unparsable;
        p++;    /* skip XID */
        if (*p++ != rpc_reply)
                goto out_unparsable;
        if (*p++ != rpc_msg_accepted)
                goto out_msg_denied;
 
-       p = rpcauth_checkverf(task, p);
-       if (IS_ERR(p))
+       error = rpcauth_checkverf(task, xdr);
+       if (error)
                goto out_verifier;
 
-       len = p - (__be32 *)iov->iov_base - 1;
-       if (len < 0)
+       p = xdr_inline_decode(xdr, sizeof(*p));
+       if (!p)
                goto out_unparsable;
-       switch (*p++) {
+       switch (*p) {
        case rpc_success:
-               return p;
+               return 0;
        case rpc_prog_unavail:
                trace_rpc__prog_unavail(task);
                error = -EPFNOSUPPORT;
@@ -2406,11 +2403,11 @@ out_garbage:
        if (task->tk_garb_retry) {
                task->tk_garb_retry--;
                task->tk_action = call_encode;
-               return ERR_PTR(-EAGAIN);
+               return -EAGAIN;
        }
 out_err:
        rpc_exit(task, error);
-       return ERR_PTR(error);
+       return error;
 
 out_badlen:
        trace_rpc__unparsable(task);
@@ -2424,10 +2421,12 @@ out_unparsable:
 
 out_verifier:
        trace_rpc_bad_verifier(task);
-       error = PTR_ERR(p);
        goto out_garbage;
 
 out_msg_denied:
+       p = xdr_inline_decode(xdr, sizeof(*p));
+       if (!p)
+               goto out_unparsable;
        switch (*p++) {
        case rpc_auth_error:
                break;
@@ -2441,6 +2440,9 @@ out_msg_denied:
                goto out_err;
        }
 
+       p = xdr_inline_decode(xdr, sizeof(*p));
+       if (!p)
+               goto out_unparsable;
        switch (*p++) {
        case rpc_autherr_rejectedcred:
        case rpc_autherr_rejectedverf:
@@ -2454,7 +2456,7 @@ out_msg_denied:
                /* Ensure we obtain a new XID! */
                xprt_release(task);
                task->tk_action = call_reserve;
-               return ERR_PTR(-EAGAIN);
+               return -EAGAIN;
        case rpc_autherr_badcred:
        case rpc_autherr_badverf:
                /* possibly garbled cred/verf? */
@@ -2463,7 +2465,7 @@ out_msg_denied:
                task->tk_garb_retry--;
                trace_rpc__bad_creds(task);
                task->tk_action = call_encode;
-               return ERR_PTR(-EAGAIN);
+               return -EAGAIN;
        case rpc_autherr_tooweak:
                trace_rpc__auth_tooweak(task);
                pr_warn("RPC: server %s requires stronger authentication.\n",