Commit acf6c670 authored by Linus Torvalds's avatar Linus Torvalds
Browse files
Pull fuse update from Miklos Szeredi:

 - Fix possible hang in virtiofs when cleaning up a DAX inode (Sergio
   Lopez)

 - Fix a warning when using large folio as the source of SPLICE_F_MOVE
   on the fuse device (Bernd)

 - Fix uninitialized value found by KMSAN (Luis Henriques)

 - Fix synchronous INIT hang (Miklos)

 - Fix race between inode initialization and FUSE_NOTIFY_INVAL_INODE
   (Horst)

 - Allow fd to be closed after passing fuse device fd to
   fsconfig(..., "fd", ...) (Miklos)

 - Support FSCONFIG_SET_FD for "fd" option (Miklos)

 - Misc fixes and cleanups

* tag 'fuse-update-7.1' of git://git.kernel.org/pub/scm/linux/kernel/git/mszeredi/fuse: (21 commits)
  fuse: support FSCONFIG_SET_FD for "fd" option
  fuse: clean up device cloning
  fuse: don't require /dev/fuse fd to be kept open during mount
  fuse: add refcount to fuse_dev
  fuse: create fuse_dev on /dev/fuse open instead of mount
  fuse: check connection state on notification
  fuse: fuse_dev_ioctl_clone() should wait for device file to be initialized
  fuse: fix inode initialization race
  fuse: abort on fatal signal during sync init
  fuse: fix uninit-value in fuse_dentry_revalidate()
  fuse: use offset_in_page() for page offset calculations
  fuse: use DIV_ROUND_UP() for page count calculations
  fuse: simplify logic in fuse_notify_store() and fuse_retrieve()
  fuse: validate outarg offset and size in notify store/retrieve
  fuse: Check for large folio with SPLICE_F_MOVE
  fuse: quiet down complaints in fuse_conn_limit_write
  fuse: drop unnecessary argument from fuse_lookup_init()
  fuse: fix premature writetrhough request for large folio
  fuse: refactor duplicate queue teardown operation
  virtiofs: add FUSE protocol validation
  ...
parents 9e1e9d66 2339f9cc
Loading
Loading
Loading
Loading
+2 −2
Original line number Diff line number Diff line
@@ -121,7 +121,7 @@ static ssize_t fuse_conn_max_background_write(struct file *file,
					      const char __user *buf,
					      size_t count, loff_t *ppos)
{
	unsigned val;
	unsigned int val = 0;
	ssize_t ret;

	ret = fuse_conn_limit_write(file, buf, count, ppos, &val,
@@ -163,7 +163,7 @@ static ssize_t fuse_conn_congestion_threshold_write(struct file *file,
						    const char __user *buf,
						    size_t count, loff_t *ppos)
{
	unsigned val;
	unsigned int val = 0;
	struct fuse_conn *fc;
	ssize_t ret;

+1 −1
Original line number Diff line number Diff line
@@ -527,7 +527,7 @@ static int cuse_channel_open(struct inode *inode, struct file *file)
	cc->fc.initialized = 1;
	rc = cuse_send_init(cc);
	if (rc) {
		fuse_dev_free(fud);
		fuse_dev_put(fud);
		return rc;
	}
	file->private_data = fud;
+78 −78
Original line number Diff line number Diff line
@@ -570,6 +570,11 @@ static void request_wait_answer(struct fuse_req *req)
		if (!err)
			return;

		if (req->args->abort_on_kill) {
			fuse_abort_conn(fc);
			return;
		}

		if (test_bit(FR_URING, &req->flags))
			removed = fuse_uring_remove_pending_req(req);
		else
@@ -676,6 +681,7 @@ ssize_t __fuse_simple_request(struct mnt_idmap *idmap,
			fuse_force_creds(req);

		__set_bit(FR_WAITING, &req->flags);
		if (!args->abort_on_kill)
			__set_bit(FR_FORCE, &req->flags);
	} else {
		WARN_ON(args->nocreds);
@@ -1011,6 +1017,9 @@ static int fuse_try_move_folio(struct fuse_copy_state *cs, struct folio **foliop
	folio_clear_uptodate(newfolio);
	folio_clear_mappedtodisk(newfolio);

	if (folio_test_large(newfolio))
		goto out_fallback_unlock;

	if (fuse_check_folio(newfolio) != 0)
		goto out_fallback_unlock;

@@ -1539,32 +1548,24 @@ static ssize_t fuse_dev_do_read(struct fuse_dev *fud, struct file *file,

static int fuse_dev_open(struct inode *inode, struct file *file)
{
	/*
	 * The fuse device's file's private_data is used to hold
	 * the fuse_conn(ection) when it is mounted, and is used to
	 * keep track of whether the file has been mounted already.
	 */
	file->private_data = NULL;
	struct fuse_dev *fud = fuse_dev_alloc();

	if (!fud)
		return -ENOMEM;

	file->private_data = fud;
	return 0;
}

struct fuse_dev *fuse_get_dev(struct file *file)
{
	struct fuse_dev *fud = __fuse_get_dev(file);
	struct fuse_dev *fud = fuse_file_to_fud(file);
	int err;

	if (likely(fud))
		return fud;

	err = wait_event_interruptible(fuse_dev_waitq,
				       READ_ONCE(file->private_data) != FUSE_DEV_SYNC_INIT);
	err = wait_event_interruptible(fuse_dev_waitq, fuse_dev_fc_get(fud) != NULL);
	if (err)
		return ERR_PTR(err);

	fud = __fuse_get_dev(file);
	if (!fud)
		return ERR_PTR(-EPERM);

	return fud;
}

@@ -1764,10 +1765,9 @@ static int fuse_notify_store(struct fuse_conn *fc, unsigned int size,
	struct address_space *mapping;
	u64 nodeid;
	int err;
	pgoff_t index;
	unsigned int offset;
	unsigned int num;
	loff_t file_size;
	loff_t pos;
	loff_t end;

	if (size < sizeof(outarg))
@@ -1780,7 +1780,12 @@ static int fuse_notify_store(struct fuse_conn *fc, unsigned int size,
	if (size - sizeof(outarg) != outarg.size)
		return -EINVAL;

	if (outarg.offset >= MAX_LFS_FILESIZE)
		return -EINVAL;

	nodeid = outarg.nodeid;
	pos = outarg.offset;
	num = min(outarg.size, MAX_LFS_FILESIZE - pos);

	down_read(&fc->killsb);

@@ -1790,33 +1795,29 @@ static int fuse_notify_store(struct fuse_conn *fc, unsigned int size,
		goto out_up_killsb;

	mapping = inode->i_mapping;
	index = outarg.offset >> PAGE_SHIFT;
	offset = outarg.offset & ~PAGE_MASK;
	file_size = i_size_read(inode);
	end = outarg.offset + outarg.size;
	end = pos + num;
	if (end > file_size) {
		file_size = end;
		fuse_write_update_attr(inode, file_size, outarg.size);
		fuse_write_update_attr(inode, file_size, num);
	}

	num = outarg.size;
	while (num) {
		struct folio *folio;
		unsigned int folio_offset;
		unsigned int nr_bytes;
		unsigned int nr_pages;
		pgoff_t index = pos >> PAGE_SHIFT;

		folio = filemap_grab_folio(mapping, index);
		err = PTR_ERR(folio);
		if (IS_ERR(folio))
			goto out_iput;

		folio_offset = ((index - folio->index) << PAGE_SHIFT) + offset;
		folio_offset = offset_in_folio(folio, pos);
		nr_bytes = min(num, folio_size(folio) - folio_offset);
		nr_pages = (offset + nr_bytes + PAGE_SIZE - 1) >> PAGE_SHIFT;

		err = fuse_copy_folio(cs, &folio, folio_offset, nr_bytes, 0);
		if (!folio_test_uptodate(folio) && !err && offset == 0 &&
		if (!folio_test_uptodate(folio) && !err && folio_offset == 0 &&
		    (nr_bytes == folio_size(folio) || file_size == end)) {
			folio_zero_segment(folio, nr_bytes, folio_size(folio));
			folio_mark_uptodate(folio);
@@ -1827,9 +1828,8 @@ static int fuse_notify_store(struct fuse_conn *fc, unsigned int size,
		if (err)
			goto out_iput;

		pos += nr_bytes;
		num -= nr_bytes;
		offset = 0;
		index += nr_pages;
	}

	err = 0;
@@ -1861,7 +1861,6 @@ static int fuse_retrieve(struct fuse_mount *fm, struct inode *inode,
{
	int err;
	struct address_space *mapping = inode->i_mapping;
	pgoff_t index;
	loff_t file_size;
	unsigned int num;
	unsigned int offset;
@@ -1872,17 +1871,18 @@ static int fuse_retrieve(struct fuse_mount *fm, struct inode *inode,
	size_t args_size = sizeof(*ra);
	struct fuse_args_pages *ap;
	struct fuse_args *args;
	loff_t pos = outarg->offset;

	offset = outarg->offset & ~PAGE_MASK;
	offset = offset_in_page(pos);
	file_size = i_size_read(inode);

	num = min(outarg->size, fc->max_write);
	if (outarg->offset > file_size)
	if (pos > file_size)
		num = 0;
	else if (outarg->offset + num > file_size)
		num = file_size - outarg->offset;
	else if (num > file_size - pos)
		num = file_size - pos;

	num_pages = (num + offset + PAGE_SIZE - 1) >> PAGE_SHIFT;
	num_pages = DIV_ROUND_UP(num + offset, PAGE_SIZE);
	num_pages = min(num_pages, fc->max_pages);
	num = min(num, num_pages << PAGE_SHIFT);

@@ -1903,31 +1903,27 @@ static int fuse_retrieve(struct fuse_mount *fm, struct inode *inode,
	args->in_pages = true;
	args->end = fuse_retrieve_end;

	index = outarg->offset >> PAGE_SHIFT;

	while (num && ap->num_folios < num_pages) {
		struct folio *folio;
		unsigned int folio_offset;
		unsigned int nr_bytes;
		unsigned int nr_pages;
		pgoff_t index = pos >> PAGE_SHIFT;

		folio = filemap_get_folio(mapping, index);
		if (IS_ERR(folio))
			break;

		folio_offset = ((index - folio->index) << PAGE_SHIFT) + offset;
		folio_offset = offset_in_folio(folio, pos);
		nr_bytes = min(folio_size(folio) - folio_offset, num);
		nr_pages = (offset + nr_bytes + PAGE_SIZE - 1) >> PAGE_SHIFT;

		ap->folios[ap->num_folios] = folio;
		ap->descs[ap->num_folios].offset = folio_offset;
		ap->descs[ap->num_folios].length = nr_bytes;
		ap->num_folios++;

		offset = 0;
		pos += nr_bytes;
		num -= nr_bytes;
		total_len += nr_bytes;
		index += nr_pages;
	}
	ra->inarg.offset = outarg->offset;
	ra->inarg.size = total_len;
@@ -1961,6 +1957,9 @@ static int fuse_notify_retrieve(struct fuse_conn *fc, unsigned int size,

	fuse_copy_finish(cs);

	if (outarg.offset >= MAX_LFS_FILESIZE)
		return -EINVAL;

	down_read(&fc->killsb);
	err = -ENOENT;
	nodeid = outarg.nodeid;
@@ -2091,6 +2090,13 @@ static int fuse_notify_prune(struct fuse_conn *fc, unsigned int size,
static int fuse_notify(struct fuse_conn *fc, enum fuse_notify_code code,
		       unsigned int size, struct fuse_copy_state *cs)
{
	/*
	 * Only allow notifications during while the connection is in an
	 * initialized and connected state
	 */
	if (!fc->initialized || !fc->connected)
		return -EINVAL;

	/* Don't try to move folios (yet) */
	cs->move_folios = false;

@@ -2533,13 +2539,15 @@ void fuse_wait_aborted(struct fuse_conn *fc)

int fuse_dev_release(struct inode *inode, struct file *file)
{
	struct fuse_dev *fud = __fuse_get_dev(file);
	struct fuse_dev *fud = fuse_file_to_fud(file);
	/* Pairs with cmpxchg() in fuse_dev_install() */
	struct fuse_conn *fc = xchg(&fud->fc, FUSE_DEV_FC_DISCONNECTED);

	if (fud) {
		struct fuse_conn *fc = fud->fc;
	if (fc) {
		struct fuse_pqueue *fpq = &fud->pq;
		LIST_HEAD(to_end);
		unsigned int i;
		bool last;

		spin_lock(&fpq->lock);
		WARN_ON(!list_empty(&fpq->io));
@@ -2549,13 +2557,19 @@ int fuse_dev_release(struct inode *inode, struct file *file)

		fuse_dev_end_requests(&to_end);

		spin_lock(&fc->lock);
		list_del(&fud->entry);
		/* Are we the last open device? */
		if (atomic_dec_and_test(&fc->dev_count)) {
		last = list_empty(&fc->devices);
		spin_unlock(&fc->lock);

		if (last) {
			WARN_ON(fc->iq.fasync != NULL);
			fuse_abort_conn(fc);
		}
		fuse_dev_free(fud);
		fuse_conn_put(fc);
	}
	fuse_dev_put(fud);
	return 0;
}
EXPORT_SYMBOL_GPL(fuse_dev_release);
@@ -2571,28 +2585,10 @@ static int fuse_dev_fasync(int fd, struct file *file, int on)
	return fasync_helper(fd, file, on, &fud->fc->iq.fasync);
}

static int fuse_device_clone(struct fuse_conn *fc, struct file *new)
{
	struct fuse_dev *fud;

	if (__fuse_get_dev(new))
		return -EINVAL;

	fud = fuse_dev_alloc_install(fc);
	if (!fud)
		return -ENOMEM;

	new->private_data = fud;
	atomic_inc(&fc->dev_count);

	return 0;
}

static long fuse_dev_ioctl_clone(struct file *file, __u32 __user *argp)
{
	int res;
	int oldfd;
	struct fuse_dev *fud = NULL;
	struct fuse_dev *fud, *new_fud;

	if (get_user(oldfd, argp))
		return -EFAULT;
@@ -2605,17 +2601,20 @@ static long fuse_dev_ioctl_clone(struct file *file, __u32 __user *argp)
	 * Check against file->f_op because CUSE
	 * uses the same ioctl handler.
	 */
	if (fd_file(f)->f_op == file->f_op)
		fud = __fuse_get_dev(fd_file(f));
	if (fd_file(f)->f_op != file->f_op)
		return -EINVAL;

	res = -EINVAL;
	if (fud) {
		mutex_lock(&fuse_mutex);
		res = fuse_device_clone(fud->fc, file);
		mutex_unlock(&fuse_mutex);
	}
	fud = fuse_get_dev(fd_file(f));
	if (IS_ERR(fud))
		return PTR_ERR(fud);

	new_fud = fuse_file_to_fud(file);
	if (fuse_dev_fc_get(new_fud))
		return -EINVAL;

	fuse_dev_install(new_fud, fud->fc);

	return res;
	return 0;
}

static long fuse_dev_ioctl_backing_open(struct file *file,
@@ -2656,10 +2655,11 @@ static long fuse_dev_ioctl_backing_close(struct file *file, __u32 __user *argp)
static long fuse_dev_ioctl_sync_init(struct file *file)
{
	int err = -EINVAL;
	struct fuse_dev *fud = fuse_file_to_fud(file);

	mutex_lock(&fuse_mutex);
	if (!__fuse_get_dev(file)) {
		WRITE_ONCE(file->private_data, FUSE_DEV_SYNC_INIT);
	if (!fuse_dev_fc_get(fud)) {
		fud->sync_init = true;
		err = 0;
	}
	mutex_unlock(&fuse_mutex);
+16 −20
Original line number Diff line number Diff line
@@ -397,6 +397,20 @@ static void fuse_uring_teardown_entries(struct fuse_ring_queue *queue)
				     FRRS_AVAILABLE);
}

static void fuse_uring_teardown_all_queues(struct fuse_ring *ring)
{
	int qid;

	for (qid = 0; qid < ring->nr_queues; qid++) {
		struct fuse_ring_queue *queue = READ_ONCE(ring->queues[qid]);

		if (!queue)
			continue;

		fuse_uring_teardown_entries(queue);
	}
}

/*
 * Log state debug info
 */
@@ -431,19 +445,10 @@ static void fuse_uring_log_ent_state(struct fuse_ring *ring)

static void fuse_uring_async_stop_queues(struct work_struct *work)
{
	int qid;
	struct fuse_ring *ring =
		container_of(work, struct fuse_ring, async_teardown_work.work);

	/* XXX code dup */
	for (qid = 0; qid < ring->nr_queues; qid++) {
		struct fuse_ring_queue *queue = READ_ONCE(ring->queues[qid]);

		if (!queue)
			continue;

		fuse_uring_teardown_entries(queue);
	}
	fuse_uring_teardown_all_queues(ring);

	/*
	 * Some ring entries might be in the middle of IO operations,
@@ -469,16 +474,7 @@ static void fuse_uring_async_stop_queues(struct work_struct *work)
 */
void fuse_uring_stop_queues(struct fuse_ring *ring)
{
	int qid;

	for (qid = 0; qid < ring->nr_queues; qid++) {
		struct fuse_ring_queue *queue = READ_ONCE(ring->queues[qid]);

		if (!queue)
			continue;

		fuse_uring_teardown_entries(queue);
	}
	fuse_uring_teardown_all_queues(ring);

	if (atomic_read(&ring->queue_refs) > 0) {
		ring->teardown_time = jiffies;
+9 −5
Original line number Diff line number Diff line
@@ -354,8 +354,8 @@ static void fuse_invalidate_entry(struct dentry *entry)
	fuse_invalidate_entry_cache(entry);
}

static void fuse_lookup_init(struct fuse_conn *fc, struct fuse_args *args,
			     u64 nodeid, const struct qstr *name,
static void fuse_lookup_init(struct fuse_args *args, u64 nodeid,
			     const struct qstr *name,
			     struct fuse_entry_out *outarg)
{
	memset(outarg, 0, sizeof(struct fuse_entry_out));
@@ -421,8 +421,7 @@ static int fuse_dentry_revalidate(struct inode *dir, const struct qstr *name,

		attr_version = fuse_get_attr_version(fm->fc);

		fuse_lookup_init(fm->fc, &args, get_node_id(dir),
				 name, &outarg);
		fuse_lookup_init(&args, get_node_id(dir), name, &outarg);
		ret = fuse_simple_request(fm, &args);
		/* Zero nodeid is same as -ENOENT */
		if (!ret && !outarg.nodeid)
@@ -481,6 +480,11 @@ static int fuse_dentry_init(struct dentry *dentry)
	fd->dentry = dentry;
	RB_CLEAR_NODE(&fd->node);
	dentry->d_fsdata = fd;
	/*
	 * Initialising d_time (epoch) to '0' ensures the dentry is invalid
	 * if compared to fc->epoch, which is initialized to '1'.
	 */
	dentry->d_time = 0;

	return 0;
}
@@ -570,7 +574,7 @@ int fuse_lookup_name(struct super_block *sb, u64 nodeid, const struct qstr *name
	attr_version = fuse_get_attr_version(fm->fc);
	evict_ctr = fuse_get_evict_ctr(fm->fc);

	fuse_lookup_init(fm->fc, &args, nodeid, name, outarg);
	fuse_lookup_init(&args, nodeid, name, outarg);
	err = fuse_simple_request(fm, &args);
	/* Zero nodeid is same as -ENOENT, but with valid timeout */
	if (err || !outarg->nodeid)
Loading