DF-5218 / ledger_model.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 | /* * ledger_model.c โ executable model of the dc_sink refcount ledger * for DF-5218 (dc_sink.c lifecycle API misused by DM forced/EDID-override paths). * * The real kernel path (amdgpu DC on Tonga/Fiji+ ASICs) cannot run on the * audit guest (no AMD GPU โ proven in env.txt), so this program models the * EXACT sequence of dc_sink_create / dc_sink_retain / dc_sink_release / * link_disconnect_sink / dc_link_detect / emulated_link_detect / * amdgpu_dm_update_connector_after_detect operations from the source, with * instrumented refcounts and use-after-free detection, to make the two * ledger violations executable and testable: * * MODE bug-leak โ amdgpu_dm.c:741-815 emulated_link_detect(): * retains prev_sink (line 754), never releases it on ANY * path, and overwrites link->local_sink (line 810) * without releasing the link reference -> every forced * detect cycle strands a struct dc_sink forever. * * MODE bug-uaf โ amdgpu_dm.c:967-995 update_connector_after_detect() * branch A: aconnector->dc_sink is assigned BORROWED * sinks (no retain; :995, :1044, :3278-3280), then * line 993 dc_sink_release()s that borrowed pointer โ * dropping the LINK's reference. On the next * dc_link_detect() with a changed EDID, the old sink is * freed (dc_link.c:902) before the update runs, and the * stale borrowed pointer is then released at :993 -> * dc_sink_release() on freed memory (kref_put UAF * write / dc_sink_free double-free). * * MODE fixed โ corrected pairing (fix semantics proof): emulated_ * link_detect disconnects the old local_sink and drops * its temp ref on every path; update_connector_after_ * detect owns exactly one ref on aconnector->dc_sink * (retain on store, release old owned ref on swap). * * Build: cc -O2 -o ledger_model ledger_model.c * Run: ./ledger_model bug-leak; ./ledger_model bug-uaf; ./ledger_model fixed */ #include <stdio.h> #include <stdlib.h> #include <string.h> static int live_objects; /* # of non-freed sinks */ static int uaf_hits; /* retain/release on freed objects */ static int double_free_hits; /* release-after-free -> re-free */ struct dc_sink { int refcount; int freed; }; struct dc_link { struct dc_sink *local_sink; }; static struct dc_sink *sink_create(void) { struct dc_sink *s = calloc(1, sizeof(*s)); if (!s) abort(); s->refcount = 1; /* creator ref: dc_sink.c:95 kref_init */ live_objects++; return s; } static void sink_free(struct dc_sink *s) /* dc_sink.c:73-78 */ { if (s->freed) { double_free_hits++; return; } s->freed = 1; live_objects--; } static void sink_retain(struct dc_sink *s) /* dc_sink.c:68-71 */ { if (s->freed) { uaf_hits++; return; } s->refcount++; } static void sink_release(struct dc_sink *s) /* dc_sink.c:80-83 */ { if (s->freed) { uaf_hits++; double_free_hits++; return; } if (--s->refcount == 0) sink_free(s); } static void link_disconnect_sink(struct dc_link *l) /* dc_link.c:492-500 */ { if (l->local_sink) { sink_release(l->local_sink); l->local_sink = NULL; } } /* ------------------------------------------------------------------ */ /* amdgpu_dm.c:741-815 emulated_link_detect(), verbatim ledger */ /* ------------------------------------------------------------------ */ static void emulated_link_detect(struct dc_link *link, int bug) { struct dc_sink *prev_sink = link->local_sink; if (prev_sink != NULL) sink_retain(prev_sink); /* :754 */ /* (the default-connector and !sink failure paths also return without * releasing the temp ref โ same bug, omitted here for brevity) */ struct dc_sink *sink = sink_create(); /* :809, refcount 1 (link's) */ if (!bug) { link_disconnect_sink(link); /* FIXED: drop old link ref */ sink_release(prev_sink); /* FIXED: drop temp ref */ } link->local_sink = sink; /* :810 (bug: old ref stranded) */ } /* ------------------------------------------------------------------ */ /* dc_link.c:620-905 dc_link_detect(), EDID-changed cycle (abridged) */ /* ------------------------------------------------------------------ */ static void dc_link_detect_edid_change(struct dc_link *link) { struct dc_sink *prev = link->local_sink; if (prev) sink_retain(prev); /* :644 temp ref */ link_disconnect_sink(link); /* :647 drop link ref */ link->local_sink = sink_create(); /* :755 new local sink */ if (prev) sink_release(prev); /* :902 EDID differs: freed */ } /* ------------------------------------------------------------------ */ /* amdgpu_dm.c:967-1005 update_connector_after_detect(), branch A */ /* (branch A runs only when force != UNSPECIFIED && dc_em_sink != NULL */ /* โ boot-forced connector with EDID firmware override) */ /* ------------------------------------------------------------------ */ static struct dc_sink dc_em_sink_marker_dummy; /* stands in for the * separately-owned dc_em_sink */ static struct dc_sink *const dc_em_sink = (struct dc_sink *)&dc_em_sink_marker_dummy; static struct dc_sink *dc_sink_dm; /* aconnector->dc_sink */ static void update_connector_after_detect(struct dc_link *link, int bug) { struct dc_sink *sink = link->local_sink; /* :967 */ if (sink) { if (dc_sink_dm) { if (dc_sink_dm != dc_em_sink) { if (!bug) ; /* FIXED: old ref already owned (retained on store) */ /* BUG :993: release of a BORROWED ref โ drops the LINK's * reference (or a freed object on the EDID-change cycle) */ sink_release(dc_sink_dm); } } if (!bug) sink_retain(sink); /* FIXED: own what we store */ dc_sink_dm = sink; /* :995 borrowed assign */ } } int main(int argc, char **argv) { if (argc < 2) { fprintf(stderr, "usage: %s bug-leak|bug-uaf|fixed\n", argv[0]); return 2; } enum { LEAK, UAF, FIXED } mode; if (!strcmp(argv[1], "bug-leak")) mode = LEAK; else if (!strcmp(argv[1], "bug-uaf")) mode = UAF; else if (!strcmp(argv[1], "fixed")) mode = FIXED; else { fprintf(stderr, "bad mode\n"); return 2; } const int bug = (mode != FIXED); struct dc_link link = { NULL }; if (mode == LEAK || mode == FIXED) { /* forced headless connector: one emulated_link_detect per * S3 resume / HPD-with-no-display cycle */ link.local_sink = sink_create(); for (int i = 0; i < 8; i++) emulated_link_detect(&link, bug); printf("[leak-scenario] live dc_sink objects after 8 emulated detects: %d\n", live_objects); if (bug) printf("[leak-scenario] BUG: %s\n", live_objects > 1 ? "LEAK REPRODUCED (stranded dc_sink per cycle)" : "no leak"); else printf("[leak-scenario] FIXED: %s\n", live_objects == 1 ? "exactly 1 live sink (correct)" : "REGRESSION"); } else { /* forced connector, EDID fw loaded, display attached: * cycle 1: real sink A borrowed into aconnector->dc_sink * cycle 2: EDID change frees A inside dc_link_detect (:902), * then branch A releases the stale pointer at :993 */ link.local_sink = sink_create(); update_connector_after_detect(&link, bug); /* borrow sink A (no ref) */ struct dc_sink *stale = dc_sink_dm; dc_link_detect_edid_change(&link); /* A freed here */ update_connector_after_detect(&link, bug); /* :993 release */ printf("[uaf-scenario] stale sink freed before the :993 release: %s\n", stale->freed ? "YES (dc_link.c:902 freed it)" : "no"); printf("[uaf-scenario] retain/release on freed objects (kref UAF writes): %d\n", uaf_hits); printf("[uaf-scenario] double-free attempts (dc_sink_free on freed): %d\n", double_free_hits); if (bug) printf("[uaf-scenario] BUG: %s\n", uaf_hits > 0 ? "UAF REPRODUCED (dc_sink_release on freed dc_sink)" : "no uaf"); else printf("[uaf-scenario] FIXED: %s\n", uaf_hits == 0 ? "no uaf" : "REGRESSION"); } return 0; } |