Re: [PATCH v7 07/15] s390/vfio-ap: File ops called to save the vfio device migration state

Anthony Krowiak <[email protected]>
Newsgroups dev.linux.lists.sashiko-reviews,org.kernel.vger.kvm,org.kernel.vger.linux-s390
Message-ID <[email protected]>

On 8/7/26 6:37 PM, [email protected] wrote:
> Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
> - [Critical] Dropping `mdevs_lock` before `copy_to_user()` in `vfio_ap_stop_copy_read` exposes `mig_data` and its `ap_config` buffer to concurrent frees, leading to a Use-After-Free and kernel memory leak.
> - [High] Concurrent reads from the stop_copy file race to initialize `ap_config` because `mdevs_lock` is temporarily dropped in `vfio_ap_get_config()`, causing memory leaks of dynamically allocated configuration buffers.
> - [High] Caching the local `mig_file` pointer before dropping `mdevs_lock` in `vfio_ap_get_config()` leads to a Use-After-Free if `mig_data` is reallocated.
> --
>
> --- Patch [7]: [PATCH v7 07/15] s390/vfio-ap: File ops called to save the vfio device migration state ---
> commit 373ad9ea7be2af5d58b1a74f7797578d1528e3b7
> Author: Anthony Krowiak <[email protected]>
>
> s390/vfio-ap: File ops called to save the vfio device migration state
>
> This patch implements the read callback for the vfio-ap device migration
> state file. It copies the guest's AP configuration and hardware information
> to userspace when the migration state transitions to STOP_COPY. This state
> data will then be transferred to the destination host.
>
>> diff --git a/drivers/s390/crypto/vfio_ap_migration.c b/drivers/s390/crypto/vfio_ap_migration.c
>> index de693b308925d..50781b61f7f18 100644
>> --- a/drivers/s390/crypto/vfio_ap_migration.c
>> +++ b/drivers/s390/crypto/vfio_ap_migration.c
> [ ... ]
>> @@ -150,6 +144,279 @@ static int vfio_ap_release_mig_file(struct inode *file_inode, struct file *filp)
>>   	return 0;
>>   }
> [ ... ]
>> +static int vfio_ap_get_config(struct ap_matrix_mdev *matrix_mdev)
>> +{The scenario requires two read() calls to simultaneously observe ap_config == NULL and both enter vfio_ap_get_config(). This cannot happen because:
>>
>> The fd is opened with stream_open(), which serializes read calls on the same fd.
>> Even if two independent fds existed (they don't — one fd per STOP_COPY transition), both callers still serialize on mdevs_lock. The second to acquire the lock will find ap_config already populated and skip the allocation.
> [ ... ]
>> +	ap_configuration = kvzalloc(ap_config_size, GFP_KERNEL_ACCOUNT);
>> +	if (!ap_configuration)
>> +		return -ENOMEM;
> [ ... ]
>> +	/*
>> +	 * Unlock the mdevs_lock so other mdevs are not precluded from being
>> +	 * accessed while a potentially long running operation is performed.
>> +	 */
>> +	mutex_unlock(&matrix_dev->mdevs_lock);
>> +	ret = vfio_ap_store_queue_info(mdev_name, ap_configuration);
>> +	mutex_lock(&matrix_dev->mdevs_lock);
>> +	if (ret) {
>> +		kvfree(ap_configuration);
>> +		return ret;
>> +	}
>> +
>> +	if (!matrix_mdev->mig_data) {
>> +		kvfree(ap_configuration);
>> +		return -ENODEV;
>> +	}
>> +
>> +	matrix_mdev->mig_data->stop_copy_mig_file.ap_config = ap_configuration;
> [Severity: High]
> Can two concurrent reads cause a memory leak here?
>
> Since mdevs_lock is temporarily dropped above, both threads might observe
> that ap_config is NULL in vfio_ap_stop_copy_read(), and both would allocate
> an ap_configuration buffer.
>
> When they reacquire the lock here, they will both blindly overwrite
> matrix_mdev->mig_data->stop_copy_mig_file.ap_config with their respective
> allocations, permanently leaking the buffer allocated by the first thread.

The scenario requires two read() calls to simultaneously observe
ap_config == NULL and both enter vfio_ap_get_config(). This cannot happen
because:

1. The fd is opened with stream_open(), which serializes read calls on the
    same fd.
2. Even if two independent fds existed (they don't — one fd per
    STOP_COPY transition), both callers still serialize on mdevs_lock. The
    second to acquire the lock will find ap_config already populated and
    skip the allocation.

>
>> +	matrix_mdev->mig_data->stop_copy_mig_file.config_sz = ap_config_size;
>> +
>> +	return 0;
>> +}
>> +
>> +static ssize_t vfio_ap_stop_copy_read(struct file *filp, char __user *buf,
>> +				      size_t len, loff_t *pos)
>> +{
> [ ... ]
>> +	matrix_mdev = filp->private_data;
>> +	mig_file = &matrix_mdev->mig_data->stop_copy_mig_file;
>> +
>> +	if (!mig_file->ap_config) {
>> +		ret = vfio_ap_get_config(matrix_mdev);
> [Severity: High]
> Could the cached mig_file pointer become stale and cause a use-after-free?
>
> Because vfio_ap_get_config() temporarily drops mdevs_lock, a concurrent
> thread could potentially free and reallocate the mig_data structure. When
> vfio_ap_get_config() returns, the stack-cached mig_file pointer might still
> point to the old, freed mig_data structure.

The concern that mig_data is freed while the lock is dropped inside
vfio_ap_get_config(), making the pointer stale when
vfio_ap_stop_copy_read() uses it afterward is unfounded:

1. All callers that free or NULL out mig_data —
    vfio_ap_release_migration_data() and vfio_ap_reset_migration_state() -
    hold mdevs_lock, same as the unlock/relock pair inside
    vfio_ap_get_config().

2. vfio_ap_get_config() already re-checks matrix_mdev->mig_data after
    re-acquiring the lock and returns -ENODEV before touching anything if it
    was freed. That return propagates up to vfio_ap_stop_copy_read(), which
    exits before the stale mig_file is accessed.

>
>> +		if (ret) {
>> +			mutex_unlock(&matrix_dev->mdevs_lock);
>> +			return ret;
>> +		}
>> +	}
>> +
>> +	/*
>> +	 * Compute the offset and clamped length fully under the lock so that
>> +	 * concurrent read()s on this stream file each see a consistent view of
>> +	 * the current position.  *pos is advanced here while we still hold the
>> +	 * lock; copy_to_user() then uses the snapshot read_pos.  This prevents
>> +	 * two threads from calculating the same offset and both copying the
>> +	 * same region (or one reading past the end of the buffer).
>> +	 */
>> +	if (*pos >= mig_file->config_sz) {
>> +		mutex_unlock(&matrix_dev->mdevs_lock);
>> +		return 0;
>> +	}
>> +
>> +	len = min_t(size_t, mig_file->config_sz - *pos, len);
>> +	if (len == 0) {
>> +		mutex_unlock(&matrix_dev->mdevs_lock);
>> +		return 0;
>> +	}
>> +
>> +	read_pos = *pos;
>> +	*pos += len;
>> +
>> +	/*
>> +	 * Drop the lock only for the copy_to_user().  The ap_config buffer is
>> +	 * stable: it is allocated once in vfio_ap_get_config() and freed only
>> +	 * in vfio_ap_release_stop_copy_file() which requires mdevs_lock.
>> +	 * Since we already advanced *pos above, no other thread will compute an
>> +	 * overlapping region.
>> +	 */
>> +	mutex_unlock(&matrix_dev->mdevs_lock);
>> +
>> +	if (copy_to_user(buf, (char *)mig_file->ap_config + read_pos, len))
> [Severity: Critical]
> Is it safe to access mig_file and its ap_config here without the lock?
>
> Once mdevs_lock is dropped, a concurrent thread could close the VFIO device
> or issue a reset, which acquires mdevs_lock and frees both the ap_config
> buffer and the mig_data struct.
>
> If that happens before or during copy_to_user(), it could result in reading
> freed kernel memory and leaking it to userspace, or crashing the kernel.

The code dropped mdevs_lock before copy_to_user(), but
vfio_ap_reset_migration_state() (e.g. triggered by VFIO_DEVICE_RESET while
the stop-copy fd is still open) calls vfio_ap_release_stop_copy_file()
under the lock, which kvfree()s the buffer. A concurrent copy_to_user()
outside the lock would then read freed kernel memory.

Since the ap_config data is at most a few KB for any realistic AP
configuration, the fix would be to hold the lock while the
copy_from_user is executing. This is safe to do under a sleeping mutex.

>
>> +		return -EFAULT;
>> +
>> +	return len;
>> +}
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.