Fix XDR encoding near page boundaries
Subsystems (3)
[email protected]
maintainerv4 cover Fix XDR encoding near page boundaries
From: Chuck Lever <[email protected]>
Refresh the patch series to address the longstanding bug pointed out by J David and Rick Macklem.
Changes since v2:
- Address same issue in NFSv4 READLINK and SECINFO operations
- Update kdoc comment for xdr_reserve_space()
Chuck Lever (9):
NFSD: Encode COMPOUND operation status on page boundaries
NFSD: Insulate nfsd4_encode_read() from page boundaries in the encode
buffer
NFSD: Insulate nfsd4_encode_read_plus() from page boundaries in the
encode buffer
NFSD: Insulate nfsd4_encode_read_plus_data() from page boundaries in
the encode buffer
NFSD: Insulate nfsd4_encode_fattr4() from page boundaries in the
encode buffer
NFSD: Insulate nfsd4_encode_readlink() from page boundaries in the
encode buffer
NFSD: Refactor nfsd4_do_encode_secinfo() again
NFSD: Insulate nfsd4_encode_secinfo() from page boundaries in the
encode buffer
SUNRPC: Document validity guarantees of the pointer returned by
reserve_space
fs/nfsd/nfs4xdr.c | 214 +++++++++++++++++++++++++--------------------- net/sunrpc/xdr.c | 6 ++ 2 files changed, 122 insertions(+), 98 deletions(-)
Signature · 1 line
2.47.0
[email protected]
maintainerPATCH v4 1/9 NFSD: Encode COMPOUND operation status on page boundaries
From: Chuck Lever <[email protected]>
J. David reports an odd corruption of a READDIR reply sent to a FreeBSD client.
xdr_reserve_space() has to do a special trick when the @nbytes value requests more space than there is in the current page of the XDR buffer.
In that case, xdr_reserve_space() returns a pointer to the start of the next page, and then the next call to xdr_reserve_space() invokes __xdr_commit_encode() to copy enough of the data item back into the previous page to make that data item contiguous across the page boundary.
But we need to be careful in the case where buffer space is reserved early for a data item whose value will be inserted into the buffer later.
One such caller, nfsd4_encode_operation(), reserves 8 bytes in the encoding buffer for each COMPOUND operation. However, a READDIR result can sometimes encode file names so that there are only 4 bytes left at the end of the current XDR buffer page (though plenty of pages are left to handle the remaining encoding tasks).
If a COMPOUND operation follows the READDIR result (say, a GETATTR), then nfsd4_encode_operation() will reserve 8 bytes for the op number
(9) and the op status (usually NFS4_OK). In this weird case, xdr_reserve_space() returns a pointer to byte zero of the next buffer page, as it assumes the data item will be copied back into place (in the previous page) on the next call to xdr_reserve_space().
nfsd4_encode_operation() writes the op num into the buffer, then saves the next 4-byte location for the op's status code. The next xdr_reserve_space() call is part of GETATTR encoding, so the op num gets copied back into the previous page, but the saved location for the op status continues to point to the wrong spot in the current XDR buffer page because __xdr_commit_encode() moved that data item.
After GETATTR encoding is complete, nfsd4_encode_operation() writes the op status over the first XDR data item in the GETATTR result. The NFS4_OK status code (0) makes it look like there are zero items in the GETATTR's attribute bitmask.
The patch description of commit 2825a7f90753 ("nfsd4: allow encoding across page boundaries") [2014] remarks that NFSD "can't handle a new operation starting close to the end of a page." This bug appears to be one reason for that remark.
X-Cc: [email protected]
--- fs/nfsd/nfs4xdr.c | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-)
fs/nfsd/nfs4xdr.c +10 -10
@@ -5760,15 +5760,14 @@ nfsd4_encode_operation(struct nfsd4_compoundres *resp, struct nfsd4_op *op)struct nfs4_stateowner *so = resp->cstate.replay_owner;struct svc_rqst *rqstp = resp->rqstp;const struct nfsd4_operation *opdesc = op->opdesc; -int post_err_offset; +unsigned int op_status_offset;nfsd4_enc encoder; -__be32 *p; - p =xdr_reserve_space (xdr,8 ); -if (!p) +if (xdr_stream_encode_u32 (xdr, op->opnum) !=XDR_UNIT ) + goto release; + op_status_offset =xdr_stream_pos (xdr); +if (!xdr_reserve_space (xdr,XDR_UNIT )) goto release; - *p++ =cpu_to_be32 (op->opnum); - post_err_offset = xdr->buf->len;if (op->opnum ==OP_ILLEGAL ) goto status;
@@ -5809,20 +5808,21 @@ nfsd4_encode_operation(struct nfsd4_compoundres *resp, struct nfsd4_op *op)* bug if we had to do this on a non-idempotent op: */ warn_on_nonidempotent_op (op); -xdr_truncate_encode (xdr, post_err_offset); +xdr_truncate_encode (xdr, op_status_offset +XDR_UNIT ); }if (so) { -int len = xdr->buf->len - post_err_offset; +int len = xdr->buf->len - (op_status_offset +XDR_UNIT ); so->so_replay.rp_status = op->status; so->so_replay.rp_buflen = len; -read_bytes_from_xdr_buf (xdr->buf, post_err_offset, +read_bytes_from_xdr_buf (xdr->buf, op_status_offset +XDR_UNIT , so->so_replay.rp_buf, len); } status: op->status =nfsd4_map_status (op->status, resp->cstate.minorversion); - *p = op->status; +write_bytes_to_xdr_buf (xdr->buf, op_status_offset, + &op->status,XDR_UNIT ); release:if (opdesc && opdesc->op_release) opdesc->op_release (&op->u);
Signature · 1 line
2.47.0
[email protected]
maintainerPATCH v4 2/9 NFSD: Insulate nfsd4_encode_read() from page boundaries in the encode buffer
In reply to [email protected]From: Chuck Lever <[email protected]>
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.
--- fs/nfsd/nfs4xdr.c | 34 +++++++++++++++++++--------------- 1 file changed, 19 insertions(+), 15 deletions(-)
fs/nfsd/nfs4xdr.c +19 -15
@@ -4316,6 +4316,15 @@ static __be32 nfsd4_encode_splice_read(int status, space_left;__be32 nfserr; +/* +* Splice read doesn't work if encoding has already wandered +* into the XDR buf's page array. +*/ +if (unlikely (xdr->buf->page_len)) { +WARN_ON_ONCE (1 ); +return nfserr_serverfault; + } +/* * Make sure there is room at the end of buf->head for * svcxdr_encode_opaque_pages() to create a tail buffer
@@ -4398,25 +4407,23 @@ nfsd4_encode_read(struct nfsd4_compoundres *resp, __be32 nfserr,struct nfsd4_compoundargs *argp = resp->rqstp->rq_argp;struct nfsd4_read *read = &u->read;struct xdr_stream *xdr = resp->xdr; -int starting_len = xdr->buf->len;bool splice_ok = argp->splice_ok; +unsigned int eof_offset;unsigned long maxcount; +__be32 wire_data[2 ];struct file *file; -__be32 *p;if (nfserr)return nfserr; + + eof_offset =xdr_stream_pos (xdr); file = read->rd_nf->nf_file; - p =xdr_reserve_space (xdr,8 );/* eof flag and byte count */ -if (!p) { +/* Reserve space for the eof flag and byte count */ +if (unlikely (!xdr_reserve_space (xdr,XDR_UNIT *2 ))) {WARN_ON_ONCE (splice_ok);return nfserr_resource; } -if (resp->xdr->buf->page_len && splice_ok) { -WARN_ON_ONCE (1 ); -return nfserr_serverfault; - }xdr_commit_encode (xdr); maxcount = min_t(unsigned long, read->rd_length,
@@ -4427,12 +4434,13 @@ nfsd4_encode_read(struct nfsd4_compoundres *resp, __be32 nfserr,else nfserr =nfsd4_encode_readv (resp, read, file, maxcount);if (nfserr) { -xdr_truncate_encode (xdr, starting_len); +xdr_truncate_encode (xdr, eof_offset);return nfserr; } - p =xdr_encode_bool (p, read->rd_eof); - *p =cpu_to_be32 (read->rd_length); + wire_data[0 ] = read->rd_eof ? xdr_one : xdr_zero; + wire_data[1 ] =cpu_to_be32 (read->rd_length); +write_bytes_to_xdr_buf (xdr->buf, eof_offset, &wire_data,XDR_UNIT *2 );return nfs_ok; }
@@ -5303,10 +5311,6 @@ nfsd4_encode_read_plus_data(struct nfsd4_compoundres *resp, p =xdr_reserve_space (xdr,4 +8 +4 );if (!p)return nfserr_io; -if (resp->xdr->buf->page_len && splice_ok) { -WARN_ON_ONCE (splice_ok); -return nfserr_serverfault; - } maxcount = min_t(unsigned long, read->rd_length, (xdr->buf->buflen - xdr->buf->len));
Signature · 1 line
2.47.0
[email protected]
maintainerPATCH v4 3/9 NFSD: Insulate nfsd4_encode_read_plus() from page boundaries in the encode buffer
In reply to [email protected]From: Chuck Lever <[email protected]>
Commit eeadcb757945 ("NFSD: Simplify READ_PLUS") replaced the use of write_bytes_to_xdr_buf(), copying what was in nfsd4_encode_read() at the time.
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.
--- fs/nfsd/nfs4xdr.c | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-)
fs/nfsd/nfs4xdr.c +10 -8
@@ -5336,16 +5336,17 @@ nfsd4_encode_read_plus(struct nfsd4_compoundres *resp, __be32 nfserr,struct nfsd4_read *read = &u->read;struct file *file = read->rd_nf->nf_file;struct xdr_stream *xdr = resp->xdr; -int starting_len = xdr->buf->len; +unsigned int eof_offset; +__be32 wire_data[2 ];u32 segments =0 ; -__be32 *p;if (nfserr)return nfserr; -/* eof flag, segment count */ - p =xdr_reserve_space (xdr,4 +4 ); -if (!p) + eof_offset =xdr_stream_pos (xdr); + +/* Reserve space for the eof flag and segment count */ +if (unlikely (!xdr_reserve_space (xdr,XDR_UNIT *2 )))return nfserr_io;xdr_commit_encode (xdr);
@@ -5355,15 +5356,16 @@ nfsd4_encode_read_plus(struct nfsd4_compoundres *resp, __be32 nfserr, nfserr =nfsd4_encode_read_plus_data (resp, read);if (nfserr) { -xdr_truncate_encode (xdr, starting_len); +xdr_truncate_encode (xdr, eof_offset);return nfserr; } segments++; out: - p =xdr_encode_bool (p, read->rd_eof); - *p =cpu_to_be32 (segments); + wire_data[0 ] = read->rd_eof ? xdr_one : xdr_zero; + wire_data[1 ] =cpu_to_be32 (segments); +write_bytes_to_xdr_buf (xdr->buf, eof_offset, &wire_data,XDR_UNIT *2 );return nfserr; }
Signature · 1 line
2.47.0
[email protected]
maintainerPATCH v4 4/9 NFSD: Insulate nfsd4_encode_read_plus_data() from page boundaries in the encode buffer
In reply to [email protected]From: Chuck Lever <[email protected]>
Commit eeadcb757945 ("NFSD: Simplify READ_PLUS") replaced the use of write_bytes_to_xdr_buf(), copying what was in nfsd4_encode_read() at the time.
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.
--- fs/nfsd/nfs4xdr.c | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-)
fs/nfsd/nfs4xdr.c +17 -8
@@ -5304,14 +5304,21 @@ nfsd4_encode_read_plus_data(struct nfsd4_compoundres *resp,struct file *file = read->rd_nf->nf_file;struct xdr_stream *xdr = resp->xdr;bool splice_ok = argp->splice_ok; +unsigned int offset_offset; +__be32 nfserr, wire_count;unsigned long maxcount; -__be32 nfserr, *p; +__be64 wire_offset; -/* Content type, offset, byte count */ - p =xdr_reserve_space (xdr,4 +8 +4 ); -if (!p) +if (xdr_stream_encode_u32 (xdr,NFS4_CONTENT_DATA ) !=XDR_UNIT )return nfserr_io; + offset_offset =xdr_stream_pos (xdr); + +/* Reserve space for the byte offset and count */ +if (unlikely (!xdr_reserve_space (xdr,XDR_UNIT *3 ))) +return nfserr_io; +xdr_commit_encode (xdr); + maxcount = min_t(unsigned long, read->rd_length, (xdr->buf->buflen - xdr->buf->len));
@@ -5322,10 +5329,12 @@ nfsd4_encode_read_plus_data(struct nfsd4_compoundres *resp,if (nfserr)return nfserr; - *p++ =cpu_to_be32 (NFS4_CONTENT_DATA ); - p =xdr_encode_hyper (p, read->rd_offset); - *p =cpu_to_be32 (read->rd_length); - + wire_offset =cpu_to_be64 (read->rd_offset); +write_bytes_to_xdr_buf (xdr->buf, offset_offset, &wire_offset, +XDR_UNIT *2 ); + wire_count =cpu_to_be32 (read->rd_length); +write_bytes_to_xdr_buf (xdr->buf, offset_offset +XDR_UNIT *2 , + &wire_count,XDR_UNIT );return nfs_ok; }
Signature · 1 line
2.47.0
[email protected]
maintainerPATCH v4 5/9 NFSD: Insulate nfsd4_encode_fattr4() from page boundaries in the encode buffer
In reply to [email protected]From: Chuck Lever <[email protected]>
Commit ab04de60ae1c ("NFSD: Optimize nfsd4_encode_fattr()") 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, there's no guarantee that the pointer returned from xdr_reserve_space() will still point to the correct reserved space in the encode buffer after one or more intervening calls to xdr_reserve_space(). It just happens to work with the current implementation of xdr_reserve_space().
This commit effectively reverts the optimization.
--- fs/nfsd/nfs4xdr.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-)
fs/nfsd/nfs4xdr.c +6 -6
@@ -3506,8 +3506,8 @@ nfsd4_encode_fattr4(struct svc_rqst *rqstp, struct xdr_stream *xdr,struct nfsd4_fattr_args args;struct svc_fh *tempfh =NULL ;int starting_len = xdr->buf->len; -__be32 *attrlen_p, status; -int attrlen_offset; +unsigned int attrlen_offset; +__be32 attrlen, status;u32 attrmask[3 ];int err;struct nfsd4_compoundres *resp = rqstp->rq_resp;
@@ -3627,9 +3627,8 @@ nfsd4_encode_fattr4(struct svc_rqst *rqstp, struct xdr_stream *xdr, goto out;/* attr_vals */ - attrlen_offset = xdr->buf->len; - attrlen_p =xdr_reserve_space (xdr,XDR_UNIT ); -if (!attrlen_p) + attrlen_offset =xdr_stream_pos (xdr); +if (unlikely (!xdr_reserve_space (xdr,XDR_UNIT ))) goto out_resource;bitmap_from_arr32 (attr_bitmap, attrmask,ARRAY_SIZE (nfsd4_enc_fattr4_encode_ops));
@@ -3639,7 +3638,8 @@ nfsd4_encode_fattr4(struct svc_rqst *rqstp, struct xdr_stream *xdr,if (status != nfs_ok) goto out; } - *attrlen_p =cpu_to_be32 (xdr->buf->len - attrlen_offset -XDR_UNIT ); + attrlen =cpu_to_be32 (xdr_stream_pos (xdr) - attrlen_offset -XDR_UNIT ); +write_bytes_to_xdr_buf (xdr->buf, attrlen_offset, &attrlen,XDR_UNIT ); status = nfs_ok; out:
Signature · 1 line
2.47.0
[email protected]
maintainerPATCH v4 6/9 NFSD: Insulate nfsd4_encode_readlink() from page boundaries in the encode buffer
In reply to [email protected]From: Chuck Lever <[email protected]>
There's no guarantee that the pointer returned from xdr_reserve_space() will still point to the correct reserved space in the encode buffer after one or more intervening calls to xdr_reserve_space(). It just happens to work with the current implementation of xdr_reserve_space().
--- fs/nfsd/nfs4xdr.c | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-)
fs/nfsd/nfs4xdr.c +10 -12
@@ -4449,25 +4449,21 @@ nfsd4_encode_readlink(struct nfsd4_compoundres *resp, __be32 nfserr, union nfsd4_op_u *u) {struct nfsd4_readlink *readlink = &u->readlink; -__be32 *p, *maxcount_p, zero = xdr_zero; +__be32 *p, wire_count, zero = xdr_zero;struct xdr_stream *xdr = resp->xdr; -int length_offset = xdr->buf->len; +unsigned int length_offset;int maxcount, status; - maxcount_p =xdr_reserve_space (xdr,XDR_UNIT ); -if (!maxcount_p) +/* linktext4.count */ + length_offset =xdr_stream_pos (xdr); +if (unlikely (!xdr_reserve_space (xdr,XDR_UNIT )))return nfserr_resource; - maxcount =PAGE_SIZE ; +/* linktext4.data */ + maxcount =PAGE_SIZE ; p =xdr_reserve_space (xdr, maxcount);if (!p)return nfserr_resource; -/* -* XXX: By default, vfs_readlink() will truncate symlinks if they -* would overflow the buffer. Is this kosher in NFSv4? If not, one -* easy fix is: if vfs_readlink() precisely fills the buffer, assume -* that truncation occurred, and return NFS4ERR_RESOURCE. -*/ nfserr =nfsd_readlink (readlink->rl_rqstp, readlink->rl_fhp, (char *)p, &maxcount);if (nfserr == nfserr_isdir)
@@ -4480,7 +4476,9 @@ nfsd4_encode_readlink(struct nfsd4_compoundres *resp, __be32 nfserr, nfserr =nfserrno (status); goto out_err; } - *maxcount_p =cpu_to_be32 (maxcount); + + wire_count =cpu_to_be32 (maxcount); +write_bytes_to_xdr_buf (xdr->buf, length_offset, &wire_count,XDR_UNIT );xdr_truncate_encode (xdr, length_offset +4 +xdr_align_size (maxcount));write_bytes_to_xdr_buf (xdr->buf, length_offset +4 + maxcount, &zero,xdr_pad_size (maxcount));
Signature · 1 line
2.47.0
[email protected]
maintainerPATCH v4 7/9 NFSD: Refactor nfsd4_do_encode_secinfo() again
In reply to [email protected]From: Chuck Lever <[email protected]>
Extract the code that encodes the secinfo4 union data type to clarify the logic. The removed warning is pretty well obscured and thus probably not terribly useful.
--- fs/nfsd/nfs4xdr.c | 65 +++++++++++++++++++++++++---------------------- 1 file changed, 34 insertions(+), 31 deletions(-)
fs/nfsd/nfs4xdr.c +34 -31
@@ -4612,13 +4612,41 @@ nfsd4_encode_rpcsec_gss_info(struct xdr_stream *xdr,return nfs_ok; } +static __be32 +nfsd4_encode_secinfo4 (struct xdr_stream *xdr,rpc_authflavor_t pf, +u32 *supported) +{ +struct rpcsec_gss_info info; +__be32 status; + +if (rpcauth_get_gssinfo (pf, &info) ==0 ) { + (*supported)++; + +/* flavor */ + status =nfsd4_encode_uint32_t (xdr,RPC_AUTH_GSS ); +if (status != nfs_ok) +return status; +/* flavor_info */ + status =nfsd4_encode_rpcsec_gss_info (xdr, &info); +if (status != nfs_ok) +return status; + }else if (pf <RPC_AUTH_MAXFLAVOR ) { + (*supported)++; + +/* flavor */ + status =nfsd4_encode_uint32_t (xdr, pf); +if (status != nfs_ok) +return status; + } +return nfs_ok; +} +static __be32 nfsd4_do_encode_secinfo (struct xdr_stream *xdr,struct svc_export *exp) {u32 i, nflavs, supported;struct exp_flavor_info *flavs;struct exp_flavor_info def_flavs[2 ]; -static bool report = true;__be32 *flavorsp;__be32 status;
@@ -4642,42 +4670,17 @@ nfsd4_do_encode_secinfo(struct xdr_stream *xdr, struct svc_export *exp) } } - supported =0 ; flavorsp =xdr_reserve_space (xdr,XDR_UNIT );if (!flavorsp)return nfserr_resource; -for (i =0 ; i < nflavs; i++) { -rpc_authflavor_t pf = flavs[i].pseudoflavor; -struct rpcsec_gss_info info; - -if (rpcauth_get_gssinfo (pf, &info) ==0 ) { - supported++; - -/* flavor */ - status =nfsd4_encode_uint32_t (xdr,RPC_AUTH_GSS ); -if (status != nfs_ok) -return status; -/* flavor_info */ - status =nfsd4_encode_rpcsec_gss_info (xdr, &info); -if (status != nfs_ok) -return status; - }else if (pf <RPC_AUTH_MAXFLAVOR ) { - supported++; - -/* flavor */ - status =nfsd4_encode_uint32_t (xdr, pf); -if (status != nfs_ok) -return status; - }else { -if (report) -pr_warn ("NFS: SECINFO: security flavor %u " -"is not supported\n" , pf); - } +for (i =0 , supported =0 ; i < nflavs; i++) { + status =nfsd4_encode_secinfo4 (xdr, flavs[i].pseudoflavor, + &supported); +if (status != nfs_ok) +return status; } -if (nflavs != supported) - report = false; *flavorsp =cpu_to_be32 (supported);return 0 ; }
Signature · 1 line
2.47.0
[email protected]
maintainerPATCH v4 8/9 NFSD: Insulate nfsd4_encode_secinfo() from page boundaries in the encode buffer
In reply to [email protected]From: Chuck Lever <[email protected]>
There's no guarantee that the pointer returned from xdr_reserve_space() will still point to the correct reserved space in the encode buffer after one or more intervening calls to xdr_reserve_space(). It just happens to work with the current implementation of xdr_reserve_space().
--- fs/nfsd/nfs4xdr.c | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-)
fs/nfsd/nfs4xdr.c +10 -8
@@ -4642,13 +4642,13 @@ nfsd4_encode_secinfo4(struct xdr_stream *xdr, rpc_authflavor_t pf, }static __be32 -nfsd4_do_encode_secinfo (struct xdr_stream *xdr,struct svc_export *exp) +nfsd4_encode_SECINFO4resok (struct xdr_stream *xdr,struct svc_export *exp) {u32 i, nflavs, supported;struct exp_flavor_info *flavs;struct exp_flavor_info def_flavs[2 ]; -__be32 *flavorsp; -__be32 status; +unsigned int count_offset; +__be32 status, wire_count;if (exp->ex_nflavors) { flavs = exp->ex_flavors;
@@ -4670,8 +4670,8 @@ nfsd4_do_encode_secinfo(struct xdr_stream *xdr, struct svc_export *exp) } } - flavorsp =xdr_reserve_space (xdr,XDR_UNIT ); -if (!flavorsp) + count_offset =xdr_stream_pos (xdr); +if (unlikely (!xdr_reserve_space (xdr,XDR_UNIT )))return nfserr_resource;for (i =0 , supported =0 ; i < nflavs; i++) {
@@ -4681,7 +4681,9 @@ nfsd4_do_encode_secinfo(struct xdr_stream *xdr, struct svc_export *exp)return status; } - *flavorsp =cpu_to_be32 (supported); + wire_count =cpu_to_be32 (supported); +write_bytes_to_xdr_buf (xdr->buf, count_offset, &wire_count, +XDR_UNIT );return 0 ; }
@@ -4692,7 +4694,7 @@ nfsd4_encode_secinfo(struct nfsd4_compoundres *resp, __be32 nfserr,struct nfsd4_secinfo *secinfo = &u->secinfo;struct xdr_stream *xdr = resp->xdr; -return nfsd4_do_encode_secinfo (xdr, secinfo->si_exp); +return nfsd4_encode_SECINFO4resok (xdr, secinfo->si_exp); } static __be32
@@ -4702,7 +4704,7 @@ nfsd4_encode_secinfo_no_name(struct nfsd4_compoundres *resp, __be32 nfserr,struct nfsd4_secinfo_no_name *secinfo = &u->secinfo_no_name;struct xdr_stream *xdr = resp->xdr; -return nfsd4_do_encode_secinfo (xdr, secinfo->sin_exp); +return nfsd4_encode_SECINFO4resok (xdr, secinfo->sin_exp); } static __be32
Signature · 1 line
2.47.0
[email protected]
maintainerPATCH v4 9/9 SUNRPC: Document validity guarantees of the pointer returned by reserve_space
In reply to [email protected]From: Chuck Lever <[email protected]>
A subtlety of this API is that if the @nbytes region traverses a page boundary, the next __xdr_commit_encode will shift the data item in the XDR encode buffer. This makes the returned pointer point to something else, leading to unexpected behavior.
There are a few cases where the caller saves the returned pointer and then later uses it to insert a computed value into an earlier part of the stream. This can be safe only if either:
- the data item is guaranteed to be in the XDR buffer's head, and thus is not ever going to be near a page boundary, or
- the data item is no larger than 4 octets, since XDR alignment rules require all data items to start on 4-octet boundaries
But that safety is only an artifact of the current implementation. It would be less brittle if these "safe" uses were eventually replaced.
--- net/sunrpc/xdr.c | 6 ++++++ 1 file changed, 6 insertions(+)
net/sunrpc/xdr.c +6 -0
@@ -1097,6 +1097,12 @@ static noinline __be32 *xdr_get_next_encode_buffer(struct xdr_stream *xdr,* Checks that we have enough buffer space to encode 'nbytes' more * bytes of data. If so, update the total xdr_buf length, and * adjust the length of the current kvec. +* +* The returned pointer is valid only until the next call to +* xdr_reserve_space() or xdr_commit_encode() on @xdr. The current +* implementation of this API guarantees that space reserved for a +* four-byte data item remains valid until @xdr is destroyed, but +* that might not always be true in the future. */ __be32 *xdr_reserve_space (struct xdr_stream *xdr,size_t nbytes) {
Signature · 1 line
2.47.0
Re: [PATCH v4 9/9] SUNRPC: Document validity guarantees of the pointer returned by reserve_space
Quoted 33 lines · On Tue, 31 Dec 2024, [email protected] wrote:
From: Chuck Lever <[email protected]>
A subtlety of this API is that if the @nbytes region traverses a page boundary, the next
__xdr_commit_encodewill shift the data item in the XDR encode buffer. This makes the returned pointer point to something else, leading to unexpected behavior.There are a few cases where the caller saves the returned pointer and then later uses it to insert a computed value into an earlier part of the stream. This can be safe only if either:
- the data item is guaranteed to be in the XDR buffer's head, and thus is not ever going to be near a page boundary, or
- the data item is no larger than 4 octets, since XDR alignment rules require all data items to start on 4-octet boundariesBut that safety is only an artifact of the current implementation. It would be less brittle if these "safe" uses were eventually replaced.
Signed-off-by Chuck Lever <[email protected]>--- net/sunrpc/xdr.c | 6 ++++++ 1 file changed, 6 insertions(+)net/sunrpc/xdr.c +0
-0@@ -1097,6 +1097,12 @@ static noinline __be32 *xdr_get_next_encode_buffer(struct xdr_stream *xdr,* Checks that we have enough buffer space to encode 'nbytes' more * bytes of data. If so, update the total xdr_buf length, and
net/sunrpc/xdr.c static noinline __be32 *xdr_get_next_encode_buffer(struct xdr_stream *xdr, · line 1108
Quoted 10 lines
net/sunrpc/xdr.c +6
-0* adjust the length of the current kvec. +* +* The returned pointer is valid only until the next call to +* xdr_reserve_space() or xdr_commit_encode() on @xdr. The current +* implementation of this API guarantees that space reserved for a +* four-byte data item remains valid until @xdr is destroyed, but +* that might not always be true in the future. */ __be32 *xdr_reserve_space (struct xdr_stream *xdr,size_t nbytes) {
This series all looks good to me
though I do wonder if it would be better make the "four-byte" behaviour a guaranteed part of the API rather than working around a problem that doesn't currently exist and quite possibly never will.
Thanks,
NeilBrown
Re: [PATCH v4 9/9] SUNRPC: Document validity guarantees of the pointer returned by reserve_space
Quoted 46 lines · NeilBrown
Quoted 33 lines · On Tue, 31 Dec 2024, [email protected] wrote:
From: Chuck Lever <[email protected]>
A subtlety of this API is that if the @nbytes region traverses a page boundary, the next
__xdr_commit_encodewill shift the data item in the XDR encode buffer. This makes the returned pointer point to something else, leading to unexpected behavior.There are a few cases where the caller saves the returned pointer and then later uses it to insert a computed value into an earlier part of the stream. This can be safe only if either:
- the data item is guaranteed to be in the XDR buffer's head, and thus is not ever going to be near a page boundary, or
- the data item is no larger than 4 octets, since XDR alignment rules require all data items to start on 4-octet boundariesBut that safety is only an artifact of the current implementation. It would be less brittle if these "safe" uses were eventually replaced.
Signed-off-by Chuck Lever <[email protected]>--- net/sunrpc/xdr.c | 6 ++++++ 1 file changed, 6 insertions(+)net/sunrpc/xdr.c +0
-0@@ -1097,6 +1097,12 @@ static noinline __be32 *xdr_get_next_encode_buffer(struct xdr_stream *xdr,* Checks that we have enough buffer space to encode 'nbytes' more * bytes of data. If so, update the total xdr_buf length, and net/sunrpc/xdr.c static noinline __be32 *xdr_get_next_encode_buffer(struct xdr_stream *xdr, · line 1108
Quoted 10 lines
net/sunrpc/xdr.c +6
-0* adjust the length of the current kvec. +* +* The returned pointer is valid only until the next call to +* xdr_reserve_space() or xdr_commit_encode() on @xdr. The current +* implementation of this API guarantees that space reserved for a +* four-byte data item remains valid until @xdr is destroyed, but +* that might not always be true in the future. */ __be32 *xdr_reserve_space (struct xdr_stream *xdr,size_t nbytes) {This series all looks good to me
Quoted 1 line
Reviewed-by NeilBrown <[email protected]>
Thanks!
Quoted 3 lines
though I do wonder if it would be better make the "four-byte" behaviour a guaranteed part of the API rather than working around a problem that doesn't currently exist and quite possibly never will.
It might be better, but I would like to fix the known problem and document this expectation for the moment. I'm not closing the book on this by any means.
Signature · 1 line
Chuck Lever
Re: [PATCH v4 9/9] SUNRPC: Document validity guarantees of the pointer returned by reserve_space
In reply to [email protected]Quoted 32 lines · On Mon, 2024-12-30 at 19:29 -0500, [email protected] wrote:
From: Chuck Lever <[email protected]>
A subtlety of this API is that if the @nbytes region traverses a page boundary, the next
__xdr_commit_encodewill shift the data item in the XDR encode buffer. This makes the returned pointer point to something else, leading to unexpected behavior.There are a few cases where the caller saves the returned pointer and then later uses it to insert a computed value into an earlier part of the stream. This can be safe only if either:
- the data item is guaranteed to be in the XDR buffer's head, and thus is not ever going to be near a page boundary, or
- the data item is no larger than 4 octets, since XDR alignment rules require all data items to start on 4-octet boundariesBut that safety is only an artifact of the current implementation. It would be less brittle if these "safe" uses were eventually replaced.
Signed-off-by Chuck Lever <[email protected]>--- net/sunrpc/xdr.c | 6 ++++++ 1 file changed, 6 insertions(+)net/sunrpc/xdr.c +0
-0@@ -1097,6 +1097,12 @@ static noinline __be32 *xdr_get_next_encode_buffer(struct xdr_stream *xdr,* Checks that we have enough buffer space to encode 'nbytes' more * bytes of data. If so, update the total xdr_buf length, and
net/sunrpc/xdr.c static noinline __be32 *xdr_get_next_encode_buffer(struct xdr_stream *xdr, · line 1108
Quoted 10 lines
net/sunrpc/xdr.c +6
-0* adjust the length of the current kvec. +* +* The returned pointer is valid only until the next call to +* xdr_reserve_space() or xdr_commit_encode() on @xdr. The current +* implementation of this API guarantees that space reserved for a +* four-byte data item remains valid until @xdr is destroyed, but +* that might not always be true in the future. */ __be32 *xdr_reserve_space (struct xdr_stream *xdr,size_t nbytes) {
I agree with Neil that this API could do with less footguns, but this does seem to be an improvement.
Quoted 27 lines · On Mon, 2024-12-30 at 19:28 -0500, [email protected] wrote:
From: Chuck Lever <[email protected]>
Refresh the patch series to address the longstanding bug pointed out by J David and Rick Macklem.
Changes since v2:
- Address same issue in NFSv4 READLINK and SECINFO operations
- Update kdoc comment forxdr_reserve_space()Chuck Lever (9):
NFSD: Encode COMPOUND operation status on page boundaries
NFSD: Insulatenfsd4_encode_read()from page boundaries in the encode
buffer
NFSD: Insulatenfsd4_encode_read_plus()from page boundaries in the
encode buffer
NFSD: Insulatenfsd4_encode_read_plus_data()from page boundaries in
the encode buffer
NFSD: Insulatenfsd4_encode_fattr4()from page boundaries in the
encode buffer
NFSD: Insulatenfsd4_encode_readlink()from page boundaries in the
encode buffer
NFSD: Refactornfsd4_do_encode_secinfo()again
NFSD: Insulatenfsd4_encode_secinfo()from page boundaries in the
encode buffer
SUNRPC: Document validity guarantees of the pointer returned by
reserve_space
Quoted 4 lines
fs/nfsd/nfs4xdr.c | 214 +++++++++++++++++++++++++--------------------- net/sunrpc/xdr.c | 6 ++ 2 files changed, 122 insertions(+), 98 deletions(-)