tun: Cleanup error handling in tun_set_iff()
Subsystems (3)
RFC PATCH 1/1 tun: Cleanup error handling in tun_set_iff()
Convert some pointless gotos into returns and ensure tun_attach() errors are handled correctly.
--
I'm sending this as an RFC patch because I'm not entirely certain that the changes to the tun_attach() error handling code are 100% correct, although I strongly suspect that the current behavor of simply returning an error code without any cleanup is broken. Can anyone comment on this?
---
drivers/net/tun.c | 19 ++++++++++--------- 1 files changed, 10 insertions(+), 9 deletions(-)
drivers/net/tun.c +10 -9
@@ -938,13 +938,10 @@ static int tun_set_iff(struct net *net, struct file *file, struct ifreq *ifr) err =tun_attach (tun, file);if (err <0 )return err; - } -else { + }else {char *name;unsigned long flags =0 ; - err = -EINVAL ; -if (!capable (CAP_NET_ADMIN ))return -EPERM ;
@@ -958,7 +955,7 @@ static int tun_set_iff(struct net *net, struct file *file, struct ifreq *ifr) flags |=TUN_TAP_DEV ; name ="tap%d" ; }else - goto failed; +return -EINVAL ;if (*ifr->ifr_name) name = ifr->ifr_name;
@@ -976,10 +973,11 @@ static int tun_set_iff(struct net *net, struct file *file, struct ifreq *ifr) tun->flags = flags; tun->txflt.count =0 ; - err = -ENOMEM ; sk =sk_alloc (net,AF_UNSPEC ,GFP_KERNEL , &tun_proto); -if (!sk) +if (!sk) { + err = -ENOMEM ; goto err_free_dev; + }init_waitqueue_head (&tun->socket.wait);sock_init_data (&tun->socket, sk);
@@ -1010,7 +1008,7 @@ static int tun_set_iff(struct net *net, struct file *file, struct ifreq *ifr) err =tun_attach (tun, file);if (err <0 ) - goto failed; + goto err_unreg_dev; }DBG (KERN_INFO "%s: tun_set_iff\n" , tun->dev->name);
@@ -1039,11 +1037,14 @@ static int tun_set_iff(struct net *net, struct file *file, struct ifreq *ifr)strcpy (ifr->ifr_name, tun->dev->name);return 0 ; + err_unreg_dev: +rtnl_lock (); +unregister_netdevice (tun->dev); +rtnl_unlock (); err_free_sk:sock_put (sk); err_free_dev:free_netdev (dev); - failed:return err; }
David Miller
maintainerFrom: Paul Moore <[email protected]>
Date: Mon, 03 Aug 2009 12:12:43 -0400
Quoted 11 lines
Convert some pointless gotos into returns and ensure
tun_attach()errors are handled correctly.Signed-off-by Paul Moore <[email protected]>--
I'm sending this as an RFC patch because I'm not entirely certain that the changes to the
tun_attach()error handling code are 100% correct, although I strongly suspect that the current behavor of simply returning an error code without any cleanup is broken. Can anyone comment on this?
It looks good to me.
But I've been wrong about this code before, so it would be nice to get some other eyes on it :-)
Quoted 4 lines · Paul Moore
Convert some pointless gotos into returns and ensure
tun_attach()errors are handled correctly.Signed-off-by Paul Moore <[email protected]>
This patch appears slightly wrong. Comments inline.
Quoted 29 lines
I'm sending this as an RFC patch because I'm not entirely certain that the changes to the
tun_attach()error handling code are 100% correct, although I strongly suspect that the current behavor of simply returning an error code without any cleanup is broken. Can anyone comment on this?
---drivers/net/tun.c | 19 ++++++++++--------- 1 files changed, 10 insertions(+), 9 deletions(-)drivers/net/tun.c +1
-4@@ -938,13 +938,10 @@ static int tun_set_iff(struct net *net, struct file *file, struct ifreq *ifr) err =tun_attach (tun, file);if (err <0 )return err; - } -else { + }else {char *name;unsigned long flags =0 ; - err = -EINVAL ; -if (!capable (CAP_NET_ADMIN ))return -EPERM ;
drivers/net/tun.c static int tun_set_iff(struct net *net, struct file *file, struct ifreq *ifr) · line 958
Quoted 5 lines
drivers/net/tun.c +1
-1@@ -958,7 +955,7 @@ static int tun_set_iff(struct net *net, struct file *file, struct ifreq *ifr) flags |=TUN_TAP_DEV ; name ="tap%d" ; }else - goto failed; +return -EINVAL ;
Trivially correct.
Quoted 4 lines
drivers/net/tun.c +0
-0if (*ifr->ifr_name) name = ifr->ifr_name;
drivers/net/tun.c static int tun_set_iff(struct net *net, struct file *file, struct ifreq *ifr) · line 980
Quoted 10 lines
drivers/net/tun.c +3
-2@@ -976,10 +973,11 @@ static int tun_set_iff(struct net *net, struct file *file, struct ifreq *ifr) tun->flags = flags; tun->txflt.count =0 ; - err = -ENOMEM ; sk =sk_alloc (net,AF_UNSPEC ,GFP_KERNEL , &tun_proto); -if (!sk) +if (!sk) { + err = -ENOMEM ; goto err_free_dev; + }
Trivially correct but I would argue uglier.
drivers/net/tun.c static int tun_set_iff(struct net *net, struct file *file, struct ifreq *ifr) · line 1012
Quoted 10 lines
drivers/net/tun.c +1
-1init_waitqueue_head (&tun->socket.wait);sock_init_data (&tun->socket, sk);@@ -1010,7 +1008,7 @@ static int tun_set_iff(struct net *net, struct file *file, struct ifreq *ifr) err =tun_attach (tun, file);if (err <0 ) - goto failed; + goto err_unreg_dev; }
This is the interesting one. And several problems with it. When Herbert reworked the reference counting here he introduced the goto failed; Instead of err_free_dev.
I think there were more possible races when I introduced the check of the return code of tun_attach, the only case I can see currently is if the file was attached to another tun device before we grabbed the rtnl_lock.
drivers/net/tun.c static int tun_set_iff(struct net *net, struct file *file, struct ifreq *ifr) · line 1043
Quoted 9 lines
drivers/net/tun.c +4
-0DBG (KERN_INFO "%s: tun_set_iff\n" , tun->dev->name);@@ -1039,11 +1037,14 @@ static int tun_set_iff(struct net *net, struct file *file, struct ifreq *ifr)strcpy (ifr->ifr_name, tun->dev->name);return 0 ; + err_unreg_dev: +rtnl_lock (); +unregister_netdevice (tun->dev); +rtnl_unlock ();
This is a guaranteed deadlock. We already hold the rtnl_lock here. Furthermore all I should need to do in this case is to callunregister_netdevice(...). As all of the rest of the pieces should
happen in the cleanup routines called from unregister_netdevice.
The current behavior of returning an error code is a little bit off as it creates a persistent tun device without setting tun->flags | TUN_PERSIST. And it leaves a tun device for someone else to clean up.
At the same time it should be perfectly legitimate for someone else to come along and attach to the tun device and delete it or to call ip link del <tun>
Eric
Quoted 13 lines
drivers/net/tun.c +0
-2err_free_sk:sock_put (sk); err_free_dev:free_netdev (dev); - failed:return err; } --To unsubscribe from this list: send the line "unsubscribe netdev" in the body of a message to [email protected]
More majordomo info at http://vger.kernel.org/majordomo-info.html
Quoted 7 lines · Eric W. Biederman
Quoted 4 lines · Paul Moore
Convert some pointless gotos into returns and ensure
tun_attach()errors are handled correctly.Signed-off-by Paul Moore <[email protected]>This patch appears slightly wrong. Comments inline.
Thanks for taking a look ...
Quoted 17 lines
Quoted 17 lines
I'm sending this as an RFC patch because I'm not entirely certain that the changes to the
tun_attach()error handling code are 100% correct, although I strongly suspect that the current behavor of simply returning an error code without any cleanup is broken. Can anyone comment on this?
---drivers/net/tun.c | 19 ++++++++++--------- 1 files changed, 10 insertions(+), 9 deletions(-)drivers/net/tun.c +0
-0@@ -976,10 +973,11 @@ static int tun_set_iff(struct net *net, struct file *file, struct ifreq *ifr) tun->flags = flags; tun->txflt.count =0 ;
Quoted 9 lines
drivers/net/tun.c static int tun_set_iff(struct net *net, struct file *file, struct ifreq *ifr) · line 980
Quoted 7 lines
drivers/net/tun.c +3
-2- err = -ENOMEM ; sk =sk_alloc (net,AF_UNSPEC ,GFP_KERNEL , &tun_proto); -if (!sk) +if (!sk) { + err = -ENOMEM ; goto err_free_dev; + }Trivially correct but I would argue uglier.
My reasoning behind the change was that code related to the error handling should be moved outside the common path as much as possible similar to what we do now with the gotos.
Quoted 17 lines
drivers/net/tun.c static int tun_set_iff(struct net *net, struct file · line 1013
Quoted 8 lines
drivers/net/tun.c +1
-1@@ -1010,7 +1008,7 @@ static int tun_set_iff(struct net *net, struct file *file, struct ifreq *ifr) err =tun_attach (tun, file);if (err <0 ) - goto failed; + goto err_unreg_dev; }This is the interesting one. And several problems with it. When Herbert reworked the reference counting here he introduced the goto failed; Instead of
err_free_dev.I think there were more possible races when I introduced the check of the return code of
tun_attach, the only case I can see currently is if the file was attached to another tun device before we grabbed thertnl_lock.
...
Quoted 4 lines
Quoted 4 lines
drivers/net/tun.c +0
-0@@ -1039,11 +1037,14 @@ static int tun_set_iff(struct net *net, struct file *file, struct ifreq *ifr) strcpy(ifr->ifr_name, tun->dev->name);return 0 ;
Quoted 9 lines
drivers/net/tun.c static int tun_set_iff(struct net *net, struct file *file, struct ifreq *ifr) · line 1043
Quoted 4 lines
drivers/net/tun.c +4
-0+ err_unreg_dev: +rtnl_lock (); +unregister_netdevice (tun->dev); +rtnl_unlock ();This is a guaranteed deadlock. We already hold the
rtnl_lockhere. Furthermore all I should need to do in this case is to callunregister_netdevice(...). As all of the rest of the pieces should
happen in the cleanup routines called fromunregister_netdevice.
Okay, so at the very least the rtnl_[un]lock() calls need to be removed and we can safely return after calling unregister_netdevice() without having to free/release anything else. The thing that concerns me the most are the potential races you talked about.
I'll admit there are many dark places here that I still don't understand but looking at the code it appears that the only way to get into tun_set_iff() is if the file is not currently attached to a TUN device which is further checked in tun_attach(). Also, I'm not sure what refcounting you are talking about - do you mean the tun_file->count refcount? I think we should be okay in this regard as the only way we would end up changing tun_file->count is if tun_attach() completed successfully; if tun_attach() fails there is not change in the refcount. I could be way off here but it _seems_ safe ... :)
Quoted 7 lines
The current behavior of returning an error code is a little bit off as it creates a persistent tun device without setting
tun->flags|TUN_PERSIST. And it leaves a tun device for someone else to clean up.At the same time it should be perfectly legitimate for someone else to come along and attach to the tun device and delete it or to call ip link del <tun>
My concern is that I believe that if the kernel fails at an operation it should do all it can to unwind any actions it took in the course of attempting to perform the requested operation. Leaving a TUN device around in the case of a tun_attach() failure seems like the kernel is being lazy, sure a user can cleanup the device but why should they have to?
Signature · 2 lines
paul moore linux @ hp
Quoted 38 lines · Paul Moore
Quoted 7 lines · Eric W. Biederman
Quoted 4 lines · Paul Moore
Convert some pointless gotos into returns and ensure
tun_attach()errors are handled correctly.Signed-off-by Paul Moore <[email protected]>This patch appears slightly wrong. Comments inline.
Thanks for taking a look ...
Quoted 17 lines
Quoted 17 lines
I'm sending this as an RFC patch because I'm not entirely certain that the changes to the
tun_attach()error handling code are 100% correct, although I strongly suspect that the current behavor of simply returning an error code without any cleanup is broken. Can anyone comment on this?
---drivers/net/tun.c | 19 ++++++++++--------- 1 files changed, 10 insertions(+), 9 deletions(-)drivers/net/tun.c +0
-0@@ -976,10 +973,11 @@ static int tun_set_iff(struct net *net, struct file *file, struct ifreq *ifr) tun->flags = flags; tun->txflt.count =0 ;Quoted 9 lines
drivers/net/tun.c static int tun_set_iff(struct net *net, struct file *file, struct ifreq *ifr) · line 980
Quoted 7 lines
drivers/net/tun.c +3
-2- err = -ENOMEM ; sk =sk_alloc (net,AF_UNSPEC ,GFP_KERNEL , &tun_proto); -if (!sk) +if (!sk) { + err = -ENOMEM ; goto err_free_dev; + }Trivially correct but I would argue uglier.
Quoted 3 lines
My reasoning behind the change was that code related to the error handling should be moved outside the common path as much as possible similar to what we do now with the gotos.
I don't understand. Generating less readable and less efficient code is preferable?
Quoted 39 lines
Quoted 17 lines
drivers/net/tun.c static int tun_set_iff(struct net *net, struct file · line 1013
Quoted 8 lines
drivers/net/tun.c +1
-1@@ -1010,7 +1008,7 @@ static int tun_set_iff(struct net *net, struct file *file, struct ifreq *ifr) err =tun_attach (tun, file);if (err <0 ) - goto failed; + goto err_unreg_dev; }This is the interesting one. And several problems with it. When Herbert reworked the reference counting here he introduced the goto failed; Instead of
err_free_dev.I think there were more possible races when I introduced the check of the return code of
tun_attach, the only case I can see currently is if the file was attached to another tun device before we grabbed thertnl_lock....
Quoted 4 lines
Quoted 4 lines
drivers/net/tun.c +0
-0@@ -1039,11 +1037,14 @@ static int tun_set_iff(struct net *net, struct file *file, struct ifreq *ifr) strcpy(ifr->ifr_name, tun->dev->name);return 0 ;Quoted 9 lines
drivers/net/tun.c static int tun_set_iff(struct net *net, struct file *file, struct ifreq *ifr) · line 1043
Quoted 4 lines
drivers/net/tun.c +4
-0+ err_unreg_dev: +rtnl_lock (); +unregister_netdevice (tun->dev); +rtnl_unlock ();This is a guaranteed deadlock. We already hold the
rtnl_lockhere. Furthermore all I should need to do in this case is to callunregister_netdevice(...). As all of the rest of the pieces should
happen in the cleanup routines called fromunregister_netdevice.Okay, so at the very least the rtnl_[un]lock() calls need to be removed and we can safely return after calling
unregister_netdevice()without having to free/release anything else. The thing that concerns me the most are the potential races you talked about.
Quoted 4 lines
I'll admit there are many dark places here that I still don't understand but looking at the code it appears that the only way to get into
tun_set_iff()is if the file is not currently attached to a TUN device which is further checked intun_attach().
Yes. But how many times can you come into tun_set_iff on the same file? Think multiple threads trying to do the same thing at the same time.
Quoted 5 lines
Also, I'm not sure what refcounting you are talking about -
do you mean thetun_file->countrefcount? I think we should be okay in this regard as the only way we would end up changingtun_file->countis iftun_attach()completed successfully; iftun_attach()fails there is not change in the refcount. I could be way off here but it _seems_ safe ... :)
I totally agree that things are not as obvious as they should be. I made things more complex when I added support for ip link del. Then Herbert's patches collided and broke things when he added the socket support. Herbert change the refcount scheme that made things simpler and easier to get correct.
Things are mostly good now, but think the tun code could use good audit, cleanup, simplification. Which takes advantage of everything that Herbert did. All of which requires someone to spend enough time looking at the code to understand what is going on. Not hard but a bit time consuming.
Quoted 8 lines
Quoted 7 lines
The current behavior of returning an error code is a little bit off as it creates a persistent tun device without setting
tun->flags|TUN_PERSIST. And it leaves a tun device for someone else to clean up.At the same time it should be perfectly legitimate for someone else to come along and attach to the tun device and delete it or to call ip link del <tun>
Quoted 5 lines
My concern is that I believe that if the kernel fails at an operation it should do all it can to unwind any actions it took in the course of attempting to perform the requested operation. Leaving a TUN device around in the case of a
tun_attach()failure seems like the kernel is being lazy, sure a user can cleanup the device but why should they have to?
Sure. I am all for that and that is how I originally coded it. My point is that this is not harmful and that it can really only be triggered
by a buggy or hostile user. So it isn't critical to get fixed. It is an
imperfect so a fix is desirable.
Eric
Quoted 21 lines · Eric W. Biederman
Quoted 20 lines · Paul Moore
Quoted 18 lines · Eric W. Biederman
Quoted 5 lines · Paul Moore
drivers/net/tun.c +0
-0drivers/net/tun.c static int tun_set_iff(struct net *net, struct · line 980
Quoted 10 lines
drivers/net/tun.c +3
-2@@ -976,10 +973,11 @@ static int tun_set_iff(struct net *net, struct file *file, struct ifreq *ifr) tun->flags = flags; tun->txflt.count =0 ; - err = -ENOMEM ; sk =sk_alloc (net,AF_UNSPEC ,GFP_KERNEL , &tun_proto); -if (!sk) +if (!sk) { + err = -ENOMEM ; goto err_free_dev; + }Trivially correct but I would argue uglier.
Quoted 6 lines
Quoted 3 lines
My reasoning behind the change was that code related to the error handling should be moved outside the common path as much as possible similar to what we do now with the gotos.
I don't understand. Generating less readable and less efficient code is preferable?
While we can probably debate the "readability" of code all day long and get no where (anyone care to argue about the color of the bikeshed?) the concept of code efficiency should be a bit easier to quantify. I'll admit that I'm far from a performance expert but here is my reasoning ...
The code currently looks something like this:
err = -ENOMEM; buf = alloc(...); if (!buf) goto label;
This means that in the common case where 'alloc()' completes without error we are doing an extra, unnecessary assignment where we set the value in 'err'. Now, if we change this slightly to match what I proposed in the patch:
buf = alloc(...);
if (!buf) {
err = -ENOMEM;
goto label;
}We eliminate that extra assignment in the case where 'alloc()' completes without error, which should result in more efficient code (less instructions in the common case). Am I wrong? If that is the case I would appreciate an explanation ...
Signature · 2 lines
paul moore linux @ hp
Quoted 18 lines · Paul Moore
The code currently looks something like this:
err = -ENOMEM; buf = alloc(...); if (!buf) goto label;This means that in the common case where '
alloc()' completes without error we are doing an extra, unnecessary assignment where we set the value in 'err'. Now, if we change this slightly to match what I proposed in the patch:buf = alloc(...); if (!buf) { err = -ENOMEM; goto label; }
Quoted 4 lines
We eliminate that extra assignment in the case where '
alloc()' completes without error, which should result in more efficient code (less instructions in the common case). Am I wrong? If that is the case I would appreciate an explanation ...
Your style potentially introduces a second jump which may end up being worse compared to the extra work on a modern CPU.
Cheers,
Signature · 4 lines
Visit Openswan at http://www.openswan.org/
Email: Herbert Xu ~{PmV>HI~} <[email protected]>
Home Page: http://gondor.apana.org.au/~herbert/
PGP Key: http://gondor.apana.org.au/~herbert/pubkey.txtQuoted 19 lines · Herbert Xu
Quoted 18 lines · Paul Moore
The code currently looks something like this:
err = -ENOMEM; buf = alloc(...); if (!buf) goto label;This means that in the common case where '
alloc()' completes without error we are doing an extra, unnecessary assignment where we set the value in 'err'. Now, if we change this slightly to match what I proposed in the patch:buf = alloc(...); if (!buf) { err = -ENOMEM; goto label; }
Quoted 7 lines
Quoted 4 lines
We eliminate that extra assignment in the case where '
alloc()' completes without error, which should result in more efficient code (less instructions in the common case). Am I wrong? If that is the case I would appreciate an explanation ...Your style potentially introduces a second jump which may end up being worse compared to the extra work on a modern CPU.
Thanks, I hadn't thought of that possibility. I suppose the impact of a second jump is going to depend quite a bit on the hardware it runs on (pipeline depth, branch prediction, etc.) and isn't as easy to quantify as I had hoped. Oh well, I appreciate the explanation anyway :)
Signature · 2 lines
paul moore linux @ hp
Quoted 6 lines · Paul Moore
My concern is that I believe that if the kernel fails at an operation it should do all it can to unwind any actions it took in the course of attempting to perform the requested operation. Leaving a TUN device around in the case of a
tun_attach()failure seems like the kernel is being lazy, sure a user can cleanup the device but why should they have to?
That particular tun_attach should never fail. Perhaps you can just add a WARN_ON.
Cheers,
Signature · 4 lines
Visit Openswan at http://www.openswan.org/
Email: Herbert Xu ~{PmV>HI~} <[email protected]>
Home Page: http://gondor.apana.org.au/~herbert/
PGP Key: http://gondor.apana.org.au/~herbert/pubkey.txtQuoted 10 lines · Herbert Xu
Quoted 6 lines · Paul Moore
My concern is that I believe that if the kernel fails at an operation it should do all it can to unwind any actions it took in the course of attempting to perform the requested operation. Leaving a TUN device around in the case of a
tun_attach()failure seems like the kernel is being lazy, sure a user can cleanup the device but why should they have to?That particular
tun_attachshould never fail. Perhaps you can just add aWARN_ON.
Two threads one file descriptor. Both simultaneously attempt to attach to a tun device. One will fail, the other succeed.
At least that is how I read the locking.
Eric
Quoted 5 lines · Eric W. Biederman
Two threads one file descriptor. Both simultaneously attempt to attach to a tun device. One will fail, the other succeed.
At least that is how I read the locking.
Yes but the "race" fixed by this patch is centred on the tun_attach call for a newly created network device. As tun_set_iff occurs under RTNL, the second thread cannot start attaching until the creation thread has completed. IOW the thread that creates the net device should always succeed in attaching.
If two threads try to attach to the same device that was already created then yes one will fail and the other succeed. However, AFAICS that case has nothing to do with this patch.
Cheers,
Signature · 4 lines
Visit Openswan at http://www.openswan.org/
Email: Herbert Xu ~{PmV>HI~} <[email protected]>
Home Page: http://gondor.apana.org.au/~herbert/
PGP Key: http://gondor.apana.org.au/~herbert/pubkey.txtQuoted 14 lines · Herbert Xu
Quoted 5 lines · Eric W. Biederman
Two threads one file descriptor. Both simultaneously attempt to attach to a tun device. One will fail, the other succeed.
At least that is how I read the locking.
Yes but the "race" fixed by this patch is centred on the
tun_attachcall for a newly created network device. Astun_set_iffoccurs under RTNL, the second thread cannot start attaching until the creation thread has completed. IOW the thread that creates the net device should always succeed in attaching.
Quoted 3 lines
If two threads try to attach to the same device that was already created then yes one will fail and the other succeed. However, AFAICS that case has nothing to do with this patch.
Summarizing:
tun = __tun_get(tfile);
if (!tun) { // No tun we are not attached.
< -------------------- race opportunity
rtnl_lock();
tun_set_iff();
rtnl_unlock();
}...
We don't test if we are attached under the rtnl
until we get to tun_attach();
So two threads can both do:
tun = __tun_get(tfile);
if (!tun) {
rtnl_lock();
tun_set_iff();
dev = __dev_get_by_name(net, "not_an_interface_name");
if (!dev) {
dev = alloc_netdev(....);
...;
register_netdev(dev);
...;
err = tun_attach(..);
}Only one thread is in tun_set_iff() at a time, but the other thread could have attached the file to a device before the one in tun_attach().
Eric
Quoted 31 lines · Eric W. Biederman
Summarizing:
tun =
__tun_get(tfile);
if (!tun) { // No tun we are not attached.< -------------------- race opportunity rtnl_lock(); tun_set_iff(); rtnl_unlock(); }...
We don't test if we are attached under the rtnl
until we get totun_attach();So two threads can both do:
tun =
__tun_get(tfile);if (!tun) { rtnl_lock(); tun_set_iff(); dev = __dev_get_by_name(net, "not_an_interface_name"); if (!dev) { dev = alloc_netdev(....); ...; register_netdev(dev); ...; err = tun_attach(..); }
Quoted 2 lines
Only one thread is in
tun_set_iff()at a time, but the other thread could have attached the file to a device before the one intun_attach().
Right, I see what you mean. However I don't think this is possible because the ioctl runs under the big kernel lock.
Cheers,
Signature · 4 lines
Visit Openswan at http://www.openswan.org/
Email: Herbert Xu ~{PmV>HI~} <[email protected]>
Home Page: http://gondor.apana.org.au/~herbert/
PGP Key: http://gondor.apana.org.au/~herbert/pubkey.txtQuoted 32 lines · Herbert Xu
Quoted 31 lines · Eric W. Biederman
Summarizing:
tun =
__tun_get(tfile);
if (!tun) { // No tun we are not attached.< -------------------- race opportunity rtnl_lock(); tun_set_iff(); rtnl_unlock(); }...
We don't test if we are attached under the rtnl
until we get totun_attach();So two threads can both do:
tun =
__tun_get(tfile);if (!tun) { rtnl_lock(); tun_set_iff(); dev = __dev_get_by_name(net, "not_an_interface_name"); if (!dev) { dev = alloc_netdev(....); ...; register_netdev(dev); ...; err = tun_attach(..); }
Quoted 5 lines
Quoted 2 lines
Only one thread is in
tun_set_iff()at a time, but the other thread could have attached the file to a device before the one intun_attach().Right, I see what you mean. However I don't think this is possible because the ioctl runs under the big kernel lock.
Why not? We can sleep on that code path.
Although now that you mention it we should use unlocked_ioctl unless we actually need the BKL.
Eric
Quoted 35 lines · Eric W. Biederman
Quoted 34 lines · Herbert Xu
Quoted 32 lines · Eric W. Biederman
Summarizing:
tun =
__tun_get(tfile);
if (!tun) { // No tun we are not attached.< -------------------- race opportunity rtnl_lock(); tun_set_iff(); rtnl_unlock(); }...
We don't test if we are attached under the rtnl
until we get totun_attach();So two threads can both do:
tun =
__tun_get(tfile);if (!tun) { rtnl_lock(); tun_set_iff(); dev = __dev_get_by_name(net, "not_an_interface_name"); if (!dev) { dev = alloc_netdev(....); ...; register_netdev(dev); ...; err = tun_attach(..); }Only one thread is in
tun_set_iff()at a time, but the other thread could have attached the file to a device before the one intun_attach().
Quoted 6 lines
Quoted 2 lines
Right, I see what you mean. However I don't think this is possible because the ioctl runs under the big kernel lock.
Why not? We can sleep on that code path.
Although now that you mention it we should useunlocked_ioctlunless we actually need the BKL.
Dave, if you haven't already, it is probably a good idea to just forget about this patch. Prior to this discussion I suspected that the TUN driver could use a closer look, after reading the comments from Eric and Herbert there isn't much suspicion left. I'll put this on my rainy day todo list to try and tackle but I won't be upset if somebody beats me to it.
Signature · 2 lines
paul moore linux @ hp
David Miller
maintainerFrom: Paul Moore <[email protected]>
Date: Thu, 6 Aug 2009 14:09:19 -0400
Quoted 2 lines
Dave, if you haven't already, it is probably a good idea to just forget about this patch.
ok.
Quoted 4 lines · Eric W. Biederman
Why not? We can sleep on that code path.
Although now that you mention it we should useunlocked_ioctlunless we actually need the BKL.
You're right of course. So the race is real, but I think there is no good reason for such parallel operations to be allowed in the first place. So how about extending the coverage of the locked section to the whole ioctl, like this:
tun: Extend RTNL lock coverage over whole ioctl
As it is, parts of the ioctl runs under the RTNL and parts of it do not. The unlocked section is still protected by the BKL, but there can be subtle races. For example, Eric Biederman and Paul Moore observed that if two threads tried to create two tun devices on the same file descriptor, then unexpected results may occur.
As there isn't anything in the ioctl that is expected to sleep indefinitely, we can prevent this from occurring by extending the RTNL lock coverage.
This also allows to get rid of the BKL.
Finally, I changed tun_get_iff to take a tun device in order to avoid calling tun_put which would dead-lock as it also tries to take the RTNL lock.
drivers/net/tun.c +19 -31
@@ -1048,20 +1048,15 @@ static int tun_set_iff(struct net *net, struct file *file, struct ifreq *ifr)return err; } -static int tun_get_iff (struct net *net,struct file *file,struct ifreq *ifr) +static int tun_get_iff (struct net *net,struct tun_struct *tun, +struct ifreq *ifr) { -struct tun_struct *tun =tun_get (file); - -if (!tun) -return -EBADFD ; -DBG (KERN_INFO "%s: tun_get_iff\n" , tun->dev->name);strcpy (ifr->ifr_name, tun->dev->name); ifr->ifr_flags =tun_flags (tun); -tun_put (tun);return 0 ; }
@@ -1105,8 +1100,8 @@ static int set_offload(struct net_device *dev, unsigned long arg)return 0 ; } -static int tun_chr_ioctl (struct inode *inode,struct file *file, -unsigned int cmd,unsigned long arg) +static long tun_chr_ioctl (struct file *file,unsigned int cmd, +unsigned long arg) {struct tun_file *tfile = file->private_data;struct tun_struct *tun;
@@ -1128,34 +1123,32 @@ static int tun_chr_ioctl(struct inode *inode, struct file *file, (unsigned int __user*)argp); } +rtnl_lock (); + tun =__tun_get (tfile);if (cmd ==TUNSETIFF && !tun) { -int err; - ifr.ifr_name[IFNAMSIZ -1 ] ='\0' ; -rtnl_lock (); - err =tun_set_iff (tfile->net, file, &ifr); -rtnl_unlock (); + ret =tun_set_iff (tfile->net, file, &ifr); -if (err) -return err; +if (ret) + goto unlock;if (copy_to_user (argp, &ifr,sizeof (ifr))) -return -EFAULT ; -return 0 ; + ret = -EFAULT ; + goto unlock; } - + ret = -EBADFD ;if (!tun) -return -EBADFD ; + goto unlock;DBG (KERN_INFO "%s: tun_chr_ioctl cmd %d\n" , tun->dev->name, cmd); ret =0 ;switch (cmd) {case TUNGETIFF : - ret =tun_get_iff (current->nsproxy->net_ns, file, &ifr); + ret =tun_get_iff (current->nsproxy->net_ns, tun, &ifr);if (ret)break ;
@@ -1201,7 +1194,6 @@ static int tun_chr_ioctl(struct inode *inode, struct file *file,case TUNSETLINK :/* Only allow setting the type when the interface is down */ -rtnl_lock ();if (tun->dev->flags &IFF_UP ) {DBG (KERN_INFO "%s: Linktype set failed because interface is up\n" , tun->dev->name);
@@ -1211,7 +1203,6 @@ static int tun_chr_ioctl(struct inode *inode, struct file *file,DBG (KERN_INFO "%s: linktype set to %d\n" , tun->dev->name, tun->dev->type); ret =0 ; } -rtnl_unlock ();break ;#ifdef TUN_DEBUG
@@ -1220,9 +1211,7 @@ static int tun_chr_ioctl(struct inode *inode, struct file *file,break ;#endif case TUNSETOFFLOAD : -rtnl_lock (); ret =set_offload (tun->dev, arg); -rtnl_unlock ();break ;case TUNSETTXFILTER :
@@ -1230,9 +1219,7 @@ static int tun_chr_ioctl(struct inode *inode, struct file *file, ret = -EINVAL ;if ((tun->flags &TUN_TYPE_MASK ) !=TUN_TAP_DEV )break ; -rtnl_lock (); ret =update_filter (&tun->txflt, (void __user *)arg); -rtnl_unlock ();break ;case SIOCGIFHWADDR :
@@ -1248,9 +1235,7 @@ static int tun_chr_ioctl(struct inode *inode, struct file *file,DBG (KERN_DEBUG "%s: set hw address: %pM\n" , tun->dev->name, ifr.ifr_hwaddr.sa_data); -rtnl_lock (); ret =dev_set_mac_address (tun->dev, &ifr.ifr_hwaddr); -rtnl_unlock ();break ;case TUNGETSNDBUF :
@@ -1273,7 +1258,10 @@ static int tun_chr_ioctl(struct inode *inode, struct file *file,break ; }; -tun_put (tun); +unlock: +rtnl_unlock (); +if (tun) +tun_put (tun);return ret; }
@@ -1361,7 +1349,7 @@ static const struct file_operations tun_fops = { .write = do_sync_write, .aio_write = tun_chr_aio_write, .poll = tun_chr_poll, - .ioctl = tun_chr_ioctl, + .unlocked_ioctl = tun_chr_ioctl, .open = tun_chr_open, .release = tun_chr_close, .fasync = tun_chr_fasync
Cheers,
Signature · 4 lines
Visit Openswan at http://www.openswan.org/
Email: Herbert Xu ~{PmV>HI~} <[email protected]>
Home Page: http://gondor.apana.org.au/~herbert/
PGP Key: http://gondor.apana.org.au/~herbert/pubkey.txtDavid Miller
maintainerFrom: Herbert Xu <[email protected]>
Date: Fri, 7 Aug 2009 10:22:44 +1000
Quoted 13 lines
tun: Extend RTNL lock coverage over whole ioctl
As it is, parts of the ioctl runs under the RTNL and parts of it do not. The unlocked section is still protected by the BKL, but there can be subtle races. For example, Eric Biederman and Paul Moore observed that if two threads tried to create two tun devices on the same file descriptor, then unexpected results may occur.
As there isn't anything in the ioctl that is expected to sleep indefinitely, we can prevent this from occurring by extending the RTNL lock coverage.
Quoted 7 lines
This also allows to get rid of the BKL.
Finally, I changed
tun_get_iffto take a tun device in order to avoid callingtun_putwhich would dead-lockt as it also tries to take the RTNL lock.Signed-off-by Herbert Xu <[email protected]>
This looks good after a quick audit, Eric what say you?
Quoted 18 lines · David Miller
From: Herbert Xu <[email protected]>
Date: Fri, 7 Aug 2009 10:22:44 +1000Quoted 15 lines
tun: Extend RTNL lock coverage over whole ioctl
As it is, parts of the ioctl runs under the RTNL and parts of it do not. The unlocked section is still protected by the BKL, but there can be subtle races. For example, Eric Biederman and Paul Moore observed that if two threads tried to create two tun devices on the same file descriptor, then unexpected results may occur.
As there isn't anything in the ioctl that is expected to sleep indefinitely, we can prevent this from occurring by extending the RTNL lock coverage.
This also allows to get rid of the BKL.
Quoted 7 lines
Quoted 5 lines
Finally, I changed
tun_get_iffto take a tun device in order to avoid callingtun_putwhich would dead-lockt as it also tries to take the RTNL lock.Signed-off-by Herbert Xu <[email protected]>This looks good after a quick audit, Eric what say you?
Looks good to me.
Eric
From: [email protected] (Eric W. Biederman)
Date: Thu, 06 Aug 2009 21:22:42 -0700
Quoted 24 lines
Quoted 22 lines · David Miller
From: Herbert Xu <[email protected]>
Date: Fri, 7 Aug 2009 10:22:44 +1000Quoted 19 lines
tun: Extend RTNL lock coverage over whole ioctl
As it is, parts of the ioctl runs under the RTNL and parts of it do not. The unlocked section is still protected by the BKL, but there can be subtle races. For example, Eric Biederman and Paul Moore observed that if two threads tried to create two tun devices on the same file descriptor, then unexpected results may occur.
As there isn't anything in the ioctl that is expected to sleep indefinitely, we can prevent this from occurring by extending the RTNL lock coverage.
This also allows to get rid of the BKL.
Finally, I changed
tun_get_iffto take a tun device in order to avoid callingtun_putwhich would dead-lockt as it also tries to take the RTNL lock.
Quoted 5 lines
Quoted 3 lines
Quoted 1 line
Signed-off-by Herbert Xu <[email protected]>This looks good after a quick audit, Eric what say you?
Looks good to me.
Applied, thanks everyone.