⬢ DragonFlyBSD Kernel Audit
DF-4353 / df4353.c
← back to finding ↓ download raw
/*
 * DF-4353 model proof - dce110_resource.c (DCE11 / Carrizo-Stoney)
 * ----------------------------------------------------------------
 * 1) dce110_create_resource_pool() (dce110_resource.c:1398-1414):
 *
 *      pool = kzalloc(sizeof(struct dce110_resource_pool));  // :1403
 *      if (construct(...))  return &pool->base;
 *      return NULL;                                          // :1413
 *
 *    construct()'s failure arm runs destruct(pool) (:1394) which frees
 *    only the SUB-objects - the ~KB pool allocation itself is never
 *    kfree'd.  Identical error-path shape to DF-4318 (dce80/81/83); the
 *    caller (core/dc.c dc_construct) never received the pointer and can
 *    only destruct(dc), so the pool is orphaned.  Happens on every
 *    failed pool build on Carrizo/Stoney (any sub-object create failure,
 *    irq-service create failure, resource_construct failure, or VBIOS
 *    without an external DP clock source -> :1253-1257).
 *
 * 2) Sibling asymmetry: construct() also does
 *
 *      if (dc->config.fbc_support)
 *              dc->fbc_compressor = dce110_compressor_create(ctx); // :1373
 *
 *    and dce110_compressor_destroy() (dce110_compressor.c:439) has ZERO
 *    callers in the tree - the compressor is never freed either.
 *
 * This userspace model reproduces the allocation accounting of the
 * failure arm (DF-4318's methodology).
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

static int live_allocs = 0;
static void *xkzalloc(size_t n)
{
    void *p = calloc(1, n);
    if (p) live_allocs++;
    return p;
}
static void xkfree(void **p)
{
    if (*p) { free(*p); (*p) = NULL; live_allocs--; }
}

struct pool {            /* stand-in for struct dce110_resource_pool */
    void *dccg, *dmcu, *abm, *irqs, *fbc_compressor;   /* sub-objects */
    int   funcs;                                       /* pool->base   */
};

/* destruct() model - dce110_resource.c:703-773: frees SUB-OBJECTS only,
 * never the pool itself (the pool is freed by
 * dce110_destroy_resource_pool:1077-1084, which is NOT on the failure arm) */
static void destruct(struct pool *pool)
{
    xkfree(&pool->dccg);
    xkfree(&pool->dmcu);
    xkfree(&pool->abm);
    xkfree(&pool->irqs);
}

/* construct() model - failure at the irq-service create step
 * (dce110_resource.c:1304-1310) with fbc enabled (:1372-1373) */
static int construct(struct pool *pool, int fail_irq, int fbc_support)
{
    pool->dccg = xkzalloc(64);
    if (!pool->dccg) goto fail;
    pool->dmcu = xkzalloc(64);
    if (!pool->dmcu) goto fail;
    if (fbc_support) {
        pool->fbc_compressor = xkzalloc(64);   /* :1373 - stored on dc */
        if (!pool->fbc_compressor) goto fail;
    }
    if (fail_irq) goto fail;                   /* :1308 */
    pool->irqs = xkzalloc(64);
    if (!pool->irqs) goto fail;
    return 1;
fail:
    destruct(pool);                            /* :1394 */
    return 0;
}

/* baseline: dce110_create_resource_pool (dce110_resource.c:1398-1414) */
static struct pool *create_resource_pool_baseline(int fail_irq,
                                                  int fbc_support)
{
    struct pool *pool = xkzalloc(sizeof(*pool));      /* :1403 */
    if (!pool)
        return NULL;
    if (construct(pool, fail_irq, fbc_support))
        return pool;                                  /* :1410 */
    /* :1412-1413: BREAK_TO_DEBUGGER(); return NULL;  -- no kfree(pool) */
    return NULL;
}

/* fixed: kfree(pool) on the failure arm (+ compressor teardown) */
static struct pool *create_resource_pool_fixed(int fail_irq,
                                               int fbc_support)
{
    struct pool *pool = xkzalloc(sizeof(*pool));
    if (!pool)
        return NULL;
    if (construct(pool, fail_irq, fbc_support))
        return pool;
    xkfree(&pool->fbc_compressor);            /* fix part 2 */
    xkfree((void **)&pool);                   /* fix part 1 */
    return NULL;
}

int main(void)
{
    struct pool *p;
    int baseline_orphans;
    printf("DF-4353 model: dce110_create_resource_pool failure-arm "
           "allocation accounting\n\n");

    /* sanity: successful path */
    live_allocs = 0;
    p = create_resource_pool_baseline(0, 1);
    printf("successful construct          : live_allocs=%d (owner: dc)\n",
           live_allocs);

    /* baseline failure arm */
    live_allocs = 0;
    p = create_resource_pool_baseline(1, 1);
    printf("\nbaseline failed construct     : returned=%p, live_allocs=%d\n",
           p, live_allocs);
    printf("  (sub-objects freed by destruct; %d allocation(s) orphaned = "
           "the kzalloc'd pool + never-destroyed fbc_compressor)\n",
           live_allocs);
    if (live_allocs >= 2)
        printf("LEAK CONFIRMED (model)\n");
    baseline_orphans = live_allocs;

    /* fixed failure arm */
    live_allocs = 0;
    p = create_resource_pool_fixed(1, 1);
    printf("\nfixed failed construct         : returned=%p, live_allocs=%d\n",
           p, live_allocs);
    if (live_allocs == 0)
        printf("FIX MODEL CLEAN (model)\n");

    printf("\nnote: sizeof(struct dce110_resource_pool) embeds the full "
           "resource_pool base\n(core_types.h:120-198, incl. 12-ptr "
           "stream_enc[], 6-ptr engine arrays); on DCE11 every failed "
           "amdgpu bind leaks it.\n");
    return baseline_orphans >= 2 ? 0 : 1;
}