[PATCH net 1/2] net: preserve rps_default_mask on failed writes

From: Yingjie Wang

Date: Wed Oct 28 2026 - 10:52:52 EST


rps_default_mask_sysctl() passes the live per-netns mask to
cpumask_parse(). bitmap_parse() stores chunks as it scans from right to
left, so a later error can leave lower chunks changed.
rps_cpumask_housekeeping() also intersects its input in place before
returning -EINVAL when no housekeeping CPUs remain.

Consequently, a failed write changes both the value read from the sysctl
and the default copied to RX queues created later. On a four-CPU VM,
after setting the mask to 1, writing zz,00000003 failed with EOVERFLOW
but changed the mask to 3. Writing a mask containing only isolated CPUs
failed with EINVAL but changed it to 0. No kernel modification or error
injection was needed to reproduce either case.

Parse and validate a temporary mask, then replace the live pointer only
after both steps succeed. Readers access and consume the pointer while
holding the same mutex, so the replaced mask can be freed after dropping
the mutex.

The rps_default_mask selftest failed on the base kernel and passed with
the fix. The fixed kernel also kept the mask at 1 for the overflow and
housekeeping failure cases, while valid writes of 2 and 0 still
succeeded.

The issue was found while fuzzing sysctl write error paths.

Fixes: 605cfa1b1090 ("net: introduce default_rps_mask netns attribute")
Signed-off-by: Yingjie Wang <x-professor@xxxxxxxxxxx>
---
net/core/sysctl_net_core.c | 23 +++++++++++++----------
1 file changed, 13 insertions(+), 10 deletions(-)

diff --git a/net/core/sysctl_net_core.c b/net/core/sysctl_net_core.c
index 473c162736d69..6abd77ef92c86 100644
--- a/net/core/sysctl_net_core.c
+++ b/net/core/sysctl_net_core.c
@@ -105,27 +105,29 @@ static int rps_default_mask_sysctl(const struct ctl_table *table, int write,
void *buffer, size_t *lenp, loff_t *ppos)
{
struct net *net = (struct net *)table->data;
+ struct cpumask *new_mask = NULL;
struct cpumask *mask;
int err = 0;

+ if (write) {
+ new_mask = kzalloc(cpumask_size(), GFP_KERNEL);
+ if (!new_mask)
+ return -ENOMEM;
+ }
+
mutex_lock(&rps_default_mask_mutex);
mask = net->core.rps_default_mask;
if (write) {
- if (!mask) {
- mask = kzalloc(cpumask_size(), GFP_KERNEL);
- net->core.rps_default_mask = mask;
- }
- err = -ENOMEM;
- if (!mask)
- goto done;
-
- err = cpumask_parse(buffer, mask);
+ err = cpumask_parse(buffer, new_mask);
if (err)
goto done;

- err = rps_cpumask_housekeeping(mask);
+ err = rps_cpumask_housekeeping(new_mask);
if (err)
goto done;
+
+ net->core.rps_default_mask = new_mask;
+ new_mask = mask;
} else {
err = dump_cpumask(buffer, lenp, ppos,
mask ?: cpu_none_mask);
@@ -133,6 +135,7 @@ static int rps_default_mask_sysctl(const struct ctl_table *table, int write,

done:
mutex_unlock(&rps_default_mask_mutex);
+ kfree(new_mask);
return err;
}

--
2.43.0