From: Dave Wysochanski <dwysocha@redhat.com>
To: "J. Bruce Fields" <bfields@fieldses.org>
Cc: neilb@suse.com, linux-nfs@vger.kernel.org
Subject: Re: [RFC PATCH] SUNRPC: Harden the cache 'channel' interface to only allow legitimate daemons
Date: Fri, 26 Jul 2019 09:59:48 -0400 [thread overview]
Message-ID: <87bd4dd07544e18ffa9ed2c80f4155781cb1363e.camel@redhat.com> (raw)
In-Reply-To: <20190725215010.GA16304@fieldses.org>
On Thu, 2019-07-25 at 17:50 -0400, J. Bruce Fields wrote:
> On Thu, Jul 25, 2019 at 05:44:48PM -0400, Dave Wysochanski wrote:
> > The 'channel' interface has a heuristic that is based on the number
> > of
> > times any process opens it for reading. This has shown to be
> > problematic
> > and any rogue userspace process that just happens to open the
> > 'channel'
> > file for reading may throw off the kernel logic and even steal a
> > message
> > from the kernel.
> >
> > Harden this interface by making a small daemon state machine that
> > is based
> > on what the current userspace daemons actually do. Specifically
> > they open
> > the file either RW or W-only, then issue a poll(). Once these two
> > operations
> > have been done by the same pid, we mark it as 'registered' as the
> > daemon for
> > this cache. We then disallow any other pid to read or write to the
> > 'channel'
> > file by EIO any attempt.
>
> Is that part really necessary? mountd has a --num-threads option.
> Despite the name, I believe that creates actual process with fork
> (hence
> different pids).
>
This is so any random process reading a channel file is not able to
"steal" a message.
Yes you're right about mountd and I think this should be perfectly
valid to fork and so the pid check needs fixed or removed. I'll see if
I can work this out some other way without requiring a lockstep change
of the daemons and the kernel.
> --b.
>
> >
> > Signed-off-by: Dave Wysochanski <dwysocha@redhat.com>
> > ---
> > fs/nfsd/nfs4idmap.c | 4 ++--
> > include/linux/sunrpc/cache.h | 34 +++++++++++++++++++++++++----
> > net/sunrpc/cache.c | 52 +++++++++++++++++++++++++++++---
> > ------------
> > 3 files changed, 66 insertions(+), 24 deletions(-)
> >
> > diff --git a/fs/nfsd/nfs4idmap.c b/fs/nfsd/nfs4idmap.c
> > index d1f2852..a1f1f02 100644
> > --- a/fs/nfsd/nfs4idmap.c
> > +++ b/fs/nfsd/nfs4idmap.c
> > @@ -187,7 +187,7 @@ static struct ent *idtoname_update(struct
> > cache_detail *, struct ent *,
> > .cache_request = idtoname_request,
> > .cache_parse = idtoname_parse,
> > .cache_show = idtoname_show,
> > - .warn_no_listener = warn_no_idmapd,
> > + .warn_no_daemon = warn_no_idmapd,
> > .match = idtoname_match,
> > .init = ent_init,
> > .update = ent_init,
> > @@ -350,7 +350,7 @@ static struct ent *nametoid_update(struct
> > cache_detail *, struct ent *,
> > .cache_request = nametoid_request,
> > .cache_parse = nametoid_parse,
> > .cache_show = nametoid_show,
> > - .warn_no_listener = warn_no_idmapd,
> > + .warn_no_daemon = warn_no_idmapd,
> > .match = nametoid_match,
> > .init = ent_init,
> > .update = ent_init,
> > diff --git a/include/linux/sunrpc/cache.h
> > b/include/linux/sunrpc/cache.h
> > index c7f38e8..7fa9300 100644
> > --- a/include/linux/sunrpc/cache.h
> > +++ b/include/linux/sunrpc/cache.h
> > @@ -61,6 +61,31 @@ struct cache_head {
> >
> > #define CACHE_NEW_EXPIRY 120 /* keep new things pending
> > confirmation for 120 seconds */
> >
> > +/*
> > + * State machine for the userspace daemon servicing a cache.
> > + * Only one pid may be registered to the 'channel' file at any
> > given time.
> > + * A pid must transition to the final "POLL" state to finish
> > registration.
> > + * Any read or write to a 'channel' file by any pid other than the
> > registered
> > + * daemon pid will result in an EIO.
> > + *
> > + * Close
> > + * Open -------------------------> Open (daemon_pid = current)
> > + * open(channel)
> > + *
> > + * Open -------------------------> Poll
> > + * poll(channel) &&
> > + * daemon_pid == current
> > + *
> > + * Poll -------------------------> Close (daemon_pid = -1)
> > + * close(channel) &&
> > + * daemon_pid == current
> > + */
> > +enum cache_daemon_state {
> > + CACHE_DAEMON_STATE_CLOSE = 1, /* Close: daemon unknown */
> > + CACHE_DAEMON_STATE_OPEN = 2, /* Open: daemon open() 'channel'
> > */
> > + CACHE_DAEMON_STATE_POLL = 3 /* Poll: daemon poll() 'channel'
> > */
> > +};
> > +
> > struct cache_detail {
> > struct module * owner;
> > int hash_size;
> > @@ -83,7 +108,7 @@ struct cache_detail {
> > int (*cache_show)(struct seq_file *m,
> > struct cache_detail *cd,
> > struct cache_head *h);
> > - void (*warn_no_listener)(struct
> > cache_detail *cd,
> > + void (*warn_no_daemon)(struct
> > cache_detail *cd,
> > int has_died);
> >
> > struct cache_head * (*alloc)(void);
> > @@ -107,15 +132,16 @@ struct cache_detail {
> > /* fields for communication over channel */
> > struct list_head queue;
> >
> > - atomic_t readers; /* how many time is
> > /chennel open */
> > - time_t last_close; /* if no
> > readers, when did last close */
> > - time_t last_warn; /* when we
> > last warned about no readers */
> > + time_t last_close; /* when did
> > last close */
> > + time_t last_warn; /* when we
> > last warned about no daemon */
> >
> > union {
> > struct proc_dir_entry *procfs;
> > struct dentry *pipefs;
> > };
> > struct net *net;
> > + int daemon_pid;
> > + enum cache_daemon_state daemon_state;
> > };
> >
> >
> > diff --git a/net/sunrpc/cache.c b/net/sunrpc/cache.c
> > index 6f1528f..5ea24c8 100644
> > --- a/net/sunrpc/cache.c
> > +++ b/net/sunrpc/cache.c
> > @@ -38,7 +38,7 @@
> >
> > static bool cache_defer_req(struct cache_req *req, struct
> > cache_head *item);
> > static void cache_revisit_request(struct cache_head *item);
> > -static bool cache_listeners_exist(struct cache_detail *detail);
> > +static bool cache_daemon_exists(struct cache_detail *detail);
> >
> > static void cache_init(struct cache_head *h, struct cache_detail
> > *detail)
> > {
> > @@ -305,7 +305,7 @@ int cache_check(struct cache_detail *detail,
> > cache_fresh_unlocked(h, detail);
> > break;
> > }
> > - } else if (!cache_listeners_exist(detail))
> > + } else if (!cache_daemon_exists(detail))
> > rv = try_to_negate_entry(detail, h);
> > }
> >
> > @@ -373,9 +373,10 @@ void sunrpc_init_cache_detail(struct
> > cache_detail *cd)
> > spin_lock(&cache_list_lock);
> > cd->nextcheck = 0;
> > cd->entries = 0;
> > - atomic_set(&cd->readers, 0);
> > cd->last_close = 0;
> > cd->last_warn = -1;
> > + cd->daemon_pid = -1;
> > + cd->daemon_state = CACHE_DAEMON_STATE_CLOSE;
> > list_add(&cd->others, &cache_list);
> > spin_unlock(&cache_list_lock);
> >
> > @@ -810,6 +811,10 @@ static ssize_t cache_read(struct file *filp,
> > char __user *buf, size_t count,
> > if (count == 0)
> > return 0;
> >
> > + if (cd->daemon_pid != task_pid_nr(current) ||
> > + cd->daemon_state != CACHE_DAEMON_STATE_POLL)
> > + return -EIO;
> > +
> > inode_lock(inode); /* protect against multiple concurrent
> > * readers on this file */
> > again:
> > @@ -948,6 +953,10 @@ static ssize_t cache_write(struct file *filp,
> > const char __user *buf,
> > if (!cd->cache_parse)
> > goto out;
> >
> > + if (cd->daemon_pid != task_pid_nr(current) ||
> > + cd->daemon_state != CACHE_DAEMON_STATE_POLL)
> > + return -EIO;
> > +
> > inode_lock(inode);
> > ret = cache_downcall(mapping, buf, count, cd);
> > inode_unlock(inode);
> > @@ -964,6 +973,9 @@ static __poll_t cache_poll(struct file *filp,
> > poll_table *wait,
> > struct cache_reader *rp = filp->private_data;
> > struct cache_queue *cq;
> >
> > + if (cd->daemon_pid == task_pid_nr(current))
> > + cd->daemon_state = CACHE_DAEMON_STATE_POLL;
> > +
> > poll_wait(filp, &queue_wait, wait);
> >
> > /* alway allow write */
> > @@ -1029,11 +1041,14 @@ static int cache_open(struct inode *inode,
> > struct file *filp,
> > }
> > rp->offset = 0;
> > rp->q.reader = 1;
> > - atomic_inc(&cd->readers);
> > spin_lock(&queue_lock);
> > list_add(&rp->q.list, &cd->queue);
> > spin_unlock(&queue_lock);
> > }
> > + if (filp->f_mode & FMODE_WRITE) {
> > + cd->daemon_pid = task_pid_nr(current);
> > + cd->daemon_state = CACHE_DAEMON_STATE_OPEN;
> > + }
> > filp->private_data = rp;
> > return 0;
> > }
> > @@ -1063,7 +1078,10 @@ static int cache_release(struct inode
> > *inode, struct file *filp,
> > kfree(rp);
> >
> > cd->last_close = seconds_since_boot();
> > - atomic_dec(&cd->readers);
> > + }
> > + if (cd->daemon_pid == task_pid_nr(current)) {
> > + cd->daemon_pid = -1;
> > + cd->daemon_state = CACHE_DAEMON_STATE_CLOSE;
> > }
> > module_put(cd->owner);
> > return 0;
> > @@ -1160,30 +1178,28 @@ void qword_addhex(char **bpp, int *lp, char
> > *buf, int blen)
> > }
> > EXPORT_SYMBOL_GPL(qword_addhex);
> >
> > -static void warn_no_listener(struct cache_detail *detail)
> > +static void warn_no_daemon(struct cache_detail *detail)
> > {
> > if (detail->last_warn != detail->last_close) {
> > detail->last_warn = detail->last_close;
> > - if (detail->warn_no_listener)
> > - detail->warn_no_listener(detail, detail-
> > >last_close != 0);
> > + if (detail->warn_no_daemon)
> > + detail->warn_no_daemon(detail, detail-
> > >last_close != 0);
> > }
> > }
> >
> > -static bool cache_listeners_exist(struct cache_detail *detail)
> > +static bool cache_daemon_exists(struct cache_detail *detail)
> > {
> > - if (atomic_read(&detail->readers))
> > + if (detail->daemon_pid != -1 &&
> > + detail->daemon_state == CACHE_DAEMON_STATE_POLL)
> > return true;
> > - if (detail->last_close == 0)
> > - /* This cache was never opened */
> > - return false;
> > - if (detail->last_close < seconds_since_boot() - 30)
> > + if (detail->last_close > seconds_since_boot() - 30)
> > /*
> > * We allow for the possibility that someone might
> > * restart a userspace daemon without restarting the
> > * server; but after 30 seconds, we give up.
> > */
> > - return false;
> > - return true;
> > + return true;
> > + return false;
> > }
> >
> > /*
> > @@ -1202,8 +1218,8 @@ int sunrpc_cache_pipe_upcall(struct
> > cache_detail *detail, struct cache_head *h)
> > if (!detail->cache_request)
> > return -EINVAL;
> >
> > - if (!cache_listeners_exist(detail)) {
> > - warn_no_listener(detail);
> > + if (!cache_daemon_exists(detail)) {
> > + warn_no_daemon(detail);
> > return -EINVAL;
> > }
> > if (test_bit(CACHE_CLEANED, &h->flags))
> > --
> > 1.8.3.1
next prev parent reply other threads:[~2019-07-26 13:59 UTC|newest]
Thread overview: 11+ messages / expand[flat|nested] mbox.gz Atom feed top
2019-07-25 16:48 RFC: Fixing net/sunrpc/cache.c: cache_listeners_exist() function for rogue process reading a 'channel' file Dave Wysochanski
2019-07-25 18:54 ` Bruce Fields
2019-07-25 21:44 ` [RFC PATCH] SUNRPC: Harden the cache 'channel' interface to only allow legitimate daemons Dave Wysochanski
2019-07-25 21:50 ` J. Bruce Fields
2019-07-26 13:59 ` Dave Wysochanski [this message]
2019-07-26 22:33 ` [RFC PATCH] SUNRPC: Track writers of the 'channel' file to improve cache_listeners_exist Dave Wysochanski
2019-07-29 21:51 ` J. Bruce Fields
2019-07-30 0:02 ` NeilBrown
2019-07-30 0:49 ` J. Bruce Fields
2019-07-30 1:14 ` NeilBrown
2019-07-30 15:46 ` J. Bruce Fields
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=87bd4dd07544e18ffa9ed2c80f4155781cb1363e.camel@redhat.com \
--to=dwysocha@redhat.com \
--cc=bfields@fieldses.org \
--cc=linux-nfs@vger.kernel.org \
--cc=neilb@suse.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).