diff mbox

net: Fix skb_set_peeked use-after-free bug

Message ID 20150804074247.GA29951@gondor.apana.org.au
State Accepted, archived
Delegated to: David Miller
Headers show

Commit Message

Herbert Xu Aug. 4, 2015, 7:42 a.m. UTC
Brenden Blanco <bblanco@plumgrid.com> wrote:
>> [  318.244596] BUG: unable to handle kernel NULL pointer dereference
>> at 000000000000008e
>> [  318.245182] IP: [<ffffffff81455e7c>] __skb_recv_datagram+0xbc/0x5a0
> 
> Replying to myself, and adding commit interested parties...
> 
> I went through the git log for the function in question, and
> positively identified that the following commit introduces the crash:
> 
> 738ac1e net: Clone skb before setting peeked flag
> 
> Null dereference is at line 224 of net/core/datagram.c (according to
> my objdump dis-assembly):

Sorry, brain fart on my part.  Please try this patch.

---8<---
The commit 738ac1ebb96d02e0d23bc320302a6ea94c612dec ("net: Clone
skb before setting peeked flag") introduced a use-after-free bug
in skb_recv_datagram.  This is because skb_set_peeked may create
a new skb and free the existing one.  As it stands the caller will
continue to use the old freed skb.

This patch fixes it by making skb_set_peeked return the new skb
(or the old one if unchanged).

Fixes: 738ac1ebb96d ("net: Clone skb before setting peeked flag")
Reported-by: Brenden Blanco <bblanco@plumgrid.com>
Signed-off-by: Herbert Xu <herbert@gondor.apana.org.au>

Comments

David Laight Aug. 4, 2015, 9:15 a.m. UTC | #1
From: Herbert Xu
> Sent: 04 August 2015 08:43
> Brenden Blanco <bblanco@plumgrid.com> wrote:
> >> [  318.244596] BUG: unable to handle kernel NULL pointer dereference
> >> at 000000000000008e
> >> [  318.245182] IP: [<ffffffff81455e7c>] __skb_recv_datagram+0xbc/0x5a0
> >
> > Replying to myself, and adding commit interested parties...
> >
> > I went through the git log for the function in question, and
> > positively identified that the following commit introduces the crash:
> >
> > 738ac1e net: Clone skb before setting peeked flag
> >
> > Null dereference is at line 224 of net/core/datagram.c (according to
> > my objdump dis-assembly):
> 
> Sorry, brain fart on my part.  Please try this patch.

You've introduced a memory leak if skb_clone() fails.

> The commit 738ac1ebb96d02e0d23bc320302a6ea94c612dec ("net: Clone
> skb before setting peeked flag") introduced a use-after-free bug
> in skb_recv_datagram.  This is because skb_set_peeked may create
> a new skb and free the existing one.  As it stands the caller will
> continue to use the old freed skb.
> 
> This patch fixes it by making skb_set_peeked return the new skb
> (or the old one if unchanged).
...
>  	nskb = skb_clone(skb, GFP_ATOMIC);
>  	if (!nskb)
> -		return -ENOMEM;
> +		return ERR_PTR(-ENOMEM);

Here the original skb is still allocated.

> -				error = skb_set_peeked(skb);
> -				if (error)
> +				skb = skb_set_peeked(skb);

You've now lost the address of the original skb.

> +				error = PTR_ERR(skb);
> +				if (IS_ERR(skb))
>  					goto unlock_err;

	David


--
To unsubscribe from this list: send the line "unsubscribe netdev" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Herbert Xu Aug. 4, 2015, 9:20 a.m. UTC | #2
On Tue, Aug 04, 2015 at 09:15:13AM +0000, David Laight wrote:
>
> You've introduced a memory leak if skb_clone() fails.

No I have not.

> >  	nskb = skb_clone(skb, GFP_ATOMIC);
> >  	if (!nskb)
> > -		return -ENOMEM;
> > +		return ERR_PTR(-ENOMEM);
> 
> Here the original skb is still allocated.
> 
> > -				error = skb_set_peeked(skb);
> > -				if (error)
> > +				skb = skb_set_peeked(skb);
> 
> You've now lost the address of the original skb.

It doesn't matter because we will take the error path and return
the ENOMEM error.  We must not free the skb as it's still on the
recv queue.

Cheers,
David Laight Aug. 4, 2015, 9:24 a.m. UTC | #3
From: Herbert Xu
> Sent: 04 August 2015 10:21
> On Tue, Aug 04, 2015 at 09:15:13AM +0000, David Laight wrote:
> >
> > You've introduced a memory leak if skb_clone() fails.
> 
> No I have not.
> 
> > >  	nskb = skb_clone(skb, GFP_ATOMIC);
> > >  	if (!nskb)
> > > -		return -ENOMEM;
> > > +		return ERR_PTR(-ENOMEM);
> >
> > Here the original skb is still allocated.
> >
> > > -				error = skb_set_peeked(skb);
> > > -				if (error)
> > > +				skb = skb_set_peeked(skb);
> >
> > You've now lost the address of the original skb.
> 
> It doesn't matter because we will take the error path and return
> the ENOMEM error.  We must not free the skb as it's still on the
> recv queue.

In that case, what happens to the receive queue when skb_clone()
takes a copy of the skb - freeing the original one?

	David

--
To unsubscribe from this list: send the line "unsubscribe netdev" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Herbert Xu Aug. 4, 2015, 9:50 a.m. UTC | #4
David Laight <David.Laight@aculab.com> wrote:
>
> In that case, what happens to the receive queue when skb_clone()
> takes a copy of the skb - freeing the original one?

The new skb is inserted into the recv queue and replacing the
existing skb.
Brenden Blanco Aug. 4, 2015, 3:30 p.m. UTC | #5
> The commit 738ac1ebb96d02e0d23bc320302a6ea94c612dec ("net: Clone
> skb before setting peeked flag") introduced a use-after-free bug
> in skb_recv_datagram.  This is because skb_set_peeked may create
> a new skb and free the existing one.  As it stands the caller will
> continue to use the old freed skb.
>
> This patch fixes it by making skb_set_peeked return the new skb
> (or the old one if unchanged).
>
> Fixes: 738ac1ebb96d ("net: Clone skb before setting peeked flag")
> Reported-by: Brenden Blanco <bblanco@plumgrid.com>
> Signed-off-by: Herbert Xu <herbert@gondor.apana.org.au>
>
> diff --git a/net/core/datagram.c b/net/core/datagram.c
> index 4967262..617088a 100644
> --- a/net/core/datagram.c
> +++ b/net/core/datagram.c
> @@ -131,12 +131,12 @@ out_noerr:
>         goto out;
>  }
>
> -static int skb_set_peeked(struct sk_buff *skb)
> +static struct sk_buff *skb_set_peeked(struct sk_buff *skb)
>  {
>         struct sk_buff *nskb;
>
>         if (skb->peeked)
> -               return 0;
> +               return skb;
>
>         /* We have to unshare an skb before modifying it. */
>         if (!skb_shared(skb))
> @@ -144,7 +144,7 @@ static int skb_set_peeked(struct sk_buff *skb)
>
>         nskb = skb_clone(skb, GFP_ATOMIC);
>         if (!nskb)
> -               return -ENOMEM;
> +               return ERR_PTR(-ENOMEM);
>
>         skb->prev->next = nskb;
>         skb->next->prev = nskb;
> @@ -157,7 +157,7 @@ static int skb_set_peeked(struct sk_buff *skb)
>  done:
>         skb->peeked = 1;
>
> -       return 0;
> +       return skb;
>  }
>
>  /**
> @@ -229,8 +229,9 @@ struct sk_buff *__skb_recv_datagram(struct sock *sk, unsigned int flags,
>                                         continue;
>                                 }
>
> -                               error = skb_set_peeked(skb);
> -                               if (error)
> +                               skb = skb_set_peeked(skb);
> +                               error = PTR_ERR(skb);
> +                               if (IS_ERR(skb))
>                                         goto unlock_err;
>
>                                 atomic_inc(&skb->users);

This patch holds good in my testing. Thanks!
--
To unsubscribe from this list: send the line "unsubscribe netdev" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Herbert Xu Aug. 5, 2015, 12:09 a.m. UTC | #6
On Tue, Aug 04, 2015 at 08:30:23AM -0700, Brenden Blanco wrote:
>
> This patch holds good in my testing. Thanks!

Thanks for testing.  Let's add a tag for patchwork:

Tested-by: Brenden Blanco <bblanco@plumgrid.com>
Konstantin Khlebnikov Aug. 5, 2015, 11:46 a.m. UTC | #7
On 04.08.2015 10:42, Herbert Xu wrote:
> Brenden Blanco <bblanco@plumgrid.com> wrote:
>>> [  318.244596] BUG: unable to handle kernel NULL pointer dereference
>>> at 000000000000008e
>>> [  318.245182] IP: [<ffffffff81455e7c>] __skb_recv_datagram+0xbc/0x5a0
>>
>> Replying to myself, and adding commit interested parties...
>>
>> I went through the git log for the function in question, and
>> positively identified that the following commit introduces the crash:
>>
>> 738ac1e net: Clone skb before setting peeked flag
>>
>> Null dereference is at line 224 of net/core/datagram.c (according to
>> my objdump dis-assembly):
>
> Sorry, brain fart on my part.  Please try this patch.
>
> ---8<---
> The commit 738ac1ebb96d02e0d23bc320302a6ea94c612dec ("net: Clone
> skb before setting peeked flag") introduced a use-after-free bug
> in skb_recv_datagram.  This is because skb_set_peeked may create
> a new skb and free the existing one.  As it stands the caller will
> continue to use the old freed skb.
>
> This patch fixes it by making skb_set_peeked return the new skb
> (or the old one if unchanged).
>
> Fixes: 738ac1ebb96d ("net: Clone skb before setting peeked flag")
> Reported-by: Brenden Blanco <bblanco@plumgrid.com>
> Signed-off-by: Herbert Xu <herbert@gondor.apana.org.au>

Seems correct. You can add:

Reviewed-by: Konstantin Khlebnikov <khlebnikov@yandex-team.ru>

Your skb_set_peeked() doesn't set prev/next to NULL when
unlinks old skb from the queue unlike to __skb_unlink().
Isn't big deal but nulling might be useful.

>
> diff --git a/net/core/datagram.c b/net/core/datagram.c
> index 4967262..617088a 100644
> --- a/net/core/datagram.c
> +++ b/net/core/datagram.c
> @@ -131,12 +131,12 @@ out_noerr:
>   	goto out;
>   }
>
> -static int skb_set_peeked(struct sk_buff *skb)
> +static struct sk_buff *skb_set_peeked(struct sk_buff *skb)
>   {
>   	struct sk_buff *nskb;
>
>   	if (skb->peeked)
> -		return 0;
> +		return skb;
>
>   	/* We have to unshare an skb before modifying it. */
>   	if (!skb_shared(skb))
> @@ -144,7 +144,7 @@ static int skb_set_peeked(struct sk_buff *skb)
>
>   	nskb = skb_clone(skb, GFP_ATOMIC);
>   	if (!nskb)
> -		return -ENOMEM;
> +		return ERR_PTR(-ENOMEM);
>
>   	skb->prev->next = nskb;
>   	skb->next->prev = nskb;
> @@ -157,7 +157,7 @@ static int skb_set_peeked(struct sk_buff *skb)
>   done:
>   	skb->peeked = 1;
>
> -	return 0;
> +	return skb;
>   }
>
>   /**
> @@ -229,8 +229,9 @@ struct sk_buff *__skb_recv_datagram(struct sock *sk, unsigned int flags,
>   					continue;
>   				}
>
> -				error = skb_set_peeked(skb);
> -				if (error)
> +				skb = skb_set_peeked(skb);
> +				error = PTR_ERR(skb);
> +				if (IS_ERR(skb))
>   					goto unlock_err;
>
>   				atomic_inc(&skb->users);
>
David Miller Aug. 7, 2015, 4:56 a.m. UTC | #8
From: Herbert Xu <herbert@gondor.apana.org.au>
Date: Tue, 4 Aug 2015 15:42:47 +0800

> The commit 738ac1ebb96d02e0d23bc320302a6ea94c612dec ("net: Clone
> skb before setting peeked flag") introduced a use-after-free bug
> in skb_recv_datagram.  This is because skb_set_peeked may create
> a new skb and free the existing one.  As it stands the caller will
> continue to use the old freed skb.
> 
> This patch fixes it by making skb_set_peeked return the new skb
> (or the old one if unchanged).
> 
> Fixes: 738ac1ebb96d ("net: Clone skb before setting peeked flag")
> Reported-by: Brenden Blanco <bblanco@plumgrid.com>
> Signed-off-by: Herbert Xu <herbert@gondor.apana.org.au>

Applied, and since the commit this is fixing is queued up for -stable
I'll of course queue this up too.
--
To unsubscribe from this list: send the line "unsubscribe netdev" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
diff mbox

Patch

diff --git a/net/core/datagram.c b/net/core/datagram.c
index 4967262..617088a 100644
--- a/net/core/datagram.c
+++ b/net/core/datagram.c
@@ -131,12 +131,12 @@  out_noerr:
 	goto out;
 }
 
-static int skb_set_peeked(struct sk_buff *skb)
+static struct sk_buff *skb_set_peeked(struct sk_buff *skb)
 {
 	struct sk_buff *nskb;
 
 	if (skb->peeked)
-		return 0;
+		return skb;
 
 	/* We have to unshare an skb before modifying it. */
 	if (!skb_shared(skb))
@@ -144,7 +144,7 @@  static int skb_set_peeked(struct sk_buff *skb)
 
 	nskb = skb_clone(skb, GFP_ATOMIC);
 	if (!nskb)
-		return -ENOMEM;
+		return ERR_PTR(-ENOMEM);
 
 	skb->prev->next = nskb;
 	skb->next->prev = nskb;
@@ -157,7 +157,7 @@  static int skb_set_peeked(struct sk_buff *skb)
 done:
 	skb->peeked = 1;
 
-	return 0;
+	return skb;
 }
 
 /**
@@ -229,8 +229,9 @@  struct sk_buff *__skb_recv_datagram(struct sock *sk, unsigned int flags,
 					continue;
 				}
 
-				error = skb_set_peeked(skb);
-				if (error)
+				skb = skb_set_peeked(skb);
+				error = PTR_ERR(skb);
+				if (IS_ERR(skb))
 					goto unlock_err;
 
 				atomic_inc(&skb->users);