On Mon, Jan 7, 2019 at 12:02 PM Stanislav Fomichev <s...@google.com> wrote: > > BUG: unable to handle kernel NULL pointer dereference at 00000000000000d1 > Call Trace: > ? napi_gro_frags+0xa7/0x2c0 > tun_get_user+0xb50/0xf20 > tun_chr_write_iter+0x53/0x70 > new_sync_write+0xff/0x160 > vfs_write+0x191/0x1e0 > __x64_sys_write+0x5e/0xd0 > do_syscall_64+0x47/0xf0 > entry_SYSCALL_64_after_hwframe+0x44/0xa9 > > I think there is a subtle race between sending a packet via tap and > attaching it: > > CPU0: CPU1: > tun_chr_ioctl(TUNSETIFF) > tun_set_iff > tun_attach > rcu_assign_pointer(tfile->tun, tun); > tun_fops->write_iter() > tun_chr_write_iter > tun_napi_alloc_frags > napi_get_frags > napi->skb = napi_alloc_skb > tun_napi_init > netif_napi_add > napi->skb = NULL > napi->skb is NULL here > napi_gro_frags > napi_frags_skb > skb = napi->skb > skb_reset_mac_header(skb) > panic() > > To fix, do the following: > * Move rcu_assign_pointer(tfile->tun, tun) to be the last thing we do > in tun_attach(); this should guarantee that when we call tun_get() > we always get an initialized object > * As another safeguard, always grab napi_mutex whenever doing any > napi operation; this should prevent napi state change between > calls to napi_get_frags and napi_gro_frags > > Reported-by: syzbot <syzkal...@googlegroups.com> > Fixes: 90e33d459407 ("tun: enable napi_gro_frags() for TUN/TAP driver") > > Signed-off-by: Stanislav Fomichev <s...@google.com> > --- > drivers/net/tun.c | 18 +++++++++++++++--- > 1 file changed, 15 insertions(+), 3 deletions(-) > > diff --git a/drivers/net/tun.c b/drivers/net/tun.c > index a4fdad475594..7875f06011f2 100644 > --- a/drivers/net/tun.c > +++ b/drivers/net/tun.c > @@ -323,22 +323,30 @@ static void tun_napi_init(struct tun_struct *tun, > struct tun_file *tfile, > tfile->napi_enabled = napi_en; > tfile->napi_frags_enabled = napi_en && napi_frags; > if (napi_en) { > + mutex_lock(&tfile->napi_mutex); > netif_napi_add(tun->dev, &tfile->napi, tun_napi_poll, > NAPI_POLL_WEIGHT); > napi_enable(&tfile->napi); > + mutex_unlock(&tfile->napi_mutex); > } > } > > static void tun_napi_disable(struct tun_file *tfile) > { > - if (tfile->napi_enabled) > + if (tfile->napi_enabled) { > + mutex_lock(&tfile->napi_mutex); > napi_disable(&tfile->napi); > + mutex_unlock(&tfile->napi_mutex); > + } > } > > static void tun_napi_del(struct tun_file *tfile) > { > - if (tfile->napi_enabled) > + if (tfile->napi_enabled) { > + mutex_lock(&tfile->napi_mutex); > netif_napi_del(&tfile->napi); > + mutex_unlock(&tfile->napi_mutex); > + } > } > > static bool tun_napi_frags_enabled(const struct tun_file *tfile) > @@ -856,7 +864,6 @@ static int tun_attach(struct tun_struct *tun, struct file > *file, > err = 0; > } > > - rcu_assign_pointer(tfile->tun, tun); > rcu_assign_pointer(tun->tfiles[tun->numqueues], tfile); > tun->numqueues++; > > @@ -876,6 +883,11 @@ static int tun_attach(struct tun_struct *tun, struct > file *file, > * refcnt. > */ > > + /* All tun_fops depend on tun_get() returning non-null pointer. > + * Thus, assigning tun to a tfile should be the last init operation, > + * otherwise we risk using half-initialized object. > + */ > + rcu_assign_pointer(tfile->tun, tun); > out: > return err; > }
Hmmm I believe the issue is different : We need to call tun_napi_init() before doing the publish in the tun->tfiles[] array My patch was : diff --git a/drivers/net/tun.c b/drivers/net/tun.c index a4fdad47559462fbd049a89f880cc3fb33d1151d..dc751d1cbc21a2e2687c5739b44322cd64d0cb46 100644 --- a/drivers/net/tun.c +++ b/drivers/net/tun.c @@ -857,15 +857,15 @@ static int tun_attach(struct tun_struct *tun, struct file *file, } rcu_assign_pointer(tfile->tun, tun); + if (!tfile->detached) { + tun_napi_init(tun, tfile, napi, napi_frags); + sock_hold(&tfile->sk); + } rcu_assign_pointer(tun->tfiles[tun->numqueues], tfile); tun->numqueues++; - if (tfile->detached) { + if (tfile->detached) tun_enable_queue(tfile); - } else { - sock_hold(&tfile->sk); - tun_napi_init(tun, tfile, napi, napi_frags); - } if (rtnl_dereference(tun->xdp_prog)) sock_set_flag(&tfile->sk, SOCK_XDP);