Message ID | 20200311195954.27117-12-fllinden@amazon.com (mailing list archive) |
---|---|
State | New, archived |
Headers | show |
Series | server side user xattr support (RFC 8276) | expand |
Hi Frank, Thank you for the patch! Perhaps something to improve: [auto build test WARNING on nfsd/nfsd-next] [also build test WARNING on nfs/linux-next linus/master v5.6-rc5 next-20200312] [cannot apply to cel/for-next] [if your patch is applied to the wrong git tree, please drop us a note to help improve the system. BTW, we also suggest to use '--base' option to specify the base tree in git format-patch, please see https://stackoverflow.com/a/37406982] url: https://github.com/0day-ci/linux/commits/Frank-van-der-Linden/server-side-user-xattr-support-RFC-8276/20200312-064928 base: git://linux-nfs.org/~bfields/linux.git nfsd-next reproduce: # apt-get install sparse # sparse version: v0.6.1-174-g094d5a94-dirty make ARCH=x86_64 allmodconfig make C=1 CF='-fdiagnostic-prefix -D__CHECK_ENDIAN__' If you fix the issue, kindly add following tag Reported-by: kbuild test robot <lkp@intel.com> sparse warnings: (new ones prefixed by >>) fs/nfsd/nfs4xdr.c:1860:16: sparse: sparse: incorrect type in assignment (different base types) @@ expected int status @@ got restricted __int status @@ fs/nfsd/nfs4xdr.c:1860:16: sparse: expected int status fs/nfsd/nfs4xdr.c:1860:16: sparse: got restricted __be32 fs/nfsd/nfs4xdr.c:1862:24: sparse: sparse: incorrect type in return expression (different base types) @@ expected restricted __be32 @@ got be32 @@ fs/nfsd/nfs4xdr.c:1862:24: sparse: expected restricted __be32 fs/nfsd/nfs4xdr.c:1862:24: sparse: got int status >> fs/nfsd/nfs4xdr.c:1913:24: sparse: sparse: incorrect type in return expression (different base types) @@ expected int @@ got restricted __be32 [usertypint @@ >> fs/nfsd/nfs4xdr.c:1913:24: sparse: expected int >> fs/nfsd/nfs4xdr.c:1913:24: sparse: got restricted __be32 [usertype] fs/nfsd/nfs4xdr.c:1949:24: sparse: sparse: incorrect type in return expression (different base types) @@ expected int @@ got restricted __be32 [usertypint @@ fs/nfsd/nfs4xdr.c:1949:24: sparse: expected int fs/nfsd/nfs4xdr.c:1949:24: sparse: got restricted __be32 [usertype] fs/nfsd/nfs4xdr.c:1958:24: sparse: sparse: incorrect type in return expression (different base types) @@ expected int @@ got restricted __be32 [usertypint @@ fs/nfsd/nfs4xdr.c:1958:24: sparse: expected int fs/nfsd/nfs4xdr.c:1958:24: sparse: got restricted __be32 [usertype] >> fs/nfsd/nfs4xdr.c:1979:9: sparse: sparse: incorrect type in return expression (different base types) @@ expected int @@ got restricted __be32 [assigned] [usertypint @@ fs/nfsd/nfs4xdr.c:1979:9: sparse: expected int >> fs/nfsd/nfs4xdr.c:1979:9: sparse: got restricted __be32 [assigned] [usertype] status >> fs/nfsd/nfs4xdr.c:1997:24: sparse: sparse: incorrect type in return expression (different base types) @@ expected restricted __be32 @@ got stricted __be32 @@ fs/nfsd/nfs4xdr.c:1997:24: sparse: expected restricted __be32 >> fs/nfsd/nfs4xdr.c:1997:24: sparse: got int [assigned] status >> fs/nfsd/nfs4xdr.c:2004:24: sparse: sparse: incorrect type in assignment (different base types) @@ expected int [assigned] status @@ got restricted __bint [assigned] status @@ >> fs/nfsd/nfs4xdr.c:2004:24: sparse: expected int [assigned] status fs/nfsd/nfs4xdr.c:2004:24: sparse: got restricted __be32 [usertype] fs/nfsd/nfs4xdr.c:2007:16: sparse: sparse: incorrect type in return expression (different base types) @@ expected restricted __be32 @@ got stricted __be32 @@ fs/nfsd/nfs4xdr.c:2007:16: sparse: expected restricted __be32 fs/nfsd/nfs4xdr.c:2007:16: sparse: got int [assigned] status >> fs/nfsd/nfs4xdr.c:2026:16: sparse: sparse: incorrect type in assignment (different base types) @@ expected restricted __be32 [usertype] status @@ got e] status @@ >> fs/nfsd/nfs4xdr.c:2026:16: sparse: expected restricted __be32 [usertype] status >> fs/nfsd/nfs4xdr.c:2026:16: sparse: got int >> fs/nfsd/nfs4xdr.c:2044:24: sparse: sparse: incorrect type in assignment (different base types) @@ expected restricted __be32 [assigned] [usertype] status @@ got e] status @@ >> fs/nfsd/nfs4xdr.c:2044:24: sparse: expected restricted __be32 [assigned] [usertype] status fs/nfsd/nfs4xdr.c:2044:24: sparse: got int >> fs/nfsd/nfs4xdr.c:2097:39: sparse: sparse: incorrect type in return expression (different base types) @@ expected restricted __be32 @@ got e32 @@ fs/nfsd/nfs4xdr.c:2097:39: sparse: expected restricted __be32 fs/nfsd/nfs4xdr.c:2097:39: sparse: got int fs/nfsd/nfs4xdr.c:4698:24: sparse: sparse: incorrect type in return expression (different base types) @@ expected int @@ got restricted __be32 [usertypint @@ fs/nfsd/nfs4xdr.c:4698:24: sparse: expected int fs/nfsd/nfs4xdr.c:4698:24: sparse: got restricted __be32 [usertype] fs/nfsd/nfs4xdr.c:4708:32: sparse: sparse: incorrect type in return expression (different base types) @@ expected int @@ got restricted __be32 [usertypint @@ fs/nfsd/nfs4xdr.c:4708:32: sparse: expected int fs/nfsd/nfs4xdr.c:4708:32: sparse: got restricted __be32 [usertype] fs/nfsd/nfs4xdr.c:4745:36: sparse: sparse: incorrect type in return expression (different base types) @@ expected restricted __be32 @@ got e32 @@ fs/nfsd/nfs4xdr.c:4745:36: sparse: expected restricted __be32 fs/nfsd/nfs4xdr.c:4745:36: sparse: got int fs/nfsd/nfs4xdr.c:4780:24: sparse: sparse: incorrect type in return expression (different base types) @@ expected int @@ got restricted __be32 [usertypint @@ fs/nfsd/nfs4xdr.c:4780:24: sparse: expected int fs/nfsd/nfs4xdr.c:4780:24: sparse: got restricted __be32 [usertype] fs/nfsd/nfs4xdr.c:4784:24: sparse: sparse: incorrect type in return expression (different base types) @@ expected int @@ got restricted __be32 [usertypint @@ fs/nfsd/nfs4xdr.c:4784:24: sparse: expected int fs/nfsd/nfs4xdr.c:4784:24: sparse: got restricted __be32 [usertype] fs/nfsd/nfs4xdr.c:4808:24: sparse: sparse: incorrect type in return expression (different base types) @@ expected restricted __be32 @@ got stricted __be32 @@ fs/nfsd/nfs4xdr.c:4808:24: sparse: expected restricted __be32 fs/nfsd/nfs4xdr.c:4808:24: sparse: got int [assigned] status >> fs/nfsd/nfs4xdr.c:4884:15: sparse: sparse: incorrect type in assignment (different base types) @@ expected unsigned int [assigned] [usertype] count @@ got ed int [assigned] [usertype] count @@ >> fs/nfsd/nfs4xdr.c:4884:15: sparse: expected unsigned int [assigned] [usertype] count fs/nfsd/nfs4xdr.c:4884:15: sparse: got restricted __be32 [usertype] vim +1913 fs/nfsd/nfs4xdr.c 1881 1882 /* 1883 * XDR data that is more than PAGE_SIZE in size is normally part of a 1884 * read or write. However, the size of extended attributes is limited 1885 * by the maximum request size, and then further limited by the underlying 1886 * filesystem limits. This can exceed PAGE_SIZE (currently, XATTR_SIZE_MAX 1887 * is 64k). Since there is no kvec- or page-based interface to xattrs, 1888 * and we're not dealing with contiguous pages, we need to do some copying. 1889 */ 1890 1891 /* 1892 * Decode int to buffer. Uses head and pages constructed by 1893 * svcxdr_construct_vector. 1894 */ 1895 static int 1896 nfsd4_vbuf_from_stream(struct nfsd4_compoundargs *argp, struct kvec *head, 1897 struct page **pages, char **bufp, u32 buflen) 1898 { 1899 char *tmp, *dp; 1900 u32 len; 1901 1902 if (buflen <= head->iov_len) { 1903 /* 1904 * We're in luck, the head has enough space. Just return 1905 * the head, no need for copying. 1906 */ 1907 *bufp = head->iov_base; 1908 return 0; 1909 } 1910 1911 tmp = svcxdr_tmpalloc(argp, buflen); 1912 if (tmp == NULL) > 1913 return nfserr_jukebox; 1914 1915 dp = tmp; 1916 memcpy(dp, head->iov_base, head->iov_len); 1917 buflen -= head->iov_len; 1918 dp += head->iov_len; 1919 1920 while (buflen > 0) { 1921 len = min_t(u32, buflen, PAGE_SIZE); 1922 memcpy(dp, page_address(*pages), len); 1923 1924 buflen -= len; 1925 dp += len; 1926 pages++; 1927 } 1928 1929 *bufp = tmp; 1930 return 0; 1931 } 1932 1933 /* 1934 * Get a user extended attribute name from the XDR buffer. 1935 * It will not have the "user." prefix, so prepend it. 1936 * Lastly, check for nul characters in the name. 1937 */ 1938 static int 1939 nfsd4_decode_xattr_name(struct nfsd4_compoundargs *argp, char **namep) 1940 { 1941 DECODE_HEAD; 1942 char *name, *sp, *dp; 1943 u32 namelen, cnt; 1944 1945 READ_BUF(4); 1946 namelen = be32_to_cpup(p++); 1947 1948 if (namelen > (XATTR_NAME_MAX - XATTR_USER_PREFIX_LEN)) 1949 return nfserr_nametoolong; 1950 1951 if (namelen == 0) 1952 goto xdr_error; 1953 1954 READ_BUF(namelen); 1955 1956 name = svcxdr_tmpalloc(argp, namelen + XATTR_USER_PREFIX_LEN + 1); 1957 if (!name) 1958 return nfserr_jukebox; 1959 1960 memcpy(name, XATTR_USER_PREFIX, XATTR_USER_PREFIX_LEN); 1961 1962 /* 1963 * Copy the extended attribute name over while checking for 0 1964 * characters. 1965 */ 1966 sp = (char *)p; 1967 dp = name + XATTR_USER_PREFIX_LEN; 1968 cnt = namelen; 1969 1970 while (cnt-- > 0) { 1971 if (*sp == '\0') 1972 goto xdr_error; 1973 *dp++ = *sp++; 1974 } 1975 *dp = '\0'; 1976 1977 *namep = name; 1978 > 1979 DECODE_TAIL; 1980 } 1981 1982 /* 1983 * A GETXATTR op request comes without a length specifier. We just set the 1984 * maximum length for the reply based on XATTR_SIZE_MAX and the maximum 1985 * channel reply size, allocate a buffer of that length and pass it to 1986 * vfs_getxattr. 1987 */ 1988 static __be32 1989 nfsd4_decode_getxattr(struct nfsd4_compoundargs *argp, 1990 struct nfsd4_getxattr *getxattr) 1991 { 1992 int status; 1993 u32 maxcount; 1994 1995 status = nfsd4_decode_xattr_name(argp, &getxattr->getxa_name); 1996 if (status) > 1997 return status; 1998 1999 maxcount = svc_max_payload(argp->rqstp); 2000 maxcount = min_t(u32, XATTR_SIZE_MAX, maxcount); 2001 2002 getxattr->getxa_buf = svcxdr_tmpalloc(argp, maxcount); 2003 if (!getxattr->getxa_buf) > 2004 status = nfserr_jukebox; 2005 getxattr->getxa_len = maxcount; 2006 2007 return status; 2008 } 2009 2010 static __be32 2011 nfsd4_decode_setxattr(struct nfsd4_compoundargs *argp, 2012 struct nfsd4_setxattr *setxattr) 2013 { 2014 DECODE_HEAD; 2015 u32 flags, maxcount, size; 2016 struct kvec head; 2017 struct page **pagelist; 2018 2019 READ_BUF(4); 2020 flags = be32_to_cpup(p++); 2021 2022 if (flags > SETXATTR4_REPLACE) 2023 return nfserr_inval; 2024 setxattr->setxa_flags = flags; 2025 > 2026 status = nfsd4_decode_xattr_name(argp, &setxattr->setxa_name); 2027 if (status) 2028 return status; 2029 2030 maxcount = svc_max_payload(argp->rqstp); 2031 maxcount = min_t(u32, XATTR_SIZE_MAX, maxcount); 2032 2033 READ_BUF(4); 2034 size = be32_to_cpup(p++); 2035 if (size > maxcount) 2036 return nfserr_xattr2big; 2037 2038 setxattr->setxa_len = size; 2039 if (size > 0) { 2040 status = svcxdr_construct_vector(argp, &head, &pagelist, size); 2041 if (status) 2042 return status; 2043 > 2044 status = nfsd4_vbuf_from_stream(argp, &head, pagelist, 2045 &setxattr->setxa_buf, size); 2046 } 2047 2048 DECODE_TAIL; 2049 } 2050 2051 static __be32 2052 nfsd4_decode_listxattrs(struct nfsd4_compoundargs *argp, 2053 struct nfsd4_listxattrs *listxattrs) 2054 { 2055 DECODE_HEAD; 2056 u32 maxcount; 2057 2058 READ_BUF(12); 2059 p = xdr_decode_hyper(p, &listxattrs->lsxa_cookie); 2060 2061 /* 2062 * If the cookie is too large to have even one user.x attribute 2063 * plus trailing '\0' left in a maximum size buffer, it's invalid. 2064 */ 2065 if (listxattrs->lsxa_cookie >= 2066 (XATTR_LIST_MAX / (XATTR_USER_PREFIX_LEN + 2))) 2067 return nfserr_badcookie; 2068 2069 maxcount = be32_to_cpup(p++); 2070 if (maxcount < 8) 2071 /* Always need at least 2 words (length and one character) */ 2072 return nfserr_inval; 2073 2074 maxcount = min(maxcount, svc_max_payload(argp->rqstp)); 2075 listxattrs->lsxa_maxcount = maxcount; 2076 2077 /* 2078 * Unfortunately, there is no interface to only list xattrs for 2079 * one prefix. So there is no good way to convert maxcount to 2080 * a maximum value to pass to vfs_listxattr, as we don't know 2081 * how many of the returned attributes will be user attributes. 2082 * 2083 * So, always ask vfs_listxattr for the maximum size, and encode 2084 * as many as possible. 2085 */ 2086 listxattrs->lsxa_buf = svcxdr_tmpalloc(argp, XATTR_LIST_MAX); 2087 if (!listxattrs->lsxa_buf) 2088 status = nfserr_jukebox; 2089 2090 DECODE_TAIL; 2091 } 2092 2093 static __be32 2094 nfsd4_decode_removexattr(struct nfsd4_compoundargs *argp, 2095 struct nfsd4_removexattr *removexattr) 2096 { > 2097 return nfsd4_decode_xattr_name(argp, &removexattr->rmxa_name); 2098 } 2099 --- 0-DAY CI Kernel Test Service, Intel Corporation https://lists.01.org/hyperkitty/list/kbuild-all@lists.01.org
> On Mar 11, 2020, at 3:59 PM, Frank van der Linden <fllinden@amazon.com> wrote: > > Add functions to calculate the reply size for the user extended attribute > operations, and implement the XDR encode / decode logic for these > operations. > > Signed-off-by: Frank van der Linden <fllinden@amazon.com> > --- > fs/nfsd/nfs4proc.c | 36 +++++ > fs/nfsd/nfs4xdr.c | 448 +++++++++++++++++++++++++++++++++++++++++++++++++++++ > 2 files changed, 484 insertions(+) > > diff --git a/fs/nfsd/nfs4proc.c b/fs/nfsd/nfs4proc.c > index a76b9025a357..44d488bdebd9 100644 > --- a/fs/nfsd/nfs4proc.c > +++ b/fs/nfsd/nfs4proc.c > @@ -2778,6 +2778,42 @@ static inline u32 nfsd4_seek_rsize(struct svc_rqst *rqstp, struct nfsd4_op *op) > return (op_encode_hdr_size + 3) * sizeof(__be32); > } > > +static inline u32 nfsd4_getxattr_rsize(struct svc_rqst *rqstp, > + struct nfsd4_op *op) > +{ > + u32 maxcount, rlen; > + > + maxcount = svc_max_payload(rqstp); > + rlen = min_t(u32, XATTR_SIZE_MAX, maxcount); > + > + return (op_encode_hdr_size + 1 + XDR_QUADLEN(rlen)) * sizeof(__be32); These should be added in the same patch that adds OP_GETXATTR and friends. Also, Trond recently added xdr_align_size which I prefer over the use of XDR_QUADLEN in new code. > +} > + > +static inline u32 nfsd4_setxattr_rsize(struct svc_rqst *rqstp, > + struct nfsd4_op *op) > +{ > + return (op_encode_hdr_size + op_encode_change_info_maxsz) > + * sizeof(__be32); > +} > +static inline u32 nfsd4_listxattrs_rsize(struct svc_rqst *rqstp, > + struct nfsd4_op *op) > +{ > + u32 maxcount, rlen; > + > + maxcount = svc_max_payload(rqstp); > + rlen = min(op->u.listxattrs.lsxa_maxcount, maxcount); > + > + return (op_encode_hdr_size + 4 + XDR_QUADLEN(rlen)) * sizeof(__be32); > +} > + > +static inline u32 nfsd4_removexattr_rsize(struct svc_rqst *rqstp, > + struct nfsd4_op *op) > +{ > + return (op_encode_hdr_size + op_encode_change_info_maxsz) > + * sizeof(__be32); > +} > + > + > static const struct nfsd4_operation nfsd4_ops[LAST_NFS4_OP + 1] = { > [OP_ACCESS] = { > .op_func = nfsd4_access, > diff --git a/fs/nfsd/nfs4xdr.c b/fs/nfsd/nfs4xdr.c > index b12d7ac6f52c..41c8b95ca1c5 100644 > --- a/fs/nfsd/nfs4xdr.c > +++ b/fs/nfsd/nfs4xdr.c > @@ -1879,6 +1879,224 @@ nfsd4_decode_seek(struct nfsd4_compoundargs *argp, struct nfsd4_seek *seek) > DECODE_TAIL; > } > > +/* > + * XDR data that is more than PAGE_SIZE in size is normally part of a > + * read or write. However, the size of extended attributes is limited > + * by the maximum request size, and then further limited by the underlying > + * filesystem limits. This can exceed PAGE_SIZE (currently, XATTR_SIZE_MAX > + * is 64k). Since there is no kvec- or page-based interface to xattrs, > + * and we're not dealing with contiguous pages, we need to do some copying. > + */ > + > +/* > + * Decode int to buffer. Uses head and pages constructed by > + * svcxdr_construct_vector. > + */ > +static int > +nfsd4_vbuf_from_stream(struct nfsd4_compoundargs *argp, struct kvec *head, > + struct page **pages, char **bufp, u32 buflen) > +{ > + char *tmp, *dp; > + u32 len; > + > + if (buflen <= head->iov_len) { > + /* > + * We're in luck, the head has enough space. Just return > + * the head, no need for copying. > + */ > + *bufp = head->iov_base; > + return 0; > + } > + > + tmp = svcxdr_tmpalloc(argp, buflen); > + if (tmp == NULL) > + return nfserr_jukebox; > + > + dp = tmp; > + memcpy(dp, head->iov_base, head->iov_len); > + buflen -= head->iov_len; > + dp += head->iov_len; > + > + while (buflen > 0) { > + len = min_t(u32, buflen, PAGE_SIZE); > + memcpy(dp, page_address(*pages), len); > + > + buflen -= len; > + dp += len; > + pages++; > + } > + > + *bufp = tmp; > + return 0; > +} > + > +/* > + * Get a user extended attribute name from the XDR buffer. > + * It will not have the "user." prefix, so prepend it. > + * Lastly, check for nul characters in the name. > + */ > +static int > +nfsd4_decode_xattr_name(struct nfsd4_compoundargs *argp, char **namep) > +{ > + DECODE_HEAD; > + char *name, *sp, *dp; > + u32 namelen, cnt; > + > + READ_BUF(4); > + namelen = be32_to_cpup(p++); > + > + if (namelen > (XATTR_NAME_MAX - XATTR_USER_PREFIX_LEN)) > + return nfserr_nametoolong; > + > + if (namelen == 0) > + goto xdr_error; > + > + READ_BUF(namelen); > + > + name = svcxdr_tmpalloc(argp, namelen + XATTR_USER_PREFIX_LEN + 1); > + if (!name) > + return nfserr_jukebox; > + > + memcpy(name, XATTR_USER_PREFIX, XATTR_USER_PREFIX_LEN); > + > + /* > + * Copy the extended attribute name over while checking for 0 > + * characters. > + */ > + sp = (char *)p; > + dp = name + XATTR_USER_PREFIX_LEN; > + cnt = namelen; > + > + while (cnt-- > 0) { > + if (*sp == '\0') > + goto xdr_error; > + *dp++ = *sp++; > + } > + *dp = '\0'; > + > + *namep = name; > + > + DECODE_TAIL; > +} > + > +/* > + * A GETXATTR op request comes without a length specifier. We just set the > + * maximum length for the reply based on XATTR_SIZE_MAX and the maximum > + * channel reply size, allocate a buffer of that length and pass it to > + * vfs_getxattr. > + */ > +static __be32 > +nfsd4_decode_getxattr(struct nfsd4_compoundargs *argp, > + struct nfsd4_getxattr *getxattr) > +{ > + int status; > + u32 maxcount; > + > + status = nfsd4_decode_xattr_name(argp, &getxattr->getxa_name); > + if (status) > + return status; > + > + maxcount = svc_max_payload(argp->rqstp); > + maxcount = min_t(u32, XATTR_SIZE_MAX, maxcount); > + > + getxattr->getxa_buf = svcxdr_tmpalloc(argp, maxcount); > + if (!getxattr->getxa_buf) > + status = nfserr_jukebox; > + getxattr->getxa_len = maxcount; > + > + return status; > +} > + > +static __be32 > +nfsd4_decode_setxattr(struct nfsd4_compoundargs *argp, > + struct nfsd4_setxattr *setxattr) > +{ > + DECODE_HEAD; > + u32 flags, maxcount, size; > + struct kvec head; > + struct page **pagelist; > + > + READ_BUF(4); > + flags = be32_to_cpup(p++); > + > + if (flags > SETXATTR4_REPLACE) > + return nfserr_inval; > + setxattr->setxa_flags = flags; > + > + status = nfsd4_decode_xattr_name(argp, &setxattr->setxa_name); > + if (status) > + return status; > + > + maxcount = svc_max_payload(argp->rqstp); > + maxcount = min_t(u32, XATTR_SIZE_MAX, maxcount); > + > + READ_BUF(4); > + size = be32_to_cpup(p++); > + if (size > maxcount) > + return nfserr_xattr2big; > + > + setxattr->setxa_len = size; > + if (size > 0) { > + status = svcxdr_construct_vector(argp, &head, &pagelist, size); > + if (status) > + return status; > + > + status = nfsd4_vbuf_from_stream(argp, &head, pagelist, > + &setxattr->setxa_buf, size); > + } > + > + DECODE_TAIL; > +} > + > +static __be32 > +nfsd4_decode_listxattrs(struct nfsd4_compoundargs *argp, > + struct nfsd4_listxattrs *listxattrs) > +{ > + DECODE_HEAD; > + u32 maxcount; > + > + READ_BUF(12); > + p = xdr_decode_hyper(p, &listxattrs->lsxa_cookie); > + > + /* > + * If the cookie is too large to have even one user.x attribute > + * plus trailing '\0' left in a maximum size buffer, it's invalid. > + */ > + if (listxattrs->lsxa_cookie >= > + (XATTR_LIST_MAX / (XATTR_USER_PREFIX_LEN + 2))) > + return nfserr_badcookie; > + > + maxcount = be32_to_cpup(p++); > + if (maxcount < 8) > + /* Always need at least 2 words (length and one character) */ > + return nfserr_inval; > + > + maxcount = min(maxcount, svc_max_payload(argp->rqstp)); > + listxattrs->lsxa_maxcount = maxcount; > + > + /* > + * Unfortunately, there is no interface to only list xattrs for > + * one prefix. So there is no good way to convert maxcount to > + * a maximum value to pass to vfs_listxattr, as we don't know > + * how many of the returned attributes will be user attributes. > + * > + * So, always ask vfs_listxattr for the maximum size, and encode > + * as many as possible. > + */ Well, this approach worries me a little bit. Wouldn't it be better if the VFS provided the APIs? Review by linux-fsdevel might help here. > + listxattrs->lsxa_buf = svcxdr_tmpalloc(argp, XATTR_LIST_MAX); > + if (!listxattrs->lsxa_buf) > + status = nfserr_jukebox; > + > + DECODE_TAIL; > +} > + > +static __be32 > +nfsd4_decode_removexattr(struct nfsd4_compoundargs *argp, > + struct nfsd4_removexattr *removexattr) > +{ > + return nfsd4_decode_xattr_name(argp, &removexattr->rmxa_name); > +} > + > static __be32 > nfsd4_decode_noop(struct nfsd4_compoundargs *argp, void *p) > { > @@ -1975,6 +2193,11 @@ static const nfsd4_dec nfsd4_dec_ops[] = { > [OP_SEEK] = (nfsd4_dec)nfsd4_decode_seek, > [OP_WRITE_SAME] = (nfsd4_dec)nfsd4_decode_notsupp, > [OP_CLONE] = (nfsd4_dec)nfsd4_decode_clone, > + /* RFC 8276 extended atributes operations */ > + [OP_GETXATTR] = (nfsd4_dec)nfsd4_decode_getxattr, > + [OP_SETXATTR] = (nfsd4_dec)nfsd4_decode_setxattr, > + [OP_LISTXATTRS] = (nfsd4_dec)nfsd4_decode_listxattrs, > + [OP_REMOVEXATTR] = (nfsd4_dec)nfsd4_decode_removexattr, > }; > > static inline bool > @@ -4458,6 +4681,225 @@ nfsd4_encode_noop(struct nfsd4_compoundres *resp, __be32 nfserr, void *p) > return nfserr; > } > > +/* > + * Encode kmalloc-ed buffer in to XDR stream. > + */ > +static int > +nfsd4_vbuf_to_stream(struct xdr_stream *xdr, char *buf, u32 buflen) > +{ > + u32 cplen; > + __be32 *p; > + > + cplen = min_t(unsigned long, buflen, > + ((void *)xdr->end - (void *)xdr->p)); > + p = xdr_reserve_space(xdr, cplen); > + if (!p) > + return nfserr_resource; > + > + memcpy(p, buf, cplen); > + buf += cplen; > + buflen -= cplen; > + > + while (buflen) { > + cplen = min_t(u32, buflen, PAGE_SIZE); > + p = xdr_reserve_space(xdr, cplen); > + if (!p) > + return nfserr_resource; > + > + memcpy(p, buf, cplen); > + > + if (cplen < PAGE_SIZE) { > + /* > + * We're done, with a length that wasn't page > + * aligned, so possibly not word aligned. Pad > + * any trailing bytes with 0. > + */ > + xdr_encode_opaque_fixed(p, NULL, cplen); > + break; > + } > + > + buflen -= PAGE_SIZE; > + buf += PAGE_SIZE; > + } > + > + return 0; > +} > + > +static __be32 > +nfsd4_encode_getxattr(struct nfsd4_compoundres *resp, __be32 nfserr, > + struct nfsd4_getxattr *getxattr) > +{ > + struct xdr_stream *xdr = &resp->xdr; > + __be32 *p; > + > + p = xdr_reserve_space(xdr, 4); > + if (!p) > + return nfserr_resource; > + > + *p = cpu_to_be32(getxattr->getxa_len); > + > + if (getxattr->getxa_len == 0) > + return 0; > + > + return nfsd4_vbuf_to_stream(xdr, getxattr->getxa_buf, > + getxattr->getxa_len); > +} > + > +static __be32 > +nfsd4_encode_setxattr(struct nfsd4_compoundres *resp, __be32 nfserr, > + struct nfsd4_setxattr *setxattr) > +{ > + struct xdr_stream *xdr = &resp->xdr; > + __be32 *p; > + > + p = xdr_reserve_space(xdr, 20); > + if (!p) > + return nfserr_resource; > + > + encode_cinfo(p, &setxattr->setxa_cinfo); > + > + return 0; > +} > + > +/* > + * See if there are cookie values that can be rejected outright. > + */ > +static int > +nfsd4_listxattr_validate_cookie(struct nfsd4_listxattrs *listxattrs, > + u32 *offsetp) > +{ > + u64 cookie = listxattrs->lsxa_cookie; > + > + /* > + * If the cookie is larger than the maximum number we can fit > + * in either the buffer we just got back from vfs_listxattr, or, > + * XDR-encoded, in the return buffer, it's invalid. > + */ > + if (cookie > (listxattrs->lsxa_len) / (XATTR_USER_PREFIX_LEN + 2)) > + return nfserr_badcookie; > + > + if (cookie > (listxattrs->lsxa_maxcount / > + (XDR_QUADLEN(XATTR_USER_PREFIX_LEN + 2) + 4))) > + return nfserr_badcookie; > + > + *offsetp = (u32)cookie; > + return 0; > +} > + > +static __be32 > +nfsd4_encode_listxattrs(struct nfsd4_compoundres *resp, __be32 nfserr, > + struct nfsd4_listxattrs *listxattrs) > +{ > + struct xdr_stream *xdr = &resp->xdr; > + u32 cookie_offset, count_offset, eof; > + u32 left, xdrleft, slen, count; > + u32 xdrlen, offset; > + u64 cookie; > + char *sp; > + int status; > + __be32 *p; > + u32 nuser; > + > + eof = 1; > + > + status = nfsd4_listxattr_validate_cookie(listxattrs, &offset); > + if (status) > + return status; > + > + /* > + * Reserve space for the cookie and the name array count. Record > + * the offsets to save them later. > + */ > + cookie_offset = xdr->buf->len; > + count_offset = cookie_offset + 8; > + p = xdr_reserve_space(xdr, 12); > + if (!p) > + return nfserr_resource; > + > + count = 0; > + left = listxattrs->lsxa_len; > + sp = listxattrs->lsxa_buf; > + nuser = 0; > + > + xdrleft = listxattrs->lsxa_maxcount; > + > + while (left > 0 && xdrleft > 0) { > + slen = strlen(sp); > + > + /* > + * Check if this a user. attribute, skip it if not. > + */ > + if (strncmp(sp, XATTR_USER_PREFIX, XATTR_USER_PREFIX_LEN)) > + goto contloop; > + > + slen -= XATTR_USER_PREFIX_LEN; > + xdrlen = 4 + ((slen + 3) & ~3); > + if (xdrlen > xdrleft) { > + if (count == 0) { > + /* > + * Can't even fit the first attribute name. > + */ > + return nfserr_toosmall; > + } > + eof = 0; > + goto wreof; > + } > + > + left -= XATTR_USER_PREFIX_LEN; > + sp += XATTR_USER_PREFIX_LEN; > + if (nuser++ < offset) > + goto contloop; > + > + > + p = xdr_reserve_space(xdr, xdrlen); > + if (!p) > + return nfserr_resource; > + > + p = xdr_encode_opaque(p, sp, slen); > + > + xdrleft -= xdrlen; > + count++; > +contloop: > + sp += slen + 1; > + left -= slen + 1; > + } > + > + /* > + * If there were user attributes to copy, but we didn't copy > + * any, the offset was too large (e.g. the cookie was invalid). > + */ > + if (nuser > 0 && count == 0) > + return nfserr_badcookie; > + > +wreof: > + p = xdr_reserve_space(xdr, 4); > + if (!p) > + return nfserr_resource; > + *p = cpu_to_be32(eof); > + > + cookie = offset + count; > + > + write_bytes_to_xdr_buf(xdr->buf, cookie_offset, &cookie, 8); > + count = htonl(count); > + write_bytes_to_xdr_buf(xdr->buf, count_offset, &count, 4); > + return 0; > +} > + > +static __be32 > +nfsd4_encode_removexattr(struct nfsd4_compoundres *resp, __be32 nfserr, > + struct nfsd4_removexattr *removexattr) > +{ > + struct xdr_stream *xdr = &resp->xdr; > + __be32 *p; > + > + p = xdr_reserve_space(xdr, 20); > + if (!p) > + return nfserr_resource; > + > + p = encode_cinfo(p, &removexattr->rmxa_cinfo); > + return 0; > +} > + > typedef __be32(* nfsd4_enc)(struct nfsd4_compoundres *, __be32, void *); > > /* > @@ -4547,6 +4989,12 @@ static const nfsd4_enc nfsd4_enc_ops[] = { > [OP_SEEK] = (nfsd4_enc)nfsd4_encode_seek, > [OP_WRITE_SAME] = (nfsd4_enc)nfsd4_encode_noop, > [OP_CLONE] = (nfsd4_enc)nfsd4_encode_noop, > + > + /* RFC 8276 extended atributes operations */ > + [OP_GETXATTR] = (nfsd4_enc)nfsd4_encode_getxattr, > + [OP_SETXATTR] = (nfsd4_enc)nfsd4_encode_setxattr, > + [OP_LISTXATTRS] = (nfsd4_enc)nfsd4_encode_listxattrs, > + [OP_REMOVEXATTR] = (nfsd4_enc)nfsd4_encode_removexattr, > }; > > /* > -- > 2.16.6 > -- Chuck Lever
> On Mar 11, 2020, at 3:59 PM, Frank van der Linden <fllinden@amazon.com> wrote: > > Add functions to calculate the reply size for the user extended attribute > operations, and implement the XDR encode / decode logic for these > operations. > > Signed-off-by: Frank van der Linden <fllinden@amazon.com> > --- > fs/nfsd/nfs4proc.c | 36 +++++ > fs/nfsd/nfs4xdr.c | 448 +++++++++++++++++++++++++++++++++++++++++++++++++++++ > 2 files changed, 484 insertions(+) > > diff --git a/fs/nfsd/nfs4proc.c b/fs/nfsd/nfs4proc.c > index a76b9025a357..44d488bdebd9 100644 > --- a/fs/nfsd/nfs4proc.c > +++ b/fs/nfsd/nfs4proc.c > @@ -2778,6 +2778,42 @@ static inline u32 nfsd4_seek_rsize(struct svc_rqst *rqstp, struct nfsd4_op *op) > return (op_encode_hdr_size + 3) * sizeof(__be32); > } > > +static inline u32 nfsd4_getxattr_rsize(struct svc_rqst *rqstp, > + struct nfsd4_op *op) > +{ > + u32 maxcount, rlen; > + > + maxcount = svc_max_payload(rqstp); > + rlen = min_t(u32, XATTR_SIZE_MAX, maxcount); > + > + return (op_encode_hdr_size + 1 + XDR_QUADLEN(rlen)) * sizeof(__be32); > +} > + > +static inline u32 nfsd4_setxattr_rsize(struct svc_rqst *rqstp, > + struct nfsd4_op *op) > +{ > + return (op_encode_hdr_size + op_encode_change_info_maxsz) > + * sizeof(__be32); > +} > +static inline u32 nfsd4_listxattrs_rsize(struct svc_rqst *rqstp, > + struct nfsd4_op *op) > +{ > + u32 maxcount, rlen; > + > + maxcount = svc_max_payload(rqstp); > + rlen = min(op->u.listxattrs.lsxa_maxcount, maxcount); > + > + return (op_encode_hdr_size + 4 + XDR_QUADLEN(rlen)) * sizeof(__be32); > +} > + > +static inline u32 nfsd4_removexattr_rsize(struct svc_rqst *rqstp, > + struct nfsd4_op *op) > +{ > + return (op_encode_hdr_size + op_encode_change_info_maxsz) > + * sizeof(__be32); > +} > + > + > static const struct nfsd4_operation nfsd4_ops[LAST_NFS4_OP + 1] = { > [OP_ACCESS] = { > .op_func = nfsd4_access, > diff --git a/fs/nfsd/nfs4xdr.c b/fs/nfsd/nfs4xdr.c > index b12d7ac6f52c..41c8b95ca1c5 100644 > --- a/fs/nfsd/nfs4xdr.c > +++ b/fs/nfsd/nfs4xdr.c > @@ -1879,6 +1879,224 @@ nfsd4_decode_seek(struct nfsd4_compoundargs *argp, struct nfsd4_seek *seek) > DECODE_TAIL; > } > > +/* > + * XDR data that is more than PAGE_SIZE in size is normally part of a > + * read or write. However, the size of extended attributes is limited > + * by the maximum request size, and then further limited by the underlying > + * filesystem limits. This can exceed PAGE_SIZE (currently, XATTR_SIZE_MAX > + * is 64k). Since there is no kvec- or page-based interface to xattrs, > + * and we're not dealing with contiguous pages, we need to do some copying. > + */ > + > +/* > + * Decode int to buffer. "int" is of course a C keyword, so this typo had me confused! I think you mean "Decode into buffer." > Uses head and pages constructed by > + * svcxdr_construct_vector. > + */ > +static int > +nfsd4_vbuf_from_stream(struct nfsd4_compoundargs *argp, struct kvec *head, > + struct page **pages, char **bufp, u32 buflen) > +{ > + char *tmp, *dp; > + u32 len; > + > + if (buflen <= head->iov_len) { > + /* > + * We're in luck, the head has enough space. Just return > + * the head, no need for copying. > + */ > + *bufp = head->iov_base; > + return 0; > + } > + > + tmp = svcxdr_tmpalloc(argp, buflen); > + if (tmp == NULL) > + return nfserr_jukebox; > + > + dp = tmp; > + memcpy(dp, head->iov_base, head->iov_len); > + buflen -= head->iov_len; > + dp += head->iov_len; > + > + while (buflen > 0) { > + len = min_t(u32, buflen, PAGE_SIZE); > + memcpy(dp, page_address(*pages), len); > + > + buflen -= len; > + dp += len; > + pages++; > + } > + > + *bufp = tmp; > + return 0; > +} > + > +/* > + * Get a user extended attribute name from the XDR buffer. > + * It will not have the "user." prefix, so prepend it. > + * Lastly, check for nul characters in the name. > + */ > +static int > +nfsd4_decode_xattr_name(struct nfsd4_compoundargs *argp, char **namep) > +{ > + DECODE_HEAD; > + char *name, *sp, *dp; > + u32 namelen, cnt; > + > + READ_BUF(4); > + namelen = be32_to_cpup(p++); > + > + if (namelen > (XATTR_NAME_MAX - XATTR_USER_PREFIX_LEN)) > + return nfserr_nametoolong; > + > + if (namelen == 0) > + goto xdr_error; > + > + READ_BUF(namelen); > + > + name = svcxdr_tmpalloc(argp, namelen + XATTR_USER_PREFIX_LEN + 1); > + if (!name) > + return nfserr_jukebox; > + > + memcpy(name, XATTR_USER_PREFIX, XATTR_USER_PREFIX_LEN); > + > + /* > + * Copy the extended attribute name over while checking for 0 > + * characters. > + */ > + sp = (char *)p; > + dp = name + XATTR_USER_PREFIX_LEN; > + cnt = namelen; > + > + while (cnt-- > 0) { > + if (*sp == '\0') > + goto xdr_error; > + *dp++ = *sp++; > + } > + *dp = '\0'; > + > + *namep = name; > + > + DECODE_TAIL; > +} > + > +/* > + * A GETXATTR op request comes without a length specifier. We just set the > + * maximum length for the reply based on XATTR_SIZE_MAX and the maximum > + * channel reply size, allocate a buffer of that length and pass it to > + * vfs_getxattr. > + */ > +static __be32 > +nfsd4_decode_getxattr(struct nfsd4_compoundargs *argp, > + struct nfsd4_getxattr *getxattr) > +{ > + int status; > + u32 maxcount; > + > + status = nfsd4_decode_xattr_name(argp, &getxattr->getxa_name); > + if (status) > + return status; > + > + maxcount = svc_max_payload(argp->rqstp); > + maxcount = min_t(u32, XATTR_SIZE_MAX, maxcount); > + > + getxattr->getxa_buf = svcxdr_tmpalloc(argp, maxcount); > + if (!getxattr->getxa_buf) > + status = nfserr_jukebox; > + getxattr->getxa_len = maxcount; > + > + return status; > +} > + > +static __be32 > +nfsd4_decode_setxattr(struct nfsd4_compoundargs *argp, > + struct nfsd4_setxattr *setxattr) > +{ > + DECODE_HEAD; > + u32 flags, maxcount, size; > + struct kvec head; > + struct page **pagelist; > + > + READ_BUF(4); > + flags = be32_to_cpup(p++); > + > + if (flags > SETXATTR4_REPLACE) > + return nfserr_inval; > + setxattr->setxa_flags = flags; > + > + status = nfsd4_decode_xattr_name(argp, &setxattr->setxa_name); > + if (status) > + return status; > + > + maxcount = svc_max_payload(argp->rqstp); > + maxcount = min_t(u32, XATTR_SIZE_MAX, maxcount); > + > + READ_BUF(4); > + size = be32_to_cpup(p++); > + if (size > maxcount) > + return nfserr_xattr2big; > + > + setxattr->setxa_len = size; > + if (size > 0) { > + status = svcxdr_construct_vector(argp, &head, &pagelist, size); > + if (status) > + return status; > + > + status = nfsd4_vbuf_from_stream(argp, &head, pagelist, > + &setxattr->setxa_buf, size); > + } Now I'm wondering if read_bytes_from_xdr_buf() might be adequate for this purpose, so you can avoid open-coding all of this logic. > + > + DECODE_TAIL; > +} > + > +static __be32 > +nfsd4_decode_listxattrs(struct nfsd4_compoundargs *argp, > + struct nfsd4_listxattrs *listxattrs) > +{ > + DECODE_HEAD; > + u32 maxcount; > + > + READ_BUF(12); > + p = xdr_decode_hyper(p, &listxattrs->lsxa_cookie); > + > + /* > + * If the cookie is too large to have even one user.x attribute > + * plus trailing '\0' left in a maximum size buffer, it's invalid. > + */ > + if (listxattrs->lsxa_cookie >= > + (XATTR_LIST_MAX / (XATTR_USER_PREFIX_LEN + 2))) > + return nfserr_badcookie; > + > + maxcount = be32_to_cpup(p++); > + if (maxcount < 8) > + /* Always need at least 2 words (length and one character) */ > + return nfserr_inval; > + > + maxcount = min(maxcount, svc_max_payload(argp->rqstp)); > + listxattrs->lsxa_maxcount = maxcount; > + > + /* > + * Unfortunately, there is no interface to only list xattrs for > + * one prefix. So there is no good way to convert maxcount to > + * a maximum value to pass to vfs_listxattr, as we don't know > + * how many of the returned attributes will be user attributes. > + * > + * So, always ask vfs_listxattr for the maximum size, and encode > + * as many as possible. > + */ > + listxattrs->lsxa_buf = svcxdr_tmpalloc(argp, XATTR_LIST_MAX); > + if (!listxattrs->lsxa_buf) > + status = nfserr_jukebox; > + > + DECODE_TAIL; > +} > + > +static __be32 > +nfsd4_decode_removexattr(struct nfsd4_compoundargs *argp, > + struct nfsd4_removexattr *removexattr) > +{ > + return nfsd4_decode_xattr_name(argp, &removexattr->rmxa_name); > +} > + > static __be32 > nfsd4_decode_noop(struct nfsd4_compoundargs *argp, void *p) > { > @@ -1975,6 +2193,11 @@ static const nfsd4_dec nfsd4_dec_ops[] = { > [OP_SEEK] = (nfsd4_dec)nfsd4_decode_seek, > [OP_WRITE_SAME] = (nfsd4_dec)nfsd4_decode_notsupp, > [OP_CLONE] = (nfsd4_dec)nfsd4_decode_clone, > + /* RFC 8276 extended atributes operations */ > + [OP_GETXATTR] = (nfsd4_dec)nfsd4_decode_getxattr, > + [OP_SETXATTR] = (nfsd4_dec)nfsd4_decode_setxattr, > + [OP_LISTXATTRS] = (nfsd4_dec)nfsd4_decode_listxattrs, > + [OP_REMOVEXATTR] = (nfsd4_dec)nfsd4_decode_removexattr, > }; > > static inline bool > @@ -4458,6 +4681,225 @@ nfsd4_encode_noop(struct nfsd4_compoundres *resp, __be32 nfserr, void *p) > return nfserr; > } > > +/* > + * Encode kmalloc-ed buffer in to XDR stream. > + */ > +static int > +nfsd4_vbuf_to_stream(struct xdr_stream *xdr, char *buf, u32 buflen) > +{ > + u32 cplen; > + __be32 *p; > + > + cplen = min_t(unsigned long, buflen, > + ((void *)xdr->end - (void *)xdr->p)); > + p = xdr_reserve_space(xdr, cplen); > + if (!p) > + return nfserr_resource; > + > + memcpy(p, buf, cplen); > + buf += cplen; > + buflen -= cplen; > + > + while (buflen) { > + cplen = min_t(u32, buflen, PAGE_SIZE); > + p = xdr_reserve_space(xdr, cplen); > + if (!p) > + return nfserr_resource; > + > + memcpy(p, buf, cplen); > + > + if (cplen < PAGE_SIZE) { > + /* > + * We're done, with a length that wasn't page > + * aligned, so possibly not word aligned. Pad > + * any trailing bytes with 0. > + */ > + xdr_encode_opaque_fixed(p, NULL, cplen); > + break; > + } > + > + buflen -= PAGE_SIZE; > + buf += PAGE_SIZE; > + } > + > + return 0; > +} > + > +static __be32 > +nfsd4_encode_getxattr(struct nfsd4_compoundres *resp, __be32 nfserr, > + struct nfsd4_getxattr *getxattr) > +{ > + struct xdr_stream *xdr = &resp->xdr; > + __be32 *p; > + > + p = xdr_reserve_space(xdr, 4); > + if (!p) > + return nfserr_resource; > + > + *p = cpu_to_be32(getxattr->getxa_len); > + > + if (getxattr->getxa_len == 0) > + return 0; > + > + return nfsd4_vbuf_to_stream(xdr, getxattr->getxa_buf, > + getxattr->getxa_len); > +} > + > +static __be32 > +nfsd4_encode_setxattr(struct nfsd4_compoundres *resp, __be32 nfserr, > + struct nfsd4_setxattr *setxattr) > +{ > + struct xdr_stream *xdr = &resp->xdr; > + __be32 *p; > + > + p = xdr_reserve_space(xdr, 20); > + if (!p) > + return nfserr_resource; > + > + encode_cinfo(p, &setxattr->setxa_cinfo); > + > + return 0; > +} > + > +/* > + * See if there are cookie values that can be rejected outright. > + */ > +static int > +nfsd4_listxattr_validate_cookie(struct nfsd4_listxattrs *listxattrs, > + u32 *offsetp) > +{ > + u64 cookie = listxattrs->lsxa_cookie; > + > + /* > + * If the cookie is larger than the maximum number we can fit > + * in either the buffer we just got back from vfs_listxattr, or, > + * XDR-encoded, in the return buffer, it's invalid. > + */ > + if (cookie > (listxattrs->lsxa_len) / (XATTR_USER_PREFIX_LEN + 2)) > + return nfserr_badcookie; > + > + if (cookie > (listxattrs->lsxa_maxcount / > + (XDR_QUADLEN(XATTR_USER_PREFIX_LEN + 2) + 4))) > + return nfserr_badcookie; > + > + *offsetp = (u32)cookie; > + return 0; > +} > + > +static __be32 > +nfsd4_encode_listxattrs(struct nfsd4_compoundres *resp, __be32 nfserr, > + struct nfsd4_listxattrs *listxattrs) > +{ > + struct xdr_stream *xdr = &resp->xdr; > + u32 cookie_offset, count_offset, eof; > + u32 left, xdrleft, slen, count; > + u32 xdrlen, offset; > + u64 cookie; > + char *sp; > + int status; > + __be32 *p; > + u32 nuser; > + > + eof = 1; > + > + status = nfsd4_listxattr_validate_cookie(listxattrs, &offset); > + if (status) > + return status; > + > + /* > + * Reserve space for the cookie and the name array count. Record > + * the offsets to save them later. > + */ > + cookie_offset = xdr->buf->len; > + count_offset = cookie_offset + 8; > + p = xdr_reserve_space(xdr, 12); > + if (!p) > + return nfserr_resource; > + > + count = 0; > + left = listxattrs->lsxa_len; > + sp = listxattrs->lsxa_buf; > + nuser = 0; > + > + xdrleft = listxattrs->lsxa_maxcount; > + > + while (left > 0 && xdrleft > 0) { > + slen = strlen(sp); > + > + /* > + * Check if this a user. attribute, skip it if not. > + */ > + if (strncmp(sp, XATTR_USER_PREFIX, XATTR_USER_PREFIX_LEN)) > + goto contloop; > + > + slen -= XATTR_USER_PREFIX_LEN; > + xdrlen = 4 + ((slen + 3) & ~3); > + if (xdrlen > xdrleft) { > + if (count == 0) { > + /* > + * Can't even fit the first attribute name. > + */ > + return nfserr_toosmall; > + } > + eof = 0; > + goto wreof; > + } > + > + left -= XATTR_USER_PREFIX_LEN; > + sp += XATTR_USER_PREFIX_LEN; > + if (nuser++ < offset) > + goto contloop; > + > + > + p = xdr_reserve_space(xdr, xdrlen); > + if (!p) > + return nfserr_resource; > + > + p = xdr_encode_opaque(p, sp, slen); > + > + xdrleft -= xdrlen; > + count++; > +contloop: > + sp += slen + 1; > + left -= slen + 1; > + } > + > + /* > + * If there were user attributes to copy, but we didn't copy > + * any, the offset was too large (e.g. the cookie was invalid). > + */ > + if (nuser > 0 && count == 0) > + return nfserr_badcookie; > + > +wreof: > + p = xdr_reserve_space(xdr, 4); > + if (!p) > + return nfserr_resource; > + *p = cpu_to_be32(eof); > + > + cookie = offset + count; > + > + write_bytes_to_xdr_buf(xdr->buf, cookie_offset, &cookie, 8); > + count = htonl(count); > + write_bytes_to_xdr_buf(xdr->buf, count_offset, &count, 4); > + return 0; > +} > + > +static __be32 > +nfsd4_encode_removexattr(struct nfsd4_compoundres *resp, __be32 nfserr, > + struct nfsd4_removexattr *removexattr) > +{ > + struct xdr_stream *xdr = &resp->xdr; > + __be32 *p; > + > + p = xdr_reserve_space(xdr, 20); > + if (!p) > + return nfserr_resource; > + > + p = encode_cinfo(p, &removexattr->rmxa_cinfo); > + return 0; > +} > + > typedef __be32(* nfsd4_enc)(struct nfsd4_compoundres *, __be32, void *); > > /* > @@ -4547,6 +4989,12 @@ static const nfsd4_enc nfsd4_enc_ops[] = { > [OP_SEEK] = (nfsd4_enc)nfsd4_encode_seek, > [OP_WRITE_SAME] = (nfsd4_enc)nfsd4_encode_noop, > [OP_CLONE] = (nfsd4_enc)nfsd4_encode_noop, > + > + /* RFC 8276 extended atributes operations */ > + [OP_GETXATTR] = (nfsd4_enc)nfsd4_encode_getxattr, > + [OP_SETXATTR] = (nfsd4_enc)nfsd4_encode_setxattr, > + [OP_LISTXATTRS] = (nfsd4_enc)nfsd4_encode_listxattrs, > + [OP_REMOVEXATTR] = (nfsd4_enc)nfsd4_encode_removexattr, > }; > > /* > -- > 2.16.6 > -- Chuck Lever
On Thu, Mar 12, 2020 at 12:24:18PM -0400, Chuck Lever wrote: > > +static inline u32 nfsd4_getxattr_rsize(struct svc_rqst *rqstp, > > + struct nfsd4_op *op) > > +{ > > + u32 maxcount, rlen; > > + > > + maxcount = svc_max_payload(rqstp); > > + rlen = min_t(u32, XATTR_SIZE_MAX, maxcount); > > + > > + return (op_encode_hdr_size + 1 + XDR_QUADLEN(rlen)) * sizeof(__be32); > > These should be added in the same patch that adds OP_GETXATTR and friends. > > Also, Trond recently added xdr_align_size which I prefer over the > use of XDR_QUADLEN in new code. Thanks, I've squashed together those patches for this and the other reasons you pointed out. As for XDR_QUADLEN: that returns the 32bit-word rounded up lenghth - in words. xdr_aligned_size returns the 32bit-word rounded up length - in bytes. So, the result would then look something like: return xdr_align_size((op_encode_hdr_size * 4) + 4 + rlen); Is that what you're suggesting? - Frank
> On Mar 19, 2020, at 6:13 PM, Frank van der Linden <fllinden@amazon.com> wrote: > > On Thu, Mar 12, 2020 at 12:24:18PM -0400, Chuck Lever wrote: >>> +static inline u32 nfsd4_getxattr_rsize(struct svc_rqst *rqstp, >>> + struct nfsd4_op *op) >>> +{ >>> + u32 maxcount, rlen; >>> + >>> + maxcount = svc_max_payload(rqstp); >>> + rlen = min_t(u32, XATTR_SIZE_MAX, maxcount); >>> + >>> + return (op_encode_hdr_size + 1 + XDR_QUADLEN(rlen)) * sizeof(__be32); >> >> These should be added in the same patch that adds OP_GETXATTR and friends. >> >> Also, Trond recently added xdr_align_size which I prefer over the >> use of XDR_QUADLEN in new code. > > Thanks, I've squashed together those patches for this and the other reasons > you pointed out. > > As for XDR_QUADLEN: that returns the 32bit-word rounded up lenghth - in words. > xdr_aligned_size returns the 32bit-word rounded up length - in bytes. > > So, the result would then look something like: > > return xdr_align_size((op_encode_hdr_size * 4) + 4 + rlen); > > Is that what you're suggesting? Oops, you're right. When you want words, XDR_QUADLEN is correct. Never mind! -- Chuck Lever
Hi Chuck, On Thu, Mar 12, 2020 at 03:16:37PM -0400, Chuck Lever wrote: > > +static __be32 > > +nfsd4_decode_setxattr(struct nfsd4_compoundargs *argp, > > + struct nfsd4_setxattr *setxattr) > > +{ > > + DECODE_HEAD; > > + u32 flags, maxcount, size; > > + struct kvec head; > > + struct page **pagelist; > > + > > + READ_BUF(4); > > + flags = be32_to_cpup(p++); > > + > > + if (flags > SETXATTR4_REPLACE) > > + return nfserr_inval; > > + setxattr->setxa_flags = flags; > > + > > + status = nfsd4_decode_xattr_name(argp, &setxattr->setxa_name); > > + if (status) > > + return status; > > + > > + maxcount = svc_max_payload(argp->rqstp); > > + maxcount = min_t(u32, XATTR_SIZE_MAX, maxcount); > > + > > + READ_BUF(4); > > + size = be32_to_cpup(p++); > > + if (size > maxcount) > > + return nfserr_xattr2big; > > + > > + setxattr->setxa_len = size; > > + if (size > 0) { > > + status = svcxdr_construct_vector(argp, &head, &pagelist, size); > > + if (status) > > + return status; > > + > > + status = nfsd4_vbuf_from_stream(argp, &head, pagelist, > > + &setxattr->setxa_buf, size); > > + } > > Now I'm wondering if read_bytes_from_xdr_buf() might be adequate > for this purpose, so you can avoid open-coding all of this logic. This took a little longer, I had to check my notes, but basically the reasons for doing it this way are: * The nfsd decode path uses nfsd4_compoundargs, which doesn't have an xdr_stream (it has a page array). So read_bytes_from_xdr_buf isn't a natural fit. * READ_BUF/read_buf don't deal with > PAGE_SIZE chunks, but xattrs may be larger than that. The other code that deals with > PAGE_SIZE chunks is the write code. So, I factored out some code from the write code, used that, and added a function to process the resulting kvec / pagelist (nfs4_vbuf_from_stream). There definitely seem to be several copy functions in both the client and server code that basically do the same, but in slightly different ways, depending on whether they use an XDR buf or not, whether the pages are mapped or not, etc. Seems like a good candidate for a cleanup, but I considered it to be out of scope for these patches. - Frank
> On Mar 20, 2020, at 12:47 PM, Frank van der Linden <fllinden@amazon.com> wrote: > > Hi Chuck, > > On Thu, Mar 12, 2020 at 03:16:37PM -0400, Chuck Lever wrote: >>> +static __be32 >>> +nfsd4_decode_setxattr(struct nfsd4_compoundargs *argp, >>> + struct nfsd4_setxattr *setxattr) >>> +{ >>> + DECODE_HEAD; >>> + u32 flags, maxcount, size; >>> + struct kvec head; >>> + struct page **pagelist; >>> + >>> + READ_BUF(4); >>> + flags = be32_to_cpup(p++); >>> + >>> + if (flags > SETXATTR4_REPLACE) >>> + return nfserr_inval; >>> + setxattr->setxa_flags = flags; >>> + >>> + status = nfsd4_decode_xattr_name(argp, &setxattr->setxa_name); >>> + if (status) >>> + return status; >>> + >>> + maxcount = svc_max_payload(argp->rqstp); >>> + maxcount = min_t(u32, XATTR_SIZE_MAX, maxcount); >>> + >>> + READ_BUF(4); >>> + size = be32_to_cpup(p++); >>> + if (size > maxcount) >>> + return nfserr_xattr2big; >>> + >>> + setxattr->setxa_len = size; >>> + if (size > 0) { >>> + status = svcxdr_construct_vector(argp, &head, &pagelist, size); >>> + if (status) >>> + return status; >>> + >>> + status = nfsd4_vbuf_from_stream(argp, &head, pagelist, >>> + &setxattr->setxa_buf, size); >>> + } >> >> Now I'm wondering if read_bytes_from_xdr_buf() might be adequate >> for this purpose, so you can avoid open-coding all of this logic. > > This took a little longer, I had to check my notes, Thanks for checking! > but basically the reasons for doing it this way are: > > * The nfsd decode path uses nfsd4_compoundargs, which doesn't have an > xdr_stream (it has a page array). So read_bytes_from_xdr_buf isn't > a natural fit. > * READ_BUF/read_buf don't deal with > PAGE_SIZE chunks, but xattrs may > be larger than that. > > The other code that deals with > PAGE_SIZE chunks is the write code. So, > I factored out some code from the write code, used that, and added a function > to process the resulting kvec / pagelist (nfs4_vbuf_from_stream). Yes, I only recently discovered that the xdr_stream helpers do not work for decoding data items that are page size or larger. For the record, my IMA prototype takes this approach for decode_fattr: + if (bmval[2] & FATTR4_WORD2_IMA) { + READ_BUF(4); + len += 4; + dummy32 = be32_to_cpup(p++); + READ_BUF(dummy32); + if (dummy32 > NFS4_MAXIMALEN) + return nfserr_badlabel; + *ima = svcxdr_tmpalloc(argp, sizeof(**ima)); + if (*ima == NULL) + return nfserr_jukebox; + + len += (XDR_QUADLEN(dummy32) << 2); + READMEM(buf, dummy32); + (*ima)->len = dummy32; + (*ima)->data = svcxdr_dupstr(argp, buf, dummy32); + if ((*ima)->data == NULL) + return nfserr_jukebox; + } else + ima = NULL; Although, an IMA blob is never larger than a page, IIRC. > There definitely seem to be several copy functions in both the client > and server code that basically do the same, but in slightly different ways, > depending on whether they use an XDR buf or not, whether the pages are > mapped or not, etc. Seems like a good candidate for a cleanup, but I > considered it to be out of scope for these patches. "out of scope" - probably so. Depending on Bruce's thinking, we can add this to the list of janitorial tasks. For my peace of mind, "from_stream" implies there _is_ an xdr_stream in use, even though the function does not have a struct xdr_stream parameter. Perhaps a different naming scheme would be wise. -- Chuck Lever
On Fri, Mar 20, 2020 at 01:34:53PM -0400, Chuck Lever wrote: > > > > On Mar 20, 2020, at 12:47 PM, Frank van der Linden <fllinden@amazon.com> wrote: > > There definitely seem to be several copy functions in both the client > > and server code that basically do the same, but in slightly different ways, > > depending on whether they use an XDR buf or not, whether the pages are > > mapped or not, etc. Seems like a good candidate for a cleanup, but I > > considered it to be out of scope for these patches. > > "out of scope" - probably so. Depending on Bruce's thinking, we can > add this to the list of janitorial tasks. The rewrite of the server xdr encoding was... oh jeez, six years ago now, and I haven't really thought about it since. I remember thinking we should do the decode side as well someday, though, and thinking it should be pretty doable. --b.
On Fri, Mar 20, 2020 at 01:34:53PM -0400, Chuck Lever wrote: > > For my peace of mind, "from_stream" implies there _is_ an xdr_stream > in use, even though the function does not have a struct xdr_stream > parameter. Perhaps a different naming scheme would be wise. Sure. I'll change it to "from_vector", which matches the name of the "svcxdr_construct_vector" function. Not 100% correct, since it's really a vector + page array, but it seems better. - Frank
On Thu, Mar 12, 2020 at 12:24:18PM -0400, Chuck Lever wrote: > > On Mar 11, 2020, at 3:59 PM, Frank van der Linden <fllinden@amazon.com> wrote: > > + /* > > + * Unfortunately, there is no interface to only list xattrs for > > + * one prefix. So there is no good way to convert maxcount to > > + * a maximum value to pass to vfs_listxattr, as we don't know > > + * how many of the returned attributes will be user attributes. > > + * > > + * So, always ask vfs_listxattr for the maximum size, and encode > > + * as many as possible. > > + */ > > Well, this approach worries me a little bit. Wouldn't it be better if the > VFS provided the APIs? Review by linux-fsdevel might help here. I missed this comment initially, sorry about the slow reply. I'll copy this one to -fsdevel for v2. It would require a modified or new entry point to all filesystems to support this properly, so I didn't touch it. It's not a complex task, it just would lead to quite a bit of code churn. - Frank
> On Mar 25, 2020, at 7:44 PM, Frank van der Linden <fllinden@amazon.com> wrote: > > On Thu, Mar 12, 2020 at 12:24:18PM -0400, Chuck Lever wrote: >>> On Mar 11, 2020, at 3:59 PM, Frank van der Linden <fllinden@amazon.com> wrote: >>> + /* >>> + * Unfortunately, there is no interface to only list xattrs for >>> + * one prefix. So there is no good way to convert maxcount to >>> + * a maximum value to pass to vfs_listxattr, as we don't know >>> + * how many of the returned attributes will be user attributes. >>> + * >>> + * So, always ask vfs_listxattr for the maximum size, and encode >>> + * as many as possible. >>> + */ >> >> Well, this approach worries me a little bit. Wouldn't it be better if the >> VFS provided the APIs? Review by linux-fsdevel might help here. > > I missed this comment initially, sorry about the slow reply. > > I'll copy this one to -fsdevel for v2. > > It would require a modified or new entry point to all filesystems to > support this properly, so I didn't touch it. It's not a complex > task, it just would lead to quite a bit of code churn. Yep, I recognize it would be a substantial chunk of work. Passing it by -fsdevel is the right thing to do, IMO. Thanks! -- Chuck Lever
diff --git a/fs/nfsd/nfs4proc.c b/fs/nfsd/nfs4proc.c index a76b9025a357..44d488bdebd9 100644 --- a/fs/nfsd/nfs4proc.c +++ b/fs/nfsd/nfs4proc.c @@ -2778,6 +2778,42 @@ static inline u32 nfsd4_seek_rsize(struct svc_rqst *rqstp, struct nfsd4_op *op) return (op_encode_hdr_size + 3) * sizeof(__be32); } +static inline u32 nfsd4_getxattr_rsize(struct svc_rqst *rqstp, + struct nfsd4_op *op) +{ + u32 maxcount, rlen; + + maxcount = svc_max_payload(rqstp); + rlen = min_t(u32, XATTR_SIZE_MAX, maxcount); + + return (op_encode_hdr_size + 1 + XDR_QUADLEN(rlen)) * sizeof(__be32); +} + +static inline u32 nfsd4_setxattr_rsize(struct svc_rqst *rqstp, + struct nfsd4_op *op) +{ + return (op_encode_hdr_size + op_encode_change_info_maxsz) + * sizeof(__be32); +} +static inline u32 nfsd4_listxattrs_rsize(struct svc_rqst *rqstp, + struct nfsd4_op *op) +{ + u32 maxcount, rlen; + + maxcount = svc_max_payload(rqstp); + rlen = min(op->u.listxattrs.lsxa_maxcount, maxcount); + + return (op_encode_hdr_size + 4 + XDR_QUADLEN(rlen)) * sizeof(__be32); +} + +static inline u32 nfsd4_removexattr_rsize(struct svc_rqst *rqstp, + struct nfsd4_op *op) +{ + return (op_encode_hdr_size + op_encode_change_info_maxsz) + * sizeof(__be32); +} + + static const struct nfsd4_operation nfsd4_ops[LAST_NFS4_OP + 1] = { [OP_ACCESS] = { .op_func = nfsd4_access, diff --git a/fs/nfsd/nfs4xdr.c b/fs/nfsd/nfs4xdr.c index b12d7ac6f52c..41c8b95ca1c5 100644 --- a/fs/nfsd/nfs4xdr.c +++ b/fs/nfsd/nfs4xdr.c @@ -1879,6 +1879,224 @@ nfsd4_decode_seek(struct nfsd4_compoundargs *argp, struct nfsd4_seek *seek) DECODE_TAIL; } +/* + * XDR data that is more than PAGE_SIZE in size is normally part of a + * read or write. However, the size of extended attributes is limited + * by the maximum request size, and then further limited by the underlying + * filesystem limits. This can exceed PAGE_SIZE (currently, XATTR_SIZE_MAX + * is 64k). Since there is no kvec- or page-based interface to xattrs, + * and we're not dealing with contiguous pages, we need to do some copying. + */ + +/* + * Decode int to buffer. Uses head and pages constructed by + * svcxdr_construct_vector. + */ +static int +nfsd4_vbuf_from_stream(struct nfsd4_compoundargs *argp, struct kvec *head, + struct page **pages, char **bufp, u32 buflen) +{ + char *tmp, *dp; + u32 len; + + if (buflen <= head->iov_len) { + /* + * We're in luck, the head has enough space. Just return + * the head, no need for copying. + */ + *bufp = head->iov_base; + return 0; + } + + tmp = svcxdr_tmpalloc(argp, buflen); + if (tmp == NULL) + return nfserr_jukebox; + + dp = tmp; + memcpy(dp, head->iov_base, head->iov_len); + buflen -= head->iov_len; + dp += head->iov_len; + + while (buflen > 0) { + len = min_t(u32, buflen, PAGE_SIZE); + memcpy(dp, page_address(*pages), len); + + buflen -= len; + dp += len; + pages++; + } + + *bufp = tmp; + return 0; +} + +/* + * Get a user extended attribute name from the XDR buffer. + * It will not have the "user." prefix, so prepend it. + * Lastly, check for nul characters in the name. + */ +static int +nfsd4_decode_xattr_name(struct nfsd4_compoundargs *argp, char **namep) +{ + DECODE_HEAD; + char *name, *sp, *dp; + u32 namelen, cnt; + + READ_BUF(4); + namelen = be32_to_cpup(p++); + + if (namelen > (XATTR_NAME_MAX - XATTR_USER_PREFIX_LEN)) + return nfserr_nametoolong; + + if (namelen == 0) + goto xdr_error; + + READ_BUF(namelen); + + name = svcxdr_tmpalloc(argp, namelen + XATTR_USER_PREFIX_LEN + 1); + if (!name) + return nfserr_jukebox; + + memcpy(name, XATTR_USER_PREFIX, XATTR_USER_PREFIX_LEN); + + /* + * Copy the extended attribute name over while checking for 0 + * characters. + */ + sp = (char *)p; + dp = name + XATTR_USER_PREFIX_LEN; + cnt = namelen; + + while (cnt-- > 0) { + if (*sp == '\0') + goto xdr_error; + *dp++ = *sp++; + } + *dp = '\0'; + + *namep = name; + + DECODE_TAIL; +} + +/* + * A GETXATTR op request comes without a length specifier. We just set the + * maximum length for the reply based on XATTR_SIZE_MAX and the maximum + * channel reply size, allocate a buffer of that length and pass it to + * vfs_getxattr. + */ +static __be32 +nfsd4_decode_getxattr(struct nfsd4_compoundargs *argp, + struct nfsd4_getxattr *getxattr) +{ + int status; + u32 maxcount; + + status = nfsd4_decode_xattr_name(argp, &getxattr->getxa_name); + if (status) + return status; + + maxcount = svc_max_payload(argp->rqstp); + maxcount = min_t(u32, XATTR_SIZE_MAX, maxcount); + + getxattr->getxa_buf = svcxdr_tmpalloc(argp, maxcount); + if (!getxattr->getxa_buf) + status = nfserr_jukebox; + getxattr->getxa_len = maxcount; + + return status; +} + +static __be32 +nfsd4_decode_setxattr(struct nfsd4_compoundargs *argp, + struct nfsd4_setxattr *setxattr) +{ + DECODE_HEAD; + u32 flags, maxcount, size; + struct kvec head; + struct page **pagelist; + + READ_BUF(4); + flags = be32_to_cpup(p++); + + if (flags > SETXATTR4_REPLACE) + return nfserr_inval; + setxattr->setxa_flags = flags; + + status = nfsd4_decode_xattr_name(argp, &setxattr->setxa_name); + if (status) + return status; + + maxcount = svc_max_payload(argp->rqstp); + maxcount = min_t(u32, XATTR_SIZE_MAX, maxcount); + + READ_BUF(4); + size = be32_to_cpup(p++); + if (size > maxcount) + return nfserr_xattr2big; + + setxattr->setxa_len = size; + if (size > 0) { + status = svcxdr_construct_vector(argp, &head, &pagelist, size); + if (status) + return status; + + status = nfsd4_vbuf_from_stream(argp, &head, pagelist, + &setxattr->setxa_buf, size); + } + + DECODE_TAIL; +} + +static __be32 +nfsd4_decode_listxattrs(struct nfsd4_compoundargs *argp, + struct nfsd4_listxattrs *listxattrs) +{ + DECODE_HEAD; + u32 maxcount; + + READ_BUF(12); + p = xdr_decode_hyper(p, &listxattrs->lsxa_cookie); + + /* + * If the cookie is too large to have even one user.x attribute + * plus trailing '\0' left in a maximum size buffer, it's invalid. + */ + if (listxattrs->lsxa_cookie >= + (XATTR_LIST_MAX / (XATTR_USER_PREFIX_LEN + 2))) + return nfserr_badcookie; + + maxcount = be32_to_cpup(p++); + if (maxcount < 8) + /* Always need at least 2 words (length and one character) */ + return nfserr_inval; + + maxcount = min(maxcount, svc_max_payload(argp->rqstp)); + listxattrs->lsxa_maxcount = maxcount; + + /* + * Unfortunately, there is no interface to only list xattrs for + * one prefix. So there is no good way to convert maxcount to + * a maximum value to pass to vfs_listxattr, as we don't know + * how many of the returned attributes will be user attributes. + * + * So, always ask vfs_listxattr for the maximum size, and encode + * as many as possible. + */ + listxattrs->lsxa_buf = svcxdr_tmpalloc(argp, XATTR_LIST_MAX); + if (!listxattrs->lsxa_buf) + status = nfserr_jukebox; + + DECODE_TAIL; +} + +static __be32 +nfsd4_decode_removexattr(struct nfsd4_compoundargs *argp, + struct nfsd4_removexattr *removexattr) +{ + return nfsd4_decode_xattr_name(argp, &removexattr->rmxa_name); +} + static __be32 nfsd4_decode_noop(struct nfsd4_compoundargs *argp, void *p) { @@ -1975,6 +2193,11 @@ static const nfsd4_dec nfsd4_dec_ops[] = { [OP_SEEK] = (nfsd4_dec)nfsd4_decode_seek, [OP_WRITE_SAME] = (nfsd4_dec)nfsd4_decode_notsupp, [OP_CLONE] = (nfsd4_dec)nfsd4_decode_clone, + /* RFC 8276 extended atributes operations */ + [OP_GETXATTR] = (nfsd4_dec)nfsd4_decode_getxattr, + [OP_SETXATTR] = (nfsd4_dec)nfsd4_decode_setxattr, + [OP_LISTXATTRS] = (nfsd4_dec)nfsd4_decode_listxattrs, + [OP_REMOVEXATTR] = (nfsd4_dec)nfsd4_decode_removexattr, }; static inline bool @@ -4458,6 +4681,225 @@ nfsd4_encode_noop(struct nfsd4_compoundres *resp, __be32 nfserr, void *p) return nfserr; } +/* + * Encode kmalloc-ed buffer in to XDR stream. + */ +static int +nfsd4_vbuf_to_stream(struct xdr_stream *xdr, char *buf, u32 buflen) +{ + u32 cplen; + __be32 *p; + + cplen = min_t(unsigned long, buflen, + ((void *)xdr->end - (void *)xdr->p)); + p = xdr_reserve_space(xdr, cplen); + if (!p) + return nfserr_resource; + + memcpy(p, buf, cplen); + buf += cplen; + buflen -= cplen; + + while (buflen) { + cplen = min_t(u32, buflen, PAGE_SIZE); + p = xdr_reserve_space(xdr, cplen); + if (!p) + return nfserr_resource; + + memcpy(p, buf, cplen); + + if (cplen < PAGE_SIZE) { + /* + * We're done, with a length that wasn't page + * aligned, so possibly not word aligned. Pad + * any trailing bytes with 0. + */ + xdr_encode_opaque_fixed(p, NULL, cplen); + break; + } + + buflen -= PAGE_SIZE; + buf += PAGE_SIZE; + } + + return 0; +} + +static __be32 +nfsd4_encode_getxattr(struct nfsd4_compoundres *resp, __be32 nfserr, + struct nfsd4_getxattr *getxattr) +{ + struct xdr_stream *xdr = &resp->xdr; + __be32 *p; + + p = xdr_reserve_space(xdr, 4); + if (!p) + return nfserr_resource; + + *p = cpu_to_be32(getxattr->getxa_len); + + if (getxattr->getxa_len == 0) + return 0; + + return nfsd4_vbuf_to_stream(xdr, getxattr->getxa_buf, + getxattr->getxa_len); +} + +static __be32 +nfsd4_encode_setxattr(struct nfsd4_compoundres *resp, __be32 nfserr, + struct nfsd4_setxattr *setxattr) +{ + struct xdr_stream *xdr = &resp->xdr; + __be32 *p; + + p = xdr_reserve_space(xdr, 20); + if (!p) + return nfserr_resource; + + encode_cinfo(p, &setxattr->setxa_cinfo); + + return 0; +} + +/* + * See if there are cookie values that can be rejected outright. + */ +static int +nfsd4_listxattr_validate_cookie(struct nfsd4_listxattrs *listxattrs, + u32 *offsetp) +{ + u64 cookie = listxattrs->lsxa_cookie; + + /* + * If the cookie is larger than the maximum number we can fit + * in either the buffer we just got back from vfs_listxattr, or, + * XDR-encoded, in the return buffer, it's invalid. + */ + if (cookie > (listxattrs->lsxa_len) / (XATTR_USER_PREFIX_LEN + 2)) + return nfserr_badcookie; + + if (cookie > (listxattrs->lsxa_maxcount / + (XDR_QUADLEN(XATTR_USER_PREFIX_LEN + 2) + 4))) + return nfserr_badcookie; + + *offsetp = (u32)cookie; + return 0; +} + +static __be32 +nfsd4_encode_listxattrs(struct nfsd4_compoundres *resp, __be32 nfserr, + struct nfsd4_listxattrs *listxattrs) +{ + struct xdr_stream *xdr = &resp->xdr; + u32 cookie_offset, count_offset, eof; + u32 left, xdrleft, slen, count; + u32 xdrlen, offset; + u64 cookie; + char *sp; + int status; + __be32 *p; + u32 nuser; + + eof = 1; + + status = nfsd4_listxattr_validate_cookie(listxattrs, &offset); + if (status) + return status; + + /* + * Reserve space for the cookie and the name array count. Record + * the offsets to save them later. + */ + cookie_offset = xdr->buf->len; + count_offset = cookie_offset + 8; + p = xdr_reserve_space(xdr, 12); + if (!p) + return nfserr_resource; + + count = 0; + left = listxattrs->lsxa_len; + sp = listxattrs->lsxa_buf; + nuser = 0; + + xdrleft = listxattrs->lsxa_maxcount; + + while (left > 0 && xdrleft > 0) { + slen = strlen(sp); + + /* + * Check if this a user. attribute, skip it if not. + */ + if (strncmp(sp, XATTR_USER_PREFIX, XATTR_USER_PREFIX_LEN)) + goto contloop; + + slen -= XATTR_USER_PREFIX_LEN; + xdrlen = 4 + ((slen + 3) & ~3); + if (xdrlen > xdrleft) { + if (count == 0) { + /* + * Can't even fit the first attribute name. + */ + return nfserr_toosmall; + } + eof = 0; + goto wreof; + } + + left -= XATTR_USER_PREFIX_LEN; + sp += XATTR_USER_PREFIX_LEN; + if (nuser++ < offset) + goto contloop; + + + p = xdr_reserve_space(xdr, xdrlen); + if (!p) + return nfserr_resource; + + p = xdr_encode_opaque(p, sp, slen); + + xdrleft -= xdrlen; + count++; +contloop: + sp += slen + 1; + left -= slen + 1; + } + + /* + * If there were user attributes to copy, but we didn't copy + * any, the offset was too large (e.g. the cookie was invalid). + */ + if (nuser > 0 && count == 0) + return nfserr_badcookie; + +wreof: + p = xdr_reserve_space(xdr, 4); + if (!p) + return nfserr_resource; + *p = cpu_to_be32(eof); + + cookie = offset + count; + + write_bytes_to_xdr_buf(xdr->buf, cookie_offset, &cookie, 8); + count = htonl(count); + write_bytes_to_xdr_buf(xdr->buf, count_offset, &count, 4); + return 0; +} + +static __be32 +nfsd4_encode_removexattr(struct nfsd4_compoundres *resp, __be32 nfserr, + struct nfsd4_removexattr *removexattr) +{ + struct xdr_stream *xdr = &resp->xdr; + __be32 *p; + + p = xdr_reserve_space(xdr, 20); + if (!p) + return nfserr_resource; + + p = encode_cinfo(p, &removexattr->rmxa_cinfo); + return 0; +} + typedef __be32(* nfsd4_enc)(struct nfsd4_compoundres *, __be32, void *); /* @@ -4547,6 +4989,12 @@ static const nfsd4_enc nfsd4_enc_ops[] = { [OP_SEEK] = (nfsd4_enc)nfsd4_encode_seek, [OP_WRITE_SAME] = (nfsd4_enc)nfsd4_encode_noop, [OP_CLONE] = (nfsd4_enc)nfsd4_encode_noop, + + /* RFC 8276 extended atributes operations */ + [OP_GETXATTR] = (nfsd4_enc)nfsd4_encode_getxattr, + [OP_SETXATTR] = (nfsd4_enc)nfsd4_encode_setxattr, + [OP_LISTXATTRS] = (nfsd4_enc)nfsd4_encode_listxattrs, + [OP_REMOVEXATTR] = (nfsd4_enc)nfsd4_encode_removexattr, }; /*
Add functions to calculate the reply size for the user extended attribute operations, and implement the XDR encode / decode logic for these operations. Signed-off-by: Frank van der Linden <fllinden@amazon.com> --- fs/nfsd/nfs4proc.c | 36 +++++ fs/nfsd/nfs4xdr.c | 448 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 484 insertions(+)