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, 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).

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