Re: general protection fault in find_entry

From: Luis R. Rodriguez
Date: Fri Jan 05 2018 - 16:31:11 EST


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.

Luis