Commit 667b591e authored by David S. Miller's avatar David S. Miller

[NET COMPAT]: Fix hangs caused by bugs in do_netfilter_replace().

It is illegal to try to access things via userspace pointers
after set_fs(KERNEL_DS), and that is exactly what this function
was doing.  Fix by using compat_alloc_user_user_space(), this
preserves the copy minimization the original code had.
parent 97bdefff
...@@ -317,12 +317,12 @@ static int do_netfilter_replace(int fd, int level, int optname, ...@@ -317,12 +317,12 @@ static int do_netfilter_replace(int fd, int level, int optname,
char *optval, int optlen) char *optval, int optlen)
{ {
struct compat_ipt_replace *urepl = (struct compat_ipt_replace *)optval; struct compat_ipt_replace *urepl = (struct compat_ipt_replace *)optval;
struct ipt_replace *krepl; struct ipt_replace *repl_nat;
u32 origsize; char name[IPT_TABLE_MAXNAMELEN];
unsigned int kreplsize; u32 origsize, tmp32, num_counters;
mm_segment_t old_fs; unsigned int repl_nat_size;
int ret; int ret;
int i; int i, num_ents;
compat_uptr_t ucntrs; compat_uptr_t ucntrs;
if (get_user(origsize, &urepl->size)) if (get_user(origsize, &urepl->size))
...@@ -335,25 +335,53 @@ static int do_netfilter_replace(int fd, int level, int optname, ...@@ -335,25 +335,53 @@ static int do_netfilter_replace(int fd, int level, int optname,
/* XXX Assumes that size of ipt_entry is the same both in /* XXX Assumes that size of ipt_entry is the same both in
* native and compat environments. * native and compat environments.
*/ */
kreplsize = sizeof(*krepl) + origsize; repl_nat_size = sizeof(*repl_nat) + origsize;
krepl = (struct ipt_replace *)kmalloc(kreplsize, GFP_KERNEL); repl_nat = compat_alloc_user_space(repl_nat_size);
if (krepl == NULL)
return -ENOMEM;
ret = -EFAULT; ret = -EFAULT;
krepl->size = origsize; if (put_user(origsize, &repl_nat->size))
goto out;
if (!access_ok(VERIFY_READ, urepl, optlen) || if (!access_ok(VERIFY_READ, urepl, optlen) ||
__copy_from_user(krepl->name, urepl->name, sizeof(urepl->name)) || !access_ok(VERIFY_WRITE, repl_nat, optlen))
__get_user(krepl->valid_hooks, &urepl->valid_hooks) || goto out;
__get_user(krepl->num_entries, &urepl->num_entries) ||
__get_user(krepl->num_counters, &urepl->num_counters) || if (__copy_from_user(name, urepl->name, sizeof(urepl->name)) ||
__get_user(ucntrs, &urepl->counters) || __copy_to_user(repl_nat->name, name, sizeof(repl_nat->name)))
__copy_from_user(krepl->entries, &urepl->entries, origsize)) goto out;
goto out_free;
if (__get_user(tmp32, &urepl->valid_hooks) ||
__put_user(tmp32, &repl_nat->valid_hooks))
goto out;
if (__get_user(tmp32, &urepl->num_entries) ||
__put_user(tmp32, &repl_nat->num_entries))
goto out;
if (__get_user(num_counters, &urepl->num_counters) ||
__put_user(num_counters, &repl_nat->num_counters))
goto out;
if (__get_user(ucntrs, &urepl->counters) ||
__put_user(compat_ptr(ucntrs), &repl_nat->counters))
goto out;
num_ents = origsize / sizeof(struct ipt_entry);
for (i = 0; i < num_ents; i++) {
struct ipt_entry ent;
if (__copy_from_user(&ent, &urepl->entries[i], sizeof(ent)) ||
__copy_to_user(&repl_nat->entries[i], &ent, sizeof(ent)))
goto out;
}
for (i = 0; i < NF_IP_NUMHOOKS; i++) { for (i = 0; i < NF_IP_NUMHOOKS; i++) {
if (__get_user(krepl->hook_entry[i], &urepl->hook_entry[i]) || if (__get_user(tmp32, &urepl->hook_entry[i]) ||
__get_user(krepl->underflow[i], &urepl->underflow[i])) __put_user(tmp32, &repl_nat->hook_entry[i]) ||
goto out_free; __get_user(tmp32, &urepl->underflow[i]) ||
__put_user(tmp32, &repl_nat->underflow[i]))
goto out;
} }
/* /*
...@@ -362,18 +390,15 @@ static int do_netfilter_replace(int fd, int level, int optname, ...@@ -362,18 +390,15 @@ static int do_netfilter_replace(int fd, int level, int optname,
* pointer into the standard syscall. We hope that the pointer is * pointer into the standard syscall. We hope that the pointer is
* not misaligned ... * not misaligned ...
*/ */
krepl->counters = compat_ptr(ucntrs); if (!access_ok(VERIFY_WRITE, compat_ptr(ucntrs),
if (!access_ok(VERIFY_WRITE, krepl->counters, num_counters * sizeof(struct ipt_counters)))
krepl->num_counters * sizeof(struct ipt_counters))) goto out;
goto out_free;
old_fs = get_fs();
set_fs(KERNEL_DS);
ret = sys_setsockopt(fd, level, optname, (char *)krepl, kreplsize);
set_fs(old_fs);
out_free: ret = sys_setsockopt(fd, level, optname,
kfree(krepl); (char *)repl_nat, repl_nat_size);
out:
return ret; return ret;
} }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment