Currently we don't check if the new MTU is valid or not and this allows
one to configure a smaller than minimum allowed by RFCs or even bigger
than interface own MTU, which is a problem as it may lead to packet
drops.
If you have a daemon like NetworkManager running, this may be exploited
by remote attackers by forging RA packets with an invalid MTU, possibly
leading to a DoS. (NetworkManager currently only validates for values
too small, but not for too big ones.)
The fix is just to make sure the new value is valid. That is, between
IPV6_MIN_MTU and interface's MTU.
Note that similar check is already performed at
ndisc_router_discovery(), for when kernel itself parses the RA.
Signed-off-by: Marcelo Ricardo Leitner <redacted>
---
net/ipv6/addrconf.c | 32 +++++++++++++++++++++++++++++++-
1 file changed, 31 insertions(+), 1 deletion(-)
@@ -4863,6 +4863,36 @@ int addrconf_sysctl_forward(struct ctl_table *ctl, int write,returnret;}+static+intaddrconf_sysctl_mtu(structctl_table*ctl,intwrite,+void__user*buffer,size_t*lenp,loff_t*ppos)+{+int*valp=ctl->data;+intval=*valp;+loff_tpos=*ppos;+structctl_tablelctl;+intret;++/* ctl->data points to idev->cnf.mtu6+*/+lctl=*ctl;+lctl.data=&val;++ret=proc_dointvec(&lctl,write,buffer,lenp,ppos);++if(write){+structinet6_dev*idev=ctl->extra1;++if(val>=IPV6_MIN_MTU&&val<=idev->dev->mtu)+*valp=val;+else+ret=-EINVAL;+}+if(ret)+*ppos=pos;+returnret;+}+staticvoiddev_disable_change(structinet6_dev*idev){structnetdev_notifier_infoinfo;
@@ -4863,6 +4863,36 @@ int addrconf_sysctl_forward(struct ctl_table *ctl, int write,returnret;}+static+intaddrconf_sysctl_mtu(structctl_table*ctl,intwrite,+void__user*buffer,size_t*lenp,loff_t*ppos)+{+int*valp=ctl->data;+intval=*valp;+loff_tpos=*ppos;+structctl_tablelctl;+intret;++/* ctl->data points to idev->cnf.mtu6+*/+lctl=*ctl;+lctl.data=&val;++ret=proc_dointvec(&lctl,write,buffer,lenp,ppos);++if(write){+structinet6_dev*idev=ctl->extra1;++if(val>=IPV6_MIN_MTU&&val<=idev->dev->mtu)
"all" and "default" don't have an idev, so you need a check here:
if (val >= IPV6_MIN_MTU && (!idev || val <= idev->dev->mtu))
quoted hunk
+ *valp = val;+ else+ ret = -EINVAL;+ }+ if (ret)+ *ppos = pos;+ return ret;+}+
You could call proc_dointvec_minmax to do the checks. Something like:
static
int addrconf_sysctl_mtu(struct ctl_table *ctl, int write,
void __user *buffer, size_t *lenp, loff_t *ppos)
{
struct inet6_dev *idev = ctl->extra1;
int min_mtu = IPV6_MIN_MTU;
struct ctl_table lctl;
lctl = *ctl;
lctl.extra1 = &min_mtu;
lctl.extra2 = idev ? &idev->dev->mtu : NULL;
return proc_dointvec_minmax(&lctl, write, buffer, lenp, ppos);
}
Thanks,
--
Sabrina
@@ -4863,6 +4863,36 @@ int addrconf_sysctl_forward(struct ctl_table *ctl, int write,returnret;}+static+intaddrconf_sysctl_mtu(structctl_table*ctl,intwrite,+void__user*buffer,size_t*lenp,loff_t*ppos)+{+int*valp=ctl->data;+intval=*valp;+loff_tpos=*ppos;+structctl_tablelctl;+intret;++/* ctl->data points to idev->cnf.mtu6+*/+lctl=*ctl;+lctl.data=&val;++ret=proc_dointvec(&lctl,write,buffer,lenp,ppos);++if(write){+structinet6_dev*idev=ctl->extra1;++if(val>=IPV6_MIN_MTU&&val<=idev->dev->mtu)
"all" and "default" don't have an idev, so you need a check here:
if (val >= IPV6_MIN_MTU && (!idev || val <= idev->dev->mtu))
Oh, indeed.
quoted
+ *valp = val;+ else+ ret = -EINVAL;+ }+ if (ret)+ *ppos = pos;+ return ret;+}+
You could call proc_dointvec_minmax to do the checks. Something like:
static
int addrconf_sysctl_mtu(struct ctl_table *ctl, int write,
void __user *buffer, size_t *lenp, loff_t *ppos)
{
struct inet6_dev *idev = ctl->extra1;
int min_mtu = IPV6_MIN_MTU;
struct ctl_table lctl;
lctl = *ctl;
lctl.extra1 = &min_mtu;
lctl.extra2 = idev ? &idev->dev->mtu : NULL;
return proc_dointvec_minmax(&lctl, write, buffer, lenp, ppos);
}
That's much better! Weird that I did look for a _minmax helper and
missed it :/
Thanks Sabrina, I'll send a v2 soon.
Marcelo