--- ./net/compat.c.iptcompat 2006-01-11 15:10:16.000000000 +0300 +++ ./net/compat.c 2006-01-18 11:35:28.000000000 +0300 @@ -312,10 +312,55 @@ void scm_detach_fds_compat(struct msghdr __scm_destroy(scm); } -/* - * For now, we assume that the compatibility and native version - * of struct ipt_entry are the same - sfr. FIXME - */ +struct compat_ipt_counters +{ + u_int32_t cnt[4]; +}; + +struct compat_ipt_entry +{ + struct ipt_ip ip; + compat_uint_t nfcache; + u_int16_t target_offset; + u_int16_t next_offset; + compat_uint_t comefrom; + struct compat_ipt_counters counters; + unsigned char elems[0]; +}; + +struct compat_ipt_entry_target +{ + union { + struct { + u_int16_t target_size; + char name[IPT_FUNCTION_MAXNAMELEN]; + } user; + u_int16_t target_size; + } u; + unsigned char data[0]; +}; + +struct compat_ipt_standard_target +{ + struct compat_ipt_entry_target target; + compat_int_t verdict; +}; + +#define IPT_ST_OFFSET (sizeof(struct ipt_standard_target) - \ + sizeof(struct compat_ipt_standard_target)) + +struct ipt_standard +{ + struct ipt_entry entry; + struct ipt_standard_target target; +}; + +struct compat_ipt_standard +{ + struct compat_ipt_entry entry; + struct compat_ipt_standard_target target; +}; + struct compat_ipt_replace { char name[IPT_TABLE_MAXNAMELEN]; u32 valid_hooks; @@ -325,9 +370,123 @@ struct compat_ipt_replace { u32 underflow[NF_IP_NUMHOOKS]; u32 num_counters; compat_uptr_t counters; /* struct ipt_counters * */ - struct ipt_entry entries[0]; + struct compat_ipt_entry entries[0]; }; +static int calc_table_size(struct compat_ipt_entry __user *optval, int size) +{ + struct compat_ipt_entry __user *e; + struct compat_ipt_entry_target __user *t; + u_int16_t target_offset; + char name[IPT_FUNCTION_MAXNAMELEN]; + u_int16_t next; + int i, ret; + + for (i = 0, ret = size, next = 0; i < size; i += next) { + e = (void __user *)optval + i; + if (get_user(next, &e->next_offset)) + goto out; + if (get_user(target_offset, &e->target_offset)) + goto out; + t = (void __user *)e + target_offset; + if (__copy_from_user(name, &t->u.user.name, IPT_FUNCTION_MAXNAMELEN)) + goto out; + if (!strcmp(name, IPT_STANDARD_TARGET)) + ret += IPT_ST_OFFSET; + } + return ret; +out: + return -EFAULT; +} + +static int copy_entry(struct ipt_entry __user *e, + u_int16_t next, void __user **dstptr, compat_uint_t *size) +{ + struct compat_ipt_entry_target __user *t; + u_int16_t target_offset; + char name[IPT_FUNCTION_MAXNAMELEN]; + int ret; + + ret = -EFAULT; + if (__copy_in_user(*dstptr, e, next)) + goto out; + + if (get_user(target_offset, &e->target_offset)) + goto out; + t = (void __user *)e + target_offset; + if (__copy_from_user(name, &t->u.user.name, IPT_FUNCTION_MAXNAMELEN)) + goto out; + if (!strcmp(name, IPT_STANDARD_TARGET)) { + struct compat_ipt_standard_target compat_st; + struct ipt_standard_target st; + struct compat_ipt_entry etmp; + + next += IPT_ST_OFFSET; + if (__copy_from_user(&etmp, (void __user *)e, + sizeof(struct compat_ipt_entry))) + goto out; + etmp.next_offset = next; + if (__copy_to_user(*dstptr, &etmp, sizeof(struct compat_ipt_entry))) + goto out; + if (__copy_from_user(&compat_st, (void __user *)t, + sizeof(struct compat_ipt_standard_target))) + goto out; + memcpy(&st.target, &compat_st.target, + sizeof(struct ipt_target)); + st.verdict = compat_st.verdict; + st.target.u.user.target_size = + sizeof(struct ipt_standard_target); + if (__copy_to_user(*dstptr + target_offset, &st, + sizeof(struct ipt_standard_target))) + goto out; + *size += IPT_ST_OFFSET; + } + *dstptr += next; + ret = 0; +out: + return ret; +} + +static int compat_copy_entries(void __user *srcptr, + void __user *dstptr, compat_uint_t *size, + int (*copy_entry) (struct ipt_entry __user *, + u_int16_t, void __user **, compat_uint_t *)) +{ + struct ipt_entry __user *entry; + compat_uint_t origsize; + u_int16_t next; + int i, ret; + + origsize = *size; + for (i = 0, ret = 0, next = 0; i < origsize ; i += next) { + entry = (void __user *)srcptr + i; + if (get_user(next, &entry->next_offset)) + return -EFAULT; + ret = copy_entry(entry, next, &dstptr, size); + if (ret) + break; + if (!next) + break; + } + return ret; +} + +static int convert_hook(compat_uint_t __user *src, unsigned int __user *dst) +{ + compat_uint_t tmp; + + if (__get_user(tmp, src)) + return -EFAULT; + if (tmp && !(tmp % sizeof(struct compat_ipt_standard))) { + int k; + k = tmp / sizeof(struct compat_ipt_standard); + tmp += k * IPT_ST_OFFSET; + } + if (__put_user(tmp, dst)) + return -EFAULT; + return 0; +} + static int do_netfilter_replace(int fd, int level, int optname, char __user *optval, int optlen) { @@ -348,16 +507,11 @@ static int do_netfilter_replace(int fd, if (optlen != sizeof(*urepl) + origsize) return -ENOPROTOOPT; - /* XXX Assumes that size of ipt_entry is the same both in - * native and compat environments. - */ - repl_nat_size = sizeof(*repl_nat) + origsize; + repl_nat_size = calc_table_size(&urepl->entries[0], origsize); + repl_nat_size += sizeof(*repl_nat); repl_nat = compat_alloc_user_space(repl_nat_size); ret = -EFAULT; - if (put_user(origsize, &repl_nat->size)) - goto out; - if (!access_ok(VERIFY_READ, urepl, optlen) || !access_ok(VERIFY_WRITE, repl_nat, optlen)) goto out; @@ -382,19 +536,21 @@ static int do_netfilter_replace(int fd, __put_user(compat_ptr(ucntrs), &repl_nat->counters)) goto out; - if (__copy_in_user(&repl_nat->entries[0], - &urepl->entries[0], - origsize)) + if (compat_copy_entries(&urepl->entries[0], + &repl_nat->entries[0], &origsize, copy_entry)) + goto out; + + if (put_user(origsize, &repl_nat->size)) goto out; for (i = 0; i < NF_IP_NUMHOOKS; i++) { - if (__get_user(tmp32, &urepl->hook_entry[i]) || - __put_user(tmp32, &repl_nat->hook_entry[i]) || - __get_user(tmp32, &urepl->underflow[i]) || - __put_user(tmp32, &repl_nat->underflow[i])) + if (convert_hook(&urepl->hook_entry[i], &repl_nat->hook_entry[i]) || + convert_hook(&urepl->underflow[i], &repl_nat->underflow[i])) goto out; } + if (repl_nat_size != origsize + sizeof(*repl_nat)) + printk("WARNING: %s: wrong size calculation!\n", __FUNCTION__); /* * Since struct ipt_counters just contains two u_int64_t members * we can just do the access_ok check here and pass the (converted) @@ -413,6 +569,45 @@ out: return ret; } +struct compat_ipt_counters_info +{ + char name[IPT_TABLE_MAXNAMELEN]; + compat_uint_t num_counters; + struct compat_ipt_counters counters[0]; +}; + +static int do_set_add_counters(int fd, int level, int optname, + char __user *optval, int optlen) +{ + long ret; + mm_segment_t old_fs; + struct ipt_counters_info *tmp; + struct compat_ipt_counters_info *user; + + user = (struct compat_ipt_counters_info *)optval; + tmp = (struct ipt_counters_info*) vmalloc(optlen + 4); + if (tmp == NULL) + return -ENOMEM; + + ret = -EFAULT; + if (copy_from_user(tmp, user, sizeof(*user))!= 0) + goto out; + if (copy_from_user(tmp->counters, user + sizeof(*user), + optlen - sizeof(*user)) != 0) + goto out; + + optlen += 4; + old_fs = get_fs(); + set_fs(KERNEL_DS); + ret = sys_setsockopt(fd, level, optname, (char *)tmp, optlen); + set_fs(old_fs); + +out: + vfree(tmp); + + return ret; +} + /* * A struct sock_filter is architecture independent. */ @@ -473,6 +668,9 @@ asmlinkage long compat_sys_setsockopt(in optval, optlen); if (optname == SO_RCVTIMEO || optname == SO_SNDTIMEO) return do_set_sock_timeout(fd, level, optname, optval, optlen); + if (level == SOL_IP && optname == IPT_SO_SET_ADD_COUNTERS) + return do_set_add_counters(fd, level, optname, + optval, optlen); return sys_setsockopt(fd, level, optname, optval, optlen); } @@ -506,11 +704,199 @@ static int do_get_sock_timeout(int fd, i return err; } +struct compat_ipt_get_entries +{ + char name[IPT_TABLE_MAXNAMELEN]; + compat_uint_t size; + struct compat_ipt_entry entrytable[0]; +}; + +struct compat_ipt_getinfo +{ + char name[IPT_TABLE_MAXNAMELEN]; + compat_uint_t valid_hooks; + compat_uint_t hook_entry[NF_IP_NUMHOOKS]; + compat_uint_t underflow[NF_IP_NUMHOOKS]; + compat_uint_t num_entries; + compat_uint_t size; +}; + +static int compat_convert_hook(unsigned int src, compat_uint_t __user *dst) +{ + if (src && !(src % sizeof(struct ipt_standard))) { + int k; + k = src / sizeof(struct ipt_standard); + src -= k * IPT_ST_OFFSET; + } + if (__put_user(src, dst)) + return -EFAULT; + return 0; +} + +static long do_ipt_get_info(int fd, int level, int optname, + char __user *optval, int __user *optlen) +{ + struct compat_ipt_getinfo __user *user; + struct ipt_getinfo info; + mm_segment_t old_fs; + int len, ret, i; + + user = (struct compat_ipt_getinfo __user *)optval; + ret = -EFAULT; + if (__copy_from_user(info.name, user->name, sizeof(user->name))) + goto out; + + len = sizeof(struct ipt_getinfo); + old_fs = get_fs(); + set_fs(KERNEL_DS); + ret = sys_getsockopt(fd, level, optname, (char __user *)&info, &len); + set_fs(old_fs); + if (ret == 0) { + ret = -EFAULT; + if (__copy_to_user(user->name, info.name, sizeof(info.name))) + goto out; + if (__put_user(info.valid_hooks, &user->valid_hooks)) + goto out; + if (__put_user(info.num_entries, &user->num_entries)) + goto out; + if (__put_user(info.size, &user->size)) + goto out; + + for (i = 0; i < NF_IP_NUMHOOKS; i++) { + ret = compat_convert_hook(info.hook_entry[i], + &user->hook_entry[i]); + if (ret) + break; + ret = compat_convert_hook(info.underflow[i], + &user->underflow[i]); + if (ret) + break; + } + ret = 0; + } +out: + return ret; +} + +static int compat_copy_entry(struct ipt_entry __user *e, + u_int16_t next, void __user **dstptr, compat_uint_t *size) +{ + struct ipt_entry_target __user *t; + u_int16_t target_offset; + char name[IPT_FUNCTION_MAXNAMELEN]; + int ret; + + ret = -EFAULT; + if (__copy_in_user(*dstptr, e, next)) + goto out; + + if (get_user(target_offset, &e->target_offset)) + goto out; + t = (void __user *)e + target_offset; + if (__copy_from_user(name, &t->u.user.name, IPT_FUNCTION_MAXNAMELEN)) + goto out; + if (!strcmp(name, IPT_STANDARD_TARGET)) { + struct compat_ipt_standard_target compat_st; + struct ipt_standard_target st; + struct ipt_entry etmp; + + next -= IPT_ST_OFFSET; + if (__copy_from_user(&etmp, (void __user *)e, + sizeof(struct ipt_entry))) + goto out; + etmp.next_offset = next; + if (__copy_to_user(*dstptr, &etmp, sizeof(struct ipt_entry))) + goto out; + if (__copy_from_user(&st, (void __user *)t, + sizeof(struct ipt_standard_target))) + goto out; + memcpy(&compat_st.target, &st.target, + sizeof(struct ipt_target)); + compat_st.verdict = st.verdict; + compat_st.target.u.user.target_size = + sizeof(struct compat_ipt_standard_target); + if (__copy_to_user(*dstptr + target_offset, &compat_st, + sizeof(struct compat_ipt_standard_target))) + goto out; + *size -= IPT_ST_OFFSET; + } + *dstptr += next; + ret = 0; +out: + return ret; +} + +static long do_ipt_get_entries(int fd, int level, int optname, + char __user *optval, int __user *optlen) +{ + struct compat_ipt_get_entries __user *user; + struct ipt_get_entries __user *get; + long ret; + int len, off; + compat_uint_t size; + + if (get_user(len, optlen)) + return -EFAULT; + + if (len < sizeof(get)) + return -EINVAL; + + user = (struct compat_ipt_get_entries *)optval; + + off = sizeof(struct ipt_get_entries) - + sizeof(struct compat_ipt_get_entries); + get = compat_alloc_user_space(len + off); + if (get == NULL) + return -ENOMEM; + + ret = -EFAULT; + if (!access_ok(VERIFY_READ, user, len) || + !access_ok(VERIFY_READ, get, len+off) || + !access_ok(VERIFY_WRITE, get, len+off)) + goto out; + + if (__copy_in_user(get, user, sizeof(struct compat_ipt_get_entries))) + goto out; + if (__copy_in_user(get->entrytable, + user + sizeof(struct compat_ipt_get_entries), + len - sizeof(struct compat_ipt_get_entries))) + goto out; + + len += off; + if (put_user(len, optlen)) + goto out; + + ret = sys_getsockopt(fd, level, optname, (char __user *)get, optlen); + if (ret == 0) { + ret = -EFAULT; + if (__copy_in_user(user, get, + sizeof(struct compat_ipt_get_entries))) + goto out; + if (get_user(size, &user->size)) + goto out; + if (compat_copy_entries(get->entrytable, user->entrytable, + &size, compat_copy_entry)) + goto out; + if (put_user(size, &user->size)) + goto out; + len = size + sizeof(struct compat_ipt_get_entries); + if (put_user(len, optlen)) + goto out; + ret = 0; + } +out: + return ret; +} + asmlinkage long compat_sys_getsockopt(int fd, int level, int optname, char __user *optval, int __user *optlen) { if (optname == SO_RCVTIMEO || optname == SO_SNDTIMEO) return do_get_sock_timeout(fd, level, optname, optval, optlen); + if (level == SOL_IP && optname == IPT_SO_GET_INFO) + return do_ipt_get_info(fd, level, optname, optval, optlen); + if (level == SOL_IP && optname == IPT_SO_GET_ENTRIES) + return do_ipt_get_entries(fd, level, optname, optval, optlen); return sys_getsockopt(fd, level, optname, optval, optlen); }