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

 - Fix a regression introduced in the last release

 - Fix a number of issues with validating data coming from userspace

 - Some cleanups in virtiofs

* tag 'fuse-update-5.5' of git://git.kernel.org/pub/scm/linux/kernel/git/mszeredi/fuse:
  fuse: fix Kconfig indentation
  fuse: fix leak of fuse_io_priv
  virtiofs: Use completions while waiting for queue to be drained
  virtiofs: Do not send forget request "struct list_head" element
  virtiofs: Use a common function to send forget
  virtiofs: Fix old-style declaration
  fuse: verify nlink
  fuse: verify write return
  fuse: verify attributes
parents 0f137416 8d66fcb7
Loading
Loading
Loading
Loading
+18 −7
Original line number Diff line number Diff line
@@ -248,7 +248,8 @@ static int fuse_dentry_revalidate(struct dentry *entry, unsigned int flags)
		kfree(forget);
		if (ret == -ENOMEM)
			goto out;
		if (ret || (outarg.attr.mode ^ inode->i_mode) & S_IFMT)
		if (ret || fuse_invalid_attr(&outarg.attr) ||
		    (outarg.attr.mode ^ inode->i_mode) & S_IFMT)
			goto invalid;

		forget_all_cached_acls(inode);
@@ -319,6 +320,12 @@ int fuse_valid_type(int m)
		S_ISBLK(m) || S_ISFIFO(m) || S_ISSOCK(m);
}

bool fuse_invalid_attr(struct fuse_attr *attr)
{
	return !fuse_valid_type(attr->mode) ||
		attr->size > LLONG_MAX;
}

int fuse_lookup_name(struct super_block *sb, u64 nodeid, const struct qstr *name,
		     struct fuse_entry_out *outarg, struct inode **inode)
{
@@ -350,7 +357,7 @@ int fuse_lookup_name(struct super_block *sb, u64 nodeid, const struct qstr *name
	err = -EIO;
	if (!outarg->nodeid)
		goto out_put_forget;
	if (!fuse_valid_type(outarg->attr.mode))
	if (fuse_invalid_attr(&outarg->attr))
		goto out_put_forget;

	*inode = fuse_iget(sb, outarg->nodeid, outarg->generation,
@@ -475,7 +482,8 @@ static int fuse_create_open(struct inode *dir, struct dentry *entry,
		goto out_free_ff;

	err = -EIO;
	if (!S_ISREG(outentry.attr.mode) || invalid_nodeid(outentry.nodeid))
	if (!S_ISREG(outentry.attr.mode) || invalid_nodeid(outentry.nodeid) ||
	    fuse_invalid_attr(&outentry.attr))
		goto out_free_ff;

	ff->fh = outopen.fh;
@@ -583,7 +591,7 @@ static int create_new_entry(struct fuse_conn *fc, struct fuse_args *args,
		goto out_put_forget_req;

	err = -EIO;
	if (invalid_nodeid(outarg.nodeid))
	if (invalid_nodeid(outarg.nodeid) || fuse_invalid_attr(&outarg.attr))
		goto out_put_forget_req;

	if ((outarg.attr.mode ^ mode) & S_IFMT)
@@ -862,6 +870,7 @@ static int fuse_link(struct dentry *entry, struct inode *newdir,

		spin_lock(&fi->lock);
		fi->attr_version = atomic64_inc_return(&fc->attr_version);
		if (likely(inode->i_nlink < UINT_MAX))
			inc_nlink(inode);
		spin_unlock(&fi->lock);
		fuse_invalidate_attr(inode);
@@ -942,7 +951,8 @@ static int fuse_do_getattr(struct inode *inode, struct kstat *stat,
	args.out_args[0].value = &outarg;
	err = fuse_simple_request(fc, &args);
	if (!err) {
		if ((inode->i_mode ^ outarg.attr.mode) & S_IFMT) {
		if (fuse_invalid_attr(&outarg.attr) ||
		    (inode->i_mode ^ outarg.attr.mode) & S_IFMT) {
			make_bad_inode(inode);
			err = -EIO;
		} else {
@@ -1563,7 +1573,8 @@ int fuse_do_setattr(struct dentry *dentry, struct iattr *attr,
		goto error;
	}

	if ((inode->i_mode ^ outarg.attr.mode) & S_IFMT) {
	if (fuse_invalid_attr(&outarg.attr) ||
	    (inode->i_mode ^ outarg.attr.mode) & S_IFMT) {
		make_bad_inode(inode);
		err = -EIO;
		goto error;
+5 −1
Original line number Diff line number Diff line
@@ -713,8 +713,10 @@ static ssize_t fuse_async_req_send(struct fuse_conn *fc,

	ia->ap.args.end = fuse_aio_complete_req;
	err = fuse_simple_background(fc, &ia->ap.args, GFP_KERNEL);
	if (err)
		fuse_aio_complete_req(fc, &ia->ap.args, err);

	return err ?: num_bytes;
	return num_bytes;
}

static ssize_t fuse_send_read(struct fuse_io_args *ia, loff_t pos, size_t count,
@@ -1096,6 +1098,8 @@ static ssize_t fuse_send_write_pages(struct fuse_io_args *ia,
	ia->write.in.flags = fuse_write_flags(iocb);

	err = fuse_simple_request(fc, &ap->args);
	if (!err && ia->write.out.size > count)
		err = -EIO;

	offset = ap->descs[0].offset;
	count = ia->write.out.size;
+2 −0
Original line number Diff line number Diff line
@@ -989,6 +989,8 @@ void fuse_ctl_remove_conn(struct fuse_conn *fc);
 */
int fuse_valid_type(int m);

bool fuse_invalid_attr(struct fuse_attr *attr);

/**
 * Is current process allowed to perform filesystem operation?
 */
+1 −1
Original line number Diff line number Diff line
@@ -184,7 +184,7 @@ static int fuse_direntplus_link(struct file *file,

	if (invalid_nodeid(o->nodeid))
		return -EIO;
	if (!fuse_valid_type(o->attr.mode))
	if (fuse_invalid_attr(&o->attr))
		return -EIO;

	fc = get_fuse_conn(dir);
+106 −104
Original line number Diff line number Diff line
@@ -35,6 +35,7 @@ struct virtio_fs_vq {
	struct fuse_dev *fud;
	bool connected;
	long in_flight;
	struct completion in_flight_zero; /* No inflight requests */
	char name[24];
} ____cacheline_aligned_in_smp;

@@ -48,11 +49,15 @@ struct virtio_fs {
	unsigned int num_request_queues; /* number of request queues */
};

struct virtio_fs_forget {
struct virtio_fs_forget_req {
	struct fuse_in_header ih;
	struct fuse_forget_in arg;
};

struct virtio_fs_forget {
	/* This request can be temporarily queued on virt queue */
	struct list_head list;
	struct virtio_fs_forget_req req;
};

static int virtio_fs_enqueue_req(struct virtio_fs_vq *fsvq,
@@ -81,6 +86,8 @@ static inline void dec_in_flight_req(struct virtio_fs_vq *fsvq)
{
	WARN_ON(fsvq->in_flight <= 0);
	fsvq->in_flight--;
	if (!fsvq->in_flight)
		complete(&fsvq->in_flight_zero);
}

static void release_virtio_fs_obj(struct kref *ref)
@@ -111,22 +118,23 @@ static void virtio_fs_drain_queue(struct virtio_fs_vq *fsvq)
	WARN_ON(fsvq->in_flight < 0);

	/* Wait for in flight requests to finish.*/
	while (1) {
	spin_lock(&fsvq->lock);
		if (!fsvq->in_flight) {
	if (fsvq->in_flight) {
		/* We are holding virtio_fs_mutex. There should not be any
		 * waiters waiting for completion.
		 */
		reinit_completion(&fsvq->in_flight_zero);
		spin_unlock(&fsvq->lock);
			break;
		}
		wait_for_completion(&fsvq->in_flight_zero);
	} else {
		spin_unlock(&fsvq->lock);
		/* TODO use completion instead of timeout */
		usleep_range(1000, 2000);
	}

	flush_work(&fsvq->done_work);
	flush_delayed_work(&fsvq->dispatch_work);
}

static void virtio_fs_drain_all_queues(struct virtio_fs *fs)
static void virtio_fs_drain_all_queues_locked(struct virtio_fs *fs)
{
	struct virtio_fs_vq *fsvq;
	int i;
@@ -137,6 +145,19 @@ static void virtio_fs_drain_all_queues(struct virtio_fs *fs)
	}
}

static void virtio_fs_drain_all_queues(struct virtio_fs *fs)
{
	/* Provides mutual exclusion between ->remove and ->kill_sb
	 * paths. We don't want both of these draining queue at the
	 * same time. Current completion logic reinits completion
	 * and that means there should not be any other thread
	 * doing reinit or waiting for completion already.
	 */
	mutex_lock(&virtio_fs_mutex);
	virtio_fs_drain_all_queues_locked(fs);
	mutex_unlock(&virtio_fs_mutex);
}

static void virtio_fs_start_all_queues(struct virtio_fs *fs)
{
	struct virtio_fs_vq *fsvq;
@@ -313,65 +334,86 @@ static void virtio_fs_request_dispatch_work(struct work_struct *work)
	}
}

static void virtio_fs_hiprio_dispatch_work(struct work_struct *work)
/*
 * Returns 1 if queue is full and sender should wait a bit before sending
 * next request, 0 otherwise.
 */
static int send_forget_request(struct virtio_fs_vq *fsvq,
			       struct virtio_fs_forget *forget,
			       bool in_flight)
{
	struct virtio_fs_forget *forget;
	struct virtio_fs_vq *fsvq = container_of(work, struct virtio_fs_vq,
						 dispatch_work.work);
	struct virtqueue *vq = fsvq->vq;
	struct scatterlist sg;
	struct scatterlist *sgs[] = {&sg};
	struct virtqueue *vq;
	int ret = 0;
	bool notify;
	int ret;
	struct virtio_fs_forget_req *req = &forget->req;

	pr_debug("virtio-fs: worker %s called.\n", __func__);
	while (1) {
	spin_lock(&fsvq->lock);
		forget = list_first_entry_or_null(&fsvq->queued_reqs,
					struct virtio_fs_forget, list);
		if (!forget) {
			spin_unlock(&fsvq->lock);
			return;
		}

		list_del(&forget->list);
	if (!fsvq->connected) {
		if (in_flight)
			dec_in_flight_req(fsvq);
			spin_unlock(&fsvq->lock);
		kfree(forget);
			continue;
		goto out;
	}

		sg_init_one(&sg, forget, sizeof(*forget));

		/* Enqueue the request */
	sg_init_one(&sg, req, sizeof(*req));
	vq = fsvq->vq;
	dev_dbg(&vq->vdev->dev, "%s\n", __func__);
		ret = virtqueue_add_sgs(vq, sgs, 1, 0, forget, GFP_ATOMIC);

	ret = virtqueue_add_outbuf(vq, &sg, 1, forget, GFP_ATOMIC);
	if (ret < 0) {
		if (ret == -ENOMEM || ret == -ENOSPC) {
			pr_debug("virtio-fs: Could not queue FORGET: err=%d. Will try later\n",
				 ret);
				list_add_tail(&forget->list,
						&fsvq->queued_reqs);
			list_add_tail(&forget->list, &fsvq->queued_reqs);
			schedule_delayed_work(&fsvq->dispatch_work,
					      msecs_to_jiffies(1));
			if (!in_flight)
				inc_in_flight_req(fsvq);
			/* Queue is full */
			ret = 1;
		} else {
			pr_debug("virtio-fs: Could not queue FORGET: err=%d. Dropping it.\n",
				 ret);
				dec_in_flight_req(fsvq);
			kfree(forget);
			if (in_flight)
				dec_in_flight_req(fsvq);
		}
			spin_unlock(&fsvq->lock);
			return;
		goto out;
	}

	if (!in_flight)
		inc_in_flight_req(fsvq);
	notify = virtqueue_kick_prepare(vq);
	spin_unlock(&fsvq->lock);

	if (notify)
		virtqueue_notify(vq);
		pr_debug("virtio-fs: worker %s dispatched one forget request.\n",
			 __func__);
	return ret;
out:
	spin_unlock(&fsvq->lock);
	return ret;
}

static void virtio_fs_hiprio_dispatch_work(struct work_struct *work)
{
	struct virtio_fs_forget *forget;
	struct virtio_fs_vq *fsvq = container_of(work, struct virtio_fs_vq,
						 dispatch_work.work);
	pr_debug("virtio-fs: worker %s called.\n", __func__);
	while (1) {
		spin_lock(&fsvq->lock);
		forget = list_first_entry_or_null(&fsvq->queued_reqs,
					struct virtio_fs_forget, list);
		if (!forget) {
			spin_unlock(&fsvq->lock);
			return;
		}

		list_del(&forget->list);
		spin_unlock(&fsvq->lock);
		if (send_forget_request(fsvq, forget, true))
			return;
	}
}

@@ -556,6 +598,7 @@ static int virtio_fs_setup_vqs(struct virtio_device *vdev,
	INIT_LIST_HEAD(&fs->vqs[VQ_HIPRIO].end_reqs);
	INIT_DELAYED_WORK(&fs->vqs[VQ_HIPRIO].dispatch_work,
			virtio_fs_hiprio_dispatch_work);
	init_completion(&fs->vqs[VQ_HIPRIO].in_flight_zero);
	spin_lock_init(&fs->vqs[VQ_HIPRIO].lock);

	/* Initialize the requests virtqueues */
@@ -566,6 +609,7 @@ static int virtio_fs_setup_vqs(struct virtio_device *vdev,
				  virtio_fs_request_dispatch_work);
		INIT_LIST_HEAD(&fs->vqs[i].queued_reqs);
		INIT_LIST_HEAD(&fs->vqs[i].end_reqs);
		init_completion(&fs->vqs[i].in_flight_zero);
		snprintf(fs->vqs[i].name, sizeof(fs->vqs[i].name),
			 "requests.%u", i - VQ_REQUEST);
		callbacks[i] = virtio_fs_vq_done;
@@ -659,7 +703,7 @@ static void virtio_fs_remove(struct virtio_device *vdev)
	/* This device is going away. No one should get new reference */
	list_del_init(&fs->list);
	virtio_fs_stop_all_queues(fs);
	virtio_fs_drain_all_queues(fs);
	virtio_fs_drain_all_queues_locked(fs);
	vdev->config->reset(vdev);
	virtio_fs_cleanup_vqs(vdev, fs);

@@ -684,12 +728,12 @@ static int virtio_fs_restore(struct virtio_device *vdev)
}
#endif /* CONFIG_PM_SLEEP */

const static struct virtio_device_id id_table[] = {
static const struct virtio_device_id id_table[] = {
	{ VIRTIO_ID_FS, VIRTIO_DEV_ANY_ID },
	{},
};

const static unsigned int feature_table[] = {};
static const unsigned int feature_table[] = {};

static struct virtio_driver virtio_fs_driver = {
	.driver.name		= KBUILD_MODNAME,
@@ -710,14 +754,10 @@ __releases(fiq->lock)
{
	struct fuse_forget_link *link;
	struct virtio_fs_forget *forget;
	struct scatterlist sg;
	struct scatterlist *sgs[] = {&sg};
	struct virtio_fs_forget_req *req;
	struct virtio_fs *fs;
	struct virtqueue *vq;
	struct virtio_fs_vq *fsvq;
	bool notify;
	u64 unique;
	int ret;

	link = fuse_dequeue_forget(fiq, 1, NULL);
	unique = fuse_get_unique(fiq);
@@ -728,57 +768,19 @@ __releases(fiq->lock)

	/* Allocate a buffer for the request */
	forget = kmalloc(sizeof(*forget), GFP_NOFS | __GFP_NOFAIL);
	req = &forget->req;

	forget->ih = (struct fuse_in_header){
	req->ih = (struct fuse_in_header){
		.opcode = FUSE_FORGET,
		.nodeid = link->forget_one.nodeid,
		.unique = unique,
		.len = sizeof(*forget),
		.len = sizeof(*req),
	};
	forget->arg = (struct fuse_forget_in){
	req->arg = (struct fuse_forget_in){
		.nlookup = link->forget_one.nlookup,
	};

	sg_init_one(&sg, forget, sizeof(*forget));

	/* Enqueue the request */
	spin_lock(&fsvq->lock);

	if (!fsvq->connected) {
		kfree(forget);
		spin_unlock(&fsvq->lock);
		goto out;
	}

	vq = fsvq->vq;
	dev_dbg(&vq->vdev->dev, "%s\n", __func__);

	ret = virtqueue_add_sgs(vq, sgs, 1, 0, forget, GFP_ATOMIC);
	if (ret < 0) {
		if (ret == -ENOMEM || ret == -ENOSPC) {
			pr_debug("virtio-fs: Could not queue FORGET: err=%d. Will try later.\n",
				 ret);
			list_add_tail(&forget->list, &fsvq->queued_reqs);
			schedule_delayed_work(&fsvq->dispatch_work,
					msecs_to_jiffies(1));
			inc_in_flight_req(fsvq);
		} else {
			pr_debug("virtio-fs: Could not queue FORGET: err=%d. Dropping it.\n",
				 ret);
			kfree(forget);
		}
		spin_unlock(&fsvq->lock);
		goto out;
	}

	inc_in_flight_req(fsvq);
	notify = virtqueue_kick_prepare(vq);

	spin_unlock(&fsvq->lock);

	if (notify)
		virtqueue_notify(vq);
out:
	send_forget_request(fsvq, forget, false);
	kfree(link);
}

@@ -1026,7 +1028,7 @@ __releases(fiq->lock)
	}
}

const static struct fuse_iqueue_ops virtio_fs_fiq_ops = {
static const struct fuse_iqueue_ops virtio_fs_fiq_ops = {
	.wake_forget_and_unlock		= virtio_fs_wake_forget_and_unlock,
	.wake_interrupt_and_unlock	= virtio_fs_wake_interrupt_and_unlock,
	.wake_pending_and_unlock	= virtio_fs_wake_pending_and_unlock,
+2 −2

File changed.

Contains only whitespace changes.

Loading