Skip to content

Commit

Permalink
sysctl: protect poll() in entries that may go away
Browse files Browse the repository at this point in the history
Protect code accessing ctl_table by grabbing the header with grab_header()
and after releasing with sysctl_head_finish().  This is needed if poll()
is called in entries created by modules: currently only hostname and
domainname support poll(), but this bug may be triggered when/if modules
use it and if user called poll() in a file that doesn't support it.

Dave Jones reported the following when using a syscall fuzzer while
hibernating/resuming:

RIP: 0010:[<ffffffff81233e3e>]  [<ffffffff81233e3e>] proc_sys_poll+0x4e/0x90
RAX: 0000000000000145 RBX: ffff88020cab6940 RCX: 0000000000000000
RDX: ffffffff81233df0 RSI: 6b6b6b6b6b6b6b6b RDI: ffff88020cab6940
[ ... ]
Code: 00 48 89 fb 48 89 f1 48 8b 40 30 4c 8b 60 e8 b8 45 01 00 00 49 83
7c 24 28 00 74 2e 49 8b 74 24 30 48 85 f6 74 24 48 85 c9 75 32 <8b> 16
b8 45 01 00 00 48 63 d2 49 39 d5 74 10 8b 06 48 98 48 89

If an entry goes away while we are polling() it, ctl_table may not exist
anymore.

Reported-by: Dave Jones <davej@redhat.com>
Signed-off-by: Lucas De Marchi <lucas.demarchi@profusion.mobi>
Cc: Al Viro <viro@zeniv.linux.org.uk>
Cc: Linus Torvalds <torvalds@linux-foundation.org>
Cc: Alexey Dobriyan <adobriyan@gmail.com>
Cc: stable@vger.kernel.org
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Eric W. Biederman <ebiederm@xmission.com>
  • Loading branch information
Lucas De Marchi authored and Eric W. Biederman committed Mar 22, 2012
1 parent 4e75732 commit 4e474a0
Showing 1 changed file with 16 additions and 1 deletion.
17 changes: 16 additions & 1 deletion fs/proc/proc_sysctl.c
Original file line number Diff line number Diff line change
Expand Up @@ -525,27 +525,40 @@ static ssize_t proc_sys_write(struct file *filp, const char __user *buf,

static int proc_sys_open(struct inode *inode, struct file *filp)
{
struct ctl_table_header *head = grab_header(inode);
struct ctl_table *table = PROC_I(inode)->sysctl_entry;

/* sysctl was unregistered */
if (IS_ERR(head))
return PTR_ERR(head);

if (table->poll)
filp->private_data = proc_sys_poll_event(table->poll);

sysctl_head_finish(head);

return 0;
}

static unsigned int proc_sys_poll(struct file *filp, poll_table *wait)
{
struct inode *inode = filp->f_path.dentry->d_inode;
struct ctl_table_header *head = grab_header(inode);
struct ctl_table *table = PROC_I(inode)->sysctl_entry;
unsigned long event = (unsigned long)filp->private_data;
unsigned int ret = DEFAULT_POLLMASK;
unsigned long event;

/* sysctl was unregistered */
if (IS_ERR(head))
return POLLERR | POLLHUP;

if (!table->proc_handler)
goto out;

if (!table->poll)
goto out;

event = (unsigned long)filp->private_data;
poll_wait(filp, &table->poll->wait, wait);

if (event != atomic_read(&table->poll->event)) {
Expand All @@ -554,6 +567,8 @@ static unsigned int proc_sys_poll(struct file *filp, poll_table *wait)
}

out:
sysctl_head_finish(head);

return ret;
}

Expand Down

0 comments on commit 4e474a0

Please sign in to comment.