Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

nfsd: use dynamic allocation for oversized NFSv4.0 replay cache

Commit 1e8e9913672a ("nfsd: fix heap overflow in NFSv4.0 LOCK
replay cache") capped the replay cache copy at NFSD4_REPLAY_ISIZE
to prevent a heap overflow, but set rp_buflen to zero when the
encoded response exceeded the inline buffer. A retransmitted LOCK
reaching the replay path then produced only a status code with no
operation body, resulting in a malformed XDR response.

When the encoded response exceeds the 112-byte inline rp_ibuf, a
buffer is kmalloc'd to hold it. If the allocation fails, rp_buflen
remains zero, preserving the behavior from the capped-copy fix.
The buffer is freed when the stateowner is released or when a
subsequent operation's response fits in the inline buffer.

Fixes: 1e8e9913672a ("nfsd: fix heap overflow in NFSv4.0 LOCK replay cache")
Signed-off-by: Chuck Lever <chuck.lever@oracle.com>

+39 -12
+16
fs/nfsd/nfs4state.c
··· 1496 1496 } 1497 1497 } 1498 1498 1499 + /** 1500 + * nfs4_replay_free_cache - release dynamically allocated replay buffer 1501 + * @rp: replay cache to reset 1502 + * 1503 + * If @rp->rp_buf points to a kmalloc'd buffer, free it and reset 1504 + * rp_buf to the inline rp_ibuf. Always zeroes rp_buflen. 1505 + */ 1506 + void nfs4_replay_free_cache(struct nfs4_replay *rp) 1507 + { 1508 + if (rp->rp_buf != rp->rp_ibuf) 1509 + kfree(rp->rp_buf); 1510 + rp->rp_buf = rp->rp_ibuf; 1511 + rp->rp_buflen = 0; 1512 + } 1513 + 1499 1514 static inline void nfs4_free_stateowner(struct nfs4_stateowner *sop) 1500 1515 { 1516 + nfs4_replay_free_cache(&sop->so_replay); 1501 1517 kfree(sop->so_owner.data); 1502 1518 sop->so_ops->so_free(sop); 1503 1519 }
+16 -7
fs/nfsd/nfs4xdr.c
··· 6282 6282 int len = xdr->buf->len - (op_status_offset + XDR_UNIT); 6283 6283 6284 6284 so->so_replay.rp_status = op->status; 6285 - if (len <= NFSD4_REPLAY_ISIZE) { 6286 - so->so_replay.rp_buflen = len; 6287 - read_bytes_from_xdr_buf(xdr->buf, 6288 - op_status_offset + XDR_UNIT, 6289 - so->so_replay.rp_buf, len); 6290 - } else { 6291 - so->so_replay.rp_buflen = 0; 6285 + if (len > NFSD4_REPLAY_ISIZE) { 6286 + char *buf = kmalloc(len, GFP_KERNEL); 6287 + 6288 + nfs4_replay_free_cache(&so->so_replay); 6289 + if (buf) { 6290 + so->so_replay.rp_buf = buf; 6291 + } else { 6292 + /* rp_buflen already zeroed; skip caching */ 6293 + goto status; 6294 + } 6295 + } else if (so->so_replay.rp_buf != so->so_replay.rp_ibuf) { 6296 + nfs4_replay_free_cache(&so->so_replay); 6292 6297 } 6298 + so->so_replay.rp_buflen = len; 6299 + read_bytes_from_xdr_buf(xdr->buf, 6300 + op_status_offset + XDR_UNIT, 6301 + so->so_replay.rp_buf, len); 6293 6302 } 6294 6303 status: 6295 6304 op->status = nfsd4_map_status(op->status,
+7 -5
fs/nfsd/state.h
··· 554 554 * ~32(deleg. ace) = 112 bytes 555 555 * 556 556 * Some responses can exceed this. A LOCK denial includes the conflicting 557 - * lock owner, which can be up to 1024 bytes (NFS4_OPAQUE_LIMIT). Responses 558 - * larger than REPLAY_ISIZE are not cached in rp_ibuf; only rp_status is 559 - * saved. Enlarging this constant increases the size of every 560 - * nfs4_stateowner. 557 + * lock owner, which can be up to 1024 bytes (NFS4_OPAQUE_LIMIT). When a 558 + * response exceeds REPLAY_ISIZE, a buffer is dynamically allocated. If 559 + * that allocation fails, only rp_status is saved. Enlarging this constant 560 + * increases the size of every nfs4_stateowner. 561 561 */ 562 562 563 563 #define NFSD4_REPLAY_ISIZE 112 ··· 569 569 struct nfs4_replay { 570 570 __be32 rp_status; 571 571 unsigned int rp_buflen; 572 - char *rp_buf; 572 + char *rp_buf; /* rp_ibuf or kmalloc'd */ 573 573 struct knfsd_fh rp_openfh; 574 574 int rp_locked; 575 575 char rp_ibuf[NFSD4_REPLAY_ISIZE]; 576 576 }; 577 + 578 + extern void nfs4_replay_free_cache(struct nfs4_replay *rp); 577 579 578 580 struct nfs4_stateowner; 579 581