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 2026 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_SET_FEATURE 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 { NVME_IOC_SET_FEATURE, true, true } 71 }; 72 73 static bool 74 ioctl_test_one(int fd, const ioctl_test_t *test, void *unmap, priv_set_t *basic, 75 priv_set_t *cur) 76 { 77 int altfd; 78 bool ret = true; 79 80 if (ioctl(fd, test->it_cmd, unmap) == 0) { 81 warnx("TEST FAILED: ioctl %s (0x%x) with invalid buffer " 82 "incorrectly succeeded", 83 nvme_ioctl_test_cmdstr(test->it_cmd), test->it_cmd); 84 ret = false; 85 } else if (errno != EFAULT) { 86 int e = errno; 87 warnx("TEST FAILED: ioctl %s (0x%x) with invalid buffer " 88 "returned %s (0x%x), not EFAULT (0x%x)", 89 nvme_ioctl_test_cmdstr(test->it_cmd), test->it_cmd, 90 strerrorname_np(e), e, EFAULT); 91 ret = false; 92 } else { 93 (void) printf("TEST PASSED: ioctl %s (0x%x) with invalid " 94 "buffer returned EFAULT\n", 95 nvme_ioctl_test_cmdstr(test->it_cmd), test->it_cmd); 96 } 97 98 altfd = nvme_ioctl_test_get_fd_flags(0, test->it_write ? 99 O_RDONLY : O_WRONLY); 100 if (ioctl(altfd, test->it_cmd, unmap) == 0) { 101 warnx("TEST FAILED: ioctl %s (0x%x) with bad fd mode " 102 "incorrectly succeeded", 103 nvme_ioctl_test_cmdstr(test->it_cmd), test->it_cmd); 104 ret = false; 105 } else if (errno != EBADF) { 106 int e = errno; 107 warnx("TEST FAILED: ioctl %s (0x%x) with bad fd mode " 108 "returned %s (0x%x), not EBADF (0x%x)", 109 nvme_ioctl_test_cmdstr(test->it_cmd), test->it_cmd, 110 strerrorname_np(e), e, EBADF); 111 ret = false; 112 } else { 113 (void) printf("TEST PASSED: ioctl %s (0x%x) with bad fd " 114 "mode returned EBADF\n", 115 nvme_ioctl_test_cmdstr(test->it_cmd), test->it_cmd); 116 } 117 VERIFY0(close(altfd)); 118 119 if (setppriv(PRIV_SET, PRIV_EFFECTIVE, basic) != 0) { 120 err(EXIT_FAILURE, "failed to drop privileges"); 121 } 122 123 /* 124 * Some ioctls check privileges regardless of how one gets an fd to the 125 * device, others don't. Those that don't should fail with EFAULT. 126 */ 127 int exp = test->it_perm ? EPERM : EFAULT; 128 if (ioctl(fd, test->it_cmd, unmap) == 0) { 129 warnx("TEST FAILED: ioctl %s (0x%x) without privs incorrectly " 130 "succeeded", nvme_ioctl_test_cmdstr(test->it_cmd), 131 test->it_cmd); 132 ret = false; 133 } else if (errno != exp) { 134 int e = errno; 135 warnx("TEST FAILED: ioctl %s (0x%x) without privs returned " 136 "%s (0x%x), not %s (0x%x)", 137 nvme_ioctl_test_cmdstr(test->it_cmd), test->it_cmd, 138 strerrorname_np(e), e, strerrorname_np(exp), exp); 139 ret = false; 140 } else { 141 (void) printf("TEST PASSED: ioctl %s (0x%x) without privs " 142 "returned %s\n", nvme_ioctl_test_cmdstr(test->it_cmd), 143 test->it_cmd, strerrorname_np(exp)); 144 } 145 146 if (setppriv(PRIV_SET, PRIV_EFFECTIVE, cur) != 0) { 147 err(EXIT_FAILURE, "failed to restore privileges"); 148 } 149 150 return (ret); 151 } 152 153 int 154 main(void) 155 { 156 int fd = nvme_ioctl_test_get_fd(0); 157 int ret = EXIT_SUCCESS; 158 void *unmap; 159 priv_set_t *basic, *cur; 160 161 unmap = mmap(NULL, 1024 * 1024, PROT_NONE, MAP_PRIVATE | MAP_ANON, -1, 162 0); 163 if (unmap == NULL) { 164 err(EXIT_FAILURE, "failed to mmap empty buffer"); 165 } 166 167 basic = priv_allocset(); 168 cur = priv_allocset(); 169 if (basic == NULL || cur == NULL) { 170 err(EXIT_FAILURE, "failed to allocate privilege sets"); 171 } 172 priv_basicset(basic); 173 174 if (getppriv(PRIV_EFFECTIVE, cur) != 0) { 175 err(EXIT_FAILURE, "failed to obtain current privilege set"); 176 } 177 178 for (size_t i = 0; i < ARRAY_SIZE(ioctl_tests); i++) { 179 VERIFY3S(ioctl_tests[i].it_cmd, !=, 0); 180 if (!ioctl_test_one(fd, &ioctl_tests[i], unmap, basic, cur)) { 181 ret = EXIT_FAILURE; 182 } 183 } 184 185 VERIFY0(close(fd)); 186 187 if (ret == EXIT_SUCCESS) { 188 (void) printf("All tests passed successfully\n"); 189 } 190 191 return (ret); 192 } 193