Skip to content

Commit

Permalink
Linux: Cross-platform user namespace xattrs compat
Browse files Browse the repository at this point in the history
ZFS on Linux originally implemented xattr namespaces in a way that is
incompatible with other operating systems.  On illumos, xattrs do not
have namespaces.  Every xattr name is visible.  FreeBSD has two
universally defined namespaces: EXTATTR_NAMESPACE_USER and
EXTATTR_NAMESPACE_SYSTEM.  The system namespace is used for protected
FreeBSD-specific attributes such as MAC labels and pnfs state.  These
attributes have the namespace string "freebsd:system:" prefixed to the
name in the encoding scheme used by ZFS.  The user namespace is used
for general purpose user attributes and obeys normal access control
mechanisms.  These attributes have no namespace string prefixed, so
xattrs written on illumos are accessible in the user namespace on
FreeBSD, and xattrs written to the user namespace on FreeBSD are
accessible by the same name on illumos.

Linux has several xattr namespaces.  The way xattrs were implemented
in ZFS for Linux encodes the namespace in the xattr name for every
namespace, including the user namespace.  As a consequence, an xattr
in the user namespace with the name "foo" is stored by ZFS with the
name "user.foo" and therefore appears on FreeBSD and illumos to have
the name "user.foo" rather than "foo".  Conversely, none of the xattrs
written on FreeBSD or illumos are accessible on Linux unless the name
happens to be prefixed with one of the Linux xattr namespaces, in which
case the namespace is stripped from the name.  This makes xattrs
entirely incompatible between Linux and other platforms.

We want to make the encoding of user namespace xattrs compatible across
platforms.  A critical requirement of this compatibility is for xattrs
from existing pools from FreeBSD and illumos to be accessible by the
same names in the user namespace on Linux.  It is also necessary that
existing pools with xattrs written by Linux retain access to those
xattrs by the same names on Linux.  Making user namespace xattrs from
Linux accessible by the correct names on other platforms is important.
The handling of other namespaces is not required to be consistent.

Add a fallback mechanism for listing and getting xattrs to treat xattrs
as being in the user namespace if they do not match a known prefix.

When setting user namespace xattrs, do not prefix the namespace to the
name.  If the xattr is already present with the namespace prefix,
remove it so only the non-prefixed version persists.  This ensures
other platforms will be able to read the xattr with the correct name.

Explicitly ignore freebsd:system namespace xattrs.

TODO:

* If xattrs with the user namespace prefix are already present, they
  will not be automatically fixed.  Any existing xattrs must be
  manually rewritten on Linux for the name to be correct on FreeBSD.
  This also means that files may have a mix of incompatible and
  compatible names.  Other platforms could strip the Linux user
  namespace prefix from xattr names so they are presented correctly.

* The newly written xattrs will no longer be visible on previous
  versions of ZFS on Linux.  This behavior needs to be made optional
  with a feature flag and possibly a per-dataset property.

* There is no attempt to handle xattr names that clash with a namespace
  prefix.  If you write an xattr named "user.foo" to the user namespace
  on FreeBSD, the "user." prefix will be stripped on Linux.  This was
  partially the case already, except now the stripped name will also
  replace the prefixed name when updating the xattr.  Likewise, setting
  an xattr to the user namespace using a name with the prefix of
  another namespace may cause the xattr to be manipulated in the other
  namespace.  This is potentially a security issue.  Such names must be
  forbidden.

* New tests should be added when the functionality is complete.

* Documentation will be needed.

Signed-off-by: Ryan Moeller <[email protected]>
  • Loading branch information
Ryan Moeller authored and Ryan Moeller committed Apr 14, 2021
1 parent fd099ae commit d39ca5b
Showing 1 changed file with 74 additions and 17 deletions.
91 changes: 74 additions & 17 deletions module/os/linux/zfs/zpl_xattr.c
Original file line number Diff line number Diff line change
Expand Up @@ -91,32 +91,39 @@ typedef struct xattr_filldir {
struct dentry *dentry;
} xattr_filldir_t;

static const struct xattr_handler *zpl_xattr_handler(const char *);
static const struct xattr_handler *zpl_xattr_handler(const char *, boolean_t *);

static int
enum xattr_permission {
XAPERM_COMPAT = -1,
XAPERM_DENY = 0,
XAPERM_ALLOW = 1,
};

static enum xattr_permission
zpl_xattr_permission(xattr_filldir_t *xf, const char *name, int name_len)
{
static const struct xattr_handler *handler;
const struct xattr_handler *handler;
struct dentry *d = xf->dentry;
boolean_t compat = B_FALSE;

handler = zpl_xattr_handler(name);
handler = zpl_xattr_handler(name, &compat);
if (!handler)
return (0);
return (XAPERM_DENY);

if (handler->list) {
#if defined(HAVE_XATTR_LIST_SIMPLE)
if (!handler->list(d))
return (0);
return (XAPERM_DENY);
#elif defined(HAVE_XATTR_LIST_DENTRY)
if (!handler->list(d, NULL, 0, name, name_len, 0))
return (0);
return (XAPERM_DENY);
#elif defined(HAVE_XATTR_LIST_HANDLER)
if (!handler->list(handler, d, NULL, 0, name, name_len))
return (0);
return (XAPERM_DENY);
#endif
}

return (1);
return (compat ? XAPERM_COMPAT : XAPERM_ALLOW);
}

/*
Expand All @@ -126,10 +133,27 @@ zpl_xattr_permission(xattr_filldir_t *xf, const char *name, int name_len)
static int
zpl_xattr_filldir(xattr_filldir_t *xf, const char *name, int name_len)
{
enum xattr_permission perm;

/* Check permissions using the per-namespace list xattr handler. */
if (!zpl_xattr_permission(xf, name, name_len))
perm = zpl_xattr_permission(xf, name, name_len);
if (perm == XAPERM_DENY)
return (0);

/* Prefix the name with "user." if it does not have a namespace. */
if (perm == XAPERM_COMPAT) {
if (xf->buf) {
if (xf->offset + XATTR_USER_PREFIX_LEN + 1 > xf->size)
return (-ERANGE);

memcpy(xf->buf + xf->offset, XATTR_USER_PREFIX,
XATTR_USER_PREFIX_LEN);
xf->buf[xf->offset + XATTR_USER_PREFIX_LEN] = '\0';
}

xf->offset += XATTR_USER_PREFIX_LEN;
}

/* When xf->buf is NULL only calculate the required size. */
if (xf->buf) {
if (xf->offset + name_len + 1 > xf->size)
Expand Down Expand Up @@ -706,7 +730,6 @@ static int
__zpl_xattr_user_get(struct inode *ip, const char *name,
void *value, size_t size)
{
char *xattr_name;
int error;
/* xattr_resolve_name will do this for us if this is defined */
#ifndef HAVE_XATTR_HANDLER_NAME
Expand All @@ -716,9 +739,18 @@ __zpl_xattr_user_get(struct inode *ip, const char *name,
if (!(ITOZSB(ip)->z_flags & ZSB_XATTR))
return (-EOPNOTSUPP);

xattr_name = kmem_asprintf("%s%s", XATTR_USER_PREFIX, name);
error = zpl_xattr_get(ip, xattr_name, value, size);
kmem_strfree(xattr_name);
/*
* Try to look up the name without the namespace prefix first for
* compatibility with xattrs from other platforms. If that fails,
* try again with the namespace prefix.
*/
error = zpl_xattr_get(ip, name, value, size);
if (error == -ENODATA) {
char *xattr_name;
xattr_name = kmem_asprintf("%s%s", XATTR_USER_PREFIX, name);
error = zpl_xattr_get(ip, xattr_name, value, size);
kmem_strfree(xattr_name);
}

return (error);
}
Expand All @@ -738,9 +770,23 @@ __zpl_xattr_user_set(struct inode *ip, const char *name,
if (!(ITOZSB(ip)->z_flags & ZSB_XATTR))
return (-EOPNOTSUPP);

/*
* Remove any namespaced version of the xattr so we only set the
* version compatible with other platforms.
*
* The following flags must be handled correctly:
*
* XATTR_CREATE: fail if xattr already exists
* XATTR_REPLACE: fail if xattr does not exist
*/
xattr_name = kmem_asprintf("%s%s", XATTR_USER_PREFIX, name);
error = zpl_xattr_set(ip, xattr_name, value, size, flags);
error = zpl_xattr_set(ip, xattr_name, NULL, 0, flags);
kmem_strfree(xattr_name);
if (error == -EEXIST)
return (error);
if (error == 0 && (flags & XATTR_REPLACE))
flags &= ~XATTR_REPLACE;
error = zpl_xattr_set(ip, name, value, size, flags);

return (error);
}
Expand Down Expand Up @@ -1358,8 +1404,10 @@ xattr_handler_t *zpl_xattr_handlers[] = {
};

static const struct xattr_handler *
zpl_xattr_handler(const char *name)
zpl_xattr_handler(const char *name, boolean_t *compat)
{
*compat = B_FALSE;

if (strncmp(name, XATTR_USER_PREFIX,
XATTR_USER_PREFIX_LEN) == 0)
return (&zpl_xattr_user_handler);
Expand All @@ -1382,7 +1430,16 @@ zpl_xattr_handler(const char *name)
return (&zpl_xattr_acl_default_handler);
#endif /* CONFIG_FS_POSIX_ACL */

return (NULL);
/* Do not expose FreeBSD system namespace xattrs. */
if (strncmp(name, "freebsd:system:", sizeof ("freebsd:system")) == 0)
return (NULL);

/*
* Anything that doesn't match a known namespace gets put in the user
* namespace for compatibility with other platforms.
*/
*compat = B_TRUE;
return (&zpl_xattr_user_handler);
}

#if !defined(HAVE_POSIX_ACL_RELEASE) || defined(HAVE_POSIX_ACL_RELEASE_GPL_ONLY)
Expand Down

0 comments on commit d39ca5b

Please sign in to comment.