From f2bf7504d68104575b243aa2e1ba2f819d61ef53 Mon Sep 17 00:00:00 2001 From: Dan Aloni Date: Mon, 20 Oct 2003 08:59:22 -0700 Subject: [PATCH] [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. --- include/linux/netdevice.h | 3 +++ net/core/neighbour.c | 29 ++++++++++++++++++++++++--- net/core/sysctl_net_core.c | 16 +++++++++++++++ net/ipv4/devinet.c | 40 +++++++++++++++++++++++++++++++++----- net/ipv6/addrconf.c | 37 ++++++++++++++++++++++++++++++++--- 5 files changed, 114 insertions(+), 11 deletions(-) diff --git a/include/linux/netdevice.h b/include/linux/netdevice.h index cf00126c50edc..2533f7b9c7017 100644 --- a/include/linux/netdevice.h +++ b/include/linux/netdevice.h @@ -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__ */ diff --git a/net/core/neighbour.c b/net/core/neighbour.c index 41c51e7e545ec..47ff1bd0a22e6 100644 --- a/net/core/neighbour.c +++ b/net/core/neighbour.c @@ -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); } } diff --git a/net/core/sysctl_net_core.c b/net/core/sysctl_net_core.c index 9ea7856f9fbcc..d8309e876d3b5 100644 --- a/net/core/sysctl_net_core.c +++ b/net/core/sysctl_net_core.c @@ -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 diff --git a/net/ipv4/devinet.c b/net/ipv4/devinet.c index 9e6ff99ca9eda..fe58fb5b59da7 100644 --- a/net/ipv4/devinet.c +++ b/net/ipv4/devinet.c @@ -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); } } diff --git a/net/ipv6/addrconf.c b/net/ipv6/addrconf.c index cd37719491af6..8bde9b397402b 100644 --- a/net/ipv6/addrconf.c +++ b/net/ipv6/addrconf.c @@ -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); } } -- 2.53.0