mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2026-07-22 02:17:36 -04:00
Merge tag 'for-linus-iommufd' of git://git.kernel.org/pub/scm/linux/kernel/git/jgg/iommufd
Pull iommufd updates from Jason Gunthorpe:
"All various fixes:
- Typo breaking the veventq uAPI for 32 bit userspace
- Several Sashiko found errors in the veventq and fault fd paths
- Fix incorrect use of dmabuf locks, and possible races with iommufd
destroy and dmabuf revoke
- Sashiko errors found in the uAPI validation for IOMMU_HWPT_INVALIDATE"
* tag 'for-linus-iommufd' of git://git.kernel.org/pub/scm/linux/kernel/git/jgg/iommufd:
iommu: Avoid copying the user array twice in the full-array copy helper
iommufd/selftest: Add invalidation entry_num and entry_len boundary tests
iommufd: Set upper bounds on cache invalidation entry_num and entry_len
iommufd: Clarify IOAS_MAP_FILE dma-buf support
iommufd: Destroy the pages content after detaching from dmabuf
iommufd: Take dma_resv lock before dma_buf_unpin() in release path
iommufd/selftest: Cover invalid read counts on vEVENTQ FD
iommufd: Avoid partial fault group delivery in iommufd_fault_fops_read()
iommufd: Break the loop on failure in iommufd_fault_fops_read()
iommufd: Reject invalid read count in iommufd_fault_fops_read()
iommufd: Propagate allocation failure in iommufd_veventq_deliver_fetch()
iommufd: Reject invalid read count in iommufd_veventq_fops_read()
iommufd: Rewind header length in done if iommufd_veventq_fops_read() fails
iommufd/selftest: Add boundary tests for veventq_depth
iommufd: Set veventq_depth upper bound
iommufd: Move vevent memory allocation outside spinlock
iommufd: Fix data_len byte-count vs element-count mismatch
iommufd: Use sizeof(*hdr) instead of sizeof(hdr) in veventq read
This commit is contained in:
@@ -149,15 +149,18 @@ int iommufd_viommu_report_event(struct iommufd_viommu *viommu,
|
||||
goto out_unlock_veventqs;
|
||||
}
|
||||
|
||||
spin_lock(&veventq->common.lock);
|
||||
if (veventq->num_events == veventq->depth) {
|
||||
/* Pre-allocate to avoid GFP_ATOMIC; use GFP_NOWAIT to avoid sleeping */
|
||||
vevent = kzalloc_flex(*vevent, event_data, data_len, GFP_NOWAIT);
|
||||
if (!vevent) {
|
||||
spin_lock(&veventq->common.lock);
|
||||
vevent = &veventq->lost_events_header;
|
||||
rc = -ENOMEM;
|
||||
goto out_set_header;
|
||||
}
|
||||
|
||||
vevent = kzalloc_flex(*vevent, event_data, data_len, GFP_ATOMIC);
|
||||
if (!vevent) {
|
||||
rc = -ENOMEM;
|
||||
spin_lock(&veventq->common.lock);
|
||||
if (veventq->num_events == veventq->depth) {
|
||||
kfree(vevent);
|
||||
vevent = &veventq->lost_events_header;
|
||||
goto out_set_header;
|
||||
}
|
||||
|
||||
@@ -139,9 +139,14 @@ static ssize_t iommufd_fault_fops_read(struct file *filep, char __user *buf,
|
||||
|
||||
mutex_lock(&fault->mutex);
|
||||
while ((group = iommufd_fault_deliver_fetch(fault))) {
|
||||
size_t group_done = done;
|
||||
|
||||
if (done >= count ||
|
||||
group->fault_count * fault_size > count - done) {
|
||||
iommufd_fault_deliver_restore(fault, group);
|
||||
/* Read count doesn't fit the first fault group */
|
||||
if (done == 0)
|
||||
rc = -EINVAL;
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -157,14 +162,17 @@ static ssize_t iommufd_fault_fops_read(struct file *filep, char __user *buf,
|
||||
iommufd_compose_fault_message(&iopf->fault,
|
||||
&data, idev,
|
||||
group->cookie);
|
||||
if (copy_to_user(buf + done, &data, fault_size)) {
|
||||
if (copy_to_user(buf + group_done, &data, fault_size)) {
|
||||
xa_erase(&fault->response, group->cookie);
|
||||
iommufd_fault_deliver_restore(fault, group);
|
||||
rc = -EFAULT;
|
||||
break;
|
||||
}
|
||||
done += fault_size;
|
||||
group_done += fault_size;
|
||||
}
|
||||
if (rc)
|
||||
break;
|
||||
done = group_done;
|
||||
}
|
||||
mutex_unlock(&fault->mutex);
|
||||
|
||||
@@ -264,8 +272,10 @@ iommufd_veventq_deliver_fetch(struct iommufd_veventq *veventq)
|
||||
/* Make a copy of the lost_events_header for copy_to_user */
|
||||
if (next == &veventq->lost_events_header) {
|
||||
vevent = kzalloc_obj(*vevent, GFP_ATOMIC);
|
||||
if (!vevent)
|
||||
if (!vevent) {
|
||||
vevent = ERR_PTR(-ENOMEM);
|
||||
goto out_unlock;
|
||||
}
|
||||
}
|
||||
list_del(&next->node);
|
||||
if (vevent)
|
||||
@@ -310,8 +320,17 @@ static ssize_t iommufd_veventq_fops_read(struct file *filep, char __user *buf,
|
||||
|
||||
if (*ppos)
|
||||
return -ESPIPE;
|
||||
/* Minimum read count is a vEVENT header */
|
||||
if (count < sizeof(*hdr))
|
||||
return -EINVAL;
|
||||
|
||||
while ((cur = iommufd_veventq_deliver_fetch(veventq))) {
|
||||
if (IS_ERR(cur)) {
|
||||
if (done == 0)
|
||||
rc = PTR_ERR(cur);
|
||||
break;
|
||||
}
|
||||
|
||||
/* Validate the remaining bytes against the header size */
|
||||
if (done >= count || sizeof(*hdr) > count - done) {
|
||||
iommufd_veventq_deliver_restore(veventq, cur);
|
||||
@@ -321,8 +340,11 @@ static ssize_t iommufd_veventq_fops_read(struct file *filep, char __user *buf,
|
||||
|
||||
/* If being a normal vEVENT, validate against the full size */
|
||||
if (!vevent_for_lost_events_header(cur) &&
|
||||
sizeof(hdr) + cur->data_len > count - done) {
|
||||
sizeof(*hdr) + cur->data_len > count - done) {
|
||||
iommufd_veventq_deliver_restore(veventq, cur);
|
||||
/* Read count doesn't fit a single normal vEVENT */
|
||||
if (done == 0)
|
||||
rc = -EINVAL;
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -336,6 +358,7 @@ static ssize_t iommufd_veventq_fops_read(struct file *filep, char __user *buf,
|
||||
if (cur->data_len &&
|
||||
copy_to_user(buf + done, cur->event_data, cur->data_len)) {
|
||||
iommufd_veventq_deliver_restore(veventq, cur);
|
||||
done -= sizeof(*hdr);
|
||||
rc = -EFAULT;
|
||||
break;
|
||||
}
|
||||
@@ -473,6 +496,9 @@ int iommufd_fault_iopf_handler(struct iopf_group *group)
|
||||
static const struct file_operations iommufd_veventq_fops =
|
||||
INIT_EVENTQ_FOPS(iommufd_veventq_fops_read, NULL);
|
||||
|
||||
/* An arbitrary upper bound for veventq_depth that fits all existing HWs */
|
||||
#define VEVENTQ_MAX_DEPTH (1U << 19)
|
||||
|
||||
int iommufd_veventq_alloc(struct iommufd_ucmd *ucmd)
|
||||
{
|
||||
struct iommu_veventq_alloc *cmd = ucmd->cmd;
|
||||
@@ -484,7 +510,7 @@ int iommufd_veventq_alloc(struct iommufd_ucmd *ucmd)
|
||||
if (cmd->flags || cmd->__reserved ||
|
||||
cmd->type == IOMMU_VEVENTQ_TYPE_DEFAULT)
|
||||
return -EOPNOTSUPP;
|
||||
if (!cmd->veventq_depth)
|
||||
if (!cmd->veventq_depth || cmd->veventq_depth > VEVENTQ_MAX_DEPTH)
|
||||
return -EINVAL;
|
||||
|
||||
viommu = iommufd_get_viommu(ucmd, cmd->viommu_id);
|
||||
|
||||
@@ -489,6 +489,9 @@ int iommufd_hwpt_get_dirty_bitmap(struct iommufd_ucmd *ucmd)
|
||||
return rc;
|
||||
}
|
||||
|
||||
/* An arbitrary entry_num cap, far above any realistic invalidation batch */
|
||||
#define IOMMU_HWPT_INVALIDATE_ENTRY_NUM_MAX (1U << 19)
|
||||
|
||||
int iommufd_hwpt_invalidate(struct iommufd_ucmd *ucmd)
|
||||
{
|
||||
struct iommu_hwpt_invalidate *cmd = ucmd->cmd;
|
||||
@@ -507,7 +510,13 @@ int iommufd_hwpt_invalidate(struct iommufd_ucmd *ucmd)
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (cmd->entry_num && (!cmd->data_uptr || !cmd->entry_len)) {
|
||||
/*
|
||||
* Bound entry_num and entry_len so a single call cannot pin the CPU;
|
||||
* entry_len also caps the copy_struct_from_user() trailing-zero scan.
|
||||
*/
|
||||
if (cmd->entry_num &&
|
||||
(!cmd->data_uptr || !cmd->entry_len || cmd->entry_len > PAGE_SIZE ||
|
||||
cmd->entry_num > IOMMU_HWPT_INVALIDATE_ENTRY_NUM_MAX)) {
|
||||
rc = -EINVAL;
|
||||
goto out;
|
||||
}
|
||||
|
||||
@@ -602,7 +602,7 @@ struct iommufd_vevent {
|
||||
struct iommufd_vevent_header header;
|
||||
struct list_head node; /* for iommufd_eventq::deliver */
|
||||
ssize_t data_len;
|
||||
u64 event_data[] __counted_by(data_len);
|
||||
u8 event_data[] __counted_by(data_len);
|
||||
};
|
||||
|
||||
#define vevent_for_lost_events_header(vevent) \
|
||||
|
||||
@@ -1656,20 +1656,22 @@ void iopt_release_pages(struct kref *kref)
|
||||
WARN_ON(!RB_EMPTY_ROOT(&pages->domains_itree.rb_root));
|
||||
WARN_ON(pages->npinned);
|
||||
WARN_ON(!xa_empty(&pages->pinned_pfns));
|
||||
mmdrop(pages->source_mm);
|
||||
mutex_destroy(&pages->mutex);
|
||||
put_task_struct(pages->source_task);
|
||||
free_uid(pages->source_user);
|
||||
if (iopt_is_dmabuf(pages) && pages->dmabuf.attach) {
|
||||
struct dma_buf *dmabuf = pages->dmabuf.attach->dmabuf;
|
||||
|
||||
dma_resv_lock(dmabuf->resv, NULL);
|
||||
dma_buf_unpin(pages->dmabuf.attach);
|
||||
dma_resv_unlock(dmabuf->resv);
|
||||
dma_buf_detach(dmabuf, pages->dmabuf.attach);
|
||||
dma_buf_put(dmabuf);
|
||||
WARN_ON(!list_empty(&pages->dmabuf.tracker));
|
||||
} else if (pages->type == IOPT_ADDRESS_FILE) {
|
||||
fput(pages->file);
|
||||
}
|
||||
mmdrop(pages->source_mm);
|
||||
mutex_destroy(&pages->mutex);
|
||||
put_task_struct(pages->source_task);
|
||||
free_uid(pages->source_user);
|
||||
kfree(pages);
|
||||
}
|
||||
|
||||
|
||||
@@ -575,6 +575,7 @@ iommu_copy_struct_from_full_user_array(void *kdst, size_t kdst_entry_size,
|
||||
user_array->entry_num *
|
||||
user_array->entry_len))
|
||||
return -EFAULT;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Copy item by item */
|
||||
|
||||
@@ -224,13 +224,17 @@ struct iommu_ioas_map {
|
||||
* @size: sizeof(struct iommu_ioas_map_file)
|
||||
* @flags: same as for iommu_ioas_map
|
||||
* @ioas_id: same as for iommu_ioas_map
|
||||
* @fd: the memfd to map
|
||||
* @start: byte offset from start of file to map from
|
||||
* @fd: the memfd or supported dma-buf file to map
|
||||
* @start: byte offset from start of the file to map from
|
||||
* @length: same as for iommu_ioas_map
|
||||
* @iova: same as for iommu_ioas_map
|
||||
*
|
||||
* Set an IOVA mapping from a memfd file. All other arguments and semantics
|
||||
* match those of IOMMU_IOAS_MAP.
|
||||
* Set an IOVA mapping from a memfd file. On kernels with dma-buf support,
|
||||
* supported dma-buf files may also be accepted. This is not a generic
|
||||
* dma-buf import path; currently supported dma-bufs include single-range
|
||||
* VFIO PCI dma-bufs exported through VFIO_DEVICE_FEATURE_DMA_BUF, and
|
||||
* other dma-bufs may be rejected. All other arguments and semantics match
|
||||
* those of IOMMU_IOAS_MAP.
|
||||
*/
|
||||
struct iommu_ioas_map_file {
|
||||
__u32 size;
|
||||
|
||||
@@ -556,6 +556,21 @@ TEST_F(iommufd_ioas, alloc_hwpt_nested)
|
||||
1, &num_inv);
|
||||
assert(!num_inv);
|
||||
|
||||
/* Negative test: entry_len is bounded by PAGE_SIZE */
|
||||
num_inv = 1;
|
||||
test_err_hwpt_invalidate(EINVAL, nested_hwpt_id[0], inv_reqs,
|
||||
IOMMU_HWPT_INVALIDATE_DATA_SELFTEST,
|
||||
PAGE_SIZE + 1, &num_inv);
|
||||
assert(!num_inv);
|
||||
|
||||
/* Negative test: entry_num is bounded */
|
||||
#define IOMMU_HWPT_INVALIDATE_ENTRY_NUM_MAX (1U << 19)
|
||||
num_inv = IOMMU_HWPT_INVALIDATE_ENTRY_NUM_MAX + 1;
|
||||
test_err_hwpt_invalidate(EINVAL, nested_hwpt_id[0], inv_reqs,
|
||||
IOMMU_HWPT_INVALIDATE_DATA_SELFTEST,
|
||||
sizeof(*inv_reqs), &num_inv);
|
||||
assert(!num_inv);
|
||||
|
||||
/* Negative test: invalid flag is passed */
|
||||
num_inv = 1;
|
||||
inv_reqs[0].flags = 0xffffffff;
|
||||
@@ -2980,22 +2995,54 @@ TEST_F(iommufd_viommu, vdevice_alloc)
|
||||
uint32_t veventq_id;
|
||||
uint32_t veventq_fd;
|
||||
int prev_seq = -1;
|
||||
size_t hdr_size = sizeof(struct iommufd_vevent_header);
|
||||
char vbuf[64];
|
||||
|
||||
if (dev_id) {
|
||||
/* Must allocate vdevice before attaching to a nested hwpt */
|
||||
test_err_mock_domain_replace(ENOENT, self->stdev_id,
|
||||
self->nested_hwpt_id);
|
||||
|
||||
/* Test depth lower and upper bounds (mirrors kernel cap) */
|
||||
#define VEVENTQ_MAX_DEPTH (1U << 19)
|
||||
test_err_veventq_alloc(EINVAL, viommu_id,
|
||||
IOMMU_VEVENTQ_TYPE_SELFTEST, 0, NULL,
|
||||
NULL);
|
||||
test_err_veventq_alloc(EINVAL, viommu_id,
|
||||
IOMMU_VEVENTQ_TYPE_SELFTEST,
|
||||
VEVENTQ_MAX_DEPTH + 1, NULL, NULL);
|
||||
test_cmd_veventq_alloc(viommu_id, IOMMU_VEVENTQ_TYPE_SELFTEST,
|
||||
VEVENTQ_MAX_DEPTH, &veventq_id,
|
||||
&veventq_fd);
|
||||
close(veventq_fd);
|
||||
test_ioctl_destroy(veventq_id);
|
||||
|
||||
/* Allocate a vEVENTQ with veventq_depth=2 */
|
||||
test_cmd_veventq_alloc(viommu_id, IOMMU_VEVENTQ_TYPE_SELFTEST,
|
||||
&veventq_id, &veventq_fd);
|
||||
2, &veventq_id, &veventq_fd);
|
||||
test_err_veventq_alloc(EEXIST, viommu_id,
|
||||
IOMMU_VEVENTQ_TYPE_SELFTEST, NULL, NULL);
|
||||
IOMMU_VEVENTQ_TYPE_SELFTEST, 2, NULL,
|
||||
NULL);
|
||||
|
||||
/* Invalid read counts on an empty vEVENTQ */
|
||||
ASSERT_EQ(-1, read(veventq_fd, vbuf, 0));
|
||||
ASSERT_EQ(EINVAL, errno);
|
||||
ASSERT_EQ(-1, read(veventq_fd, vbuf, hdr_size - 1));
|
||||
ASSERT_EQ(EINVAL, errno);
|
||||
|
||||
/* Set vdev_id to 0x99, unset it, and set to 0x88 */
|
||||
test_cmd_vdevice_alloc(viommu_id, dev_id, 0x99, &vdev_id);
|
||||
test_cmd_mock_domain_replace(self->stdev_id,
|
||||
self->nested_hwpt_id);
|
||||
test_cmd_trigger_vevents(dev_id, 1);
|
||||
|
||||
/* Invalid read counts on a non-empty vEVENTQ */
|
||||
ASSERT_EQ(-1, read(veventq_fd, vbuf, 0));
|
||||
ASSERT_EQ(EINVAL, errno);
|
||||
/* header fits but the event's payload doesn't */
|
||||
ASSERT_EQ(-1, read(veventq_fd, vbuf, hdr_size));
|
||||
ASSERT_EQ(EINVAL, errno);
|
||||
|
||||
test_cmd_read_vevents(veventq_fd, 1, 0x99, &prev_seq);
|
||||
test_err_vdevice_alloc(EEXIST, viommu_id, dev_id, 0x99,
|
||||
&vdev_id);
|
||||
|
||||
@@ -712,7 +712,7 @@ TEST_FAIL_NTH(basic_fail_nth, device)
|
||||
return -1;
|
||||
|
||||
if (_test_cmd_veventq_alloc(self->fd, viommu_id,
|
||||
IOMMU_VEVENTQ_TYPE_SELFTEST, &veventq_id,
|
||||
IOMMU_VEVENTQ_TYPE_SELFTEST, 2, &veventq_id,
|
||||
&veventq_fd))
|
||||
return -1;
|
||||
close(veventq_fd);
|
||||
|
||||
@@ -1060,12 +1060,13 @@ static int _test_cmd_hw_queue_alloc(int fd, __u32 viommu_id, __u32 type,
|
||||
base_addr, len, out_qid))
|
||||
|
||||
static int _test_cmd_veventq_alloc(int fd, __u32 viommu_id, __u32 type,
|
||||
__u32 *veventq_id, __u32 *veventq_fd)
|
||||
__u32 depth, __u32 *veventq_id,
|
||||
__u32 *veventq_fd)
|
||||
{
|
||||
struct iommu_veventq_alloc cmd = {
|
||||
.size = sizeof(cmd),
|
||||
.type = type,
|
||||
.veventq_depth = 2,
|
||||
.veventq_depth = depth,
|
||||
.viommu_id = viommu_id,
|
||||
};
|
||||
int ret;
|
||||
@@ -1080,13 +1081,13 @@ static int _test_cmd_veventq_alloc(int fd, __u32 viommu_id, __u32 type,
|
||||
return 0;
|
||||
}
|
||||
|
||||
#define test_cmd_veventq_alloc(viommu_id, type, veventq_id, veventq_fd) \
|
||||
ASSERT_EQ(0, _test_cmd_veventq_alloc(self->fd, viommu_id, type, \
|
||||
#define test_cmd_veventq_alloc(viommu_id, type, depth, veventq_id, veventq_fd) \
|
||||
ASSERT_EQ(0, _test_cmd_veventq_alloc(self->fd, viommu_id, type, depth, \
|
||||
veventq_id, veventq_fd))
|
||||
#define test_err_veventq_alloc(_errno, viommu_id, type, veventq_id, \
|
||||
veventq_fd) \
|
||||
EXPECT_ERRNO(_errno, \
|
||||
_test_cmd_veventq_alloc(self->fd, viommu_id, type, \
|
||||
#define test_err_veventq_alloc(_errno, viommu_id, type, depth, veventq_id, \
|
||||
veventq_fd) \
|
||||
EXPECT_ERRNO(_errno, \
|
||||
_test_cmd_veventq_alloc(self->fd, viommu_id, type, depth, \
|
||||
veventq_id, veventq_fd))
|
||||
|
||||
static int _test_cmd_trigger_vevents(int fd, __u32 dev_id, __u32 nvevents)
|
||||
|
||||
Reference in New Issue
Block a user