From mboxrd@z Thu Jan 1 00:00:00 1970 Received: from mail-qk1-f179.google.com (mail-qk1-f179.google.com [209.85.222.179]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 7FF0A3290C7 for ; Mon, 15 Jun 2026 23:50:03 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.222.179 ARC-Seal:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1781567405; cv=none; b=ZkKum3bDXf7MxKMFE9F4ubunJDxPKPuvQ41XY/Gc3KwNAnNX0Vn4daTj/6VhQHSQWqf2oH6MoVhZPcCjX26q6R+CMBNVE+/3qOMCMvVWVBbZini63oC5tiF5fMkH5yBTbrbcPJfirS9obkO1EtchEUn+i1A56++qCFU2+nVQxSY= ARC-Message-Signature:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1781567405; c=relaxed/simple; bh=y9BehPLSpoMGhJv/ElIdSNj/5ox3Roh1VLY2QLdTmYw=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=Kay+BWLUUT48wyiOiBEPiD2kUZJh2w2voEo7QCahrkaT1wyl2hWNEhJ7Mi8Bf8uSH8J0+88ASf7zKlV7epiFMF3LgA8gk3t0yRK2UWTMxRtpEZziLibGZ1wyaCzM7KBCPMgttsezZqaD4sfVtcznN3m9e44Qk8JSPXrL2IUp/Tw= ARC-Authentication-Results:i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=jcDTP7nW; arc=none smtp.client-ip=209.85.222.179 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="jcDTP7nW" Received: by mail-qk1-f179.google.com with SMTP id af79cd13be357-915d64fead9so504925585a.0 for ; Mon, 15 Jun 2026 16:50:03 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1781567402; x=1782172202; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=gMomLA5yc+OAXvOs3SpKTXvia9aXXpiO5BxVYuDjyFA=; b=jcDTP7nWBafylig6+J7+BzSNN3BDKEfI7fh2lVUZ9vswEy6WEOfk20xJHRnRuKnvUQ 9wwnckSoEXMTP0m5nxLIcStTDs37X1zgp5xX8ROh5zTXIWejvBZctVtSddm8CjzA+uEX zf/RQRVZgLu0G0OudhK5tgtMAsLaM2U0bIxOAHItAOOfnroFKVETM2Q8HD/E7XeeEYnI gnfpRpG7hNEu3dSHqjPDntFFg4JGfsUfDWU450gCuGG6bHaYPMhLAmOUuHXpz8OYIUHY dnwQpQXrU2giJK35SjSVtxpG/sXU0nrW9x28vQT0SKBULniENx+18HngwHscjvQJhbub k+pA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1781567402; x=1782172202; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to; bh=gMomLA5yc+OAXvOs3SpKTXvia9aXXpiO5BxVYuDjyFA=; b=WHC+3qIl/CQTEVs6eAfUKp7yObtrqCPjMNDbzvC9i/eUv5Ms4zuyuaxPnbPh/jUMZj ylYc8QTCCv8fvH3MaPFyS9FiTQrkonJQWhTMxrZnUTN+Eb22vE4srF51PcGPaA4Q7hl3 /86cG2P2D1rA6PubQzYlECHO8RncEXzR13ENSyO+1iH0oeZv+CgYASVQ9Pn1KGjSp5e5 QEqN2k4EeAzf6s9rtH7T9x8pYC25VVihc+f213u0EamgvMaRKKwVxGEI9w/CCWoIHrmk bqj2ljAVSrO5J42LUtqQaXIlu5tgGCOifpKBR67bePuwKr7hsQmbbzAueAStDt6cydA+ swfg== X-Forwarded-Encrypted: i=1; AFNElJ9O/UJQMBZ/GNgX9rSSpkOagk+tPtVKfhuTli7s8FN6r8JXfEpPUAPpp7sCV3kFC8FonOc3GoHt1vvgegQ=@vger.kernel.org X-Gm-Message-State: AOJu0YyQYuTa5c0FrbS/RgkyCoN5/FnCMAEY0ctWgXJA930WeUxurrSg WbMYv+qRGhLATxaNydYm78G0uVRPGMIDnFEmFHryg1lGKDSnrr8unnba X-Gm-Gg: Acq92OH0nNC25ttabhVRFMgftXfy0cHlZIwdy0BC2Os8fP52AzB7AOQ9w9ICTBeG4Sv nuGZBl606MRZwHFK6FN67/dI7YlPn6uJ998PAuiF2ZR747BXMy8/kunEiuHa7zlXpbb+ZKoBc3o fDB3+kGivWo7iRcwANreEy4oDA8Bx99uxNCRuEkBLl2etvqj8EPTo62UM5e52r9aOlp7PLiARcy +ftREHRqm4wCEzASPOFS7bfi/CYRCi3PzHjR1dK4BSoP25LZRHTmXAYRXpeq+LfMjToO0Z3nf0R nOIWidvYZhHyzKYwm9qEKTxlN6dei9lUjnLiyqB50LsZxoCTyiEK5MhIfUyTct0LGbK1te0v552 pZZsTfxfkOT/VELRiK04MZvO6Wru1hdm2PKtrWfc3tAMeNyazJuzGHE0ezij+1MePQa4zoiZQAa 9EyqxBzcbJMKf8pyfXW3ucN0+zwKfqcL0X6iD9xeRxnhfFAfCpWgdNJ39F5Bp0H+mvAGSzSC9x7 8mGvN6Zo7vh X-Received: by 2002:a05:620a:4008:b0:8f2:1ccf:46d2 with SMTP id af79cd13be357-91c2f1caa82mr230132685a.32.1781567402022; Mon, 15 Jun 2026 16:50:02 -0700 (PDT) Received: from tropical-turnip.tail32462.ts.net ([216.132.43.94]) by smtp.gmail.com with ESMTPSA id af79cd13be357-9161a00af50sm1387942285a.30.2026.06.15.16.50.01 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Mon, 15 Jun 2026 16:50:01 -0700 (PDT) From: Samuel Ainsworth To: =?UTF-8?q?Christian=20K=C3=B6nig?= , Huang Rui Cc: =?UTF-8?q?Thomas=20Hellstr=C3=B6m?= , Matthew Auld , Matthew Brost , dri-devel@lists.freedesktop.org, linux-kernel@vger.kernel.org, Samuel Ainsworth Subject: [PATCH v1 2/2] drm/ttm/tests: add bulk_move cursor regression tests Date: Mon, 15 Jun 2026 19:49:22 -0400 Message-ID: <20260615234922.151263-3-skainsworth@gmail.com> X-Mailer: git-send-email 2.54.0 In-Reply-To: <20260615234922.151263-1-skainsworth@gmail.com> References: <20260615234922.151263-1-skainsworth@gmail.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: 8bit Add two kunit tests for the bulk_move LRU cursor accounting fixed in the previous commit: - ttm_bo_bulk_move_swapped_free_dangles frees a swapped (unevictable) resource that is still referenced by the bulk_move cursor and asserts the cursor no longer points at the freed resource. - ttm_bo_bulk_move_dangling_corrupts then allocates on the same bulk_move, which without the fix dereferences the dangling cursor -- reported by KASAN as a slab-use-after-free in ttm_resource_add_bulk_move(). Both run with no GPU under the existing TTM mock device. Without the fix the first fails its expectation and the second triggers the use-after-free; with it the whole TTM kunit suite passes. Signed-off-by: Samuel Ainsworth --- drivers/gpu/drm/ttm/tests/ttm_bo_test.c | 163 ++++++++++++++++++++++++ 1 file changed, 163 insertions(+) diff --git a/drivers/gpu/drm/ttm/tests/ttm_bo_test.c b/drivers/gpu/drm/ttm/tests/ttm_bo_test.c index d468f8322072..07ad1a7821fd 100644 --- a/drivers/gpu/drm/ttm/tests/ttm_bo_test.c +++ b/drivers/gpu/drm/ttm/tests/ttm_bo_test.c @@ -603,7 +603,170 @@ static void ttm_bo_multiple_pin_one_unpin(struct kunit *test) ttm_resource_free(bo, &res); } +/* + * Regression tests for bulk_move LRU cursor accounting: a resource added to a + * bo's bulk_move cursor can become unevictable (pinned or swapped) before it is + * freed. ttm_resource_del_bulk_move() must still take it off the cursor, or + * pos->first/last are left dangling at the freed resource. + */ + +/* + * Free a swapped (unevictable) resource that is still referenced by the + * bulk_move cursor and check that the cursor no longer points at it. + */ +static void ttm_bo_bulk_move_swapped_free_dangles(struct kunit *test) +{ + struct ttm_test_devices *priv = test->priv; + struct ttm_lru_bulk_move lru_bulk_move; + struct ttm_lru_bulk_move_pos *pos; + struct ttm_operation_ctx ctx = { }; + struct ttm_resource *res1, *res1_saved; + struct ttm_buffer_object *bo1; + struct ttm_device *ttm_dev; + struct ttm_place *place; + struct dma_resv *resv; + struct ttm_tt *tt; + int err; + + ttm_lru_bulk_move_init(&lru_bulk_move); + place = ttm_place_kunit_init(test, TTM_PL_SYSTEM, 0); + + ttm_dev = kunit_kzalloc(test, sizeof(*ttm_dev), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, ttm_dev); + err = ttm_device_kunit_init(priv, ttm_dev, 0); + KUNIT_ASSERT_EQ(test, err, 0); + priv->ttm_dev = ttm_dev; + + resv = kunit_kzalloc(test, sizeof(*resv), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, resv); + dma_resv_init(resv); + + bo1 = ttm_bo_kunit_init(test, priv, BO_SIZE, resv); + + /* bo1: put a SYSTEM resource on the bulk_move pos */ + dma_resv_lock(bo1->base.resv, NULL); + ttm_bo_set_bulk_move(bo1, &lru_bulk_move); + err = ttm_resource_alloc(bo1, place, &res1, NULL); + KUNIT_ASSERT_EQ(test, err, 0); + bo1->resource = res1; + + pos = &lru_bulk_move.pos[TTM_PL_SYSTEM][bo1->priority]; + /* res1 is now tracked by the bulk_move pos */ + KUNIT_EXPECT_PTR_EQ(test, pos->first, res1); + KUNIT_EXPECT_PTR_EQ(test, pos->last, res1); + + /* make bo1->ttm swapped so res1 is "unevictable" (ttm_resource_is_swapped) */ + tt = kunit_kzalloc(test, sizeof(*tt), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, tt); + err = ttm_tt_init(tt, bo1, 0, ttm_cached, 0); + KUNIT_ASSERT_EQ(test, err, 0); + bo1->ttm = tt; + err = ttm_tt_populate(ttm_dev, tt, &ctx); + KUNIT_ASSERT_EQ(test, err, 0); + ttm_tt_swapout(ttm_dev, tt, GFP_KERNEL); + KUNIT_ASSERT_TRUE(test, tt->page_flags & TTM_TT_FLAG_SWAPPED); + + /* res1 is still tracked by the pos right before the free */ + KUNIT_EXPECT_PTR_EQ(test, pos->first, res1); + + /* + * Free res1 while it is swapped (unevictable). ttm_resource_free() calls + * ttm_resource_del_bulk_move(), which is a no-op for an unevictable + * resource -- so res1 is NOT removed from the bulk_move pos before the + * underlying memory is freed. + */ + res1_saved = res1; + ttm_resource_free(bo1, &res1); + dma_resv_unlock(bo1->base.resv); + + /* + * A correct implementation must not leave the bulk_move pos pointing at + * the freed resource. On the buggy code these still equal the freed + * res1_saved (a dangling pointer that the next add/move dereferences). + */ + KUNIT_EXPECT_PTR_NE(test, pos->first, res1_saved); + KUNIT_EXPECT_PTR_NE(test, pos->last, res1_saved); + + dma_resv_fini(resv); +} + +/* + * After a swapped-free leaves the bulk_move cursor dangling, a later + * ttm_resource_alloc() on the same bulk_move dereferences the freed cursor in + * ttm_lru_bulk_move_add() (use-after-free, catchable with KASAN) and corrupts + * the LRU list. + */ +static void ttm_bo_bulk_move_dangling_corrupts(struct kunit *test) +{ + struct ttm_test_devices *priv = test->priv; + struct ttm_lru_bulk_move lru_bulk_move; + struct ttm_operation_ctx ctx = { }; + struct ttm_buffer_object *bo1, *bo2; + struct ttm_resource *res1, *res2; + struct ttm_device *ttm_dev; + struct ttm_place *place; + struct dma_resv *resv1, *resv2; + struct ttm_tt *tt; + int err; + + ttm_lru_bulk_move_init(&lru_bulk_move); + place = ttm_place_kunit_init(test, TTM_PL_SYSTEM, 0); + + ttm_dev = kunit_kzalloc(test, sizeof(*ttm_dev), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, ttm_dev); + err = ttm_device_kunit_init(priv, ttm_dev, 0); + KUNIT_ASSERT_EQ(test, err, 0); + priv->ttm_dev = ttm_dev; + + resv1 = kunit_kzalloc(test, sizeof(*resv1), GFP_KERNEL); + resv2 = kunit_kzalloc(test, sizeof(*resv2), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, resv1); + KUNIT_ASSERT_NOT_NULL(test, resv2); + dma_resv_init(resv1); + dma_resv_init(resv2); + + bo1 = ttm_bo_kunit_init(test, priv, BO_SIZE, resv1); + bo2 = ttm_bo_kunit_init(test, priv, BO_SIZE, resv2); + + /* bo1: resource on the bulk_move pos, then swap + free -> dangling pos */ + dma_resv_lock(bo1->base.resv, NULL); + ttm_bo_set_bulk_move(bo1, &lru_bulk_move); + err = ttm_resource_alloc(bo1, place, &res1, NULL); + KUNIT_ASSERT_EQ(test, err, 0); + bo1->resource = res1; + + tt = kunit_kzalloc(test, sizeof(*tt), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, tt); + err = ttm_tt_init(tt, bo1, 0, ttm_cached, 0); + KUNIT_ASSERT_EQ(test, err, 0); + bo1->ttm = tt; + err = ttm_tt_populate(ttm_dev, tt, &ctx); + KUNIT_ASSERT_EQ(test, err, 0); + ttm_tt_swapout(ttm_dev, tt, GFP_KERNEL); + + ttm_resource_free(bo1, &res1); + dma_resv_unlock(bo1->base.resv); + + /* + * bo2 allocates on the SAME bulk_move. ttm_lru_bulk_move_add() reads the + * dangling pos->first (freed res1) and list_move()s relative to the freed + * pos->last -> use-after-free + list corruption. + */ + dma_resv_lock(bo2->base.resv, NULL); + ttm_bo_set_bulk_move(bo2, &lru_bulk_move); + err = ttm_resource_alloc(bo2, place, &res2, NULL); + KUNIT_ASSERT_EQ(test, err, 0); + bo2->resource = res2; + ttm_resource_free(bo2, &res2); + dma_resv_unlock(bo2->base.resv); + + dma_resv_fini(resv1); + dma_resv_fini(resv2); +} + static struct kunit_case ttm_bo_test_cases[] = { + KUNIT_CASE(ttm_bo_bulk_move_swapped_free_dangles), + KUNIT_CASE(ttm_bo_bulk_move_dangling_corrupts), KUNIT_CASE_PARAM(ttm_bo_reserve_optimistic_no_ticket, ttm_bo_reserve_gen_params), KUNIT_CASE(ttm_bo_reserve_locked_no_sleep), -- 2.54.0