All of lore.kernel.org
 help / color / mirror / Atom feed
From: Matthew Brost <matthew.brost@intel.com>
To: <sashiko-reviews@lists.linux.dev>
Cc: Tejas Upadhyay <tejas.upadhyay@intel.com>,
	<intel-xe@lists.freedesktop.org>
Subject: Re: [PATCH V20 09/15] drm/xe/vram: Add VRAM page offline fault handler
Date: Wed, 2 Sep 2026 16:04:09 -0700	[thread overview]
Message-ID: <apiraQytMqBPagvH@gsse-cloud1.jf.intel.com> (raw)
In-Reply-To: <20260902162540.841011F000E9@smtp.kernel.org>

On Wed, Sep 02, 2026 at 04:25:40PM +0000, sashiko-bot@kernel.org wrote:
> Thank you for your contribution! Sashiko AI review found 5 potential issue(s) to consider:
> - [High] Lockless read of `res->bo` in `xe_ttm_vram_block_owner_get` leads to a Use-After-Free when racing with a TTM ghost object transfer.
> - [High] Race condition when offlining a free VRAM page allows the faulty page to be allocated and never purged.
> - [High] Forcefully unpinning BOs in `xe_ttm_vram_purge_page` violates pin counting semantics and creates TOCTOU vulnerabilities.
> - [High] `xe_ttm_vram_addr_to_region` ignores the `kernel_vram` region, causing hardware faults in critical kernel memory to be silently ignored.
> - [Medium] A race condition between two-phase page reservation and synchronous BO eviction falsely marks successfully offlined pages as failed.
> --
> 
> commit 23a2bf9a5461265765230ffd0e7a9d24c27ca918
> Author: Tejas Upadhyay <tejas.upadhyay@intel.com>
> Subject: drm/xe/vram: Add VRAM page offline fault handler
> 
> This commit introduces a hardware VRAM page offline fault handler for the xe
> driver. It adds functionality to purge the buffer object containing the faulty
> address, ban the associated VM or exec queue, and reserve the hardware page in
> the buddy allocator to prevent future use.
> 
> > diff --git a/drivers/gpu/drm/xe/xe_ttm_vram_mgr.c b/drivers/gpu/drm/xe/xe_ttm_vram_mgr.c
> > index d97739a84a2d1..0ab92bc3c6998 100644
> > --- a/drivers/gpu/drm/xe/xe_ttm_vram_mgr.c
> > +++ b/drivers/gpu/drm/xe/xe_ttm_vram_mgr.c
> [ ... ]
> > @@ -580,3 +618,295 @@ u64 xe_ttm_vram_get_avail(struct ttm_resource_manager *man)
> [ ... ]
> > +static int xe_ttm_vram_purge_page(struct xe_device *xe, struct xe_bo *bo)
> > +{
> [ ... ]
> > +	xe_bo_set_purgeable_state(bo, XE_MADV_PURGEABLE_DONTNEED);
> > +	ttm_bo_unmap_virtual(&bo->ttm);   /* nuke CPU mmap + VRAM IO mappings */
> > +	if (xe_bo_is_pinned(bo))
> > +		xe_bo_unpin(bo);
> 
> [Severity: High]
> Does this forcefully break the pin counting contract by unpinning a BO on
> behalf of an unknown owner?
> 
> In xe_ttm_vram_purge_page(), this unconditionally unpins the BO if it is
> pinned, stealing a pin reference belonging to another subsystem (such as the
> display controller). When the true owner later unpins the BO, could this
> trigger an underflow crash?
> 
> > +	ret = xe_ttm_bo_purge(&bo->ttm, &ctx);
> > +	xe_bo_unlock(bo);
> > +
> > +out:
> [ ... ]
> > +static struct xe_bo *xe_ttm_vram_block_owner_get(struct gpu_buddy_block *block)
> > +{
> > +	struct ttm_resource *res = block->private;
> > +	struct ttm_buffer_object *tbo;
> > +	struct xe_bo *bo;
> > +
> > +	if (!res)
> > +		return NULL;
> > +
> > +	/*
> > +	 * res->bo is updated under bdev->lru_lock by ttm_resource_set_bo().
> > +	 * Racing with a ghost transfer here is benign: we either see the old
> > +	 * owner (whose purge is a no-op and the retry path recovers) or the
> > +	 * ghost (rejected below).
> > +	 *
> > +	 * A ghost is a bare ttm_transfer_obj, not an xe_bo, so ttm_to_xe_bo()
> > +	 * on one would be out of bounds. xe_bo_is_xe_bo() rejects it since
> > +	 * only our own BOs carry xe_ttm_bo_destroy().
> > +	 */
> > +	tbo = READ_ONCE(res->bo);
> > +	if (!tbo || !xe_bo_is_xe_bo(tbo))
> > +		return NULL;
> > +
> > +	bo = ttm_to_xe_bo(tbo);
> 
> [Severity: High]
> Is it safe to read res->bo locklessly and pass it to xe_bo_is_xe_bo()?
> 
> In xe_ttm_vram_block_owner_get(), tbo is read locklessly. A concurrent
> accelerated move or pipelined gutting can transfer the resource to a TTM
> ghost object under bdev->lru_lock. This drops the last reference to the
> original BO, freeing it via kfree(). 
> 
> If the thread is preempted and resumes after the original BO is freed,
> passing the stale tbo pointer to xe_bo_is_xe_bo(tbo) will dereference
> tbo->destroy on freed memory, causing a use-after-free regression.
> 

This one is correct and this memory unsafe here.

Luckily, this fix is easy...

This one will need to be fixed, snippet below:

diff --git a/drivers/gpu/drm/xe/xe_ttm_vram_mgr.c b/drivers/gpu/drm/xe/xe_ttm_vram_mgr.c
index 6a954018012a..ff937ab3837a 100644
--- a/drivers/gpu/drm/xe/xe_ttm_vram_mgr.c
+++ b/drivers/gpu/drm/xe/xe_ttm_vram_mgr.c
@@ -733,7 +733,8 @@ static bool xe_ttm_vram_page_already_processed(struct xe_ttm_vram_mgr *mgr,
  * blocks will hit xe_ttm_vram_mgr_del() on their own and the pending
  * reservation is retried from there.
  */
-static struct xe_bo *xe_ttm_vram_block_owner_get(struct gpu_buddy_block *block)
+static struct xe_bo *xe_ttm_vram_block_owner_get(struct xe_device *xe,
+                                                struct gpu_buddy_block *block)
 {
        struct ttm_resource *res = block->private;
        struct ttm_buffer_object *tbo;
@@ -742,6 +743,8 @@ static struct xe_bo *xe_ttm_vram_block_owner_get(struct gpu_buddy_block *block)
        if (!res)
                return NULL;

+       guard(spinlock)(&xe->ttm.lru_lock);
+
        /*
         * res->bo is updated under bdev->lru_lock by ttm_resource_set_bo().
         * Racing with a ghost transfer here is benign: we either see the old
@@ -788,7 +791,7 @@ static int xe_ttm_vram_reserve_page_at_addr(struct xe_device *xe, u64 addr,
                nentry->addr = addr;

                if (block) {
-                       pbo = xe_ttm_vram_block_owner_get(block);
+                       pbo = xe_ttm_vram_block_owner_get(xe, block);

                        /*
                         * Critical kernel BO? Best-effort check without resv lock;


> > +
> > +	/* The BO may already be in teardown with a zero refcount */
> > +	return xe_bo_get_unless_zero(bo) ? bo : NULL;
> > +}
> > +
> > +static int xe_ttm_vram_reserve_page_at_addr(struct xe_device *xe, u64 addr,
> > +					    struct xe_ttm_vram_mgr *vram_mgr, struct gpu_buddy *mm)
> > +{
> [ ... ]
> > +		if (block) {
> > +			pbo = xe_ttm_vram_block_owner_get(block);
> > +
> > +			/*
> > +			 * Critical kernel BO? Best-effort check without resv lock;
> > +			 * worst case a concurrent pin causes reset path unnecessarily.
> > +			 */
> > +			if (pbo && ((pbo->ttm.type == ttm_bo_type_kernel &&
> > +				     !(pbo->flags & XE_BO_FLAG_PINNED_LATE_RESTORE)) ||
> > +				    (xe_bo_is_user(pbo) && xe_bo_is_pinned(pbo)))) {
> 
> [Severity: High]
> Does this lockless pin check create a time-of-check to time-of-use race condition?
> 
> In xe_ttm_vram_reserve_page_at_addr(), xe_bo_is_pinned(pbo) is checked
> without holding the appropriate locks. A user BO could be pinned right after
> this check (e.g., for display scanout), and then forcefully unpinned and
> purged. 
> 
> The display hardware could then continue scanning out the old physical
> address, potentially leaking data of whatever new BO is allocated there.
>

We should probably file a Jira for this, as I agree this possible with
the right race + NIC trying to pin the BO, but unsure how to handle one
properly.
 
> > +				kfree(nentry);
> > +				pbo_to_put = pbo;
> > +				drm_err(&xe->drm,
> > +					"%s: addr: 0x%llx is critical kernel bo, requesting SBR\n",
> > +					__func__, addr);
> > +				break;
> > +			}
> > +		}
> > +		/* Queue both free and occupied (to-be-purged) pages */
> > +		++vram_mgr->n_queued_pages;
> > +		list_add_rcu(&nentry->queued_link, &vram_mgr->queued_pages);
> > +	}
> > +
> > +	/* Deferred put outside lock to avoid recursive deadlock */
> 
> [Severity: High]
> Can a concurrent allocation claim a faulty free page when the lock is dropped?
> 
> In xe_ttm_vram_reserve_page_at_addr(), when a hardware fault occurs at a free
> address, gpu_buddy_allocated_addr_to_block() returns NULL, meaning pbo is
> NULL. The page is queued and vram_mgr->lock is dropped to prevent deadlocks.
> 
> During this lockless window, a concurrent TTM allocation can claim this
> exact free buddy block. When the fault handler later re-acquires the lock
> and attempts xe_ttm_vram_buddy_alloc(), it will fail, returning 0 without
> purging (since pbo was NULL). This leaves the newly allocated BO using a
> faulty hardware page, which will cause persistent hardware errors.
> 

This also looks right. So I think:

@@ -804,10 +807,17 @@ static int xe_ttm_vram_reserve_page_at_addr(struct xe_device *xe, u64 addr,
                                        __func__, addr);
                                break;
                        }
+
+                       /* Queue both free and occupied (to-be-purged) pages */
+                       ++vram_mgr->n_queued_pages;
+                       list_add_rcu(&nentry->queued_link, &vram_mgr->queued_pages);
+               } else {
+                       /* Immediately offline */
+                       return xe_ttm_vram_buddy_alloc(vram_mgr, addr, addr + size,
+                                                      size, size, &nentry->blocks,
+                                                      GPU_BUDDY_RANGE_ALLOCATION,
+                                                      NULL, &nentry->used_visible_size);
                }
-               /* Queue both free and occupied (to-be-purged) pages */
-               ++vram_mgr->n_queued_pages;
-               list_add_rcu(&nentry->queued_link, &vram_mgr->queued_pages);
        }


> > +	if (pbo_to_put) {
> > +		xe_bo_put(pbo_to_put);
> > +		/* Hint System controller driver for reset with -EIO  */
> > +		return -EIO;
> > +	}
> > +
> > +	if (pbo) {
> > +		/*
> > +		 * Purge BO containing address - reference held from above.
> > +		 * This does not necessarily free the blocks synchronously: if
> > +		 * the BO is not idle, ttm_bo_pipeline_gutting() hands the
> > +		 * resource to a ghost object and it is released only once the
> > +		 * move fences signal. The reservation below then fails and is
> > +		 * retried from xe_ttm_vram_mgr_del().
> > +		 */
> > +		ret = xe_ttm_vram_purge_page(xe, pbo);
> > +		xe_bo_put(pbo);
> > +		if (ret)
> > +			drm_warn(&xe->drm, "Purge failed at addr:0x%llx, ret:%d\n", addr, ret);
> > +	}
> > +
> > +	scoped_guard(mutex, &vram_mgr->lock) {
> > +		ret = xe_ttm_vram_buddy_alloc(vram_mgr, addr, addr + size,
> > +					      size, size, &nentry->blocks,
> > +					      GPU_BUDDY_RANGE_ALLOCATION,
> > +					      NULL, &nentry->used_visible_size);
> > +		if (ret) {
> > +			nentry->status = XE_PAGE_RESERVE_FAIL;
> > +			drm_dbg(&xe->drm,
> > +				"Page at addr:0x%llx still busy (%d), deferring reservation\n",
> > +				addr, ret);
> > +			return 0;
> > +		}
> 
> [Severity: Medium]
> Does this unconditionally attempt buddy allocation without checking if the
> retry path already succeeded?
> 
> In xe_ttm_vram_reserve_page_at_addr(), if xe_ttm_vram_purge_page() frees the
> BO's VRAM resource synchronously, it invokes xe_ttm_vram_mgr_del() which
> triggers xe_ttm_vram_retry_queued_pages(). The retry logic will successfully
> allocate the buddy block.
> 
> When the original thread re-acquires the lock here and blindly calls
> xe_ttm_vram_buddy_alloc(), it will fail with -ENOSPC because the block is
> already reserved. The page's status is then falsely marked as
> XE_PAGE_RESERVE_FAIL, leading to misleading internal state.
> 
> > +
> > +		list_for_each_entry_safe(pos, n, &vram_mgr->queued_pages, queued_link) {
> [ ... ]
> > +static struct xe_vram_region *xe_ttm_vram_addr_to_region(struct xe_device *xe, u64 addr)
> > +{
> > +	struct xe_tile *tile;
> > +	u8 id;
> > +
> > +	for_each_tile(tile, xe, id) {
> > +		struct xe_vram_region *vr = tile->mem.vram;
> > +
> > +		if (!vr)
> > +			continue;
> 
> [Severity: High]
> Does this skip checking the kernel-dedicated VRAM region?
> 
> In xe_ttm_vram_addr_to_region(), the address lookup loop only checks
> tile->mem.vram and neglects tile->mem.kernel_vram. If a hardware fault occurs
> within kernel_vram, the address mapping fails and returns
> ERR_PTR(-EOPNOTSUPP). This causes faults in kernel memory to be silently
> ignored instead of triggering a system reset.
> 

tile->mem.kernel_vram = tile->mem.vram in the current code but for
completeness, this likely right too. I'd check the kernel_vram if
tile->mem.kernel_vram != tile->mem.vram.

Matt

> > +
> > +		if (addr >= vr->dpa_base && addr < (vr->dpa_base + vr->usable_size))
> > +			return vr;
> [ ... ]
> 
> -- 
> Sashiko AI review · https://sashiko.dev/#/patchset/20260902145343.465686-17-tejas.upadhyay@intel.com?part=9

  reply	other threads:[~2026-09-02 23:04 UTC|newest]

Thread overview: 38+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-09-02 14:53 [PATCH V20 00/15] Add memory page offlining support Tejas Upadhyay
2026-09-02 14:53 ` [PATCH V20 01/15] drm/xe: Link VRAM object with gpu buddy Tejas Upadhyay
2026-09-02 14:53 ` [PATCH V20 02/15] drm/xe: Link LRC BO and its execution queue with safe lifetime rules Tejas Upadhyay
2026-09-02 14:53 ` [PATCH V20 03/15] drm/xe: Export xe_ttm_bo_purge() Tejas Upadhyay
2026-09-02 14:53 ` [PATCH V20 04/15] drm/xe: Handle NULL resource and allow purging of VRAM pages Tejas Upadhyay
2026-09-02 15:28   ` sashiko-bot
2026-09-02 14:53 ` [PATCH V20 05/15] drm/xe/bo: Make xe_bo_is_user() public Tejas Upadhyay
2026-09-02 14:53 ` [PATCH V20 06/15] drm/xe: Guard teardown paths against purged BOs Tejas Upadhyay
2026-09-02 15:57   ` sashiko-bot
2026-09-02 17:52     ` Upadhyay, Tejas
2026-09-02 14:53 ` [PATCH V20 07/15] drm/xe/vram: Extract buddy allocation and free helpers Tejas Upadhyay
2026-09-02 14:53 ` [PATCH V20 08/15] drm/xe/vram: Add page offline data structures and lifecycle Tejas Upadhyay
2026-09-02 14:53 ` [PATCH V20 09/15] drm/xe/vram: Add VRAM page offline fault handler Tejas Upadhyay
2026-09-02 16:25   ` sashiko-bot
2026-09-02 23:04     ` Matthew Brost [this message]
2026-09-03  4:22       ` Matthew Brost
2026-09-03  7:57         ` Upadhyay, Tejas
2026-09-02 14:53 ` [PATCH V20 10/15] drm/xe/configfs: Add disable_vram_page_offline attribute Tejas Upadhyay
2026-09-02 16:35   ` sashiko-bot
2026-09-02 17:56     ` Upadhyay, Tejas
2026-09-02 14:53 ` [PATCH V20 11/15] drm/xe/ras: Cache disable_vram_page_offline policy at init Tejas Upadhyay
2026-09-02 14:53 ` [PATCH V20 12/15] drm/xe/vram: Check disable_vram_page_offline policy in fault handler Tejas Upadhyay
2026-09-02 16:48   ` sashiko-bot
2026-09-02 17:57     ` Upadhyay, Tejas
2026-09-02 18:31   ` Michal Wajdeczko
2026-09-02 14:53 ` [PATCH V20 13/15] drm/xe: Expose bad VRAM pages via debugfs Tejas Upadhyay
2026-09-02 16:56   ` sashiko-bot
2026-09-02 18:00     ` Upadhyay, Tejas
2026-09-02 23:08       ` Matthew Brost
2026-09-02 18:36   ` Michal Wajdeczko
2026-09-02 14:53 ` [PATCH V20 14/15] drm/xe/uapi: Expose ban reason in EXEC_QUEUE_GET_PROPERTY_BAN Tejas Upadhyay
2026-09-02 17:10   ` sashiko-bot
2026-09-02 23:12     ` Matthew Brost
2026-09-02 14:53 ` [PATCH V20 15/15] drm/xe: Add fault-inject based VRAM page offline injection Tejas Upadhyay
2026-09-02 17:02 ` ✗ CI.checkpatch: warning for Add memory page offlining support (rev24) Patchwork
2026-09-02 17:04 ` ✓ CI.KUnit: success " Patchwork
2026-09-02 17:42 ` ✓ Xe.CI.BAT: " Patchwork
2026-09-03  6:57 ` ✓ Xe.CI.FULL: " Patchwork

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=apiraQytMqBPagvH@gsse-cloud1.jf.intel.com \
    --to=matthew.brost@intel.com \
    --cc=intel-xe@lists.freedesktop.org \
    --cc=sashiko-reviews@lists.linux.dev \
    --cc=tejas.upadhyay@intel.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.