| // SPDX-License-Identifier: GPL-2.0 |
| /* |
| * selftest for the Ultravisor UAPI device |
| * |
| * Copyright IBM Corp. 2022 |
| * Author(s): Steffen Eiden <seiden@linux.ibm.com> |
| */ |
| |
| #include <stdint.h> |
| #include <fcntl.h> |
| #include <errno.h> |
| #include <sys/ioctl.h> |
| #include <sys/mman.h> |
| |
| #include <asm/uvdevice.h> |
| |
| #include "../../../kselftest_harness.h" |
| |
| #define UV_PATH "/dev/uv" |
| #define BUFFER_SIZE 0x200 |
| FIXTURE(uvio_fixture) { |
| int uv_fd; |
| struct uvio_ioctl_cb uvio_ioctl; |
| uint8_t buffer[BUFFER_SIZE]; |
| __u64 fault_page; |
| }; |
| |
| FIXTURE_VARIANT(uvio_fixture) { |
| unsigned long ioctl_cmd; |
| uint32_t arg_size; |
| }; |
| |
| FIXTURE_VARIANT_ADD(uvio_fixture, att) { |
| .ioctl_cmd = UVIO_IOCTL_ATT, |
| .arg_size = sizeof(struct uvio_attest), |
| }; |
| |
| FIXTURE_SETUP(uvio_fixture) |
| { |
| self->uv_fd = open(UV_PATH, O_ACCMODE); |
| |
| self->uvio_ioctl.argument_addr = (__u64)self->buffer; |
| self->uvio_ioctl.argument_len = variant->arg_size; |
| self->fault_page = |
| (__u64)mmap(NULL, (size_t)getpagesize(), PROT_NONE, MAP_ANONYMOUS, -1, 0); |
| } |
| |
| FIXTURE_TEARDOWN(uvio_fixture) |
| { |
| if (self->uv_fd) |
| close(self->uv_fd); |
| munmap((void *)self->fault_page, (size_t)getpagesize()); |
| } |
| |
| TEST_F(uvio_fixture, fault_ioctl_arg) |
| { |
| int rc, errno_cache; |
| |
| rc = ioctl(self->uv_fd, variant->ioctl_cmd, NULL); |
| errno_cache = errno; |
| ASSERT_EQ(rc, -1); |
| ASSERT_EQ(errno_cache, EFAULT); |
| |
| rc = ioctl(self->uv_fd, variant->ioctl_cmd, self->fault_page); |
| errno_cache = errno; |
| ASSERT_EQ(rc, -1); |
| ASSERT_EQ(errno_cache, EFAULT); |
| } |
| |
| TEST_F(uvio_fixture, fault_uvio_arg) |
| { |
| int rc, errno_cache; |
| |
| self->uvio_ioctl.argument_addr = 0; |
| rc = ioctl(self->uv_fd, variant->ioctl_cmd, &self->uvio_ioctl); |
| errno_cache = errno; |
| ASSERT_EQ(rc, -1); |
| ASSERT_EQ(errno_cache, EFAULT); |
| |
| self->uvio_ioctl.argument_addr = self->fault_page; |
| rc = ioctl(self->uv_fd, variant->ioctl_cmd, &self->uvio_ioctl); |
| errno_cache = errno; |
| ASSERT_EQ(rc, -1); |
| ASSERT_EQ(errno_cache, EFAULT); |
| } |
| |
| /* |
| * Test to verify that IOCTLs with invalid values in the ioctl_control block |
| * are rejected. |
| */ |
| TEST_F(uvio_fixture, inval_ioctl_cb) |
| { |
| int rc, errno_cache; |
| |
| self->uvio_ioctl.argument_len = 0; |
| rc = ioctl(self->uv_fd, variant->ioctl_cmd, &self->uvio_ioctl); |
| errno_cache = errno; |
| ASSERT_EQ(rc, -1); |
| ASSERT_EQ(errno_cache, EINVAL); |
| |
| self->uvio_ioctl.argument_len = (uint32_t)-1; |
| rc = ioctl(self->uv_fd, variant->ioctl_cmd, &self->uvio_ioctl); |
| errno_cache = errno; |
| ASSERT_EQ(rc, -1); |
| ASSERT_EQ(errno_cache, EINVAL); |
| self->uvio_ioctl.argument_len = variant->arg_size; |
| |
| self->uvio_ioctl.flags = (uint32_t)-1; |
| rc = ioctl(self->uv_fd, variant->ioctl_cmd, &self->uvio_ioctl); |
| errno_cache = errno; |
| ASSERT_EQ(rc, -1); |
| ASSERT_EQ(errno_cache, EINVAL); |
| self->uvio_ioctl.flags = 0; |
| |
| memset(self->uvio_ioctl.reserved14, 0xff, sizeof(self->uvio_ioctl.reserved14)); |
| rc = ioctl(self->uv_fd, variant->ioctl_cmd, &self->uvio_ioctl); |
| errno_cache = errno; |
| ASSERT_EQ(rc, -1); |
| ASSERT_EQ(errno_cache, EINVAL); |
| |
| memset(&self->uvio_ioctl, 0x11, sizeof(self->uvio_ioctl)); |
| rc = ioctl(self->uv_fd, variant->ioctl_cmd, &self->uvio_ioctl); |
| ASSERT_EQ(rc, -1); |
| } |
| |
| TEST_F(uvio_fixture, inval_ioctl_cmd) |
| { |
| int rc, errno_cache; |
| uint8_t nr = _IOC_NR(variant->ioctl_cmd); |
| unsigned long cmds[] = { |
| _IOWR('a', nr, struct uvio_ioctl_cb), |
| _IOWR(UVIO_TYPE_UVC, nr, int), |
| _IO(UVIO_TYPE_UVC, nr), |
| _IOR(UVIO_TYPE_UVC, nr, struct uvio_ioctl_cb), |
| _IOW(UVIO_TYPE_UVC, nr, struct uvio_ioctl_cb), |
| }; |
| |
| for (size_t i = 0; i < ARRAY_SIZE(cmds); i++) { |
| rc = ioctl(self->uv_fd, cmds[i], &self->uvio_ioctl); |
| errno_cache = errno; |
| ASSERT_EQ(rc, -1); |
| ASSERT_EQ(errno_cache, ENOTTY); |
| } |
| } |
| |
| struct test_attest_buffer { |
| uint8_t arcb[0x180]; |
| uint8_t meas[64]; |
| uint8_t add[32]; |
| }; |
| |
| FIXTURE(attest_fixture) { |
| int uv_fd; |
| struct uvio_ioctl_cb uvio_ioctl; |
| struct uvio_attest uvio_attest; |
| struct test_attest_buffer attest_buffer; |
| __u64 fault_page; |
| }; |
| |
| FIXTURE_SETUP(attest_fixture) |
| { |
| self->uv_fd = open(UV_PATH, O_ACCMODE); |
| |
| self->uvio_ioctl.argument_addr = (__u64)&self->uvio_attest; |
| self->uvio_ioctl.argument_len = sizeof(self->uvio_attest); |
| |
| self->uvio_attest.arcb_addr = (__u64)&self->attest_buffer.arcb; |
| self->uvio_attest.arcb_len = sizeof(self->attest_buffer.arcb); |
| |
| self->uvio_attest.meas_addr = (__u64)&self->attest_buffer.meas; |
| self->uvio_attest.meas_len = sizeof(self->attest_buffer.meas); |
| |
| self->uvio_attest.add_data_addr = (__u64)&self->attest_buffer.add; |
| self->uvio_attest.add_data_len = sizeof(self->attest_buffer.add); |
| self->fault_page = |
| (__u64)mmap(NULL, (size_t)getpagesize(), PROT_NONE, MAP_ANONYMOUS, -1, 0); |
| } |
| |
| FIXTURE_TEARDOWN(attest_fixture) |
| { |
| if (self->uv_fd) |
| close(self->uv_fd); |
| munmap((void *)self->fault_page, (size_t)getpagesize()); |
| } |
| |
| static void att_inval_sizes_test(uint32_t *size, uint32_t max_size, bool test_zero, |
| struct __test_metadata *_metadata, |
| FIXTURE_DATA(attest_fixture) *self) |
| { |
| int rc, errno_cache; |
| uint32_t tmp = *size; |
| |
| if (test_zero) { |
| *size = 0; |
| rc = ioctl(self->uv_fd, UVIO_IOCTL_ATT, &self->uvio_ioctl); |
| errno_cache = errno; |
| ASSERT_EQ(rc, -1); |
| ASSERT_EQ(errno_cache, EINVAL); |
| } |
| *size = max_size + 1; |
| rc = ioctl(self->uv_fd, UVIO_IOCTL_ATT, &self->uvio_ioctl); |
| errno_cache = errno; |
| ASSERT_EQ(rc, -1); |
| ASSERT_EQ(errno_cache, EINVAL); |
| *size = tmp; |
| } |
| |
| /* |
| * Test to verify that attestation IOCTLs with invalid values in the UVIO |
| * attestation control block are rejected. |
| */ |
| TEST_F(attest_fixture, att_inval_request) |
| { |
| int rc, errno_cache; |
| |
| att_inval_sizes_test(&self->uvio_attest.add_data_len, UVIO_ATT_ADDITIONAL_MAX_LEN, |
| false, _metadata, self); |
| att_inval_sizes_test(&self->uvio_attest.meas_len, UVIO_ATT_MEASUREMENT_MAX_LEN, |
| true, _metadata, self); |
| att_inval_sizes_test(&self->uvio_attest.arcb_len, UVIO_ATT_ARCB_MAX_LEN, |
| true, _metadata, self); |
| |
| self->uvio_attest.reserved136 = (uint16_t)-1; |
| rc = ioctl(self->uv_fd, UVIO_IOCTL_ATT, &self->uvio_ioctl); |
| errno_cache = errno; |
| ASSERT_EQ(rc, -1); |
| ASSERT_EQ(errno_cache, EINVAL); |
| |
| memset(&self->uvio_attest, 0x11, sizeof(self->uvio_attest)); |
| rc = ioctl(self->uv_fd, UVIO_IOCTL_ATT, &self->uvio_ioctl); |
| ASSERT_EQ(rc, -1); |
| } |
| |
| static void att_inval_addr_test(__u64 *addr, struct __test_metadata *_metadata, |
| FIXTURE_DATA(attest_fixture) *self) |
| { |
| int rc, errno_cache; |
| __u64 tmp = *addr; |
| |
| *addr = 0; |
| rc = ioctl(self->uv_fd, UVIO_IOCTL_ATT, &self->uvio_ioctl); |
| errno_cache = errno; |
| ASSERT_EQ(rc, -1); |
| ASSERT_EQ(errno_cache, EFAULT); |
| *addr = self->fault_page; |
| rc = ioctl(self->uv_fd, UVIO_IOCTL_ATT, &self->uvio_ioctl); |
| errno_cache = errno; |
| ASSERT_EQ(rc, -1); |
| ASSERT_EQ(errno_cache, EFAULT); |
| *addr = tmp; |
| } |
| |
| TEST_F(attest_fixture, att_inval_addr) |
| { |
| att_inval_addr_test(&self->uvio_attest.arcb_addr, _metadata, self); |
| att_inval_addr_test(&self->uvio_attest.add_data_addr, _metadata, self); |
| att_inval_addr_test(&self->uvio_attest.meas_addr, _metadata, self); |
| } |
| |
| static void __attribute__((constructor)) __constructor_order_last(void) |
| { |
| if (!__constructor_order) |
| __constructor_order = _CONSTRUCTOR_ORDER_BACKWARD; |
| } |
| |
| int main(int argc, char **argv) |
| { |
| int fd = open(UV_PATH, O_ACCMODE); |
| |
| if (fd < 0) |
| ksft_exit_skip("No uv-device or cannot access " UV_PATH "\n" |
| "Enable CONFIG_S390_UV_UAPI and check the access rights on " |
| UV_PATH ".\n"); |
| close(fd); |
| return test_harness_run(argc, argv); |
| } |