diff mbox series

[net] tipc: fix the skb_unshare() in tipc_buf_append()

Message ID 20201008041250.22642-1-xiyou.wangcong@gmail.com
State New
Headers show
Series [net] tipc: fix the skb_unshare() in tipc_buf_append() | expand

Commit Message

Cong Wang Oct. 8, 2020, 4:12 a.m. UTC
skb_unshare() drops a reference count on the old skb unconditionally,
so in the failure case, we end up freeing the skb twice here.
And because the skb is allocated in fclone and cloned by caller
tipc_msg_reassemble(), the consequence is actually freeing the
original skb too, thus triggered the UAF by syzbot.

Fix this by replacing this skb_unshare() with skb_cloned()+skb_copy().

Fixes: ff48b6222e65 ("tipc: use skb_unshare() instead in tipc_buf_append()")
Reported-and-tested-by: syzbot+e96a7ba46281824cc46a@syzkaller.appspotmail.com
Cc: Xin Long <lucien.xin@gmail.com>
Cc: Jon Maloy <jmaloy@redhat.com>
Cc: Ying Xue <ying.xue@windriver.com>
Signed-off-by: Cong Wang <xiyou.wangcong@gmail.com>
---
 net/tipc/msg.c | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

Comments

Xin Long Oct. 8, 2020, 8:45 a.m. UTC | #1
On Thu, Oct 8, 2020 at 12:12 PM Cong Wang <xiyou.wangcong@gmail.com> wrote:
>
> skb_unshare() drops a reference count on the old skb unconditionally,
> so in the failure case, we end up freeing the skb twice here.
> And because the skb is allocated in fclone and cloned by caller
> tipc_msg_reassemble(), the consequence is actually freeing the
> original skb too, thus triggered the UAF by syzbot.
Do you mean:
                frag = skb_clone(skb, GFP_ATOMIC);
frag = skb_unshare(frag) will free the 'skb' too?

>
> Fix this by replacing this skb_unshare() with skb_cloned()+skb_copy().
>
> Fixes: ff48b6222e65 ("tipc: use skb_unshare() instead in tipc_buf_append()")
> Reported-and-tested-by: syzbot+e96a7ba46281824cc46a@syzkaller.appspotmail.com
> Cc: Xin Long <lucien.xin@gmail.com>
> Cc: Jon Maloy <jmaloy@redhat.com>
> Cc: Ying Xue <ying.xue@windriver.com>
> Signed-off-by: Cong Wang <xiyou.wangcong@gmail.com>
> ---
>  net/tipc/msg.c | 3 ++-
>  1 file changed, 2 insertions(+), 1 deletion(-)
>
> diff --git a/net/tipc/msg.c b/net/tipc/msg.c
> index 52e93ba4d8e2..681224401871 100644
> --- a/net/tipc/msg.c
> +++ b/net/tipc/msg.c
> @@ -150,7 +150,8 @@ int tipc_buf_append(struct sk_buff **headbuf, struct sk_buff **buf)
>         if (fragid == FIRST_FRAGMENT) {
>                 if (unlikely(head))
>                         goto err;
> -               frag = skb_unshare(frag, GFP_ATOMIC);
> +               if (skb_cloned(frag))
> +                       frag = skb_copy(frag, GFP_ATOMIC);
>                 if (unlikely(!frag))
>                         goto err;
>                 head = *headbuf = frag;
> --
> 2.28.0
>
Cong Wang Oct. 8, 2020, 5:44 p.m. UTC | #2
On Thu, Oct 8, 2020 at 1:45 AM Xin Long <lucien.xin@gmail.com> wrote:
>
> On Thu, Oct 8, 2020 at 12:12 PM Cong Wang <xiyou.wangcong@gmail.com> wrote:
> >
> > skb_unshare() drops a reference count on the old skb unconditionally,
> > so in the failure case, we end up freeing the skb twice here.
> > And because the skb is allocated in fclone and cloned by caller
> > tipc_msg_reassemble(), the consequence is actually freeing the
> > original skb too, thus triggered the UAF by syzbot.
> Do you mean:
>                 frag = skb_clone(skb, GFP_ATOMIC);
> frag = skb_unshare(frag) will free the 'skb' too?

Yes, more precisely, I mean:

new = skb_clone(old)
kfree_skb(new)
kfree_skb(new)

would free 'old' eventually when 'old' is a fast clone. The skb_clone()
sets ->fclone_ref to 2 and returns the clone, whose skb->fclone is
SKB_FCLONE_CLONE. So, the first call of kfree_skbmem() will
just decrease ->fclone_ref by 1, but the second call will trigger
kmem_cache_free() which frees _both_  skb's.

Thanks.
Xin Long Oct. 9, 2020, 3:07 a.m. UTC | #3
On Fri, Oct 9, 2020 at 1:45 AM Cong Wang <xiyou.wangcong@gmail.com> wrote:
>
> On Thu, Oct 8, 2020 at 1:45 AM Xin Long <lucien.xin@gmail.com> wrote:
> >
> > On Thu, Oct 8, 2020 at 12:12 PM Cong Wang <xiyou.wangcong@gmail.com> wrote:
> > >
> > > skb_unshare() drops a reference count on the old skb unconditionally,
> > > so in the failure case, we end up freeing the skb twice here.
> > > And because the skb is allocated in fclone and cloned by caller
> > > tipc_msg_reassemble(), the consequence is actually freeing the
> > > original skb too, thus triggered the UAF by syzbot.
> > Do you mean:
> >                 frag = skb_clone(skb, GFP_ATOMIC);
> > frag = skb_unshare(frag) will free the 'skb' too?
>
> Yes, more precisely, I mean:
>
> new = skb_clone(old)
> kfree_skb(new)
> kfree_skb(new)
>
> would free 'old' eventually when 'old' is a fast clone. The skb_clone()
> sets ->fclone_ref to 2 and returns the clone, whose skb->fclone is
> SKB_FCLONE_CLONE. So, the first call of kfree_skbmem() will
> just decrease ->fclone_ref by 1, but the second call will trigger
> kmem_cache_free() which frees _both_  skb's.
Thanks. Didn't notice kfree_skb 'buf' on the err path.

Reviewed-by: Xin Long <lucien.xin@gmail.com>
Jakub Kicinski Oct. 10, 2020, 1:26 a.m. UTC | #4
On Wed,  7 Oct 2020 21:12:50 -0700 Cong Wang wrote:
> skb_unshare() drops a reference count on the old skb unconditionally,
> so in the failure case, we end up freeing the skb twice here.
> And because the skb is allocated in fclone and cloned by caller
> tipc_msg_reassemble(), the consequence is actually freeing the
> original skb too, thus triggered the UAF by syzbot.
> 
> Fix this by replacing this skb_unshare() with skb_cloned()+skb_copy().
> 
> Fixes: ff48b6222e65 ("tipc: use skb_unshare() instead in tipc_buf_append()")
> Reported-and-tested-by: syzbot+e96a7ba46281824cc46a@syzkaller.appspotmail.com
> Cc: Xin Long <lucien.xin@gmail.com>
> Cc: Jon Maloy <jmaloy@redhat.com>
> Cc: Ying Xue <ying.xue@windriver.com>
> Signed-off-by: Cong Wang <xiyou.wangcong@gmail.com>

Applied and queued for stable, thank you!
diff mbox series

Patch

diff --git a/net/tipc/msg.c b/net/tipc/msg.c
index 52e93ba4d8e2..681224401871 100644
--- a/net/tipc/msg.c
+++ b/net/tipc/msg.c
@@ -150,7 +150,8 @@  int tipc_buf_append(struct sk_buff **headbuf, struct sk_buff **buf)
 	if (fragid == FIRST_FRAGMENT) {
 		if (unlikely(head))
 			goto err;
-		frag = skb_unshare(frag, GFP_ATOMIC);
+		if (skb_cloned(frag))
+			frag = skb_copy(frag, GFP_ATOMIC);
 		if (unlikely(!frag))
 			goto err;
 		head = *headbuf = frag;