2024-04-11 09:46:57

by Vasily Gorbik

[permalink] [raw]
Subject: [PATCH] NFSD: fix endianness issue in nfsd4_encode_fattr4

The nfs4 mount fails with EIO on 64-bit big endian architectures since
v6.7. The issue arises from employing a union in the nfsd4_encode_fattr4()
function to overlay a 32-bit array with a 64-bit values based bitmap,
which does not function as intended. Address the endianness issue by
utilizing bitmap_from_arr32() to copy 32-bit attribute masks into a
bitmap in an endianness-agnostic manner.

Cc: <[email protected]>
Fixes: fce7913b13d0 ("NFSD: Use a bitmask loop to encode FATTR4 results")
Link: https://bugs.launchpad.net/ubuntu/+source/nfs-utils/+bug/2060217
Signed-off-by: Vasily Gorbik <[email protected]>
---
fs/nfsd/nfs4xdr.c | 47 +++++++++++++++++++++++------------------------
1 file changed, 23 insertions(+), 24 deletions(-)

diff --git a/fs/nfsd/nfs4xdr.c b/fs/nfsd/nfs4xdr.c
index 10439d569d9c..85d43b3249f9 100644
--- a/fs/nfsd/nfs4xdr.c
+++ b/fs/nfsd/nfs4xdr.c
@@ -3519,11 +3519,13 @@ nfsd4_encode_fattr4(struct svc_rqst *rqstp, struct xdr_stream *xdr,
struct dentry *dentry, const u32 *bmval,
int ignore_crossmnt)
{
+ DECLARE_BITMAP(attr_bitmap, ARRAY_SIZE(nfsd4_enc_fattr4_encode_ops));
struct nfsd4_fattr_args args;
struct svc_fh *tempfh = NULL;
int starting_len = xdr->buf->len;
__be32 *attrlen_p, status;
int attrlen_offset;
+ u32 attrmask[3];
int err;
struct nfsd4_compoundres *resp = rqstp->rq_resp;
u32 minorversion = resp->cstate.minorversion;
@@ -3531,10 +3533,6 @@ nfsd4_encode_fattr4(struct svc_rqst *rqstp, struct xdr_stream *xdr,
.mnt = exp->ex_path.mnt,
.dentry = dentry,
};
- union {
- u32 attrmask[3];
- unsigned long mask[2];
- } u;
unsigned long bit;
bool file_modified = false;
u64 size = 0;
@@ -3550,20 +3548,19 @@ nfsd4_encode_fattr4(struct svc_rqst *rqstp, struct xdr_stream *xdr,
/*
* Make a local copy of the attribute bitmap that can be modified.
*/
- memset(&u, 0, sizeof(u));
- u.attrmask[0] = bmval[0];
- u.attrmask[1] = bmval[1];
- u.attrmask[2] = bmval[2];
+ attrmask[0] = bmval[0];
+ attrmask[1] = bmval[1];
+ attrmask[2] = bmval[2];

args.rdattr_err = 0;
if (exp->ex_fslocs.migrated) {
- status = fattr_handle_absent_fs(&u.attrmask[0], &u.attrmask[1],
- &u.attrmask[2], &args.rdattr_err);
+ status = fattr_handle_absent_fs(&attrmask[0], &attrmask[1],
+ &attrmask[2], &args.rdattr_err);
if (status)
goto out;
}
args.size = 0;
- if (u.attrmask[0] & (FATTR4_WORD0_CHANGE | FATTR4_WORD0_SIZE)) {
+ if (attrmask[0] & (FATTR4_WORD0_CHANGE | FATTR4_WORD0_SIZE)) {
status = nfsd4_deleg_getattr_conflict(rqstp, d_inode(dentry),
&file_modified, &size);
if (status)
@@ -3582,16 +3579,16 @@ nfsd4_encode_fattr4(struct svc_rqst *rqstp, struct xdr_stream *xdr,

if (!(args.stat.result_mask & STATX_BTIME))
/* underlying FS does not offer btime so we can't share it */
- u.attrmask[1] &= ~FATTR4_WORD1_TIME_CREATE;
- if ((u.attrmask[0] & (FATTR4_WORD0_FILES_AVAIL | FATTR4_WORD0_FILES_FREE |
+ attrmask[1] &= ~FATTR4_WORD1_TIME_CREATE;
+ if ((attrmask[0] & (FATTR4_WORD0_FILES_AVAIL | FATTR4_WORD0_FILES_FREE |
FATTR4_WORD0_FILES_TOTAL | FATTR4_WORD0_MAXNAME)) ||
- (u.attrmask[1] & (FATTR4_WORD1_SPACE_AVAIL | FATTR4_WORD1_SPACE_FREE |
+ (attrmask[1] & (FATTR4_WORD1_SPACE_AVAIL | FATTR4_WORD1_SPACE_FREE |
FATTR4_WORD1_SPACE_TOTAL))) {
err = vfs_statfs(&path, &args.statfs);
if (err)
goto out_nfserr;
}
- if ((u.attrmask[0] & (FATTR4_WORD0_FILEHANDLE | FATTR4_WORD0_FSID)) &&
+ if ((attrmask[0] & (FATTR4_WORD0_FILEHANDLE | FATTR4_WORD0_FSID)) &&
!fhp) {
tempfh = kmalloc(sizeof(struct svc_fh), GFP_KERNEL);
status = nfserr_jukebox;
@@ -3606,10 +3603,10 @@ nfsd4_encode_fattr4(struct svc_rqst *rqstp, struct xdr_stream *xdr,
args.fhp = fhp;

args.acl = NULL;
- if (u.attrmask[0] & FATTR4_WORD0_ACL) {
+ if (attrmask[0] & FATTR4_WORD0_ACL) {
err = nfsd4_get_nfs4_acl(rqstp, dentry, &args.acl);
if (err == -EOPNOTSUPP)
- u.attrmask[0] &= ~FATTR4_WORD0_ACL;
+ attrmask[0] &= ~FATTR4_WORD0_ACL;
else if (err == -EINVAL) {
status = nfserr_attrnotsupp;
goto out;
@@ -3621,17 +3618,17 @@ nfsd4_encode_fattr4(struct svc_rqst *rqstp, struct xdr_stream *xdr,

#ifdef CONFIG_NFSD_V4_SECURITY_LABEL
args.context = NULL;
- if ((u.attrmask[2] & FATTR4_WORD2_SECURITY_LABEL) ||
- u.attrmask[0] & FATTR4_WORD0_SUPPORTED_ATTRS) {
+ if ((attrmask[2] & FATTR4_WORD2_SECURITY_LABEL) ||
+ attrmask[0] & FATTR4_WORD0_SUPPORTED_ATTRS) {
if (exp->ex_flags & NFSEXP_SECURITY_LABEL)
err = security_inode_getsecctx(d_inode(dentry),
&args.context, &args.contextlen);
else
err = -EOPNOTSUPP;
args.contextsupport = (err == 0);
- if (u.attrmask[2] & FATTR4_WORD2_SECURITY_LABEL) {
+ if (attrmask[2] & FATTR4_WORD2_SECURITY_LABEL) {
if (err == -EOPNOTSUPP)
- u.attrmask[2] &= ~FATTR4_WORD2_SECURITY_LABEL;
+ attrmask[2] &= ~FATTR4_WORD2_SECURITY_LABEL;
else if (err)
goto out_nfserr;
}
@@ -3639,8 +3636,8 @@ nfsd4_encode_fattr4(struct svc_rqst *rqstp, struct xdr_stream *xdr,
#endif /* CONFIG_NFSD_V4_SECURITY_LABEL */

/* attrmask */
- status = nfsd4_encode_bitmap4(xdr, u.attrmask[0],
- u.attrmask[1], u.attrmask[2]);
+ status = nfsd4_encode_bitmap4(xdr, attrmask[0], attrmask[1],
+ attrmask[2]);
if (status)
goto out;

@@ -3649,7 +3646,9 @@ nfsd4_encode_fattr4(struct svc_rqst *rqstp, struct xdr_stream *xdr,
attrlen_p = xdr_reserve_space(xdr, XDR_UNIT);
if (!attrlen_p)
goto out_resource;
- for_each_set_bit(bit, (const unsigned long *)&u.mask,
+ bitmap_from_arr32(attr_bitmap, attrmask,
+ ARRAY_SIZE(nfsd4_enc_fattr4_encode_ops));
+ for_each_set_bit(bit, attr_bitmap,
ARRAY_SIZE(nfsd4_enc_fattr4_encode_ops)) {
status = nfsd4_enc_fattr4_encode_ops[bit](xdr, &args);
if (status != nfs_ok)
--
2.41.0


2024-04-11 10:20:39

by Jeffrey Layton

[permalink] [raw]
Subject: Re: [PATCH] NFSD: fix endianness issue in nfsd4_encode_fattr4

On Thu, 2024-04-11 at 11:45 +0200, Vasily Gorbik wrote:
> The nfs4 mount fails with EIO on 64-bit big endian architectures since
> v6.7. The issue arises from employing a union in the nfsd4_encode_fattr4()
> function to overlay a 32-bit array with a 64-bit values based bitmap,
> which does not function as intended. Address the endianness issue by
> utilizing bitmap_from_arr32() to copy 32-bit attribute masks into a
> bitmap in an endianness-agnostic manner.
>
> Cc: <[email protected]>
> Fixes: fce7913b13d0 ("NFSD: Use a bitmask loop to encode FATTR4 results")
> Link: https://bugs.launchpad.net/ubuntu/+source/nfs-utils/+bug/2060217
> Signed-off-by: Vasily Gorbik <[email protected]>
> ---
> fs/nfsd/nfs4xdr.c | 47 +++++++++++++++++++++++------------------------
> 1 file changed, 23 insertions(+), 24 deletions(-)
>
> diff --git a/fs/nfsd/nfs4xdr.c b/fs/nfsd/nfs4xdr.c
> index 10439d569d9c..85d43b3249f9 100644
> --- a/fs/nfsd/nfs4xdr.c
> +++ b/fs/nfsd/nfs4xdr.c
> @@ -3519,11 +3519,13 @@ nfsd4_encode_fattr4(struct svc_rqst *rqstp, struct xdr_stream *xdr,
> struct dentry *dentry, const u32 *bmval,
> int ignore_crossmnt)
> {
> + DECLARE_BITMAP(attr_bitmap, ARRAY_SIZE(nfsd4_enc_fattr4_encode_ops));
> struct nfsd4_fattr_args args;
> struct svc_fh *tempfh = NULL;
> int starting_len = xdr->buf->len;
> __be32 *attrlen_p, status;
> int attrlen_offset;
> + u32 attrmask[3];
> int err;
> struct nfsd4_compoundres *resp = rqstp->rq_resp;
> u32 minorversion = resp->cstate.minorversion;
> @@ -3531,10 +3533,6 @@ nfsd4_encode_fattr4(struct svc_rqst *rqstp, struct xdr_stream *xdr,
> .mnt = exp->ex_path.mnt,
> .dentry = dentry,
> };
> - union {
> - u32 attrmask[3];
> - unsigned long mask[2];
> - } u;
> unsigned long bit;
> bool file_modified = false;
> u64 size = 0;
> @@ -3550,20 +3548,19 @@ nfsd4_encode_fattr4(struct svc_rqst *rqstp, struct xdr_stream *xdr,
> /*
> * Make a local copy of the attribute bitmap that can be modified.
> */
> - memset(&u, 0, sizeof(u));
> - u.attrmask[0] = bmval[0];
> - u.attrmask[1] = bmval[1];
> - u.attrmask[2] = bmval[2];
> + attrmask[0] = bmval[0];
> + attrmask[1] = bmval[1];
> + attrmask[2] = bmval[2];
>
> args.rdattr_err = 0;
> if (exp->ex_fslocs.migrated) {
> - status = fattr_handle_absent_fs(&u.attrmask[0], &u.attrmask[1],
> - &u.attrmask[2], &args.rdattr_err);
> + status = fattr_handle_absent_fs(&attrmask[0], &attrmask[1],
> + &attrmask[2], &args.rdattr_err);
> if (status)
> goto out;
> }
> args.size = 0;
> - if (u.attrmask[0] & (FATTR4_WORD0_CHANGE | FATTR4_WORD0_SIZE)) {
> + if (attrmask[0] & (FATTR4_WORD0_CHANGE | FATTR4_WORD0_SIZE)) {
> status = nfsd4_deleg_getattr_conflict(rqstp, d_inode(dentry),
> &file_modified, &size);
> if (status)
> @@ -3582,16 +3579,16 @@ nfsd4_encode_fattr4(struct svc_rqst *rqstp, struct xdr_stream *xdr,
>
> if (!(args.stat.result_mask & STATX_BTIME))
> /* underlying FS does not offer btime so we can't share it */
> - u.attrmask[1] &= ~FATTR4_WORD1_TIME_CREATE;
> - if ((u.attrmask[0] & (FATTR4_WORD0_FILES_AVAIL | FATTR4_WORD0_FILES_FREE |
> + attrmask[1] &= ~FATTR4_WORD1_TIME_CREATE;
> + if ((attrmask[0] & (FATTR4_WORD0_FILES_AVAIL | FATTR4_WORD0_FILES_FREE |
> FATTR4_WORD0_FILES_TOTAL | FATTR4_WORD0_MAXNAME)) ||
> - (u.attrmask[1] & (FATTR4_WORD1_SPACE_AVAIL | FATTR4_WORD1_SPACE_FREE |
> + (attrmask[1] & (FATTR4_WORD1_SPACE_AVAIL | FATTR4_WORD1_SPACE_FREE |
> FATTR4_WORD1_SPACE_TOTAL))) {
> err = vfs_statfs(&path, &args.statfs);
> if (err)
> goto out_nfserr;
> }
> - if ((u.attrmask[0] & (FATTR4_WORD0_FILEHANDLE | FATTR4_WORD0_FSID)) &&
> + if ((attrmask[0] & (FATTR4_WORD0_FILEHANDLE | FATTR4_WORD0_FSID)) &&
> !fhp) {
> tempfh = kmalloc(sizeof(struct svc_fh), GFP_KERNEL);
> status = nfserr_jukebox;
> @@ -3606,10 +3603,10 @@ nfsd4_encode_fattr4(struct svc_rqst *rqstp, struct xdr_stream *xdr,
> args.fhp = fhp;
>
> args.acl = NULL;
> - if (u.attrmask[0] & FATTR4_WORD0_ACL) {
> + if (attrmask[0] & FATTR4_WORD0_ACL) {
> err = nfsd4_get_nfs4_acl(rqstp, dentry, &args.acl);
> if (err == -EOPNOTSUPP)
> - u.attrmask[0] &= ~FATTR4_WORD0_ACL;
> + attrmask[0] &= ~FATTR4_WORD0_ACL;
> else if (err == -EINVAL) {
> status = nfserr_attrnotsupp;
> goto out;
> @@ -3621,17 +3618,17 @@ nfsd4_encode_fattr4(struct svc_rqst *rqstp, struct xdr_stream *xdr,
>
> #ifdef CONFIG_NFSD_V4_SECURITY_LABEL
> args.context = NULL;
> - if ((u.attrmask[2] & FATTR4_WORD2_SECURITY_LABEL) ||
> - u.attrmask[0] & FATTR4_WORD0_SUPPORTED_ATTRS) {
> + if ((attrmask[2] & FATTR4_WORD2_SECURITY_LABEL) ||
> + attrmask[0] & FATTR4_WORD0_SUPPORTED_ATTRS) {
> if (exp->ex_flags & NFSEXP_SECURITY_LABEL)
> err = security_inode_getsecctx(d_inode(dentry),
> &args.context, &args.contextlen);
> else
> err = -EOPNOTSUPP;
> args.contextsupport = (err == 0);
> - if (u.attrmask[2] & FATTR4_WORD2_SECURITY_LABEL) {
> + if (attrmask[2] & FATTR4_WORD2_SECURITY_LABEL) {
> if (err == -EOPNOTSUPP)
> - u.attrmask[2] &= ~FATTR4_WORD2_SECURITY_LABEL;
> + attrmask[2] &= ~FATTR4_WORD2_SECURITY_LABEL;
> else if (err)
> goto out_nfserr;
> }
> @@ -3639,8 +3636,8 @@ nfsd4_encode_fattr4(struct svc_rqst *rqstp, struct xdr_stream *xdr,
> #endif /* CONFIG_NFSD_V4_SECURITY_LABEL */
>
> /* attrmask */
> - status = nfsd4_encode_bitmap4(xdr, u.attrmask[0],
> - u.attrmask[1], u.attrmask[2]);
> + status = nfsd4_encode_bitmap4(xdr, attrmask[0], attrmask[1],
> + attrmask[2]);
> if (status)
> goto out;
>
> @@ -3649,7 +3646,9 @@ nfsd4_encode_fattr4(struct svc_rqst *rqstp, struct xdr_stream *xdr,
> attrlen_p = xdr_reserve_space(xdr, XDR_UNIT);
> if (!attrlen_p)
> goto out_resource;
> - for_each_set_bit(bit, (const unsigned long *)&u.mask,
> + bitmap_from_arr32(attr_bitmap, attrmask,
> + ARRAY_SIZE(nfsd4_enc_fattr4_encode_ops));
> + for_each_set_bit(bit, attr_bitmap,
> ARRAY_SIZE(nfsd4_enc_fattr4_encode_ops)) {
> status = nfsd4_enc_fattr4_encode_ops[bit](xdr, &args);
> if (status != nfs_ok)

I learned something new today -- I wasn't aware of lib/bitmap.c! This
looks like a nice cleanup too. The union was sort of nasty.

Reviewed-by: Jeff Layton <[email protected]>