CVE-2026-46042
Description
In the Linux kernel, the following vulnerability has been resolved:
mm/mempolicy: fix memory leaks in weighted_interleave_auto_store()
weighted_interleave_auto_store() fetches old_wi_state inside the if (!input) block only. This causes two memory leaks:
1. When a user writes "false" and the current mode is already manual, the function returns early without freeing the freshly allocated new_wi_state.
2. When a user writes "true", old_wi_state stays NULL because the fetch is skipped entirely. The old state is then overwritten by rcu_assign_pointer() but never freed, since the cleanup path is gated on old_wi_state being non-NULL. A user can trigger this repeatedly by writing "1" in a loop.
Fix both leaks by moving the old_wi_state fetch before the input check, making it unconditional. This also allows a unified early return for both "true" and "false" when the requested mode matches the current mode.
Reviewed by: Donet Tom <donettom@linux.ibm.com>
Affected products
2Patches
639caa9ca863fmm/mempolicy: fix memory leaks in weighted_interleave_auto_store()
1 file changed · +12 −12
mm/mempolicy.c+12 −12 modifieddiff --git a/mm/mempolicy.c b/mm/mempolicy.c index cf92bd6a8226ee..ebe4bc8220b14e 100644 --- a/mm/mempolicy.c +++ b/mm/mempolicy.c @@ -3706,18 +3706,19 @@ static ssize_t weighted_interleave_auto_store(struct kobject *kobj, new_wi_state->iw_table[i] = 1; mutex_lock(&wi_state_lock); - if (!input) { - old_wi_state = rcu_dereference_protected(wi_state, - lockdep_is_held(&wi_state_lock)); - if (!old_wi_state) - goto update_wi_state; - if (input == old_wi_state->mode_auto) { - mutex_unlock(&wi_state_lock); - return count; - } + old_wi_state = rcu_dereference_protected(wi_state, + lockdep_is_held(&wi_state_lock)); - memcpy(new_wi_state->iw_table, old_wi_state->iw_table, - nr_node_ids * sizeof(u8)); + if (old_wi_state && input == old_wi_state->mode_auto) { + mutex_unlock(&wi_state_lock); + kfree(new_wi_state); + return count; + } + + if (!input) { + if (old_wi_state) + memcpy(new_wi_state->iw_table, old_wi_state->iw_table, + nr_node_ids * sizeof(u8)); goto update_wi_state; } -- cgit 1.3-korg
6fae274ce0e3mm/mempolicy: fix memory leaks in weighted_interleave_auto_store()
1 file changed · +12 −12
mm/mempolicy.c+12 −12 modifieddiff --git a/mm/mempolicy.c b/mm/mempolicy.c index fd08771e2057b3..62108a5b74c4ed 100644 --- a/mm/mempolicy.c +++ b/mm/mempolicy.c @@ -3700,18 +3700,19 @@ static ssize_t weighted_interleave_auto_store(struct kobject *kobj, new_wi_state->iw_table[i] = 1; mutex_lock(&wi_state_lock); - if (!input) { - old_wi_state = rcu_dereference_protected(wi_state, - lockdep_is_held(&wi_state_lock)); - if (!old_wi_state) - goto update_wi_state; - if (input == old_wi_state->mode_auto) { - mutex_unlock(&wi_state_lock); - return count; - } + old_wi_state = rcu_dereference_protected(wi_state, + lockdep_is_held(&wi_state_lock)); - memcpy(new_wi_state->iw_table, old_wi_state->iw_table, - nr_node_ids * sizeof(u8)); + if (old_wi_state && input == old_wi_state->mode_auto) { + mutex_unlock(&wi_state_lock); + kfree(new_wi_state); + return count; + } + + if (!input) { + if (old_wi_state) + memcpy(new_wi_state->iw_table, old_wi_state->iw_table, + nr_node_ids * sizeof(u8)); goto update_wi_state; } -- cgit 1.3-korg
c42a7efb9060mm/mempolicy: fix memory leaks in weighted_interleave_auto_store()
1 file changed · +12 −12
mm/mempolicy.c+12 −12 modifieddiff --git a/mm/mempolicy.c b/mm/mempolicy.c index 94327574fbbbbf..779a8cc17a8324 100644 --- a/mm/mempolicy.c +++ b/mm/mempolicy.c @@ -3636,18 +3636,19 @@ static ssize_t weighted_interleave_auto_store(struct kobject *kobj, new_wi_state->iw_table[i] = 1; mutex_lock(&wi_state_lock); - if (!input) { - old_wi_state = rcu_dereference_protected(wi_state, - lockdep_is_held(&wi_state_lock)); - if (!old_wi_state) - goto update_wi_state; - if (input == old_wi_state->mode_auto) { - mutex_unlock(&wi_state_lock); - return count; - } + old_wi_state = rcu_dereference_protected(wi_state, + lockdep_is_held(&wi_state_lock)); - memcpy(new_wi_state->iw_table, old_wi_state->iw_table, - nr_node_ids * sizeof(u8)); + if (old_wi_state && input == old_wi_state->mode_auto) { + mutex_unlock(&wi_state_lock); + kfree(new_wi_state); + return count; + } + + if (!input) { + if (old_wi_state) + memcpy(new_wi_state->iw_table, old_wi_state->iw_table, + nr_node_ids * sizeof(u8)); goto update_wi_state; } -- cgit 1.3-korg
39caa9ca863fmm/mempolicy: fix memory leaks in weighted_interleave_auto_store()
1 file changed · +12 −12
mm/mempolicy.c+12 −12 modifieddiff --git a/mm/mempolicy.c b/mm/mempolicy.c index cf92bd6a8226ee..ebe4bc8220b14e 100644 --- a/mm/mempolicy.c +++ b/mm/mempolicy.c @@ -3706,18 +3706,19 @@ static ssize_t weighted_interleave_auto_store(struct kobject *kobj, new_wi_state->iw_table[i] = 1; mutex_lock(&wi_state_lock); - if (!input) { - old_wi_state = rcu_dereference_protected(wi_state, - lockdep_is_held(&wi_state_lock)); - if (!old_wi_state) - goto update_wi_state; - if (input == old_wi_state->mode_auto) { - mutex_unlock(&wi_state_lock); - return count; - } + old_wi_state = rcu_dereference_protected(wi_state, + lockdep_is_held(&wi_state_lock)); - memcpy(new_wi_state->iw_table, old_wi_state->iw_table, - nr_node_ids * sizeof(u8)); + if (old_wi_state && input == old_wi_state->mode_auto) { + mutex_unlock(&wi_state_lock); + kfree(new_wi_state); + return count; + } + + if (!input) { + if (old_wi_state) + memcpy(new_wi_state->iw_table, old_wi_state->iw_table, + nr_node_ids * sizeof(u8)); goto update_wi_state; } -- cgit 1.3-korg
6fae274ce0e3mm/mempolicy: fix memory leaks in weighted_interleave_auto_store()
1 file changed · +12 −12
mm/mempolicy.c+12 −12 modifieddiff --git a/mm/mempolicy.c b/mm/mempolicy.c index fd08771e2057b3..62108a5b74c4ed 100644 --- a/mm/mempolicy.c +++ b/mm/mempolicy.c @@ -3700,18 +3700,19 @@ static ssize_t weighted_interleave_auto_store(struct kobject *kobj, new_wi_state->iw_table[i] = 1; mutex_lock(&wi_state_lock); - if (!input) { - old_wi_state = rcu_dereference_protected(wi_state, - lockdep_is_held(&wi_state_lock)); - if (!old_wi_state) - goto update_wi_state; - if (input == old_wi_state->mode_auto) { - mutex_unlock(&wi_state_lock); - return count; - } + old_wi_state = rcu_dereference_protected(wi_state, + lockdep_is_held(&wi_state_lock)); - memcpy(new_wi_state->iw_table, old_wi_state->iw_table, - nr_node_ids * sizeof(u8)); + if (old_wi_state && input == old_wi_state->mode_auto) { + mutex_unlock(&wi_state_lock); + kfree(new_wi_state); + return count; + } + + if (!input) { + if (old_wi_state) + memcpy(new_wi_state->iw_table, old_wi_state->iw_table, + nr_node_ids * sizeof(u8)); goto update_wi_state; } -- cgit 1.3-korg
c42a7efb9060mm/mempolicy: fix memory leaks in weighted_interleave_auto_store()
1 file changed · +12 −12
mm/mempolicy.c+12 −12 modifieddiff --git a/mm/mempolicy.c b/mm/mempolicy.c index 94327574fbbbbf..779a8cc17a8324 100644 --- a/mm/mempolicy.c +++ b/mm/mempolicy.c @@ -3636,18 +3636,19 @@ static ssize_t weighted_interleave_auto_store(struct kobject *kobj, new_wi_state->iw_table[i] = 1; mutex_lock(&wi_state_lock); - if (!input) { - old_wi_state = rcu_dereference_protected(wi_state, - lockdep_is_held(&wi_state_lock)); - if (!old_wi_state) - goto update_wi_state; - if (input == old_wi_state->mode_auto) { - mutex_unlock(&wi_state_lock); - return count; - } + old_wi_state = rcu_dereference_protected(wi_state, + lockdep_is_held(&wi_state_lock)); - memcpy(new_wi_state->iw_table, old_wi_state->iw_table, - nr_node_ids * sizeof(u8)); + if (old_wi_state && input == old_wi_state->mode_auto) { + mutex_unlock(&wi_state_lock); + kfree(new_wi_state); + return count; + } + + if (!input) { + if (old_wi_state) + memcpy(new_wi_state->iw_table, old_wi_state->iw_table, + nr_node_ids * sizeof(u8)); goto update_wi_state; } -- cgit 1.3-korg
Vulnerability mechanics
Root cause
"Conditional placement of old_wi_state fetch inside the `if (!input)` block causes two memory leak paths: early return without freeing new_wi_state when writing "false" in manual mode, and NULL old_wi_state when writing "true" so the old state is never freed after being overwritten."
Attack vector
An attacker with local access to the sysfs interface for weighted interleave auto-tuning can trigger memory leaks by writing specific values. Writing "false" when the current mode is already manual causes an early return that leaks the freshly allocated `new_wi_state`. Writing "true" causes `old_wi_state` to remain NULL because the fetch is inside the `!input` block, so the old state is overwritten by `rcu_assign_pointer()` but never freed. A user can repeatedly write "1" in a loop to exhaust kernel memory [patch_id=2660195].
Affected code
The vulnerability is in the `weighted_interleave_auto_store()` function in `mm/mempolicy.c` [patch_id=2660195]. The buggy code placed the `old_wi_state = rcu_dereference_protected(wi_state, ...)` fetch inside the `if (!input)` block, so it was only executed when the input was "false" (0).
What the fix does
The patch moves the `old_wi_state = rcu_dereference_protected(...)` fetch before the `if (!input)` check, making it unconditional [patch_id=2660195]. This ensures `old_wi_state` is always populated regardless of the input value. The early-return path for matching modes now also calls `kfree(new_wi_state)` before returning, preventing leak #1. When `!input` is true, the code conditionally copies the iw_table only if `old_wi_state` is non-NULL, and the old state will be properly freed via the existing cleanup path since `old_wi_state` is now always set.
Preconditions
- authAttacker must have local access to the sysfs interface for weighted interleave auto-tuning (the `weighted_interleave_auto_store` function).
- configThe kernel must be built with CONFIG_NUMA and the weighted interleave auto-tuning feature enabled (introduced in v6.16).
- authNo special capabilities beyond write access to the sysfs file are required.
Generated on May 27, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
3News mentions
0No linked articles in our index yet.