From: "Dr. David Alan Gilbert" <dgilbert@redhat.com>
To: Christophe de Dinechin <dinechin@redhat.com>
Cc: virtio-fs@redhat.com, stefanha@redhat.com, vgoyal@redhat.com,
qemu-devel@nongnu.org
Subject: Re: [PATCH v2 3/6] tools/virtiofsd: xattr name mappings: Add option
Date: Wed, 14 Oct 2020 16:40:11 +0100 [thread overview]
Message-ID: <20201014154011.GA20147@work-vm> (raw)
In-Reply-To: <lyimbnl5ej.fsf@redhat.com>
* Christophe de Dinechin (dinechin@redhat.com) wrote:
>
> On 2020-08-27 at 17:36 CEST, Dr. David Alan Gilbert (git) wrote...
> > From: "Dr. David Alan Gilbert" <dgilbert@redhat.com>
> >
> > Add an option to define mappings of xattr names so that
> > the client and server filesystems see different views.
> > This can be used to have different SELinux mappings as
> > seen by the guest, to run the virtiofsd with less privileges
> > (e.g. in a case where it can't set trusted/system/security
> > xattrs but you want the guest to be able to), or to isolate
> > multiple users of the same name; e.g. trusted attributes
> > used by stacking overlayfs.
> >
> > A mapping engine is used wit 3 simple rules; the rules can
> > be combined to allow most useful mapping scenarios.
> > The ruleset is defined by -o xattrmap='rules...'.
> >
> > This patch doesn't use the rule maps yet.
> >
> > Signed-off-by: Dr. David Alan Gilbert <dgilbert@redhat.com>
> > ---
> > docs/tools/virtiofsd.rst | 55 ++++++++++++
> > tools/virtiofsd/passthrough_ll.c | 148 +++++++++++++++++++++++++++++++
> > 2 files changed, 203 insertions(+)
> >
> > diff --git a/docs/tools/virtiofsd.rst b/docs/tools/virtiofsd.rst
> > index 824e713491..2efa16d3c5 100644
> > --- a/docs/tools/virtiofsd.rst
> > +++ b/docs/tools/virtiofsd.rst
> > @@ -107,6 +107,60 @@ Options
> > performance. ``auto`` acts similar to NFS with a 1 second metadata cache
> > timeout. ``always`` sets a long cache lifetime at the expense of coherency.
> >
> > +xattr-mapping
> > +-------------
> > +
> > +By default the name of xattr's used by the client are passed through to the server
> > +file system. This can be a problem where either those xattr names are used
> > +by something on the server (e.g. selinux client/server confusion) or if the
> > +virtiofsd is running in a container with restricted priviliges where it cannot
> > +access some attributes.
> > +
> > +A mapping of xattr names can be made using -o xattrmap=mapping where the ``mapping``
> > +string consists of a series of rules.
> > +
> > +The first matching rule terminates the mapping.
> > +
> > +Each rule consists of a number of fields separated with a separator that is the
> > +first non-white space character in the rule. This separator must then be used
> > +for the whole rule.
> > +White space may be added before and after each rule.
> > +Using ':' as the separator a rule is of the form:
> > +
> > +``:scope:type:key:prepend:``
> > +
> > +**scope** is:
> > +
> > +- 'client' - match 'key' against a xattr name from the client for
> > + setxattr/getxattr/removexattr
> > +- 'server' - match 'prepend' against a xattr name from the server
> > + for listxattr
> > +- 'all' - can be used to match both cases.
> > +
> > +**type** is one of:
> > +
> > +- 'prefix' - If 'key' matches the client then the 'prepend'
> > + is added before the name is passed to the server.
> > + For a server case, the prepend is tested and stripped
> > + if matching.
> > +
> > +- 'ok' - The attribute name is OK and passed through to
> > + the server unchanged.
> > +
> > +- 'bad' - If a client tries to use this name it's
> > + denied using EPERM; when the server passes an attribute
> > + name matching it's hidden.
> > +
> > +**key** is a string tested as a prefix on an attribute name originating
> > +on the client. It maybe empty in which case a 'client' rule
> > +will always match on client names.
> > +
> > +**prepend** is a string tested as a prefix on an attribute name originiating
> > +on the server, and used as a new prefix. It maybe empty
> > +in which case a 'server' rule will always match on all names from
> > +the server.
> > +
> > +
> > Examples
> > --------
> >
> > @@ -123,3 +177,4 @@ Export ``/var/lib/fs/vm001/`` on vhost-user UNIX domain socket
> > -numa node,memdev=mem \
> > ...
> > guest# mount -t virtiofs myfs /mnt
> > +
> > diff --git a/tools/virtiofsd/passthrough_ll.c b/tools/virtiofsd/passthrough_ll.c
> > index 083d17a960..00e96a10cd 100644
> > --- a/tools/virtiofsd/passthrough_ll.c
> > +++ b/tools/virtiofsd/passthrough_ll.c
> > @@ -64,6 +64,7 @@
> > #include <syslog.h>
> > #include <unistd.h>
> >
> > +#include "qemu/cutils.h"
> > #include "passthrough_helpers.h"
> > #include "passthrough_seccomp.h"
> >
> > @@ -144,6 +145,7 @@ struct lo_data {
> > int flock;
> > int posix_lock;
> > int xattr;
> > + char *xattrmap;
>
> Who owns that field? Should it be cleaned up in fuse_lo_data_cleanup() just like
> source is?
Done.
> > char *source;
> > char *modcaps;
> > double timeout;
> > @@ -171,6 +173,7 @@ static const struct fuse_opt lo_opts[] = {
> > { "no_posix_lock", offsetof(struct lo_data, posix_lock), 0 },
> > { "xattr", offsetof(struct lo_data, xattr), 1 },
> > { "no_xattr", offsetof(struct lo_data, xattr), 0 },
> > + { "xattrmap=%s", offsetof(struct lo_data, xattrmap), 0 },
> > { "modcaps=%s", offsetof(struct lo_data, modcaps), 0 },
> > { "timeout=%lf", offsetof(struct lo_data, timeout), 0 },
> > { "timeout=", offsetof(struct lo_data, timeout_set), 1 },
> > @@ -2003,6 +2006,146 @@ static void lo_flock(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi,
> > fuse_reply_err(req, res == -1 ? errno : 0);
> > }
> >
> > +typedef struct xattr_map_entry {
> > + const char *key;
> > + const char *prepend;
> > + unsigned int flags;
> > +} XattrMapEntry;
> > +
> > +/*
> > + * Exit; process attribute unmodified if matched.
> > + * An empty key applies to all.
> > + */
> > +#define XATTR_MAP_FLAG_END_OK (1 << 0)
> > +/*
> > + * The attribute is unwanted;
> > + * EPERM on write hidden on read.
> > + */
> > +#define XATTR_MAP_FLAG_END_BAD (1 << 1)
> > +/*
> > + * For attr that start with 'key' prepend 'prepend'
> > + * 'key' maybe empty to prepend for all attrs
> > + * key is defined from set/remove point of view.
> > + * Automatically reversed on read
> > + */
> > +#define XATTR_MAP_FLAG_PREFIX (1 << 2)
> > +/* Apply rule to get/set/remove */
> > +#define XATTR_MAP_FLAG_CLIENT (1 << 16)
> > +/* Apply rule to list */
> > +#define XATTR_MAP_FLAG_SERVER (1 << 17)
> > +/* Apply rule to all */
> > +#define XATTR_MAP_FLAG_ALL (XATTR_MAP_FLAG_SERVER | XATTR_MAP_FLAG_CLIENT)
> > +
> > +static XattrMapEntry *xattr_map_list;
>
> Curious why you made it a static variable and not a field in struct lo_data?
Done.
> > +
> > +static XattrMapEntry *parse_xattrmap(const char *map)
> > +{
> > + XattrMapEntry *res = NULL;
> > + size_t nentries = 0;
> > + const char *tmp;
> > +
> > + while (*map) {
> > + char sep;
> > +
> > + if (isspace(*map)) {
> > + map++;
> > + continue;
> > + }
> > + /* The separator is the first non-space of the rule */
> > + sep = *map++;
> > + if (!sep) {
> > + break;
> > + }
> > +
> > + /* Allocate some space for the rule */
> > + res = g_realloc_n(res, ++nentries, sizeof(XattrMapEntry));
> > + res[nentries - 1].flags = 0;
>
> I would probably create an `entry` pointer to `res[nentries - 1]`
> since there are 9 uses for it.
I've reworked that whole bit; we've now got a temporary and a function
that adds an entry.
> > +
> > + if (strstart(map, "client", &map)) {
> > + res[nentries - 1].flags |= XATTR_MAP_FLAG_CLIENT;
> > + } else if (strstart(map, "server", &map)) {
> > + res[nentries - 1].flags |= XATTR_MAP_FLAG_SERVER;
> > + } else if (strstart(map, "all", &map)) {
> > + res[nentries - 1].flags |= XATTR_MAP_FLAG_ALL;
> > + } else {
> > + fuse_log(FUSE_LOG_ERR,
> > + "%s: Unexpected scope;"
> > + " Expecting 'client', 'server', or 'all', in rule %zu\n",
> > + __func__, nentries);
> > + exit(1);
> > + }
> > +
> > +
> > + if (*map != sep) {
> > + fuse_log(FUSE_LOG_ERR,
> > + "%s: Expecting '%c' found '%c'"
> > + " after scope in rule %zu\n",
> > + __func__, sep, *map, nentries + 1);
>
> I think it should be `nentries` here like in the others
Done.
> > + exit(1);
> > + }
> > + /* Skip the separator, now at the start of the 'type' */
> > + map++;
> > +
> > + /* Start of 'type' */
> > + if (strstart(map, "prefix", &map)) {
> > + res[nentries - 1].flags |= XATTR_MAP_FLAG_PREFIX;
> > + } else if (strstart(map, "ok", &map)) {
> > + res[nentries - 1].flags |= XATTR_MAP_FLAG_END_OK;
> > + } else if (strstart(map, "bad", &map)) {
> > + res[nentries - 1].flags |= XATTR_MAP_FLAG_END_BAD;
> > + } else {
> > + fuse_log(FUSE_LOG_ERR,
> > + "%s: Unexpected type;"
> > + "Expecting 'prefix', 'ok', or 'bad' in rule %zu\n",
> > + __func__, nentries);
> > + exit(1);
> > + }
> > +
> > + if (*map++ != sep) {
> > + fuse_log(FUSE_LOG_ERR,
> > + "%s: Missing '%c' at end of type field of rule %zu\n",
> > + __func__, sep, nentries);
> > + exit(1);
> > + }
> > +
> > + /* At start of 'key' field */
> > + tmp = strchr(map, sep);
> > + if (!tmp) {
> > + fuse_log(FUSE_LOG_ERR,
> > + "%s: Missing '%c' at end of key field of rule %zu",
> > + __func__, sep, nentries);
> > + exit(1);
> > + }
> > + res[nentries - 1].key = g_strndup(map, tmp - map);
> > + map = tmp + 1;
> > +
> > + /* At start of 'prepend' field */
> > + tmp = strchr(map, sep);
> > + if (!tmp) {
> > + fuse_log(FUSE_LOG_ERR,
> > + "%s: Missing '%c' at end of prepend field of rule %zu",
> > + __func__, sep, nentries);
> > + exit(1);
> > + }
> > + res[nentries - 1].prepend = g_strndup(map, tmp - map);
> > + map = tmp + 1;
> > + /* End of rule - go around again for another rule */
> > + }
> > +
> > + if (!nentries) {
> > + fuse_log(FUSE_LOG_ERR, "Empty xattr map\n");
> > + exit(1);
> > + }
> > +
> > + /* Add a terminator to error in cases the user hasn't specified */
> > + res = g_realloc_n(res, ++nentries, sizeof(XattrMapEntry));
> > + res[nentries - 1].flags = XATTR_MAP_FLAG_ALL | XATTR_MAP_FLAG_END_BAD;
> > + res[nentries - 1].key = g_strdup("");
> > + res[nentries - 1].prepend = g_strdup("");
> > +
> > + return res;
> > +}
> > +
> > static void lo_getxattr(fuse_req_t req, fuse_ino_t ino, const char *name,
> > size_t size)
> > {
> > @@ -2909,6 +3052,11 @@ int main(int argc, char *argv[])
> > } else {
> > lo.source = strdup("/");
> > }
> > +
> > + if (lo.xattrmap) {
> > + xattr_map_list = parse_xattrmap(lo.xattrmap);
>
> This is never freed. If you put the static in struct lo_data, you could
> naturally clean it up in fuse_lo_data_cleanup.
Cleanup added.
Dave
> > + }
> > +
> > if (!lo.timeout_set) {
> > switch (lo.cache) {
> > case CACHE_NONE:
>
>
> --
> Cheers,
> Christophe de Dinechin (IRC c3d)
--
Dr. David Alan Gilbert / dgilbert@redhat.com / Manchester, UK
next prev parent reply other threads:[~2020-10-14 15:44 UTC|newest]
Thread overview: 21+ messages / expand[flat|nested] mbox.gz Atom feed top
2020-08-27 15:36 [PATCH v2 0/6] virtiofsd xattr name mappings Dr. David Alan Gilbert (git)
2020-08-27 15:36 ` [PATCH v2 1/6] virtiofsd: Silence gcc warning Dr. David Alan Gilbert (git)
2020-09-09 11:16 ` Ján Tomko
2020-10-07 10:42 ` Dr. David Alan Gilbert
2020-08-27 15:36 ` [PATCH v2 2/6] virtiofsd: Add printf checking to fuse_log Dr. David Alan Gilbert (git)
2020-08-27 15:36 ` [PATCH v2 3/6] tools/virtiofsd: xattr name mappings: Add option Dr. David Alan Gilbert (git)
2020-09-09 11:20 ` Ján Tomko
2020-09-10 18:38 ` Dr. David Alan Gilbert
2020-09-11 21:13 ` [Virtio-fs] " Vivek Goyal
2020-09-18 17:38 ` Dr. David Alan Gilbert
2020-10-20 17:20 ` Vivek Goyal
2020-10-06 15:51 ` Christophe de Dinechin
2020-10-14 15:40 ` Dr. David Alan Gilbert [this message]
2020-08-27 15:36 ` [PATCH v2 4/6] tools/virtiofsd: xattr name mappings: Map client xattr names Dr. David Alan Gilbert (git)
2020-08-27 15:36 ` [PATCH v2 5/6] tools/virtiofsd: xattr name mappings: Map server " Dr. David Alan Gilbert (git)
2020-10-06 16:03 ` Christophe de Dinechin
2020-10-14 16:04 ` Dr. David Alan Gilbert
2020-10-06 16:17 ` Christophe de Dinechin
2020-08-27 15:36 ` [PATCH v2 6/6] tools/virtiofsd: xattr name mapping examples Dr. David Alan Gilbert (git)
2020-09-09 11:35 ` Ján Tomko
2020-09-10 18:42 ` Dr. David Alan Gilbert
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20201014154011.GA20147@work-vm \
--to=dgilbert@redhat.com \
--cc=dinechin@redhat.com \
--cc=qemu-devel@nongnu.org \
--cc=stefanha@redhat.com \
--cc=vgoyal@redhat.com \
--cc=virtio-fs@redhat.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).