VYPR
Unrated severityNVD Advisory· Published May 27, 2026· Updated May 27, 2026

CVE-2026-45878

CVE-2026-45878

Description

In the Linux kernel, the following vulnerability has been resolved:

drm/amdkfd: Fix watch_id bounds checking in debug address watch v2

The address watch clear code receives watch_id as an unsigned value (u32), but some helper functions were using a signed int and checked bits by shifting with watch_id.

If a very large watch_id is passed from userspace, it can be converted to a negative value. This can cause invalid shifts and may access memory outside the watch_points array.

drm/amdkfd: Fix watch_id bounds checking in debug address watch v2

Fix this by checking that watch_id is within MAX_WATCH_ADDRESSES before using it. Also use BIT(watch_id) to test and clear bits safely.

This keeps the behavior unchanged for valid watch IDs and avoids undefined behavior for invalid ones.

Fixes the below: drivers/gpu/drm/amd/amdgpu/../amdkfd/kfd_debug.c:448 kfd_dbg_trap_clear_dev_address_watch() error: buffer overflow 'pdd->watch_points' 4 <= u32max user_rl='0-3,2147483648-u32max' uncapped

drivers/gpu/drm/amd/amdgpu/../amdkfd/kfd_debug.c 433 int kfd_dbg_trap_clear_dev_address_watch(struct kfd_process_device *pdd, 434 uint32_t watch_id) 435 { 436 int r; 437 438 if (!kfd_dbg_owns_dev_watch_id(pdd, watch_id))

kfd_dbg_owns_dev_watch_id() doesn't check for negative values so if watch_id is larger than INT_MAX it leads to a buffer overflow. (Negative shifts are undefined).

439 return -EINVAL; 440 441 if (!pdd->dev->kfd->shared_resources.enable_mes) { 442 r = debug_lock_and_unmap(pdd->dev->dqm); 443 if (r) 444 return r; 445 } 446 447 amdgpu_gfx_off_ctrl(pdd->dev->adev, false); --> 448 pdd->watch_points[watch_id] = pdd->dev->kfd2kgd->clear_address_watch( 449 pdd->dev->adev, 450 watch_id);

v2: (as per, Jonathan Kim) - Add early watch_id >= MAX_WATCH_ADDRESSES validation in the set path to match the clear path. - Drop the redundant bounds check in kfd_dbg_owns_dev_watch_id().

AI Insight

LLM-synthesized narrative grounded in this CVE's description and references.

A missing bounds check on a user-supplied watch_id in the Linux kernel's AMDKFD driver allows a large unsigned value to be treated as negative, leading to invalid shifts and out-of-bounds array access.

Vulnerability

The Linux kernel's drm/amdkfd driver contains a bounds checking flaw in the debug address watch feature. In kfd_dbg_trap_clear_dev_address_watch(), the watch_id is received as an unsigned 32-bit integer (u32) [1]. However, some internal helper functions, such as kfd_dbg_owns_dev_watch_id(), treat it as a signed int and perform bit-shifting operations using BIT(watch_id). If a very large watch_id (greater than INT_MAX) is passed from userspace, it can be implicitly converted to a negative value, resulting in undefined behavior due to invalid shifts and potentially accessing memory outside the watch_points array (bounded to MAX_WATCH_ADDRESSES, which is 4) [1]. The vulnerability affects all kernel versions containing this code path, up to and including versions where the fix commit 2b36c0c1bcbbe is not applied [1].

Exploitation

An attacker must be able to send a crafted ioctl to the AMDKFD device from userspace, requiring local access to the system and sufficient privileges to interact with the GPU debugging interface. No special user interaction or race condition is needed. The attacker supplies a watch_id value greater than INT_MAX (e.g., 0x80000000) to kfd_dbg_trap_clear_dev_address_watch(). The kfd_dbg_owns_dev_watch_id() check does not reject the large value, and the subsequent use of watch_id as an index into pdd->watch_points and in bit-shift operations triggers undefined behavior, leading to out-of-bounds access [1].

Impact

Successful exploitation can result in a buffer overflow, causing a denial of service (system crash) or potentially enabling arbitrary code execution in the kernel context, depending on the memory layout. The attacker could gain elevated privileges, as the bug resides in a kernel driver path reachable from userspace. The CVSS score is not explicitly provided, but the described severity indicates a high-impact vulnerability affecting confidentiality, integrity, and availability (CIA) due to arbitrary memory access [1].

Mitigation

The fix is committed in the Linux kernel stable tree as commit 2b36c0c1bcbbe15f6cfa9652084b3124c835a150 [1]. The fix adds a bounds check verifying that watch_id is less than MAX_WATCH_ADDRESSES before use, and uses BIT(watch_id) safely. Users should update to a kernel version containing this commit. There are no known workarounds other than applying the patch. The vulnerability is not listed in CISA's Known Exploited Vulnerabilities catalog at the time of publication.

AI Insight generated on May 27, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.

Affected products

1

Patches

10
2b36c0c1bcbb

drm/amdkfd: Fix watch_id bounds checking in debug address watch v2

https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.gitSrinivasan ShanmugamFixed in 6.18.14via kernel-cna
1 file changed · +12 9
  • drivers/gpu/drm/amd/amdkfd/kfd_debug.c+12 9 modified
    diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_debug.c b/drivers/gpu/drm/amd/amdkfd/kfd_debug.c
    index ba99e0f258aee2..986cb297de8f83 100644
    --- a/drivers/gpu/drm/amd/amdkfd/kfd_debug.c
    +++ b/drivers/gpu/drm/amd/amdkfd/kfd_debug.c
    @@ -401,27 +401,25 @@ static int kfd_dbg_get_dev_watch_id(struct kfd_process_device *pdd, int *watch_i
     	return -ENOMEM;
     }
     
    -static void kfd_dbg_clear_dev_watch_id(struct kfd_process_device *pdd, int watch_id)
    +static void kfd_dbg_clear_dev_watch_id(struct kfd_process_device *pdd, u32 watch_id)
     {
     	spin_lock(&pdd->dev->watch_points_lock);
     
     	/* process owns device watch point so safe to clear */
    -	if ((pdd->alloc_watch_ids >> watch_id) & 0x1) {
    -		pdd->alloc_watch_ids &= ~(0x1 << watch_id);
    -		pdd->dev->alloc_watch_ids &= ~(0x1 << watch_id);
    +	if (pdd->alloc_watch_ids & BIT(watch_id)) {
    +		pdd->alloc_watch_ids &= ~BIT(watch_id);
    +		pdd->dev->alloc_watch_ids &= ~BIT(watch_id);
     	}
     
     	spin_unlock(&pdd->dev->watch_points_lock);
     }
     
    -static bool kfd_dbg_owns_dev_watch_id(struct kfd_process_device *pdd, int watch_id)
    +static bool kfd_dbg_owns_dev_watch_id(struct kfd_process_device *pdd, u32 watch_id)
     {
     	bool owns_watch_id = false;
     
     	spin_lock(&pdd->dev->watch_points_lock);
    -	owns_watch_id = watch_id < MAX_WATCH_ADDRESSES &&
    -			((pdd->alloc_watch_ids >> watch_id) & 0x1);
    -
    +	owns_watch_id = pdd->alloc_watch_ids & BIT(watch_id);
     	spin_unlock(&pdd->dev->watch_points_lock);
     
     	return owns_watch_id;
    @@ -432,6 +430,9 @@ int kfd_dbg_trap_clear_dev_address_watch(struct kfd_process_device *pdd,
     {
     	int r;
     
    +	if (watch_id >= MAX_WATCH_ADDRESSES)
    +		return -EINVAL;
    +
     	if (!kfd_dbg_owns_dev_watch_id(pdd, watch_id))
     		return -EINVAL;
     
    @@ -469,6 +470,9 @@ int kfd_dbg_trap_set_dev_address_watch(struct kfd_process_device *pdd,
     	if (r)
     		return r;
     
    +	if (*watch_id >= MAX_WATCH_ADDRESSES)
    +		return -EINVAL;
    +
     	if (!pdd->dev->kfd->shared_resources.enable_mes) {
     		r = debug_lock_and_unmap(pdd->dev->dqm);
     		if (r) {
    -- 
    cgit 1.3-korg
    
    
    
5a19302cab5c

drm/amdkfd: Fix watch_id bounds checking in debug address watch v2

https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.gitSrinivasan ShanmugamFixed in 7.0via kernel-cna
1 file changed · +12 9
  • drivers/gpu/drm/amd/amdkfd/kfd_debug.c+12 9 modified
    diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_debug.c b/drivers/gpu/drm/amd/amdkfd/kfd_debug.c
    index 1dae317858e932..bedb95ce965908 100644
    --- a/drivers/gpu/drm/amd/amdkfd/kfd_debug.c
    +++ b/drivers/gpu/drm/amd/amdkfd/kfd_debug.c
    @@ -404,27 +404,25 @@ static int kfd_dbg_get_dev_watch_id(struct kfd_process_device *pdd, int *watch_i
     	return -ENOMEM;
     }
     
    -static void kfd_dbg_clear_dev_watch_id(struct kfd_process_device *pdd, int watch_id)
    +static void kfd_dbg_clear_dev_watch_id(struct kfd_process_device *pdd, u32 watch_id)
     {
     	spin_lock(&pdd->dev->watch_points_lock);
     
     	/* process owns device watch point so safe to clear */
    -	if ((pdd->alloc_watch_ids >> watch_id) & 0x1) {
    -		pdd->alloc_watch_ids &= ~(0x1 << watch_id);
    -		pdd->dev->alloc_watch_ids &= ~(0x1 << watch_id);
    +	if (pdd->alloc_watch_ids & BIT(watch_id)) {
    +		pdd->alloc_watch_ids &= ~BIT(watch_id);
    +		pdd->dev->alloc_watch_ids &= ~BIT(watch_id);
     	}
     
     	spin_unlock(&pdd->dev->watch_points_lock);
     }
     
    -static bool kfd_dbg_owns_dev_watch_id(struct kfd_process_device *pdd, int watch_id)
    +static bool kfd_dbg_owns_dev_watch_id(struct kfd_process_device *pdd, u32 watch_id)
     {
     	bool owns_watch_id = false;
     
     	spin_lock(&pdd->dev->watch_points_lock);
    -	owns_watch_id = watch_id < MAX_WATCH_ADDRESSES &&
    -			((pdd->alloc_watch_ids >> watch_id) & 0x1);
    -
    +	owns_watch_id = pdd->alloc_watch_ids & BIT(watch_id);
     	spin_unlock(&pdd->dev->watch_points_lock);
     
     	return owns_watch_id;
    @@ -435,6 +433,9 @@ int kfd_dbg_trap_clear_dev_address_watch(struct kfd_process_device *pdd,
     {
     	int r;
     
    +	if (watch_id >= MAX_WATCH_ADDRESSES)
    +		return -EINVAL;
    +
     	if (!kfd_dbg_owns_dev_watch_id(pdd, watch_id))
     		return -EINVAL;
     
    @@ -472,6 +473,9 @@ int kfd_dbg_trap_set_dev_address_watch(struct kfd_process_device *pdd,
     	if (r)
     		return r;
     
    +	if (*watch_id >= MAX_WATCH_ADDRESSES)
    +		return -EINVAL;
    +
     	if (!pdd->dev->kfd->shared_resources.enable_mes) {
     		r = debug_lock_and_unmap(pdd->dev->dqm);
     		if (r) {
    -- 
    cgit 1.3-korg
    
    
    
a0d367e13db6

drm/amdkfd: Fix watch_id bounds checking in debug address watch v2

https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.gitSrinivasan ShanmugamFixed in 6.12.75via kernel-cna
1 file changed · +12 9
  • drivers/gpu/drm/amd/amdkfd/kfd_debug.c+12 9 modified
    diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_debug.c b/drivers/gpu/drm/amd/amdkfd/kfd_debug.c
    index a8abc309180137..3a8df47aa58216 100644
    --- a/drivers/gpu/drm/amd/amdkfd/kfd_debug.c
    +++ b/drivers/gpu/drm/amd/amdkfd/kfd_debug.c
    @@ -401,27 +401,25 @@ static int kfd_dbg_get_dev_watch_id(struct kfd_process_device *pdd, int *watch_i
     	return -ENOMEM;
     }
     
    -static void kfd_dbg_clear_dev_watch_id(struct kfd_process_device *pdd, int watch_id)
    +static void kfd_dbg_clear_dev_watch_id(struct kfd_process_device *pdd, u32 watch_id)
     {
     	spin_lock(&pdd->dev->watch_points_lock);
     
     	/* process owns device watch point so safe to clear */
    -	if ((pdd->alloc_watch_ids >> watch_id) & 0x1) {
    -		pdd->alloc_watch_ids &= ~(0x1 << watch_id);
    -		pdd->dev->alloc_watch_ids &= ~(0x1 << watch_id);
    +	if (pdd->alloc_watch_ids & BIT(watch_id)) {
    +		pdd->alloc_watch_ids &= ~BIT(watch_id);
    +		pdd->dev->alloc_watch_ids &= ~BIT(watch_id);
     	}
     
     	spin_unlock(&pdd->dev->watch_points_lock);
     }
     
    -static bool kfd_dbg_owns_dev_watch_id(struct kfd_process_device *pdd, int watch_id)
    +static bool kfd_dbg_owns_dev_watch_id(struct kfd_process_device *pdd, u32 watch_id)
     {
     	bool owns_watch_id = false;
     
     	spin_lock(&pdd->dev->watch_points_lock);
    -	owns_watch_id = watch_id < MAX_WATCH_ADDRESSES &&
    -			((pdd->alloc_watch_ids >> watch_id) & 0x1);
    -
    +	owns_watch_id = pdd->alloc_watch_ids & BIT(watch_id);
     	spin_unlock(&pdd->dev->watch_points_lock);
     
     	return owns_watch_id;
    @@ -432,6 +430,9 @@ int kfd_dbg_trap_clear_dev_address_watch(struct kfd_process_device *pdd,
     {
     	int r;
     
    +	if (watch_id >= MAX_WATCH_ADDRESSES)
    +		return -EINVAL;
    +
     	if (!kfd_dbg_owns_dev_watch_id(pdd, watch_id))
     		return -EINVAL;
     
    @@ -469,6 +470,9 @@ int kfd_dbg_trap_set_dev_address_watch(struct kfd_process_device *pdd,
     	if (r)
     		return r;
     
    +	if (*watch_id >= MAX_WATCH_ADDRESSES)
    +		return -EINVAL;
    +
     	if (!pdd->dev->kfd->shared_resources.enable_mes) {
     		r = debug_lock_and_unmap(pdd->dev->dqm);
     		if (r) {
    -- 
    cgit 1.3-korg
    
    
    
971bf8e61e9b

drm/amdkfd: Fix watch_id bounds checking in debug address watch v2

https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.gitSrinivasan ShanmugamFixed in 6.6.128via kernel-cna
1 file changed · +12 9
  • drivers/gpu/drm/amd/amdkfd/kfd_debug.c+12 9 modified
    diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_debug.c b/drivers/gpu/drm/amd/amdkfd/kfd_debug.c
    index eb973f16848d31..267650dcced9d1 100644
    --- a/drivers/gpu/drm/amd/amdkfd/kfd_debug.c
    +++ b/drivers/gpu/drm/amd/amdkfd/kfd_debug.c
    @@ -400,27 +400,25 @@ static int kfd_dbg_get_dev_watch_id(struct kfd_process_device *pdd, int *watch_i
     	return -ENOMEM;
     }
     
    -static void kfd_dbg_clear_dev_watch_id(struct kfd_process_device *pdd, int watch_id)
    +static void kfd_dbg_clear_dev_watch_id(struct kfd_process_device *pdd, u32 watch_id)
     {
     	spin_lock(&pdd->dev->watch_points_lock);
     
     	/* process owns device watch point so safe to clear */
    -	if ((pdd->alloc_watch_ids >> watch_id) & 0x1) {
    -		pdd->alloc_watch_ids &= ~(0x1 << watch_id);
    -		pdd->dev->alloc_watch_ids &= ~(0x1 << watch_id);
    +	if (pdd->alloc_watch_ids & BIT(watch_id)) {
    +		pdd->alloc_watch_ids &= ~BIT(watch_id);
    +		pdd->dev->alloc_watch_ids &= ~BIT(watch_id);
     	}
     
     	spin_unlock(&pdd->dev->watch_points_lock);
     }
     
    -static bool kfd_dbg_owns_dev_watch_id(struct kfd_process_device *pdd, int watch_id)
    +static bool kfd_dbg_owns_dev_watch_id(struct kfd_process_device *pdd, u32 watch_id)
     {
     	bool owns_watch_id = false;
     
     	spin_lock(&pdd->dev->watch_points_lock);
    -	owns_watch_id = watch_id < MAX_WATCH_ADDRESSES &&
    -			((pdd->alloc_watch_ids >> watch_id) & 0x1);
    -
    +	owns_watch_id = pdd->alloc_watch_ids & BIT(watch_id);
     	spin_unlock(&pdd->dev->watch_points_lock);
     
     	return owns_watch_id;
    @@ -431,6 +429,9 @@ int kfd_dbg_trap_clear_dev_address_watch(struct kfd_process_device *pdd,
     {
     	int r;
     
    +	if (watch_id >= MAX_WATCH_ADDRESSES)
    +		return -EINVAL;
    +
     	if (!kfd_dbg_owns_dev_watch_id(pdd, watch_id))
     		return -EINVAL;
     
    @@ -468,6 +469,9 @@ int kfd_dbg_trap_set_dev_address_watch(struct kfd_process_device *pdd,
     	if (r)
     		return r;
     
    +	if (*watch_id >= MAX_WATCH_ADDRESSES)
    +		return -EINVAL;
    +
     	if (!pdd->dev->kfd->shared_resources.enable_mes) {
     		r = debug_lock_and_unmap(pdd->dev->dqm);
     		if (r) {
    -- 
    cgit 1.3-korg
    
    
    
3c38a0f07aa2

drm/amdkfd: Fix watch_id bounds checking in debug address watch v2

https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.gitSrinivasan ShanmugamFixed in 6.19.4via kernel-cna
1 file changed · +12 9
  • drivers/gpu/drm/amd/amdkfd/kfd_debug.c+12 9 modified
    diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_debug.c b/drivers/gpu/drm/amd/amdkfd/kfd_debug.c
    index ba99e0f258aee2..986cb297de8f83 100644
    --- a/drivers/gpu/drm/amd/amdkfd/kfd_debug.c
    +++ b/drivers/gpu/drm/amd/amdkfd/kfd_debug.c
    @@ -401,27 +401,25 @@ static int kfd_dbg_get_dev_watch_id(struct kfd_process_device *pdd, int *watch_i
     	return -ENOMEM;
     }
     
    -static void kfd_dbg_clear_dev_watch_id(struct kfd_process_device *pdd, int watch_id)
    +static void kfd_dbg_clear_dev_watch_id(struct kfd_process_device *pdd, u32 watch_id)
     {
     	spin_lock(&pdd->dev->watch_points_lock);
     
     	/* process owns device watch point so safe to clear */
    -	if ((pdd->alloc_watch_ids >> watch_id) & 0x1) {
    -		pdd->alloc_watch_ids &= ~(0x1 << watch_id);
    -		pdd->dev->alloc_watch_ids &= ~(0x1 << watch_id);
    +	if (pdd->alloc_watch_ids & BIT(watch_id)) {
    +		pdd->alloc_watch_ids &= ~BIT(watch_id);
    +		pdd->dev->alloc_watch_ids &= ~BIT(watch_id);
     	}
     
     	spin_unlock(&pdd->dev->watch_points_lock);
     }
     
    -static bool kfd_dbg_owns_dev_watch_id(struct kfd_process_device *pdd, int watch_id)
    +static bool kfd_dbg_owns_dev_watch_id(struct kfd_process_device *pdd, u32 watch_id)
     {
     	bool owns_watch_id = false;
     
     	spin_lock(&pdd->dev->watch_points_lock);
    -	owns_watch_id = watch_id < MAX_WATCH_ADDRESSES &&
    -			((pdd->alloc_watch_ids >> watch_id) & 0x1);
    -
    +	owns_watch_id = pdd->alloc_watch_ids & BIT(watch_id);
     	spin_unlock(&pdd->dev->watch_points_lock);
     
     	return owns_watch_id;
    @@ -432,6 +430,9 @@ int kfd_dbg_trap_clear_dev_address_watch(struct kfd_process_device *pdd,
     {
     	int r;
     
    +	if (watch_id >= MAX_WATCH_ADDRESSES)
    +		return -EINVAL;
    +
     	if (!kfd_dbg_owns_dev_watch_id(pdd, watch_id))
     		return -EINVAL;
     
    @@ -469,6 +470,9 @@ int kfd_dbg_trap_set_dev_address_watch(struct kfd_process_device *pdd,
     	if (r)
     		return r;
     
    +	if (*watch_id >= MAX_WATCH_ADDRESSES)
    +		return -EINVAL;
    +
     	if (!pdd->dev->kfd->shared_resources.enable_mes) {
     		r = debug_lock_and_unmap(pdd->dev->dqm);
     		if (r) {
    -- 
    cgit 1.3-korg
    
    
    
5a19302cab5c

drm/amdkfd: Fix watch_id bounds checking in debug address watch v2

1 file changed · +12 9
  • drivers/gpu/drm/amd/amdkfd/kfd_debug.c+12 9 modified
    diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_debug.c b/drivers/gpu/drm/amd/amdkfd/kfd_debug.c
    index 1dae317858e932..bedb95ce965908 100644
    --- a/drivers/gpu/drm/amd/amdkfd/kfd_debug.c
    +++ b/drivers/gpu/drm/amd/amdkfd/kfd_debug.c
    @@ -404,27 +404,25 @@ static int kfd_dbg_get_dev_watch_id(struct kfd_process_device *pdd, int *watch_i
     	return -ENOMEM;
     }
     
    -static void kfd_dbg_clear_dev_watch_id(struct kfd_process_device *pdd, int watch_id)
    +static void kfd_dbg_clear_dev_watch_id(struct kfd_process_device *pdd, u32 watch_id)
     {
     	spin_lock(&pdd->dev->watch_points_lock);
     
     	/* process owns device watch point so safe to clear */
    -	if ((pdd->alloc_watch_ids >> watch_id) & 0x1) {
    -		pdd->alloc_watch_ids &= ~(0x1 << watch_id);
    -		pdd->dev->alloc_watch_ids &= ~(0x1 << watch_id);
    +	if (pdd->alloc_watch_ids & BIT(watch_id)) {
    +		pdd->alloc_watch_ids &= ~BIT(watch_id);
    +		pdd->dev->alloc_watch_ids &= ~BIT(watch_id);
     	}
     
     	spin_unlock(&pdd->dev->watch_points_lock);
     }
     
    -static bool kfd_dbg_owns_dev_watch_id(struct kfd_process_device *pdd, int watch_id)
    +static bool kfd_dbg_owns_dev_watch_id(struct kfd_process_device *pdd, u32 watch_id)
     {
     	bool owns_watch_id = false;
     
     	spin_lock(&pdd->dev->watch_points_lock);
    -	owns_watch_id = watch_id < MAX_WATCH_ADDRESSES &&
    -			((pdd->alloc_watch_ids >> watch_id) & 0x1);
    -
    +	owns_watch_id = pdd->alloc_watch_ids & BIT(watch_id);
     	spin_unlock(&pdd->dev->watch_points_lock);
     
     	return owns_watch_id;
    @@ -435,6 +433,9 @@ int kfd_dbg_trap_clear_dev_address_watch(struct kfd_process_device *pdd,
     {
     	int r;
     
    +	if (watch_id >= MAX_WATCH_ADDRESSES)
    +		return -EINVAL;
    +
     	if (!kfd_dbg_owns_dev_watch_id(pdd, watch_id))
     		return -EINVAL;
     
    @@ -472,6 +473,9 @@ int kfd_dbg_trap_set_dev_address_watch(struct kfd_process_device *pdd,
     	if (r)
     		return r;
     
    +	if (*watch_id >= MAX_WATCH_ADDRESSES)
    +		return -EINVAL;
    +
     	if (!pdd->dev->kfd->shared_resources.enable_mes) {
     		r = debug_lock_and_unmap(pdd->dev->dqm);
     		if (r) {
    -- 
    cgit 1.3-korg
    
    
    
3c38a0f07aa2

drm/amdkfd: Fix watch_id bounds checking in debug address watch v2

1 file changed · +12 9
  • drivers/gpu/drm/amd/amdkfd/kfd_debug.c+12 9 modified
    diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_debug.c b/drivers/gpu/drm/amd/amdkfd/kfd_debug.c
    index ba99e0f258aee2..986cb297de8f83 100644
    --- a/drivers/gpu/drm/amd/amdkfd/kfd_debug.c
    +++ b/drivers/gpu/drm/amd/amdkfd/kfd_debug.c
    @@ -401,27 +401,25 @@ static int kfd_dbg_get_dev_watch_id(struct kfd_process_device *pdd, int *watch_i
     	return -ENOMEM;
     }
     
    -static void kfd_dbg_clear_dev_watch_id(struct kfd_process_device *pdd, int watch_id)
    +static void kfd_dbg_clear_dev_watch_id(struct kfd_process_device *pdd, u32 watch_id)
     {
     	spin_lock(&pdd->dev->watch_points_lock);
     
     	/* process owns device watch point so safe to clear */
    -	if ((pdd->alloc_watch_ids >> watch_id) & 0x1) {
    -		pdd->alloc_watch_ids &= ~(0x1 << watch_id);
    -		pdd->dev->alloc_watch_ids &= ~(0x1 << watch_id);
    +	if (pdd->alloc_watch_ids & BIT(watch_id)) {
    +		pdd->alloc_watch_ids &= ~BIT(watch_id);
    +		pdd->dev->alloc_watch_ids &= ~BIT(watch_id);
     	}
     
     	spin_unlock(&pdd->dev->watch_points_lock);
     }
     
    -static bool kfd_dbg_owns_dev_watch_id(struct kfd_process_device *pdd, int watch_id)
    +static bool kfd_dbg_owns_dev_watch_id(struct kfd_process_device *pdd, u32 watch_id)
     {
     	bool owns_watch_id = false;
     
     	spin_lock(&pdd->dev->watch_points_lock);
    -	owns_watch_id = watch_id < MAX_WATCH_ADDRESSES &&
    -			((pdd->alloc_watch_ids >> watch_id) & 0x1);
    -
    +	owns_watch_id = pdd->alloc_watch_ids & BIT(watch_id);
     	spin_unlock(&pdd->dev->watch_points_lock);
     
     	return owns_watch_id;
    @@ -432,6 +430,9 @@ int kfd_dbg_trap_clear_dev_address_watch(struct kfd_process_device *pdd,
     {
     	int r;
     
    +	if (watch_id >= MAX_WATCH_ADDRESSES)
    +		return -EINVAL;
    +
     	if (!kfd_dbg_owns_dev_watch_id(pdd, watch_id))
     		return -EINVAL;
     
    @@ -469,6 +470,9 @@ int kfd_dbg_trap_set_dev_address_watch(struct kfd_process_device *pdd,
     	if (r)
     		return r;
     
    +	if (*watch_id >= MAX_WATCH_ADDRESSES)
    +		return -EINVAL;
    +
     	if (!pdd->dev->kfd->shared_resources.enable_mes) {
     		r = debug_lock_and_unmap(pdd->dev->dqm);
     		if (r) {
    -- 
    cgit 1.3-korg
    
    
    
2b36c0c1bcbb

drm/amdkfd: Fix watch_id bounds checking in debug address watch v2

1 file changed · +12 9
  • drivers/gpu/drm/amd/amdkfd/kfd_debug.c+12 9 modified
    diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_debug.c b/drivers/gpu/drm/amd/amdkfd/kfd_debug.c
    index ba99e0f258aee2..986cb297de8f83 100644
    --- a/drivers/gpu/drm/amd/amdkfd/kfd_debug.c
    +++ b/drivers/gpu/drm/amd/amdkfd/kfd_debug.c
    @@ -401,27 +401,25 @@ static int kfd_dbg_get_dev_watch_id(struct kfd_process_device *pdd, int *watch_i
     	return -ENOMEM;
     }
     
    -static void kfd_dbg_clear_dev_watch_id(struct kfd_process_device *pdd, int watch_id)
    +static void kfd_dbg_clear_dev_watch_id(struct kfd_process_device *pdd, u32 watch_id)
     {
     	spin_lock(&pdd->dev->watch_points_lock);
     
     	/* process owns device watch point so safe to clear */
    -	if ((pdd->alloc_watch_ids >> watch_id) & 0x1) {
    -		pdd->alloc_watch_ids &= ~(0x1 << watch_id);
    -		pdd->dev->alloc_watch_ids &= ~(0x1 << watch_id);
    +	if (pdd->alloc_watch_ids & BIT(watch_id)) {
    +		pdd->alloc_watch_ids &= ~BIT(watch_id);
    +		pdd->dev->alloc_watch_ids &= ~BIT(watch_id);
     	}
     
     	spin_unlock(&pdd->dev->watch_points_lock);
     }
     
    -static bool kfd_dbg_owns_dev_watch_id(struct kfd_process_device *pdd, int watch_id)
    +static bool kfd_dbg_owns_dev_watch_id(struct kfd_process_device *pdd, u32 watch_id)
     {
     	bool owns_watch_id = false;
     
     	spin_lock(&pdd->dev->watch_points_lock);
    -	owns_watch_id = watch_id < MAX_WATCH_ADDRESSES &&
    -			((pdd->alloc_watch_ids >> watch_id) & 0x1);
    -
    +	owns_watch_id = pdd->alloc_watch_ids & BIT(watch_id);
     	spin_unlock(&pdd->dev->watch_points_lock);
     
     	return owns_watch_id;
    @@ -432,6 +430,9 @@ int kfd_dbg_trap_clear_dev_address_watch(struct kfd_process_device *pdd,
     {
     	int r;
     
    +	if (watch_id >= MAX_WATCH_ADDRESSES)
    +		return -EINVAL;
    +
     	if (!kfd_dbg_owns_dev_watch_id(pdd, watch_id))
     		return -EINVAL;
     
    @@ -469,6 +470,9 @@ int kfd_dbg_trap_set_dev_address_watch(struct kfd_process_device *pdd,
     	if (r)
     		return r;
     
    +	if (*watch_id >= MAX_WATCH_ADDRESSES)
    +		return -EINVAL;
    +
     	if (!pdd->dev->kfd->shared_resources.enable_mes) {
     		r = debug_lock_and_unmap(pdd->dev->dqm);
     		if (r) {
    -- 
    cgit 1.3-korg
    
    
    
971bf8e61e9b

drm/amdkfd: Fix watch_id bounds checking in debug address watch v2

1 file changed · +12 9
  • drivers/gpu/drm/amd/amdkfd/kfd_debug.c+12 9 modified
    diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_debug.c b/drivers/gpu/drm/amd/amdkfd/kfd_debug.c
    index eb973f16848d31..267650dcced9d1 100644
    --- a/drivers/gpu/drm/amd/amdkfd/kfd_debug.c
    +++ b/drivers/gpu/drm/amd/amdkfd/kfd_debug.c
    @@ -400,27 +400,25 @@ static int kfd_dbg_get_dev_watch_id(struct kfd_process_device *pdd, int *watch_i
     	return -ENOMEM;
     }
     
    -static void kfd_dbg_clear_dev_watch_id(struct kfd_process_device *pdd, int watch_id)
    +static void kfd_dbg_clear_dev_watch_id(struct kfd_process_device *pdd, u32 watch_id)
     {
     	spin_lock(&pdd->dev->watch_points_lock);
     
     	/* process owns device watch point so safe to clear */
    -	if ((pdd->alloc_watch_ids >> watch_id) & 0x1) {
    -		pdd->alloc_watch_ids &= ~(0x1 << watch_id);
    -		pdd->dev->alloc_watch_ids &= ~(0x1 << watch_id);
    +	if (pdd->alloc_watch_ids & BIT(watch_id)) {
    +		pdd->alloc_watch_ids &= ~BIT(watch_id);
    +		pdd->dev->alloc_watch_ids &= ~BIT(watch_id);
     	}
     
     	spin_unlock(&pdd->dev->watch_points_lock);
     }
     
    -static bool kfd_dbg_owns_dev_watch_id(struct kfd_process_device *pdd, int watch_id)
    +static bool kfd_dbg_owns_dev_watch_id(struct kfd_process_device *pdd, u32 watch_id)
     {
     	bool owns_watch_id = false;
     
     	spin_lock(&pdd->dev->watch_points_lock);
    -	owns_watch_id = watch_id < MAX_WATCH_ADDRESSES &&
    -			((pdd->alloc_watch_ids >> watch_id) & 0x1);
    -
    +	owns_watch_id = pdd->alloc_watch_ids & BIT(watch_id);
     	spin_unlock(&pdd->dev->watch_points_lock);
     
     	return owns_watch_id;
    @@ -431,6 +429,9 @@ int kfd_dbg_trap_clear_dev_address_watch(struct kfd_process_device *pdd,
     {
     	int r;
     
    +	if (watch_id >= MAX_WATCH_ADDRESSES)
    +		return -EINVAL;
    +
     	if (!kfd_dbg_owns_dev_watch_id(pdd, watch_id))
     		return -EINVAL;
     
    @@ -468,6 +469,9 @@ int kfd_dbg_trap_set_dev_address_watch(struct kfd_process_device *pdd,
     	if (r)
     		return r;
     
    +	if (*watch_id >= MAX_WATCH_ADDRESSES)
    +		return -EINVAL;
    +
     	if (!pdd->dev->kfd->shared_resources.enable_mes) {
     		r = debug_lock_and_unmap(pdd->dev->dqm);
     		if (r) {
    -- 
    cgit 1.3-korg
    
    
    
a0d367e13db6

drm/amdkfd: Fix watch_id bounds checking in debug address watch v2

1 file changed · +12 9
  • drivers/gpu/drm/amd/amdkfd/kfd_debug.c+12 9 modified
    diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_debug.c b/drivers/gpu/drm/amd/amdkfd/kfd_debug.c
    index a8abc309180137..3a8df47aa58216 100644
    --- a/drivers/gpu/drm/amd/amdkfd/kfd_debug.c
    +++ b/drivers/gpu/drm/amd/amdkfd/kfd_debug.c
    @@ -401,27 +401,25 @@ static int kfd_dbg_get_dev_watch_id(struct kfd_process_device *pdd, int *watch_i
     	return -ENOMEM;
     }
     
    -static void kfd_dbg_clear_dev_watch_id(struct kfd_process_device *pdd, int watch_id)
    +static void kfd_dbg_clear_dev_watch_id(struct kfd_process_device *pdd, u32 watch_id)
     {
     	spin_lock(&pdd->dev->watch_points_lock);
     
     	/* process owns device watch point so safe to clear */
    -	if ((pdd->alloc_watch_ids >> watch_id) & 0x1) {
    -		pdd->alloc_watch_ids &= ~(0x1 << watch_id);
    -		pdd->dev->alloc_watch_ids &= ~(0x1 << watch_id);
    +	if (pdd->alloc_watch_ids & BIT(watch_id)) {
    +		pdd->alloc_watch_ids &= ~BIT(watch_id);
    +		pdd->dev->alloc_watch_ids &= ~BIT(watch_id);
     	}
     
     	spin_unlock(&pdd->dev->watch_points_lock);
     }
     
    -static bool kfd_dbg_owns_dev_watch_id(struct kfd_process_device *pdd, int watch_id)
    +static bool kfd_dbg_owns_dev_watch_id(struct kfd_process_device *pdd, u32 watch_id)
     {
     	bool owns_watch_id = false;
     
     	spin_lock(&pdd->dev->watch_points_lock);
    -	owns_watch_id = watch_id < MAX_WATCH_ADDRESSES &&
    -			((pdd->alloc_watch_ids >> watch_id) & 0x1);
    -
    +	owns_watch_id = pdd->alloc_watch_ids & BIT(watch_id);
     	spin_unlock(&pdd->dev->watch_points_lock);
     
     	return owns_watch_id;
    @@ -432,6 +430,9 @@ int kfd_dbg_trap_clear_dev_address_watch(struct kfd_process_device *pdd,
     {
     	int r;
     
    +	if (watch_id >= MAX_WATCH_ADDRESSES)
    +		return -EINVAL;
    +
     	if (!kfd_dbg_owns_dev_watch_id(pdd, watch_id))
     		return -EINVAL;
     
    @@ -469,6 +470,9 @@ int kfd_dbg_trap_set_dev_address_watch(struct kfd_process_device *pdd,
     	if (r)
     		return r;
     
    +	if (*watch_id >= MAX_WATCH_ADDRESSES)
    +		return -EINVAL;
    +
     	if (!pdd->dev->kfd->shared_resources.enable_mes) {
     		r = debug_lock_and_unmap(pdd->dev->dqm);
     		if (r) {
    -- 
    cgit 1.3-korg
    
    
    

Vulnerability mechanics

Root cause

"Missing bounds check on watch_id combined with signed/unsigned type mismatch allows out-of-bounds array access and undefined shift behavior."

Attack vector

An attacker with the ability to issue debug address watch IOCTL calls from userspace can pass a `watch_id` value larger than `INT_MAX` (e.g., values in the range `2147483648` to `u32max`). Because the helper functions `kfd_dbg_owns_dev_watch_id()` and `kfd_dbg_clear_dev_watch_id()` used a signed `int` parameter, such a value would be implicitly converted to a negative integer. This negative value then causes undefined behavior when used as a shift operand (e.g., `pdd->alloc_watch_ids >> watch_id`) and, critically, is used as an out-of-bounds index into the `pdd->watch_points[]` array at line 448 [patch_id=2661771]. The result is a buffer overflow that could corrupt kernel memory.

Affected code

The vulnerability is in `drivers/gpu/drm/amd/amdkfd/kfd_debug.c` in the functions `kfd_dbg_trap_clear_dev_address_watch()`, `kfd_dbg_clear_dev_watch_id()`, and `kfd_dbg_owns_dev_watch_id()` [patch_id=2661771]. These functions accept a `watch_id` parameter that originates as an unsigned `u32` from userspace but was stored in signed `int` parameters in the helper functions, and the array `pdd->watch_points` (size `MAX_WATCH_ADDRESSES`, typically 4) was indexed without proper bounds checking.

What the fix does

The patch makes three coordinated changes [patch_id=2661771]. First, it changes the `watch_id` parameter type from `int` to `u32` in both `kfd_dbg_clear_dev_watch_id()` and `kfd_dbg_owns_dev_watch_id()`, eliminating the signed-to-unsigned conversion that could produce negative values. Second, it adds an early bounds check (`if (watch_id >= MAX_WATCH_ADDRESSES) return -EINVAL`) at the entry of both `kfd_dbg_trap_clear_dev_address_watch()` and `kfd_dbg_trap_set_dev_address_watch()`, preventing any out-of-bounds array access. Third, it replaces the manual shift-and-mask bit tests (`(pdd->alloc_watch_ids >> watch_id) & 0x1`) with the safer `BIT(watch_id)` macro, which avoids undefined behavior for large shift values.

Preconditions

  • authAttacker must have access to the debug address watch IOCTL interface exposed by the amdkfd driver
  • inputAttacker must be able to pass a watch_id value larger than INT_MAX (>= 2147483648) to the clear or set address watch operations

Generated on May 27, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

5

News mentions

0

No linked articles in our index yet.