Commit f4b20bb3 authored by Jason Gunthorpe's avatar Jason Gunthorpe

iommufd: Add kernel support for testing iommufd

Provide a mock kernel module for the iommu_domain that allows it to run
without any HW and the mocking provides a way to directly validate that
the PFNs loaded into the iommu_domain are correct. This exposes the access
kAPI toward userspace to allow userspace to explore the functionality of
pages.c and io_pagetable.c

The mock also simulates the rare case of PAGE_SIZE > iommu page size as
the mock will operate at a 2K iommu page size. This allows exercising all
of the calculations to support this mismatch.

This is also intended to support syzkaller exploring the same space.

However, it is an unusually invasive config option to enable all of
this. The config option should not be enabled in a production kernel.

Link: https://lore.kernel.org/r/16-v6-a196d26f289e+11787-iommufd_jgg@nvidia.com
Tested-by: Matthew Rosato <mjrosato@linux.ibm.com> # s390
Tested-by: Eric Auger <eric.auger@redhat.com> # aarch64
Signed-off-by: default avatarJason Gunthorpe <jgg@nvidia.com>
parent d624d665
...@@ -10,3 +10,15 @@ config IOMMUFD ...@@ -10,3 +10,15 @@ config IOMMUFD
it relates to managing IO page tables that point at user space memory. it relates to managing IO page tables that point at user space memory.
If you don't know what to do here, say N. If you don't know what to do here, say N.
if IOMMUFD
config IOMMUFD_TEST
bool "IOMMU Userspace API Test support"
depends on DEBUG_KERNEL
depends on FAULT_INJECTION
depends on RUNTIME_TESTING_MENU
default n
help
This is dangerous, do not enable unless running
tools/testing/selftests/iommu
endif
...@@ -8,4 +8,6 @@ iommufd-y := \ ...@@ -8,4 +8,6 @@ iommufd-y := \
pages.o \ pages.o \
vfio_compat.o vfio_compat.o
iommufd-$(CONFIG_IOMMUFD_TEST) += selftest.o
obj-$(CONFIG_IOMMUFD) += iommufd.o obj-$(CONFIG_IOMMUFD) += iommufd.o
...@@ -733,3 +733,41 @@ int iommufd_access_rw(struct iommufd_access *access, unsigned long iova, ...@@ -733,3 +733,41 @@ int iommufd_access_rw(struct iommufd_access *access, unsigned long iova,
return rc; return rc;
} }
EXPORT_SYMBOL_NS_GPL(iommufd_access_rw, IOMMUFD); EXPORT_SYMBOL_NS_GPL(iommufd_access_rw, IOMMUFD);
#ifdef CONFIG_IOMMUFD_TEST
/*
* Creating a real iommufd_device is too hard, bypass creating a iommufd_device
* and go directly to attaching a domain.
*/
struct iommufd_hw_pagetable *
iommufd_device_selftest_attach(struct iommufd_ctx *ictx,
struct iommufd_ioas *ioas,
struct device *mock_dev)
{
struct iommufd_hw_pagetable *hwpt;
int rc;
hwpt = iommufd_hw_pagetable_alloc(ictx, ioas, mock_dev);
if (IS_ERR(hwpt))
return hwpt;
rc = iopt_table_add_domain(&hwpt->ioas->iopt, hwpt->domain);
if (rc)
goto out_hwpt;
refcount_inc(&hwpt->obj.users);
iommufd_object_finalize(ictx, &hwpt->obj);
return hwpt;
out_hwpt:
iommufd_object_abort_and_destroy(ictx, &hwpt->obj);
return ERR_PTR(rc);
}
void iommufd_device_selftest_detach(struct iommufd_ctx *ictx,
struct iommufd_hw_pagetable *hwpt)
{
iopt_table_remove_domain(&hwpt->ioas->iopt, hwpt->domain);
refcount_dec(&hwpt->obj.users);
}
#endif
...@@ -242,6 +242,9 @@ int iommufd_ioas_copy(struct iommufd_ucmd *ucmd) ...@@ -242,6 +242,9 @@ int iommufd_ioas_copy(struct iommufd_ucmd *ucmd)
unsigned long iova; unsigned long iova;
int rc; int rc;
iommufd_test_syz_conv_iova_id(ucmd, cmd->src_ioas_id, &cmd->src_iova,
&cmd->flags);
if ((cmd->flags & if ((cmd->flags &
~(IOMMU_IOAS_MAP_FIXED_IOVA | IOMMU_IOAS_MAP_WRITEABLE | ~(IOMMU_IOAS_MAP_FIXED_IOVA | IOMMU_IOAS_MAP_WRITEABLE |
IOMMU_IOAS_MAP_READABLE))) IOMMU_IOAS_MAP_READABLE)))
......
...@@ -113,6 +113,9 @@ enum iommufd_object_type { ...@@ -113,6 +113,9 @@ enum iommufd_object_type {
IOMMUFD_OBJ_HW_PAGETABLE, IOMMUFD_OBJ_HW_PAGETABLE,
IOMMUFD_OBJ_IOAS, IOMMUFD_OBJ_IOAS,
IOMMUFD_OBJ_ACCESS, IOMMUFD_OBJ_ACCESS,
#ifdef CONFIG_IOMMUFD_TEST
IOMMUFD_OBJ_SELFTEST,
#endif
}; };
/* Base struct for all objects with a userspace ID handle. */ /* Base struct for all objects with a userspace ID handle. */
...@@ -269,4 +272,36 @@ void iopt_remove_access(struct io_pagetable *iopt, ...@@ -269,4 +272,36 @@ void iopt_remove_access(struct io_pagetable *iopt,
struct iommufd_access *access); struct iommufd_access *access);
void iommufd_access_destroy_object(struct iommufd_object *obj); void iommufd_access_destroy_object(struct iommufd_object *obj);
#ifdef CONFIG_IOMMUFD_TEST
struct iommufd_hw_pagetable *
iommufd_device_selftest_attach(struct iommufd_ctx *ictx,
struct iommufd_ioas *ioas,
struct device *mock_dev);
void iommufd_device_selftest_detach(struct iommufd_ctx *ictx,
struct iommufd_hw_pagetable *hwpt);
int iommufd_test(struct iommufd_ucmd *ucmd);
void iommufd_selftest_destroy(struct iommufd_object *obj);
extern size_t iommufd_test_memory_limit;
void iommufd_test_syz_conv_iova_id(struct iommufd_ucmd *ucmd,
unsigned int ioas_id, u64 *iova, u32 *flags);
bool iommufd_should_fail(void);
void __init iommufd_test_init(void);
void iommufd_test_exit(void);
#else
static inline void iommufd_test_syz_conv_iova_id(struct iommufd_ucmd *ucmd,
unsigned int ioas_id,
u64 *iova, u32 *flags)
{
}
static inline bool iommufd_should_fail(void)
{
return false;
}
static inline void __init iommufd_test_init(void)
{
}
static inline void iommufd_test_exit(void)
{
}
#endif
#endif #endif
/* SPDX-License-Identifier: GPL-2.0 */
/* Copyright (c) 2021-2022, NVIDIA CORPORATION & AFFILIATES.
*/
#ifndef _UAPI_IOMMUFD_TEST_H
#define _UAPI_IOMMUFD_TEST_H
#include <linux/types.h>
#include <linux/iommufd.h>
enum {
IOMMU_TEST_OP_ADD_RESERVED = 1,
IOMMU_TEST_OP_MOCK_DOMAIN,
IOMMU_TEST_OP_MD_CHECK_MAP,
IOMMU_TEST_OP_MD_CHECK_REFS,
IOMMU_TEST_OP_CREATE_ACCESS,
IOMMU_TEST_OP_DESTROY_ACCESS_PAGES,
IOMMU_TEST_OP_ACCESS_PAGES,
IOMMU_TEST_OP_ACCESS_RW,
IOMMU_TEST_OP_SET_TEMP_MEMORY_LIMIT,
};
enum {
MOCK_APERTURE_START = 1UL << 24,
MOCK_APERTURE_LAST = (1UL << 31) - 1,
};
enum {
MOCK_FLAGS_ACCESS_WRITE = 1 << 0,
MOCK_FLAGS_ACCESS_SYZ = 1 << 16,
};
enum {
MOCK_ACCESS_RW_WRITE = 1 << 0,
MOCK_ACCESS_RW_SLOW_PATH = 1 << 2,
};
enum {
MOCK_FLAGS_ACCESS_CREATE_NEEDS_PIN_PAGES = 1 << 0,
};
struct iommu_test_cmd {
__u32 size;
__u32 op;
__u32 id;
__u32 __reserved;
union {
struct {
__aligned_u64 start;
__aligned_u64 length;
} add_reserved;
struct {
__u32 out_device_id;
__u32 out_hwpt_id;
} mock_domain;
struct {
__aligned_u64 iova;
__aligned_u64 length;
__aligned_u64 uptr;
} check_map;
struct {
__aligned_u64 length;
__aligned_u64 uptr;
__u32 refs;
} check_refs;
struct {
__u32 out_access_fd;
__u32 flags;
} create_access;
struct {
__u32 access_pages_id;
} destroy_access_pages;
struct {
__u32 flags;
__u32 out_access_pages_id;
__aligned_u64 iova;
__aligned_u64 length;
__aligned_u64 uptr;
} access_pages;
struct {
__aligned_u64 iova;
__aligned_u64 length;
__aligned_u64 uptr;
__u32 flags;
} access_rw;
struct {
__u32 limit;
} memory_limit;
};
__u32 last;
};
#define IOMMU_TEST_CMD _IO(IOMMUFD_TYPE, IOMMUFD_CMD_BASE + 32)
#endif
...@@ -19,6 +19,7 @@ ...@@ -19,6 +19,7 @@
#include <linux/iommufd.h> #include <linux/iommufd.h>
#include "iommufd_private.h" #include "iommufd_private.h"
#include "iommufd_test.h"
struct iommufd_object_ops { struct iommufd_object_ops {
void (*destroy)(struct iommufd_object *obj); void (*destroy)(struct iommufd_object *obj);
...@@ -239,6 +240,9 @@ union ucmd_buffer { ...@@ -239,6 +240,9 @@ union ucmd_buffer {
struct iommu_ioas_iova_ranges iova_ranges; struct iommu_ioas_iova_ranges iova_ranges;
struct iommu_ioas_map map; struct iommu_ioas_map map;
struct iommu_ioas_unmap unmap; struct iommu_ioas_unmap unmap;
#ifdef CONFIG_IOMMUFD_TEST
struct iommu_test_cmd test;
#endif
}; };
struct iommufd_ioctl_op { struct iommufd_ioctl_op {
...@@ -275,6 +279,9 @@ static const struct iommufd_ioctl_op iommufd_ioctl_ops[] = { ...@@ -275,6 +279,9 @@ static const struct iommufd_ioctl_op iommufd_ioctl_ops[] = {
val64), val64),
IOCTL_OP(IOMMU_VFIO_IOAS, iommufd_vfio_ioas, struct iommu_vfio_ioas, IOCTL_OP(IOMMU_VFIO_IOAS, iommufd_vfio_ioas, struct iommu_vfio_ioas,
__reserved), __reserved),
#ifdef CONFIG_IOMMUFD_TEST
IOCTL_OP(IOMMU_TEST_CMD, iommufd_test, struct iommu_test_cmd, last),
#endif
}; };
static long iommufd_fops_ioctl(struct file *filp, unsigned int cmd, static long iommufd_fops_ioctl(struct file *filp, unsigned int cmd,
...@@ -375,6 +382,11 @@ static const struct iommufd_object_ops iommufd_object_ops[] = { ...@@ -375,6 +382,11 @@ static const struct iommufd_object_ops iommufd_object_ops[] = {
[IOMMUFD_OBJ_HW_PAGETABLE] = { [IOMMUFD_OBJ_HW_PAGETABLE] = {
.destroy = iommufd_hw_pagetable_destroy, .destroy = iommufd_hw_pagetable_destroy,
}, },
#ifdef CONFIG_IOMMUFD_TEST
[IOMMUFD_OBJ_SELFTEST] = {
.destroy = iommufd_selftest_destroy,
},
#endif
}; };
static struct miscdevice iommu_misc_dev = { static struct miscdevice iommu_misc_dev = {
...@@ -392,11 +404,13 @@ static int __init iommufd_init(void) ...@@ -392,11 +404,13 @@ static int __init iommufd_init(void)
ret = misc_register(&iommu_misc_dev); ret = misc_register(&iommu_misc_dev);
if (ret) if (ret)
return ret; return ret;
iommufd_test_init();
return 0; return 0;
} }
static void __exit iommufd_exit(void) static void __exit iommufd_exit(void)
{ {
iommufd_test_exit();
misc_deregister(&iommu_misc_dev); misc_deregister(&iommu_misc_dev);
} }
......
...@@ -56,7 +56,11 @@ ...@@ -56,7 +56,11 @@
#include "io_pagetable.h" #include "io_pagetable.h"
#include "double_span.h" #include "double_span.h"
#ifndef CONFIG_IOMMUFD_TEST
#define TEMP_MEMORY_LIMIT 65536 #define TEMP_MEMORY_LIMIT 65536
#else
#define TEMP_MEMORY_LIMIT iommufd_test_memory_limit
#endif
#define BATCH_BACKUP_SIZE 32 #define BATCH_BACKUP_SIZE 32
/* /*
...@@ -1756,6 +1760,10 @@ int iopt_pages_rw_access(struct iopt_pages *pages, unsigned long start_byte, ...@@ -1756,6 +1760,10 @@ int iopt_pages_rw_access(struct iopt_pages *pages, unsigned long start_byte,
bool change_mm = current->mm != pages->source_mm; bool change_mm = current->mm != pages->source_mm;
int rc = 0; int rc = 0;
if (IS_ENABLED(CONFIG_IOMMUFD_TEST) &&
(flags & __IOMMUFD_ACCESS_RW_SLOW_PATH))
change_mm = true;
if ((flags & IOMMUFD_ACCESS_RW_WRITE) && !pages->writable) if ((flags & IOMMUFD_ACCESS_RW_WRITE) && !pages->writable)
return -EPERM; return -EPERM;
......
This diff is collapsed.
...@@ -34,6 +34,9 @@ enum { ...@@ -34,6 +34,9 @@ enum {
IOMMUFD_ACCESS_RW_WRITE = 1 << 0, IOMMUFD_ACCESS_RW_WRITE = 1 << 0,
/* Set if the caller is in a kthread then rw will use kthread_use_mm() */ /* Set if the caller is in a kthread then rw will use kthread_use_mm() */
IOMMUFD_ACCESS_RW_KTHREAD = 1 << 1, IOMMUFD_ACCESS_RW_KTHREAD = 1 << 1,
/* Only for use by selftest */
__IOMMUFD_ACCESS_RW_SLOW_PATH = 1 << 2,
}; };
struct iommufd_access * struct iommufd_access *
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment