DF-3891 / double_free.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 | /* * DF-3891 harness — double free of adev->pm.dpm.ps on the kzalloc-failure * path of kv_parse_power_table. * * Mirrors sys/dev/drm/amd/amdgpu/kv_dpm.c:2736-2752 (kv_parse_power_table) * and :2912-2922 (kv_dpm_fini), with malloc/free standing in for * kzalloc/kfree. Sequence in the kernel: * * kv_parse_power_table: * adev->pm.dpm.ps = kcalloc(ucNumEntries, sizeof(struct amdgpu_ps)); :2736 * for (i = 0; i < ucNumEntries; i++) { * ps = kzalloc(sizeof(struct kv_ps)); :2748 * if (ps == NULL) { * kfree(adev->pm.dpm.ps); <-- freed, pointer NOT nulled :2750 * return -ENOMEM; <-- num_ps still 0 :2751 * } * adev->pm.dpm.ps[i].ps_priv = ps; :2753 * } * adev->pm.dpm.num_ps = ucNumEntries; <-- never reached :2775 * * kv_dpm_sw_init dpm_failed -> kv_dpm_fini: * for (i = 0; i < num_ps; i++) -- num_ps is still 0, zero iterations, * so the earlier ps_priv allocations leak * kfree(adev->pm.dpm.ps); <-- DOUBLE FREE :2919 * * Modes: * ./t accounting: prints the call sequence. * ./t crash performs the verbatim free(); free() sequence -> the C * allocator detects the double free -> abort (SIGABRT). * (kernel equivalent: kfree() of an already-freed block -> * heap freelist corruption / "double free" panic.) */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <stdint.h> struct amdgpu_ps { void *ps_priv; }; struct dpm { struct amdgpu_ps *ps; int num_ps; }; static void kv_parse_power_table(struct dpm *pm, int fail_at) { pm->ps = calloc(4, sizeof(struct amdgpu_ps)); /* :2736 */ printf("kv_parse_power_table: ps=%p (kcalloc)\n", (void *)pm->ps); for (int i = 0; i < 4; i++) { void *ps = (i == fail_at) ? NULL : malloc(64); /* :2748 kzalloc */ if (ps == NULL) { printf("kv_parse_power_table: kzalloc failed at state %d\n", i); printf("kv_parse_power_table: kfree(ps) at :2750 (pointer left dangling; num_ps still %d)\n", pm->num_ps); free(pm->ps); /* :2750 */ return; /* -ENOMEM */ } pm->ps[i].ps_priv = ps; /* :2753 */ } pm->num_ps = 4; /* :2775 (not reached) */ } static void kv_dpm_fini(struct dpm *pm, int do_free) { for (int i = 0; i < pm->num_ps; i++) /* :2916 (0 iterations) */ free(pm->ps[i].ps_priv); /* -> states 0..fail_at-1 LEAK */ printf("kv_dpm_fini: loop skipped %d iterations (earlier ps_priv allocations leaked)\n", pm->num_ps); printf("kv_dpm_fini: kfree(ps) at :2919 on %p\n", (void *)pm->ps); if (do_free) free(pm->ps); /* :2919 SECOND free */ } int main(int argc, char **argv) { int crash = (argc > 1 && strcmp(argv[1], "crash") == 0); struct dpm pm = { NULL, 0 }; kv_parse_power_table(&pm, 2); /* allocation fails at state #2 */ fflush(stdout); kv_dpm_fini(&pm, crash); /* sw_init dpm_failed path */ if (!crash) { printf("accounting mode: second free would corrupt the allocator freelist\n"); return 0; } /* Demonstrate the double-free consequence: the same block re-entering * the freelist can be handed out twice -> aliasing. */ printf("post-double-free allocations:\n"); void *a[6]; for (int k = 0; k < 6; k++) { a[k] = malloc(256); printf(" malloc(256) = %p\n", a[k]); } int dup = 0; for (int k = 0; k < 6; k++) for (int j = k + 1; j < 6; j++) if (a[k] == a[j] && a[k] != NULL) { printf(" ALIASED: a[%d] == a[%d] == %p (two live allocations at one address)\n", k, j, a[k]); dup++; } printf("%s (allocator did not detect the double free: second free() accepted silently%s)\n", dup ? "freelist aliasing demonstrated" : "no aliasing observed this run", dup ? "" : ", exit=0"); return 0; } |