Commit f2bf7504 authored by Dan Aloni's avatar Dan Aloni Committed by David S. Miller

[NET]: Fix sysctl breakage during network device renaming.

Sysctl assumes its ctl_table.procname field is const, but the 
networking points ctl_table.procname to dev->name. When renaming 
a network device using SIOCSIFNAME, dev->name is modified and 
sysctl's assumption breaks, causing this behaviour, at least:

  1. sysctl wouldn't be able to remove the proc entry when the 
     device requests to be unregistered, because it would be 
     using the new name instead of the old one.
  2. proc entries for devices remain with the old name after 
     rename.
  
This change includes allocating the current device name to a
new copy upon registering with sysctl, plus re-registering with 
sysctl when the device is renamed.

This only fixes IPv4, IPv6, and net/core/neightbour.c.
Fixes for ax25 and decnet are also planned.
parent 0b8c03e4
......@@ -906,6 +906,9 @@ extern int netdev_fastroute_obstacles;
extern void dev_clear_fastroute(struct net_device *dev);
#endif
#ifdef CONFIG_SYSCTL
extern char *net_sysctl_strdup(const char *s);
#endif
#endif /* __KERNEL__ */
......
......@@ -1628,6 +1628,9 @@ int neigh_sysctl_register(struct net_device *dev, struct neigh_parms *p,
int p_id, int pdev_id, char *p_name)
{
struct neigh_sysctl_table *t = kmalloc(sizeof(*t), GFP_KERNEL);
const char *dev_name_source = NULL;
char *dev_name = NULL;
int err = 0;
if (!t)
return -ENOBUFS;
......@@ -1644,8 +1647,10 @@ int neigh_sysctl_register(struct net_device *dev, struct neigh_parms *p,
t->neigh_vars[9].data = &p->anycast_delay;
t->neigh_vars[10].data = &p->proxy_delay;
t->neigh_vars[11].data = &p->locktime;
dev_name_source = t->neigh_dev[0].procname;
if (dev) {
t->neigh_dev[0].procname = dev->name;
dev_name_source = dev->name;
t->neigh_dev[0].ctl_name = dev->ifindex;
memset(&t->neigh_vars[12], 0, sizeof(ctl_table));
} else {
......@@ -1654,6 +1659,15 @@ int neigh_sysctl_register(struct net_device *dev, struct neigh_parms *p,
t->neigh_vars[14].data = (int *)(p + 1) + 2;
t->neigh_vars[15].data = (int *)(p + 1) + 3;
}
dev_name = net_sysctl_strdup(dev_name_source);
if (!dev_name) {
err = -ENOBUFS;
goto free;
}
t->neigh_dev[0].procname = dev_name;
t->neigh_neigh_dir[0].ctl_name = pdev_id;
t->neigh_proto_dir[0].procname = p_name;
......@@ -1666,11 +1680,19 @@ int neigh_sysctl_register(struct net_device *dev, struct neigh_parms *p,
t->sysctl_header = register_sysctl_table(t->neigh_root_dir, 0);
if (!t->sysctl_header) {
kfree(t);
return -ENOBUFS;
err = -ENOBUFS;
goto free_procname;
}
p->sysctl_table = t;
return 0;
/* error path */
free_procname:
kfree(dev_name);
free:
kfree(t);
return err;
}
void neigh_sysctl_unregister(struct neigh_parms *p)
......@@ -1679,6 +1701,7 @@ void neigh_sysctl_unregister(struct neigh_parms *p)
struct neigh_sysctl_table *t = p->sysctl_table;
p->sysctl_table = NULL;
unregister_sysctl_table(t->sysctl_header);
kfree(t->neigh_dev[0].procname);
kfree(t);
}
}
......
......@@ -33,6 +33,19 @@ extern int sysctl_optmem_max;
extern char sysctl_divert_version[];
#endif /* CONFIG_NET_DIVERT */
/*
* This strdup() is used for creating copies of network
* device names to be handed over to sysctl.
*/
char *net_sysctl_strdup(const char *s)
{
char *rv = kmalloc(strlen(s)+1, GFP_KERNEL);
if (rv)
strcpy(rv, s);
return rv;
}
ctl_table core_table[] = {
#ifdef CONFIG_NET
{
......@@ -162,4 +175,7 @@ ctl_table core_table[] = {
#endif /* CONFIG_NET */
{ .ctl_name = 0 }
};
EXPORT_SYMBOL(net_sysctl_strdup);
#endif
......@@ -905,6 +905,14 @@ static int inetdev_event(struct notifier_block *this, unsigned long event,
* not interesting to applications using netlink.
*/
inetdev_changename(dev, in_dev);
#ifdef CONFIG_SYSCTL
devinet_sysctl_unregister(&in_dev->cnf);
neigh_sysctl_unregister(in_dev->arp_parms);
neigh_sysctl_register(dev, in_dev->arp_parms, NET_IPV4,
NET_IPV4_NEIGH, "ipv4");
devinet_sysctl_register(in_dev, &in_dev->cnf);
#endif
break;
}
out:
......@@ -1302,6 +1310,7 @@ static void devinet_sysctl_register(struct in_device *in_dev,
int i;
struct net_device *dev = in_dev ? in_dev->dev : NULL;
struct devinet_sysctl_table *t = kmalloc(sizeof(*t), GFP_KERNEL);
char *dev_name = NULL;
if (!t)
return;
......@@ -1310,13 +1319,25 @@ static void devinet_sysctl_register(struct in_device *in_dev,
t->devinet_vars[i].data += (char *)p - (char *)&ipv4_devconf;
t->devinet_vars[i].de = NULL;
}
if (dev) {
t->devinet_dev[0].procname = dev->name;
dev_name = dev->name;
t->devinet_dev[0].ctl_name = dev->ifindex;
} else {
t->devinet_dev[0].procname = "default";
dev_name = "default";
t->devinet_dev[0].ctl_name = NET_PROTO_CONF_DEFAULT;
}
/*
* Make a copy of dev_name, because '.procname' is regarded as const
* by sysctl and we wouldn't want anyone to change it under our feet
* (see SIOCSIFNAME).
*/
dev_name = net_sysctl_strdup(dev_name);
if (!dev_name)
goto free;
t->devinet_dev[0].procname = dev_name;
t->devinet_dev[0].child = t->devinet_vars;
t->devinet_dev[0].de = NULL;
t->devinet_conf_dir[0].child = t->devinet_dev;
......@@ -1328,9 +1349,17 @@ static void devinet_sysctl_register(struct in_device *in_dev,
t->sysctl_header = register_sysctl_table(t->devinet_root_dir, 0);
if (!t->sysctl_header)
kfree(t);
else
p->sysctl = t;
goto free_procname;
p->sysctl = t;
return;
/* error path */
free_procname:
kfree(dev_name);
free:
kfree(t);
return;
}
static void devinet_sysctl_unregister(struct ipv4_devconf *p)
......@@ -1339,6 +1368,7 @@ static void devinet_sysctl_unregister(struct ipv4_devconf *p)
struct devinet_sysctl_table *t = p->sysctl;
p->sysctl = NULL;
unregister_sysctl_table(t->sysctl_header);
kfree(t->devinet_dev[0].procname);
kfree(t);
}
}
......
......@@ -1880,6 +1880,14 @@ int addrconf_notify(struct notifier_block *this, unsigned long event,
break;
case NETDEV_CHANGE:
break;
case NETDEV_CHANGENAME:
#ifdef CONFIG_SYSCTL
addrconf_sysctl_unregister(&idev->cnf);
neigh_sysctl_unregister(idev->nd_parms);
neigh_sysctl_register(dev, idev->nd_parms, NET_IPV6, NET_IPV6_NEIGH, "ipv6");
addrconf_sysctl_register(idev, &idev->cnf);
#endif
break;
};
return NOTIFY_OK;
......@@ -3037,6 +3045,7 @@ static void addrconf_sysctl_register(struct inet6_dev *idev, struct ipv6_devconf
int i;
struct net_device *dev = idev ? idev->dev : NULL;
struct addrconf_sysctl_table *t;
char *dev_name = NULL;
t = kmalloc(sizeof(*t), GFP_KERNEL);
if (t == NULL)
......@@ -3048,12 +3057,24 @@ static void addrconf_sysctl_register(struct inet6_dev *idev, struct ipv6_devconf
t->addrconf_vars[i].extra1 = idev; /* embedded; no ref */
}
if (dev) {
t->addrconf_dev[0].procname = dev->name;
dev_name = dev->name;
t->addrconf_dev[0].ctl_name = dev->ifindex;
} else {
t->addrconf_dev[0].procname = "default";
dev_name = "default";
t->addrconf_dev[0].ctl_name = NET_PROTO_CONF_DEFAULT;
}
/*
* Make a copy of dev_name, because '.procname' is regarded as const
* by sysctl and we wouldn't want anyone to change it under our feet
* (see SIOCSIFNAME).
*/
dev_name = net_sysctl_strdup(dev_name);
if (!dev_name)
goto free;
t->addrconf_dev[0].procname = dev_name;
t->addrconf_dev[0].child = t->addrconf_vars;
t->addrconf_dev[0].de = NULL;
t->addrconf_conf_dir[0].child = t->addrconf_dev;
......@@ -3065,9 +3086,18 @@ static void addrconf_sysctl_register(struct inet6_dev *idev, struct ipv6_devconf
t->sysctl_header = register_sysctl_table(t->addrconf_root_dir, 0);
if (t->sysctl_header == NULL)
kfree(t);
goto free_procname;
else
p->sysctl = t;
return;
/* error path */
free_procname:
kfree(dev_name);
free:
kfree(t);
return;
}
static void addrconf_sysctl_unregister(struct ipv6_devconf *p)
......@@ -3076,6 +3106,7 @@ static void addrconf_sysctl_unregister(struct ipv6_devconf *p)
struct addrconf_sysctl_table *t = p->sysctl;
p->sysctl = NULL;
unregister_sysctl_table(t->sysctl_header);
kfree(t->addrconf_dev[0].procname);
kfree(t);
}
}
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment