DF-4679 / i915_stolen_harness.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 | /* * i915_stolen_harness.c -- DF-4679 logic-level verification * * Verifies, on the DragonFly guest, the u64-wrap chain in * sys/dev/drm/i915/i915_gem_stolen.c * g4x_get_stolen_reserved() (:201-229, *size = stolen_top - *base :228) * bdw_get_stolen_reserved() (:349-366, *size = stolen_top - *base :365) * i915_gem_init_stolen() (:475-503: !reserved_base check, containment * :485, reserved_total = stolen_top - * reserved_base :493, stolen_usable_size * :499-500, drm_mm_init(0, usable) :503) * * The inline helpers are transcribed VERBATIM from * sys/dev/drm/include/linux/pci.h:621-642 (struct linux_resource, * DEFINE_RES_MEM, resource_size, resource_contains) * and the two register masks from sys/dev/drm/i915/i915_reg.h:401,3507. * build.sh grep-verifies every transcribed line against the guest's * /usr/src copy before compiling. * * Register values (CTG/ELK/GEN6_STOLEN_RESERVED) are chipset config * space programmed by the BIOS: garbage / malicious firmware values are * the threat model. A reserved base ABOVE stolen_top makes * "stolen_top - *base" wrap mod 2^64; DEFINE_RES_MEM then computes * dsm_reserved.end = base + wrapped_size - 1 == stolen_top - 1 == dsm.end, * so resource_contains() PASSES even though the reservation starts above * the stolen region -- and stolen_usable_size inflates ABOVE the real * stolen size, so the drm_mm stolen allocator hands out offsets beyond * dsm.end. i915_pages_create_for_stolen() (:516,536) then builds * sg_dma_address = dsm.start + offset pointing OUTSIDE stolen memory * (the GEM_BUG_ON at :516 is a no-op in release: i915_gem.h:38-61), * i.e. the GPU is pointed at arbitrary physical memory it must not touch. */ #include <stdint.h> #include <stdio.h> #include <string.h> #include <stdbool.h> typedef uint64_t resource_size_t; /* linux/types.h:35 */ /* ---- verbatim: sys/dev/drm/include/linux/pci.h:621-642 ---- */ struct linux_resource { resource_size_t start; resource_size_t end; }; #define DEFINE_RES_MEM(_start, _size) \ { \ .start = (_start), \ .end = (_start) + (_size) - 1, \ } static inline resource_size_t resource_size(const struct linux_resource *res) { return res->end - res->start + 1; } static inline bool resource_contains(struct linux_resource *a, struct linux_resource *b) { return a->start <= b->start && a->end >= b->end; } /* ---- transcribed from i915_gem_stolen.c (see build.sh grep checks) ---- */ static struct linux_resource dsm, dsm_reserved; static void g4x_get_stolen_reserved(resource_size_t reg_val, resource_size_t *base, resource_size_t *size) { resource_size_t stolen_top = dsm.end + 1; /* :208 */ *base = (reg_val & 0xFFF0u) << 16; /* G4X_STOLEN_RESERVED_ADDR2_MASK * = (0xFFF << 4), then <<16 :225 */ *size = stolen_top - *base; /* :228 */ } static void bdw_get_stolen_reserved(resource_size_t reg_val, resource_size_t *base, resource_size_t *size) { resource_size_t stolen_top = dsm.end + 1; /* :354 */ *base = reg_val & 0xFFF00000ull; /* GEN6_STOLEN_RESERVED_ADDR_MASK * = (0xFFF << 20) :364 */ *size = stolen_top - *base; /* :365 */ } /* i915_gem_init_stolen() tail, :475-503 (reserved_base != 0 by * construction here). Returns stolen_usable_size passed to * drm_mm_init(&dev_priv->mm.stolen, 0, ...) at :503. */ static resource_size_t init_stolen_tail(resource_size_t reserved_base, resource_size_t reserved_size) { resource_size_t stolen_top = dsm.end + 1; resource_size_t reserved_total; dsm_reserved = (struct linux_resource) DEFINE_RES_MEM(reserved_base, reserved_size); /* :482-483 */ if (!resource_contains(&dsm, &dsm_reserved)) /* :485 */ return 0; /* stolen disabled */ reserved_total = stolen_top - reserved_base; /* :493 */ return resource_size(&dsm) - reserved_total; /* :499-500 */ } /* The fix.diff variant of the same tail: the sanity check at :475 * becomes "if (!reserved_base || reserved_base > stolen_top)" and the * reservation is ignored (existing !reserved_base fallback semantics) * when the BIOS-programmed base sits above stolen_top. */ static resource_size_t init_stolen_tail_fixed(resource_size_t reserved_base, resource_size_t reserved_size) { resource_size_t stolen_top = dsm.end + 1; resource_size_t reserved_total; if (!reserved_base || reserved_base > stolen_top) { reserved_base = stolen_top; reserved_size = 0; } dsm_reserved = (struct linux_resource) DEFINE_RES_MEM(reserved_base, reserved_size); if (!resource_contains(&dsm, &dsm_reserved)) return 0; reserved_total = stolen_top - reserved_base; return resource_size(&dsm) - reserved_total; } static int g_fixed; static int case_run(const char *name, struct linux_resource region, void (*get)(resource_size_t, resource_size_t *, resource_size_t *), resource_size_t reg_val, bool expect_inflate) { resource_size_t base = 0, size = 0, usable, real, maxphys; int fail = 0; dsm = region; real = resource_size(&dsm); get(reg_val, &base, &size); usable = g_fixed ? init_stolen_tail_fixed(base, size) : init_stolen_tail(base, size); maxphys = dsm.start + (usable ? usable - 1 : 0); /* :536 worst case */ printf("== %s ==\n", name); printf(" stolen (dsm) : [%#llx..%#llx] (%llu MiB)\n", (unsigned long long)dsm.start, (unsigned long long)dsm.end, (unsigned long long)real >> 20); printf(" reserved base (reg) : %#llx size(raw u64): %#llx\n", (unsigned long long)base, (unsigned long long)size); printf(" resource_contains : %s\n", resource_contains(&dsm, &dsm_reserved) ? "PASS" : "FAIL"); printf(" stolen_usable_size : %llu (%.2fx the real %llu)\n", (unsigned long long)usable, real ? (double)usable / (double)real : 0.0, (unsigned long long)real); printf(" worst-case alloc phys : dsm.start+usable-1 = %#llx " "(dsm.end=%#llx => %s stolen)\n", (unsigned long long)maxphys, (unsigned long long)dsm.end, maxphys > dsm.end ? "OUTSIDE" : "inside"); if (expect_inflate) { bool bypassed = resource_contains(&dsm, &dsm_reserved) && base > dsm.end; bool inflated = usable > real; if (g_fixed) { if (inflated) { printf(" !! FIX FAILED: inflation still occurs\n"); fail = 1; } else { printf(" FIX VERIFIED: no wrap, no containment " "bypass, usable (%llu) <= real (%llu); " "allocations stay inside stolen\n", (unsigned long long)usable, (unsigned long long)real); } } else if (!bypassed || !inflated) { printf(" !! expected containment bypass + usable-size " "inflation, did not occur\n"); fail = 1; } else { printf(" DF-4679 REPRODUCED: reserved base above " "stolen_top wraps u64 size; containment check " "(:485) PASSES via end-wrap; drm_mm initialized " "with %llu bytes (%.2fx real) => allocations " "beyond dsm.end => OOB physical memory\n", (unsigned long long)usable, (double)usable / (double)real); } } else { if (usable == 0 || usable > real) { printf(" !! sane-firmware control case misbehaved\n"); fail = 1; } else { printf(" (control: sane firmware base => usable < " "real; behavior normal)\n"); } } return fail; } int main(int argc, char **argv) { int fail = 0; g_fixed = (argc > 1 && strcmp(argv[1], "fixed") == 0); if (g_fixed) printf(">>> FIXED build (fix.diff guard active) <<<\n"); else printf(">>> BASELINE build (pristine :475 logic) <<<\n"); const unsigned long long MB = 1ull << 20; /* Realistic G4X: 32 MiB stolen at 0x00D00000 (top of low RAM) */ fail |= case_run("G4X garbage reserved base (0xABC -> 0x0ABC0000)", (struct linux_resource) DEFINE_RES_MEM(0x00D00000, 32 * MB), g4x_get_stolen_reserved, 0x0ABC0u /* ADDR2 field 0xABC, EN set */, true); /* Control: same machine, sane BIOS reserving top 1 MiB of stolen * (base 0x2C00000 = reg ADDR2 field 0x2C0) */ fail |= case_run("G4X sane reserved base (top 1 MiB of stolen)", (struct linux_resource) DEFINE_RES_MEM(0x00D00000, 32 * MB), g4x_get_stolen_reserved, 0x002C0u, false); /* BDW: 32 MiB stolen at 0x40000000; ADDR field all-ones 0xFFF */ fail |= case_run("BDW garbage reserved base (0xFFF -> 0xFFF00000)", (struct linux_resource) DEFINE_RES_MEM(0x40000000, 32 * MB), bdw_get_stolen_reserved, 0xFFF00001ull /* ADDR + EN bit */, true); printf(fail ? "\nRESULT: FAIL\n" : "\nRESULT: ALL CASES OK\n"); return fail; } |