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: Insulate nfsd4_encode_read() from page boundaries in the encode buffer

Commit 28d5bc468efe ("NFSD: Optimize nfsd4_encode_readv()") replaced
the use of write_bytes_to_xdr_buf() because it's expensive and the
data items to be encoded are already properly aligned.

However, the current code will corrupt the encoded data if the XDR
data items that are reserved early and then poked into the XDR
buffer later happen to fall on a page boundary in the XDR encoding
buffer.

__xdr_commit_encode can shift encoded data items in the encoding
buffer so that pointers returned from xdr_reserve_space() no longer
address the same part of the encoding stream.

This isn't an issue for splice reads because the reserved encode
buffer areas must fall in the XDR buffers header for the splice to
work without error. For vectored reads, however, there is a
possibility of send buffer corruption in rare cases.

Fixes: 28d5bc468efe ("NFSD: Optimize nfsd4_encode_readv()")
Reviewed-by: NeilBrown <neilb@suse.de>
Reviewed-by: Jeff Layton <jlayton@kernel.org>
Signed-off-by: Chuck Lever <chuck.lever@oracle.com>

+19 -15
+19 -15
fs/nfsd/nfs4xdr.c
··· 4318 4318 __be32 nfserr; 4319 4319 4320 4320 /* 4321 + * Splice read doesn't work if encoding has already wandered 4322 + * into the XDR buf's page array. 4323 + */ 4324 + if (unlikely(xdr->buf->page_len)) { 4325 + WARN_ON_ONCE(1); 4326 + return nfserr_serverfault; 4327 + } 4328 + 4329 + /* 4321 4330 * Make sure there is room at the end of buf->head for 4322 4331 * svcxdr_encode_opaque_pages() to create a tail buffer 4323 4332 * to XDR-pad the payload. ··· 4408 4399 struct nfsd4_compoundargs *argp = resp->rqstp->rq_argp; 4409 4400 struct nfsd4_read *read = &u->read; 4410 4401 struct xdr_stream *xdr = resp->xdr; 4411 - int starting_len = xdr->buf->len; 4412 4402 bool splice_ok = argp->splice_ok; 4403 + unsigned int eof_offset; 4413 4404 unsigned long maxcount; 4405 + __be32 wire_data[2]; 4414 4406 struct file *file; 4415 - __be32 *p; 4416 4407 4417 4408 if (nfserr) 4418 4409 return nfserr; 4410 + 4411 + eof_offset = xdr->buf->len; 4419 4412 file = read->rd_nf->nf_file; 4420 4413 4421 - p = xdr_reserve_space(xdr, 8); /* eof flag and byte count */ 4422 - if (!p) { 4414 + /* Reserve space for the eof flag and byte count */ 4415 + if (unlikely(!xdr_reserve_space(xdr, XDR_UNIT * 2))) { 4423 4416 WARN_ON_ONCE(splice_ok); 4424 4417 return nfserr_resource; 4425 - } 4426 - if (resp->xdr->buf->page_len && splice_ok) { 4427 - WARN_ON_ONCE(1); 4428 - return nfserr_serverfault; 4429 4418 } 4430 4419 xdr_commit_encode(xdr); 4431 4420 ··· 4435 4428 else 4436 4429 nfserr = nfsd4_encode_readv(resp, read, file, maxcount); 4437 4430 if (nfserr) { 4438 - xdr_truncate_encode(xdr, starting_len); 4431 + xdr_truncate_encode(xdr, eof_offset); 4439 4432 return nfserr; 4440 4433 } 4441 4434 4442 - p = xdr_encode_bool(p, read->rd_eof); 4443 - *p = cpu_to_be32(read->rd_length); 4435 + wire_data[0] = read->rd_eof ? xdr_one : xdr_zero; 4436 + wire_data[1] = cpu_to_be32(read->rd_length); 4437 + write_bytes_to_xdr_buf(xdr->buf, eof_offset, &wire_data, XDR_UNIT * 2); 4444 4438 return nfs_ok; 4445 4439 } 4446 4440 ··· 5312 5304 p = xdr_reserve_space(xdr, 4 + 8 + 4); 5313 5305 if (!p) 5314 5306 return nfserr_io; 5315 - if (resp->xdr->buf->page_len && splice_ok) { 5316 - WARN_ON_ONCE(splice_ok); 5317 - return nfserr_serverfault; 5318 - } 5319 5307 5320 5308 maxcount = min_t(unsigned long, read->rd_length, 5321 5309 (xdr->buf->buflen - xdr->buf->len));