[PATCH net 1/2] ipv4: devinet: update inheritance state only after parsed writes

From: Yingjie Wang

Date: Thu Oct 29 2026 - 10:32:33 EST


devinet_conf_proc() marks tracked per-device IPv4 settings as explicitly
configured after every write attempt. A rejected integer leaves the value
unchanged but sets its state bit, so a later update to conf/default skips
the device.

Zero-length writes and writes ignored because their file position is
nonzero in strict sysctl mode have the same effect. proc_dointvec()
returns success for both without parsing a value.

Use proc_dointvec_conv() with a converter that records successful integer
conversion. Update inheritance state and run post-set handling only after
both parsing and conversion succeed. The converter uses the same integer
helpers as proc_dointvec(), and valid same-value writes continue to mark a
setting as explicit.

The issue was reproduced with ordinary sysctl writes in fresh network
namespaces, without kernel modification or error injection. On the base
kernel, lo/arp_ignore remained 0 after default/arp_ignore was set to 1
following invalid, blank, zero-byte, or strict nonzero-offset writes. With
this change it inherited 1 in all four cases, while valid same-value and
changed-value writes retained 0 and 2.

Fixes: 31be308541e9 ("[IPV4]: Add default config support after inetdev_init")
Signed-off-by: Yingjie Wang <x-professor@xxxxxxxxxxx>
---
net/ipv4/devinet.c | 29 ++++++++++++++++++++++++++---
1 file changed, 26 insertions(+), 3 deletions(-)

diff --git a/net/ipv4/devinet.c b/net/ipv4/devinet.c
index 1e9d988849c9e..e17c82fcdefed 100644
--- a/net/ipv4/devinet.c
+++ b/net/ipv4/devinet.c
@@ -2555,14 +2555,37 @@ static int devinet_conf_ifindex(struct net *net, struct ipv4_devconf *cnf)
}
}

+static int devinet_conf_int_conv(bool *negp, unsigned long *u_ptr, int *k_ptr,
+ int dir, const struct ctl_table *ctl)
+{
+ int ret;
+
+ if (dir)
+ ret = proc_int_u2k_conv_uop(u_ptr, k_ptr, negp, NULL);
+ else
+ ret = proc_int_k2u_conv_kop(u_ptr, k_ptr, negp, NULL);
+
+ if (!ret && dir)
+ *(bool *)ctl->extra1 = true;
+
+ return ret;
+}
+
static int devinet_conf_proc(const struct ctl_table *ctl, int write,
void *buffer, size_t *lenp, loff_t *ppos)
{
int old_value = *(int *)ctl->data;
- int ret = proc_dointvec(ctl, write, buffer, lenp, ppos);
- int new_value = *(int *)ctl->data;
+ struct ctl_table tmp = *ctl;
+ bool parsed = false;
+ int ret, new_value;
+
+ /* An ignored write returns success without calling the converter. */
+ tmp.extra1 = &parsed;
+ ret = proc_dointvec_conv(&tmp, write, buffer, lenp, ppos,
+ devinet_conf_int_conv);
+ new_value = *(int *)ctl->data;

- if (write) {
+ if (write && !ret && parsed) {
struct ipv4_devconf *cnf = ctl->extra1;
int i = (int *)ctl->data - cnf->data;
struct net *net = ctl->extra2;
--
2.43.0