aboutsummaryrefslogtreecommitdiffstats
path: root/net/ipv6/addrconf.c
diff options
context:
space:
mode:
authorFrancesco Ruggeri <fruggeri@aristanetworks.com>2012-01-16 05:40:10 -0500
committerDavid S. Miller <davem@davemloft.net>2012-01-18 16:38:34 -0500
commit013d97e9da1877f1334aa8ff3a19921ebbfe99b5 (patch)
treeed05c2420ea75eb036507d556d46d713ebe3bd9a /net/ipv6/addrconf.c
parent35d87e3325e91ec6bc446bf71f50eccc9a7b3b8f (diff)
net: race condition in ipv6 forwarding and disable_ipv6 parameters
There is a race condition in addrconf_sysctl_forward() and addrconf_sysctl_disable(). These functions change idev->cnf.forwarding (resp. idev->cnf.disable_ipv6) and then try to grab the rtnl lock before performing any actions. If that fails they restore the original value and restart the syscall. This creates race conditions if ipv6 code tries to access these parameters, or if multiple instances try to do the same operation. As an example of the former, if __ipv6_ifa_notify() finds a 0 in idev->cnf.forwarding when invoked by addrconf_ifdown() it may not free anycast addresses, ultimately resulting in the net_device not being freed. This patch reads the user parameters into a temporary location and only writes the actual parameters when the rtnl lock is acquired. Tested in 2.6.38.8. Signed-off-by: Francesco Ruggeri <fruggeri@aristanetworks.com> Signed-off-by: David S. Miller <davem@davemloft.net>
Diffstat (limited to 'net/ipv6/addrconf.c')
-rw-r--r--net/ipv6/addrconf.c61
1 files changed, 40 insertions, 21 deletions
diff --git a/net/ipv6/addrconf.c b/net/ipv6/addrconf.c
index a225d5ee3c2f..c02280a4d126 100644
--- a/net/ipv6/addrconf.c
+++ b/net/ipv6/addrconf.c
@@ -502,29 +502,31 @@ static void addrconf_forward_change(struct net *net, __s32 newf)
502 rcu_read_unlock(); 502 rcu_read_unlock();
503} 503}
504 504
505static int addrconf_fixup_forwarding(struct ctl_table *table, int *p, int old) 505static int addrconf_fixup_forwarding(struct ctl_table *table, int *p, int newf)
506{ 506{
507 struct net *net; 507 struct net *net;
508 int old;
509
510 if (!rtnl_trylock())
511 return restart_syscall();
508 512
509 net = (struct net *)table->extra2; 513 net = (struct net *)table->extra2;
510 if (p == &net->ipv6.devconf_dflt->forwarding) 514 old = *p;
511 return 0; 515 *p = newf;
512 516
513 if (!rtnl_trylock()) { 517 if (p == &net->ipv6.devconf_dflt->forwarding) {
514 /* Restore the original values before restarting */ 518 rtnl_unlock();
515 *p = old; 519 return 0;
516 return restart_syscall();
517 } 520 }
518 521
519 if (p == &net->ipv6.devconf_all->forwarding) { 522 if (p == &net->ipv6.devconf_all->forwarding) {
520 __s32 newf = net->ipv6.devconf_all->forwarding;
521 net->ipv6.devconf_dflt->forwarding = newf; 523 net->ipv6.devconf_dflt->forwarding = newf;
522 addrconf_forward_change(net, newf); 524 addrconf_forward_change(net, newf);
523 } else if ((!*p) ^ (!old)) 525 } else if ((!newf) ^ (!old))
524 dev_forward_change((struct inet6_dev *)table->extra1); 526 dev_forward_change((struct inet6_dev *)table->extra1);
525 rtnl_unlock(); 527 rtnl_unlock();
526 528
527 if (*p) 529 if (newf)
528 rt6_purge_dflt_routers(net); 530 rt6_purge_dflt_routers(net);
529 return 1; 531 return 1;
530} 532}
@@ -4260,9 +4262,17 @@ int addrconf_sysctl_forward(ctl_table *ctl, int write,
4260 int *valp = ctl->data; 4262 int *valp = ctl->data;
4261 int val = *valp; 4263 int val = *valp;
4262 loff_t pos = *ppos; 4264 loff_t pos = *ppos;
4265 ctl_table lctl;
4263 int ret; 4266 int ret;
4264 4267
4265 ret = proc_dointvec(ctl, write, buffer, lenp, ppos); 4268 /*
4269 * ctl->data points to idev->cnf.forwarding, we should
4270 * not modify it until we get the rtnl lock.
4271 */
4272 lctl = *ctl;
4273 lctl.data = &val;
4274
4275 ret = proc_dointvec(&lctl, write, buffer, lenp, ppos);
4266 4276
4267 if (write) 4277 if (write)
4268 ret = addrconf_fixup_forwarding(ctl, valp, val); 4278 ret = addrconf_fixup_forwarding(ctl, valp, val);
@@ -4300,26 +4310,27 @@ static void addrconf_disable_change(struct net *net, __s32 newf)
4300 rcu_read_unlock(); 4310 rcu_read_unlock();
4301} 4311}
4302 4312
4303static int addrconf_disable_ipv6(struct ctl_table *table, int *p, int old) 4313static int addrconf_disable_ipv6(struct ctl_table *table, int *p, int newf)
4304{ 4314{
4305 struct net *net; 4315 struct net *net;
4316 int old;
4317
4318 if (!rtnl_trylock())
4319 return restart_syscall();
4306 4320
4307 net = (struct net *)table->extra2; 4321 net = (struct net *)table->extra2;
4322 old = *p;
4323 *p = newf;
4308 4324
4309 if (p == &net->ipv6.devconf_dflt->disable_ipv6) 4325 if (p == &net->ipv6.devconf_dflt->disable_ipv6) {
4326 rtnl_unlock();
4310 return 0; 4327 return 0;
4311
4312 if (!rtnl_trylock()) {
4313 /* Restore the original values before restarting */
4314 *p = old;
4315 return restart_syscall();
4316 } 4328 }
4317 4329
4318 if (p == &net->ipv6.devconf_all->disable_ipv6) { 4330 if (p == &net->ipv6.devconf_all->disable_ipv6) {
4319 __s32 newf = net->ipv6.devconf_all->disable_ipv6;
4320 net->ipv6.devconf_dflt->disable_ipv6 = newf; 4331 net->ipv6.devconf_dflt->disable_ipv6 = newf;
4321 addrconf_disable_change(net, newf); 4332 addrconf_disable_change(net, newf);
4322 } else if ((!*p) ^ (!old)) 4333 } else if ((!newf) ^ (!old))
4323 dev_disable_change((struct inet6_dev *)table->extra1); 4334 dev_disable_change((struct inet6_dev *)table->extra1);
4324 4335
4325 rtnl_unlock(); 4336 rtnl_unlock();
@@ -4333,9 +4344,17 @@ int addrconf_sysctl_disable(ctl_table *ctl, int write,
4333 int *valp = ctl->data; 4344 int *valp = ctl->data;
4334 int val = *valp; 4345 int val = *valp;
4335 loff_t pos = *ppos; 4346 loff_t pos = *ppos;
4347 ctl_table lctl;
4336 int ret; 4348 int ret;
4337 4349
4338 ret = proc_dointvec(ctl, write, buffer, lenp, ppos); 4350 /*
4351 * ctl->data points to idev->cnf.disable_ipv6, we should
4352 * not modify it until we get the rtnl lock.
4353 */
4354 lctl = *ctl;
4355 lctl.data = &val;
4356
4357 ret = proc_dointvec(&lctl, write, buffer, lenp, ppos);
4339 4358
4340 if (write) 4359 if (write)
4341 ret = addrconf_disable_ipv6(ctl, valp, val); 4360 ret = addrconf_disable_ipv6(ctl, valp, val);