DF-4366 / amdgpu_parser_replica.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 | /* * amdgpu_parser_replica.c - replica of the token-walk in * amdgpu_set_pp_power_profile_mode() (sys/dev/drm/amd/amdgpu/amdgpu_pm.c:916-975). * * The sysfs store handler for pp_power_profile_mode parses a user write * ("5 <params...>" selects the CUSTOM profile). Its loop is: * * amdgpu_pm.c:944 memcpy(buf_cpy, buf, count-i); <-- NO NUL terminator * amdgpu_pm.c:946 tmp_str = buf_cpy; * amdgpu_pm.c:947 while (tmp_str[0]) { * amdgpu_pm.c:948 sub_str = strsep(&tmp_str, delimiter); * amdgpu_pm.c:949 ret = kstrtol(sub_str, 0, ¶meter[parameter_size]); * ... parameter_size++; * amdgpu_pm.c:955 while (isspace(*tmp_str)) <-- strsep returned NULL * tmp_str++; when the input did * not end with a delimiter * * DragonFly strsep (sys/libkern/strsep.c:63-65) sets *stringp = NULL when * it consumes the remainder without finding a delimiter. A write that * does NOT end in ' ' or '\n' (e.g. `printf '5 1 2 3' > pp_power_profile_mode`, * i.e. no echo-added newline) therefore makes tmp_str NULL and the very * next `isspace(*tmp_str)` dereferences NULL (sys/sys/ctype.h:43 expands * isspace(c) to ((c) == ' ' || ...) with c = *tmp_str) -> kernel panic. * * Secondary defect in the same loop: buf_cpy[128] is filled with * count-i <= 127 bytes and NEVER NUL-terminated, so on inputs that DO end * with a delimiter the walk continues into uninitialized kernel stack * until it happens to hit a zero byte. * * This module reproduces the exact walk (same strsep/isspace sequence, * same 3-delimiter set {' ', '\n', '\0'}) on a static kernel buffer. * kstrtol() is stood in for by a digit-accepting check so the module does * not depend on the DRM compat headers; the crash is in the delimiter * walk, not in the numeric conversion. * * Sysctl (root): * kern.amdgparun = 1 run the amdgpu_pm.c:946-957 walk on "5 1 2 3" * (no trailing delimiter) -> NULL deref panic. */ #include <sys/param.h> #include <sys/kernel.h> #include <sys/module.h> #include <sys/systm.h> #include <sys/sysctl.h> #include <sys/libkern.h> #include <sys/ctype.h> static const char delimiter[3] = { ' ', '\n', '\0' }; static int fake_kstrtol_ok(const char *s) { /* stands in for kstrtol(sub_str, 0, ...) == 0 on numeric tokens */ if (*s == '-' || *s == '+') s++; return (*s >= '0' && *s <= '9'); } static int run_parse(SYSCTL_HANDLER_ARGS) { int error, v = 0; long parameter[64]; char *sub_str, *tmp_str; uint32_t parameter_size = 0, i = 0; /* what the sysfs layer hands the handler for `printf '5 1 2 3' >` */ static char sysfs_buf[16] = "5 1 2 3"; char *buf = sysfs_buf; size_t count = 7; /* "5 1 2 3", no trailing \n */ char buf_cpy[128]; error = sysctl_handle_int(oidp, &v, 0, req); if (error || req->newptr == NULL || v == 0) return (error); /* ---- amdgpu_pm.c:938-944 (profile_mode==CUSTOM path) ---- */ /* while (isspace(*++buf)) i++; -- buf[1] is ' ', buf[2] is '1' */ while (isspace(*++buf)) i++; memcpy(buf_cpy, buf, count - i); /* <-- NO NUL terminator */ /* ---- amdgpu_pm.c:946-957: the fatal token walk ---- */ tmp_str = buf_cpy; kprintf("amdgparun: walking %zu-byte content, last byte 0x%02x\n", count - i, (unsigned char)buf_cpy[count - i - 1]); while (tmp_str[0]) { sub_str = strsep(&tmp_str, delimiter); if (!fake_kstrtol_ok(sub_str)) { kprintf("amdgparun: kstrtol fail -> -EINVAL (safe)\n"); return (0); /* handler would goto fail */ } parameter[parameter_size] = 1; /* stand-in store */ parameter_size++; /* * amdgpu_pm.c:955-956: after the final token ("3") strsep * found NO delimiter -> tmp_str == NULL -> *tmp_str is a * NULL pointer dereference -> panic. */ while (isspace(*tmp_str)) tmp_str++; } parameter[parameter_size] = 5; /* profile_mode = CUSTOM */ kprintf("amdgparun: walk completed without crash (input must have " "ended in a delimiter + NUL)\n"); return (0); } static int amdgparun_load(module_t mod, int what, void *arg) { switch (what) { case MOD_LOAD: sysctl_add_oid(NULL, SYSCTL_STATIC_CHILDREN(_kern), OID_AUTO, "amdgparun", CTLTYPE_INT|CTLFLAG_RW, NULL, 0, run_parse, "I", "run the amdgpu_pm.c:946-957 token walk (panics)"); kprintf("amdgparun: loaded (pp_power_profile_mode parser " "replica)\n"); return (0); case MOD_UNLOAD: return (0); } return (EOPNOTSUPP); } static moduledata_t amdgparun_mod = { "amdgparun", amdgparun_load, NULL }; DECLARE_MODULE(amdgparun, amdgparun_mod, SI_SUB_DRIVERS, SI_ORDER_ANY); |