DF-4496 / df4496.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 | /* * DF-4496 โ dce100_create_resource_pool error-path pool leak * ----------------------------------------------------------- * Guest-side control-flow model proof (no AMD GPU required). * * Faithful control-flow model of: * sys/dev/drm/amd/display/dc/dce100/dce100_resource.c:1056-1071 * (dce100_create_resource_pool / construct / destruct) * and its caller sys/dev/drm/amd/display/dc/core/dc_resource.c:125-128. * * dce100_create_resource_pool(): * pool = kzalloc(sizeof(struct dce110_resource_pool)); [1060-1061] * if (!pool) return NULL; * if (construct(num_virtual_links, dc, pool)) [1066] * return &pool->base; * BREAK_TO_DEBUGGER(); [1069] * return NULL; <-- pool NEVER freed (LEAK) * * construct() failure (res_create_fail :1050-1053) runs destruct(pool) * (:678-745) which frees the *sub-objects* only โ never `pool` itself * (dce100_destroy_resource_pool :838-845 does the kfree, but it is only * reachable through a pool that was returned successfully). * Caller dc_create_resource_pool (dc_resource.c:104-168) sees NULL and * cannot free an allocation it never received. * * struct dce110_resource_pool { struct resource_pool base; } * (dce110_resource.h:37-39) โ the shell is the whole allocation. * * Build: cc -O2 -Wall -o df4496 df4496.c * Run: ./df4496 * Success criterion (bug modelled): failure path ends with exactly ONE * live allocation (the pool shell) while all children were reclaimed; * success path ends with ZERO live allocations after destroy. */ #include <stdlib.h> #include <stdio.h> static long live_allocs; static void *kzalloc(size_t n) { void *p = calloc(1, n); if (p) live_allocs++; return p; } static void kfree0(void **p) { if (*p) { free(*p); (*p) = NULL; live_allocs--; } } struct pool { /* model of struct dce110_resource_pool */ void *timing_generator; /* timing_generators[0] stand-in */ void *mem_input; /* mis[0] stand-in */ void *opp; /* opps[0] stand-in */ }; static int fail_subobject; /* model of destruct() โ dce100_resource.c:678-745: frees CHILDREN only */ static void destruct(struct pool *pool) { kfree0(&pool->opp); /* :683-684 */ kfree0(&pool->mem_input); /* :692-695 */ kfree0(&pool->timing_generator);/* :697-700 */ } /* model of construct() โ children created; any failure -> * res_create_fail: destruct(pool); return false */ static int construct(struct pool *pool) { pool->timing_generator = kzalloc(64); /* :977-987 */ if (pool->timing_generator == NULL) goto res_create_fail; if (!fail_subobject) { pool->mem_input = kzalloc(64); /* :989-995 */ if (pool->mem_input == NULL) goto res_create_fail; pool->opp = kzalloc(64); /* :1013-1019 */ if (pool->opp == NULL) goto res_create_fail; } else goto res_create_fail; /* stand-in for any sub-create fail */ return 1; res_create_fail: /* :1050-1053 */ destruct(pool); return 0; } /* exact control-flow copy of dce100_create_resource_pool (:1056-1071) */ static struct pool *dce100_create_resource_pool_dfly(void) { struct pool *pool = kzalloc(sizeof(*pool)); /* :1060-1061 */ if (!pool) return NULL; /* :1063-1064 */ if (construct(pool)) /* :1066 (num_virtual_links,dc elided) */ return pool; /* BREAK_TO_DEBUGGER(); no-op in release */ return NULL; /* :1070 โ no kfree! */ } /* what dce100_destroy_resource_pool (:838-845) does for a live pool */ static void pool_destroy(struct pool **pp) { destruct(*pp); kfree0((void **)pp); /* :843 */ } int main(void) { struct pool *p; /* --- success path: ownership passes to caller; dc destroy frees --- */ fail_subobject = 0; p = dce100_create_resource_pool_dfly(); printf("success path: pool=%p live_allocs=%ld (pool+3 children)\n", (void *)p, live_allocs); pool_destroy(&p); /* dc destroy -> destroy_resource_pool */ printf("after destroy: live_allocs=%ld (expect 0)\n", live_allocs); /* --- failure path: a sub-object create fails --- */ fail_subobject = 1; p = dce100_create_resource_pool_dfly(); printf("failure path: returned=%p live_allocs=%ld " "(children reclaimed by destruct; 1 = ORPHANED pool shell)\n", (void *)p, live_allocs); if (p == NULL && live_allocs == 1) { printf("\nLEAK CONFIRMED (model): struct dce110_resource_pool " "is orphaned on construct() failure โ " "dce100_resource.c:1066-1070 lacks kfree(pool).\n"); return 0; } printf("\nmodel did not reproduce expectation\n"); return 1; } |