[Devel] [PATCH hci-8.1 5/5] dm/push_backup: add event file descriptor interface

no-reply at sashiko.virtuozzo.com no-reply at sashiko.virtuozzo.com
Tue Nov 3 02:51:46 MSK 2026


Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
- [High] dm-push-backup: fd write races pb_destroy() and uses freed pbio_pool in resubmit_pbios()
- [Low] dm-push-backup: push_backup_get_fd() installs fd before result is delivered
- [Low] dm-push-backup: event fd has no .poll handler, so it cannot be polled
--

commit 013b902b0f480dc76bc700b18a002953deb80967
Author: Andrey Zhadchenko <andrey.zhadchenko at virtuozzo.com>

dm/push_backup: add event file descriptor interface

This patch adds an anon-inode file descriptor interface to dm-push-backup.
The fd is obtained with a new push_backup_get_fd target message. Userspace
then uses read() to get the next cluster range to back up and write() to
acknowledge it. A kref on struct push_backup is tied to the number of open
fds.

Link: https://virtuozzo.atlassian.net/browse/VSTOR-152912

> diff --git a/drivers/md/dm-push-backup.c b/drivers/md/dm-push-backup.c
> --- a/drivers/md/dm-push-backup.c
> +++ b/drivers/md/dm-push-backup.c

[ ... ]

> @@ -534,6 +591,73 @@ static int push_backup_write(struct push_backup *pb,
>  	return 0;
>  }
>  
> +static ssize_t pb_fd_write(struct file *file, const char __user *buf,
> +			   size_t count, loff_t *ppos)
> +{
> +	struct push_backup *pb = file->private_data;
> +	struct push_backup_range range;
> +	int ret;
> +
> +	if (count != sizeof(range))
> +		return -EINVAL;
> +	if (copy_from_user(&range, buf, sizeof(range)))
> +		return -EFAULT;
> +
> +	ret = push_backup_write(pb, range.clu, range.nr);
> +	return ret ? : sizeof(range);
> +}

[Severity: High]
Can this race with pb_destroy() and use freed memory?

Before this patch, push_backup_write() was reachable only through
pb_message(), which runs under dm_get_live_table() and ctl_rwsem, so it
could not overlap the target destructor. Now pb_fd_write() calls it with
neither.

The dead check in push_backup_write() is made under pb->lock, but
resubmit_pbios() runs after the lock is dropped:

push_backup_write()
    spin_unlock_irq(&pb->lock);
    ...
    if (!list_empty(&pbio_list))
        resubmit_pbios(pb, &pbio_list);

resubmit_pbios()
    list_del_init(&pbio->list);
    dm_requeue_original_rq(pbio->rq);
    mempool_free(pbio, pb->pbio_pool);

If pb_destroy() runs on another CPU after the writer has unlinked the
pbios and dropped the lock, it can proceed as follows:

pb_destroy()
    spin_lock_irq(&pb->lock);
    pb->dead = true;
    ...
    spin_unlock_irq(&pb->lock);
    ...
    destroy_workqueue(pb->wq);
    kvfree(pb->pending_htable);
    mempool_destroy(pb->pbio_pool);
    ...
    pb_put(pb);

The writer would then call mempool_free() on a pbio_pool that
mempool_destroy() has already freed. WARN_ON_ONCE(pb->nr_delayed) in
pb_destroy() would not fire in this case, because the writer has already
decremented nr_delayed.

The new kref keeps only the struct push_backup allocation alive. It does not
keep pbio_pool, pending_htable, wq or origin_dev alive. pb_destroy() does
not wait for fd readers or writers that have already passed the dead check.

The fd can also be inherited or passed to a process other than the one that
opened it. Also, __dm_destroy() suspends the device without draining
postponed requests, so pending pbios can exist when dtr runs.

Is there something that serializes pb_fd_write() against pb_destroy(), for
example draining in-flight fd operations before freeing the sub-resources?

> +static int pb_fd_release(struct inode *inode, struct file *file)
> +{
> +	pb_put(file->private_data);
> +	return 0;
> +}
> +
> +static const struct file_operations pb_fops = {
> +	.owner		= THIS_MODULE,
> +	.read		= pb_fd_read,
> +	.write		= pb_fd_write,
> +	.release	= pb_fd_release,
> +};

[Severity: Low]
The interface is described as an event file descriptor, but pb_fops has no
.poll callback.

Can a backup tool multiplex this fd with its other I/O? As written, it can
only block in read() or busy-poll with O_NONBLOCK and get -EAGAIN.

pb_has_event() and pb->waitq already exist and look like a natural basis
for a poll handler. Would it make sense to add .poll before the UAPI is
fixed? Adding it later is harder because read() does not consume anything
and returns 0 to mean "finished".

> +static int push_backup_get_fd(struct push_backup *pb,
> +			      char *result, unsigned int maxlen)
> +{
> +	unsigned int sz = 0;
> +	int fd;
> +
> +	kref_get(&pb->ref);
> +	fd = anon_inode_getfd("[push_backup]", &pb_fops, pb,
> +			      O_RDWR | O_CLOEXEC);
> +	if (fd < 0) {
> +		pb_put(pb);
> +		return fd;
> +	}
> +
> +	DMEMIT("%d", fd);
> +	return 1;
> +}

[Severity: Low]
Can this leak the fd when the result is not delivered?

anon_inode_getfd() does fd_install() irrevocably. DMEMIT() never reports
truncation, so push_backup_get_fd() returns 1 even if the result buffer is
too small and the fd number is truncated or omitted. The same applies if the
dm-ioctl copy-out fails afterwards.

In those cases the caller never learns the fd number, and the open fd
continues to hold a pb kref and a module reference through pb_fops.owner
until the process exits.

Would it be better to check maxlen before creating the fd, or to use
get_unused_fd_flags() and call fd_install() only after the result has been
formatted?

-- 
Sashiko AI review · https://sashiko.vzint.dev/#/patchset/20261102233849.613155-1-andrey.zhadchenko@virtuozzo.com?part=5



More information about the Devel mailing list