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; } -- 2.20.1.97.g81188d93c3-goog