Re: general protection fault in find_entry

From: Eric Biggers
Date: Tue Jan 30 2018 - 18:29:46 EST


On Fri, Jan 05, 2018 at 10:31:04PM +0100, Luis R. Rodriguez wrote:
> On Fri, Dec 22, 2017 at 11:33:02PM -0800, syzbot wrote:
> > Hello,
> >
> > syzkaller hit the following crash on
> > 6084b576dca2e898f5c101baef151f7bfdbb606d
> > git://git.kernel.org/pub/scm/linux/kernel/git/next/linux-next.git/master
> > compiler: gcc (GCC) 7.1.1 20170620
> > .config is attached
> > Raw console output is attached.
> >
> > Unfortunately, I don't have any reproducer for this bug yet.
> >
> >
> > netlink: 13 bytes leftover after parsing attributes in process
> > `syz-executor2'.
> > general protection fault: 0000 [#1] SMP
> > Dumping ftrace buffer:
> > (ftrace buffer empty)
> > Modules linked in:
> > CPU: 1 PID: 26639 Comm: syz-executor5 Not tainted 4.15.0-rc3-next-20171214+
> > #67
> > Hardware name: Google Google Compute Engine/Google Compute Engine, BIOS
> > Google 01/01/2011
> > RIP: 0010:find_entry.isra.14+0x45/0xc0 fs/proc/proc_sysctl.c:119
> > RSP: 0018:ffffc90000dcba60 EFLAGS: 00010282
> > RAX: 0000000000010000 RBX: ffff1003f68e0980 RCX: ffffffff814becad
> > RDX: 000000000000674d RSI: ffffc90000fe1000 RDI: ffffc90000dcbaa8
> > RBP: ffffc90000dcba98 R08: 0000000000000001 R09: 0000000000000004
> > R10: ffffc90000dcba48 R11: 0000000000000004 R12: ffff8801fb4704d0
> > R13: ffff8801fb470400 R14: ffff8801fc7ecc00 R15: ffffc90000dcbb7d
> > FS: 00007f125b750700(0000) GS:ffff88021fd00000(0000) knlGS:0000000000000000
> > CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033
> > CR2: 0000000001680cd8 CR3: 00000001fd3ac001 CR4: 00000000001606e0
> > DR0: 0000000020001008 DR1: 0000000000000000 DR2: 0000000000000000
> > DR3: 0000000000000000 DR6: 00000000fffe0ff0 DR7: 0000000000000600
> > Call Trace:
> > find_subdir+0x35/0x80 fs/proc/proc_sysctl.c:923
> > get_subdir fs/proc/proc_sysctl.c:977 [inline]
> > __register_sysctl_table+0x392/0x7c0 fs/proc/proc_sysctl.c:1327
>
> find_subdir() accepts as part of its seconds argument a struct ctl_dir *dir,
> which we then find_subdir() then dereferences using find_entry():
>
> struct rb_node *node = dir->root.rb_node;
>
> The RIP however is on find_entry.isra.14+0x45/0xc0 fs/proc/proc_sysctl.c:119
> which as of linux-next is:
>
> entry = &head->ctl_table[ctl_node - head->node];
>
> So a few questions to review.
>
> 1) Is it sensible to assume we can always reference the dir?
> 2) Is the entry computation above really safe always?
>
> Let's try:
>
> 1) Is it sensible to assume we can always reference the dir?
>
> 1a) When registering a sysctl table register_sysctl() calls:
>
> return __register_sysctl_table(&sysctl_table_root.default_set,
> path, table);
>
> So the sysctl_table_root.default_set set is used. When registering
> then, the first dir used is this one. init_header() sets the
> set on the header to this one, and we use the same set for
> the first dir:
>
> dir = &set->dir;
>
> Whether or not this is safe will depend on the life cycle of this root entry's
> dir pointer, and at init sysctl_table_root.default_set.dir is just a pointer
> in static memory. It cannot be freed. So on the first iteration
> the dereference should always be safe.
>
> On the loop, since strchr() is used we iterate over the path given
> for the target proc directory entry by first looking at the first
> pattern with '/'.
>
> > register_net_sysctl+0x29/0x30 net/sysctl_net.c:120
> > neigh_sysctl_register+0x150/0x220 net/core/neighbour.c:3235
>
> In this particular case of addrconf_sysctl_register()
> the path is something along the lines of: "net/%s/neigh/%s"
>
> So if we had path as net/foo/neigh/bar we'd have a few iterations as
> follows:
>
> Path: net/foo/neigh/bar
> ------------------------------------
> name: net/foo/neigh/bar
> nextname: foo/neigh/bar
> ------------------------------------
> name: foo/neigh/bar
> nextname: neigh/bar
> ------------------------------------
> name: neigh/bar
> nextname: bar
> ------------------------------------
> name: bar
> nextname: (null)
>
> And for each one we'd call get_subdir() to ensure that
> the dir exists:
>
> dir = get_subdir(dir, name, namelen);
> if (IS_ERR(dir))
> goto fail;
>
> Its in these lookups where we hit an issue. Note that
> get_subdir() calls find_subdir() twice, the one we hit the
> issue with is the first call.
>
> The loop will keep setting up dir to traverse the entire path
> ensuring we have a dir entry up to the end of the path. Each
> sub directory must have an entry in order for this
> to work.
>
> The safety of relying on dereferencing dir will then depend on our locking
> sanity and management of the dirs here. I'm not so sure about the sanity
> on locking here yet.
>
> 2) Is the entry computation above really safe always?
> 2A) The find_entry() call and entry lookup we are concerned with which we see
> on RIP happens off of the get_subdir() calls on the loop inspected above. The
> trace above shows its on the get_subdir() call, and this trace:
>
> get_subdir fs/proc/proc_sysctl.c:977 [inline]
>
> Tells us its the first find_subdir() call within get_subdir() where the issue
> was hit. find_subdir() looks at the registered ctl table and looks for a
> matching directory, and if found returns it it, a struct ctl_dir pointer.
> From what I can gather this is safe so long as our locking is sane.
>
> Not sure yet what could be wrong.. being able to reproduce would help.
> I'd recommend trying to expand on lib/test_sysctl.c with more elaborate
> entanglements and trying to register/deregister as a tests a series of
> subdirectories which some depend entries depend on.
>

Hi Luis, this crash has only occurred once, and it was actually reported while
KASAN was accidentally disabled in the syzbot kconfig due to a change to the
kconfig menus in linux-next -- so the crash was possibly caused by slab
corruption elsewhere.

I will just invalidate the bug so that syzbot can report crashes with the same
signature again, but feel free to look into it more if you think there is still
a real problem somewhere.

#syz invalid

Thanks,

Eric