1 /* 2 * This file and its contents are supplied under the terms of the 3 * Common Development and Distribution License ("CDDL"), version 1.0. 4 * You may only use this file in accordance with the terms of version 5 * 1.0 of the CDDL. 6 * 7 * A full copy of the text of the CDDL should have accompanied this 8 * source. A copy of the CDDL is also available via the Internet at 9 * http://www.illumos.org/license/CDDL. 10 */ 11 12 /* 13 * Copyright 2025 Oxide Computer Company 14 */ 15 16 /* 17 * This test iterates over every NVMe ioctl and tries to trigger various errors 18 * that are generic across all of them. In particular: 19 * 20 * - Invalid buffer (EFAULT) 21 * - Bad file descriptor mode (EBADF) 22 * - Missing privileges (EPERM) 23 * 24 * This is considered a destructive test as a side effect from some of these 25 * would be bad. This performs all of these cases on just the controller fd at 26 * this time. This test starts from the device-reset profile. 27 */ 28 29 #include <err.h> 30 #include <stdlib.h> 31 #include <unistd.h> 32 #include <sys/debug.h> 33 #include <sys/sysmacros.h> 34 #include <sys/mman.h> 35 #include <priv.h> 36 #include <errno.h> 37 #include <string.h> 38 #include <fcntl.h> 39 40 #include "nvme_ioctl_util.h" 41 42 #if NVME_IOC_MAX != NVME_IOC_NS_DELETE 43 #error "NVME_IOC_MAX has grown, update this test!" 44 #endif 45 46 typedef struct { 47 int it_cmd; 48 bool it_write; 49 bool it_perm; 50 } ioctl_test_t; 51 52 static ioctl_test_t ioctl_tests[NVME_IOC_MAX - NVME_IOC + 1] = { 53 { NVME_IOC_CTRL_INFO, false, false }, 54 { NVME_IOC_IDENTIFY, false, false }, 55 { NVME_IOC_GET_LOGPAGE, false, false }, 56 { NVME_IOC_GET_FEATURE, false, false }, 57 { NVME_IOC_FORMAT, true, true }, 58 { NVME_IOC_BD_DETACH, true, true }, 59 { NVME_IOC_BD_ATTACH, true, true }, 60 { NVME_IOC_FIRMWARE_DOWNLOAD, true, true }, 61 { NVME_IOC_FIRMWARE_COMMIT, true, true }, 62 { NVME_IOC_PASSTHRU, true, true }, 63 { NVME_IOC_NS_INFO, false, false }, 64 { NVME_IOC_LOCK, true, true }, 65 { NVME_IOC_UNLOCK, true, false }, 66 { NVME_IOC_CTRL_ATTACH, true, true }, 67 { NVME_IOC_CTRL_DETACH, true, true }, 68 { NVME_IOC_NS_CREATE, true, true }, 69 { NVME_IOC_NS_DELETE, true, true } 70 }; 71 72 static bool 73 ioctl_test_one(int fd, const ioctl_test_t *test, void *unmap, priv_set_t *basic, 74 priv_set_t *cur) 75 { 76 int altfd; 77 bool ret = true; 78 79 if (ioctl(fd, test->it_cmd, unmap) == 0) { 80 warnx("TEST FAILED: ioctl %s (0x%x) with invalid buffer " 81 "incorrectly succeeded", 82 nvme_ioctl_test_cmdstr(test->it_cmd), test->it_cmd); 83 ret = false; 84 } else if (errno != EFAULT) { 85 int e = errno; 86 warnx("TEST FAILED: ioctl %s (0x%x) with invalid buffer " 87 "returned %s (0x%x), not EFAULT (0x%x)", 88 nvme_ioctl_test_cmdstr(test->it_cmd), test->it_cmd, 89 strerrorname_np(e), e, EFAULT); 90 ret = false; 91 } else { 92 (void) printf("TEST PASSED: ioctl %s (0x%x) with invalid " 93 "buffer returned EFAULT\n", 94 nvme_ioctl_test_cmdstr(test->it_cmd), test->it_cmd); 95 } 96 97 altfd = nvme_ioctl_test_get_fd_flags(0, test->it_write ? 98 O_RDONLY : O_WRONLY); 99 if (ioctl(altfd, test->it_cmd, unmap) == 0) { 100 warnx("TEST FAILED: ioctl %s (0x%x) with bad fd mode " 101 "incorrectly succeeded", 102 nvme_ioctl_test_cmdstr(test->it_cmd), test->it_cmd); 103 ret = false; 104 } else if (errno != EBADF) { 105 int e = errno; 106 warnx("TEST FAILED: ioctl %s (0x%x) with bad fd mode " 107 "returned %s (0x%x), not EBADF (0x%x)", 108 nvme_ioctl_test_cmdstr(test->it_cmd), test->it_cmd, 109 strerrorname_np(e), e, EBADF); 110 ret = false; 111 } else { 112 (void) printf("TEST PASSED: ioctl %s (0x%x) with bad fd " 113 "mode returned EBADF\n", 114 nvme_ioctl_test_cmdstr(test->it_cmd), test->it_cmd); 115 } 116 VERIFY0(close(altfd)); 117 118 if (setppriv(PRIV_SET, PRIV_EFFECTIVE, basic) != 0) { 119 err(EXIT_FAILURE, "failed to drop privileges"); 120 } 121 122 /* 123 * Some ioctls check privileges regardless of how one gets an fd to the 124 * device, others don't. Those that don't should fail with EFAULT. 125 */ 126 int exp = test->it_perm ? EPERM : EFAULT; 127 if (ioctl(fd, test->it_cmd, unmap) == 0) { 128 warnx("TEST FAILED: ioctl %s (0x%x) without privs incorrectly " 129 "succeeded", nvme_ioctl_test_cmdstr(test->it_cmd), 130 test->it_cmd); 131 ret = false; 132 } else if (errno != exp) { 133 int e = errno; 134 warnx("TEST FAILED: ioctl %s (0x%x) without privs returned " 135 "%s (0x%x), not %s (0x%x)", 136 nvme_ioctl_test_cmdstr(test->it_cmd), test->it_cmd, 137 strerrorname_np(e), e, strerrorname_np(exp), exp); 138 ret = false; 139 } else { 140 (void) printf("TEST PASSED: ioctl %s (0x%x) without privs " 141 "returned %s\n", nvme_ioctl_test_cmdstr(test->it_cmd), 142 test->it_cmd, strerrorname_np(exp)); 143 } 144 145 if (setppriv(PRIV_SET, PRIV_EFFECTIVE, cur) != 0) { 146 err(EXIT_FAILURE, "failed to restore privileges"); 147 } 148 149 return (ret); 150 } 151 152 int 153 main(void) 154 { 155 int fd = nvme_ioctl_test_get_fd(0); 156 int ret = EXIT_SUCCESS; 157 void *unmap; 158 priv_set_t *basic, *cur; 159 160 unmap = mmap(NULL, 1024 * 1024, PROT_NONE, MAP_PRIVATE | MAP_ANON, -1, 161 0); 162 if (unmap == NULL) { 163 err(EXIT_FAILURE, "failed to mmap empty buffer"); 164 } 165 166 basic = priv_allocset(); 167 cur = priv_allocset(); 168 if (basic == NULL || cur == NULL) { 169 err(EXIT_FAILURE, "failed to allocate privilege sets"); 170 } 171 priv_basicset(basic); 172 173 if (getppriv(PRIV_EFFECTIVE, cur) != 0) { 174 err(EXIT_FAILURE, "failed to obtain current privilege set"); 175 } 176 177 for (size_t i = 0; i < ARRAY_SIZE(ioctl_tests); i++) { 178 VERIFY3S(ioctl_tests[i].it_cmd, !=, 0); 179 if (!ioctl_test_one(fd, &ioctl_tests[i], unmap, basic, cur)) { 180 ret = EXIT_FAILURE; 181 } 182 } 183 184 VERIFY0(close(fd)); 185 186 if (ret == EXIT_SUCCESS) { 187 (void) printf("All tests passed successfully\n"); 188 } 189 190 return (ret); 191 } 192