NFSv4.2: decode_read_plus_hole() needs to check the extent offset
authorTrond Myklebust <trond.myklebust@hammerspace.com>
Tue, 8 Dec 2020 13:14:55 +0000 (08:14 -0500)
committerTrond Myklebust <trond.myklebust@hammerspace.com>
Mon, 14 Dec 2020 11:51:07 +0000 (06:51 -0500)
The server is allowed to return a hole extent with an offset that starts
before the offset supplied in the READ_PLUS argument. Ensure that we
support that case too.

Signed-off-by: Trond Myklebust <trond.myklebust@hammerspace.com>
fs/nfs/nfs42xdr.c

index 4c6bce3..f9faa13 100644 (file)
@@ -1053,8 +1053,9 @@ static int decode_read_plus_data(struct xdr_stream *xdr, struct nfs_pgio_res *re
        return 0;
 }
 
-static int decode_read_plus_hole(struct xdr_stream *xdr, struct nfs_pgio_res *res,
-                                uint32_t *eof)
+static int decode_read_plus_hole(struct xdr_stream *xdr,
+                                struct nfs_pgio_args *args,
+                                struct nfs_pgio_res *res, uint32_t *eof)
 {
        uint64_t offset, length, recvd;
        __be32 *p;
@@ -1065,6 +1066,20 @@ static int decode_read_plus_hole(struct xdr_stream *xdr, struct nfs_pgio_res *re
 
        p = xdr_decode_hyper(p, &offset);
        p = xdr_decode_hyper(p, &length);
+       if (offset != args->offset + res->count) {
+               /* Server returned an out-of-sequence extent */
+               if (offset > args->offset + res->count ||
+                   offset + length < args->offset + res->count) {
+                       dprintk("NFS: server returned out of sequence extent: "
+                               "offset/size = %llu/%llu != expected %llu\n",
+                               (unsigned long long)offset,
+                               (unsigned long long)length,
+                               (unsigned long long)(args->offset +
+                                                    res->count));
+                       return 1;
+               }
+               length -= args->offset + res->count - offset;
+       }
        recvd = xdr_expand_hole(xdr, res->count, length);
        res->count += recvd;
 
@@ -1077,6 +1092,9 @@ static int decode_read_plus_hole(struct xdr_stream *xdr, struct nfs_pgio_res *re
 
 static int decode_read_plus(struct xdr_stream *xdr, struct nfs_pgio_res *res)
 {
+       struct nfs_pgio_header *hdr =
+               container_of(res, struct nfs_pgio_header, res);
+       struct nfs_pgio_args *args = &hdr->args;
        uint32_t eof, segments, type;
        int status, i;
        __be32 *p;
@@ -1104,7 +1122,7 @@ static int decode_read_plus(struct xdr_stream *xdr, struct nfs_pgio_res *res)
                if (type == NFS4_CONTENT_DATA)
                        status = decode_read_plus_data(xdr, res, &eof);
                else if (type == NFS4_CONTENT_HOLE)
-                       status = decode_read_plus_hole(xdr, res, &eof);
+                       status = decode_read_plus_hole(xdr, args, res, &eof);
                else
                        return -EINVAL;