Re: [RFC PATCH] SUNRPC: Harden the cache 'channel' interface to only allow legitimate daemons

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



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@xxxxxxxxxx>
> > ---
> >  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





[Index of Archives]     [Linux Filesystem Development]     [Linux USB Development]     [Linux Media Development]     [Video for Linux]     [Linux NILFS]     [Linux Audio Users]     [Yosemite Info]     [Linux SCSI]

  Powered by Linux