Commit 2722971c authored by Dmitry Mishin's avatar Dmitry Mishin Committed by David S. Miller

[NETFILTER]: iptables 32bit compat layer

This patch extends current iptables compatibility layer in order to get
32bit iptables to work on 64bit kernel. Current layer is insufficient due
to alignment checks both in kernel and user space tools.

Patch is for current net-2.6.17 with addition of move of ipt_entry_{match|
target} definitions to xt_entry_{match|target}.
Signed-off-by: default avatarDmitry Mishin <dim@openvz.org>
Acked-off-by: default avatarKirill Korotaev <dev@openvz.org>
Signed-off-by: default avatarPatrick McHardy <kaber@trash.net>
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parent e64a70be
...@@ -142,6 +142,12 @@ struct xt_counters_info ...@@ -142,6 +142,12 @@ struct xt_counters_info
#define ASSERT_WRITE_LOCK(x) #define ASSERT_WRITE_LOCK(x)
#include <linux/netfilter_ipv4/listhelp.h> #include <linux/netfilter_ipv4/listhelp.h>
#ifdef CONFIG_COMPAT
#define COMPAT_TO_USER 1
#define COMPAT_FROM_USER -1
#define COMPAT_CALC_SIZE 0
#endif
struct xt_match struct xt_match
{ {
struct list_head list; struct list_head list;
...@@ -175,6 +181,9 @@ struct xt_match ...@@ -175,6 +181,9 @@ struct xt_match
void (*destroy)(const struct xt_match *match, void *matchinfo, void (*destroy)(const struct xt_match *match, void *matchinfo,
unsigned int matchinfosize); unsigned int matchinfosize);
/* Called when userspace align differs from kernel space one */
int (*compat)(void *match, void **dstptr, int *size, int convert);
/* Set this to THIS_MODULE if you are a module, otherwise NULL */ /* Set this to THIS_MODULE if you are a module, otherwise NULL */
struct module *me; struct module *me;
...@@ -220,6 +229,9 @@ struct xt_target ...@@ -220,6 +229,9 @@ struct xt_target
void (*destroy)(const struct xt_target *target, void *targinfo, void (*destroy)(const struct xt_target *target, void *targinfo,
unsigned int targinfosize); unsigned int targinfosize);
/* Called when userspace align differs from kernel space one */
int (*compat)(void *target, void **dstptr, int *size, int convert);
/* Set this to THIS_MODULE if you are a module, otherwise NULL */ /* Set this to THIS_MODULE if you are a module, otherwise NULL */
struct module *me; struct module *me;
...@@ -314,6 +326,61 @@ extern void xt_proto_fini(int af); ...@@ -314,6 +326,61 @@ extern void xt_proto_fini(int af);
extern struct xt_table_info *xt_alloc_table_info(unsigned int size); extern struct xt_table_info *xt_alloc_table_info(unsigned int size);
extern void xt_free_table_info(struct xt_table_info *info); extern void xt_free_table_info(struct xt_table_info *info);
#ifdef CONFIG_COMPAT
#include <net/compat.h>
struct compat_xt_entry_match
{
union {
struct {
u_int16_t match_size;
char name[XT_FUNCTION_MAXNAMELEN - 1];
u_int8_t revision;
} user;
u_int16_t match_size;
} u;
unsigned char data[0];
};
struct compat_xt_entry_target
{
union {
struct {
u_int16_t target_size;
char name[XT_FUNCTION_MAXNAMELEN - 1];
u_int8_t revision;
} user;
u_int16_t target_size;
} u;
unsigned char data[0];
};
/* FIXME: this works only on 32 bit tasks
* need to change whole approach in order to calculate align as function of
* current task alignment */
struct compat_xt_counters
{
u_int32_t cnt[4];
};
struct compat_xt_counters_info
{
char name[XT_TABLE_MAXNAMELEN];
compat_uint_t num_counters;
struct compat_xt_counters counters[0];
};
#define COMPAT_XT_ALIGN(s) (((s) + (__alignof__(struct compat_xt_counters)-1)) \
& ~(__alignof__(struct compat_xt_counters)-1))
extern void xt_compat_lock(int af);
extern void xt_compat_unlock(int af);
extern int xt_compat_match(void *match, void **dstptr, int *size, int convert);
extern int xt_compat_target(void *target, void **dstptr, int *size,
int convert);
#endif /* CONFIG_COMPAT */
#endif /* __KERNEL__ */ #endif /* __KERNEL__ */
#endif /* _X_TABLES_H */ #endif /* _X_TABLES_H */
...@@ -316,5 +316,23 @@ extern unsigned int ipt_do_table(struct sk_buff **pskb, ...@@ -316,5 +316,23 @@ extern unsigned int ipt_do_table(struct sk_buff **pskb,
void *userdata); void *userdata);
#define IPT_ALIGN(s) XT_ALIGN(s) #define IPT_ALIGN(s) XT_ALIGN(s)
#ifdef CONFIG_COMPAT
#include <net/compat.h>
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_xt_counters counters;
unsigned char elems[0];
};
#define COMPAT_IPT_ALIGN(s) COMPAT_XT_ALIGN(s)
#endif /* CONFIG_COMPAT */
#endif /*__KERNEL__*/ #endif /*__KERNEL__*/
#endif /* _IPTABLES_H */ #endif /* _IPTABLES_H */
...@@ -476,8 +476,7 @@ asmlinkage long compat_sys_setsockopt(int fd, int level, int optname, ...@@ -476,8 +476,7 @@ asmlinkage long compat_sys_setsockopt(int fd, int level, int optname,
int err; int err;
struct socket *sock; struct socket *sock;
/* SO_SET_REPLACE seems to be the same in all levels */ if (level == SOL_IPV6 && optname == IPT_SO_SET_REPLACE)
if (optname == IPT_SO_SET_REPLACE)
return do_netfilter_replace(fd, level, optname, return do_netfilter_replace(fd, level, optname,
optval, optlen); optval, optlen);
......
...@@ -24,6 +24,7 @@ ...@@ -24,6 +24,7 @@
#include <linux/module.h> #include <linux/module.h>
#include <linux/icmp.h> #include <linux/icmp.h>
#include <net/ip.h> #include <net/ip.h>
#include <net/compat.h>
#include <asm/uaccess.h> #include <asm/uaccess.h>
#include <linux/mutex.h> #include <linux/mutex.h>
#include <linux/proc_fs.h> #include <linux/proc_fs.h>
...@@ -799,17 +800,11 @@ get_counters(const struct xt_table_info *t, ...@@ -799,17 +800,11 @@ get_counters(const struct xt_table_info *t,
} }
} }
static int static inline struct xt_counters * alloc_counters(struct ipt_table *table)
copy_entries_to_user(unsigned int total_size,
struct ipt_table *table,
void __user *userptr)
{ {
unsigned int off, num, countersize; unsigned int countersize;
struct ipt_entry *e;
struct xt_counters *counters; struct xt_counters *counters;
struct xt_table_info *private = table->private; struct xt_table_info *private = table->private;
int ret = 0;
void *loc_cpu_entry;
/* We need atomic snapshot of counters: rest doesn't change /* We need atomic snapshot of counters: rest doesn't change
(other than comefrom, which userspace doesn't care (other than comefrom, which userspace doesn't care
...@@ -818,13 +813,32 @@ copy_entries_to_user(unsigned int total_size, ...@@ -818,13 +813,32 @@ copy_entries_to_user(unsigned int total_size,
counters = vmalloc_node(countersize, numa_node_id()); counters = vmalloc_node(countersize, numa_node_id());
if (counters == NULL) if (counters == NULL)
return -ENOMEM; return ERR_PTR(-ENOMEM);
/* First, sum counters... */ /* First, sum counters... */
write_lock_bh(&table->lock); write_lock_bh(&table->lock);
get_counters(private, counters); get_counters(private, counters);
write_unlock_bh(&table->lock); write_unlock_bh(&table->lock);
return counters;
}
static int
copy_entries_to_user(unsigned int total_size,
struct ipt_table *table,
void __user *userptr)
{
unsigned int off, num;
struct ipt_entry *e;
struct xt_counters *counters;
struct xt_table_info *private = table->private;
int ret = 0;
void *loc_cpu_entry;
counters = alloc_counters(table);
if (IS_ERR(counters))
return PTR_ERR(counters);
/* choose the copy that is on our node/cpu, ... /* choose the copy that is on our node/cpu, ...
* This choice is lazy (because current thread is * This choice is lazy (because current thread is
* allowed to migrate to another cpu) * allowed to migrate to another cpu)
...@@ -884,44 +898,899 @@ copy_entries_to_user(unsigned int total_size, ...@@ -884,44 +898,899 @@ copy_entries_to_user(unsigned int total_size,
return ret; return ret;
} }
#ifdef CONFIG_COMPAT
struct compat_delta {
struct compat_delta *next;
u_int16_t offset;
short delta;
};
static struct compat_delta *compat_offsets = NULL;
static int compat_add_offset(u_int16_t offset, short delta)
{
struct compat_delta *tmp;
tmp = kmalloc(sizeof(struct compat_delta), GFP_KERNEL);
if (!tmp)
return -ENOMEM;
tmp->offset = offset;
tmp->delta = delta;
if (compat_offsets) {
tmp->next = compat_offsets->next;
compat_offsets->next = tmp;
} else {
compat_offsets = tmp;
tmp->next = NULL;
}
return 0;
}
static void compat_flush_offsets(void)
{
struct compat_delta *tmp, *next;
if (compat_offsets) {
for(tmp = compat_offsets; tmp; tmp = next) {
next = tmp->next;
kfree(tmp);
}
compat_offsets = NULL;
}
}
static short compat_calc_jump(u_int16_t offset)
{
struct compat_delta *tmp;
short delta;
for(tmp = compat_offsets, delta = 0; tmp; tmp = tmp->next)
if (tmp->offset < offset)
delta += tmp->delta;
return delta;
}
struct compat_ipt_standard_target
{
struct compat_xt_entry_target target;
compat_int_t verdict;
};
#define IPT_ST_OFFSET (sizeof(struct ipt_standard_target) - \
sizeof(struct compat_ipt_standard_target))
struct compat_ipt_standard
{
struct compat_ipt_entry entry;
struct compat_ipt_standard_target target;
};
static int compat_ipt_standard_fn(void *target,
void **dstptr, int *size, int convert)
{
struct compat_ipt_standard_target compat_st, *pcompat_st;
struct ipt_standard_target st, *pst;
int ret;
ret = 0;
switch (convert) {
case COMPAT_TO_USER:
pst = (struct ipt_standard_target *)target;
memcpy(&compat_st.target, &pst->target,
sizeof(struct ipt_entry_target));
compat_st.verdict = pst->verdict;
if (compat_st.verdict > 0)
compat_st.verdict -=
compat_calc_jump(compat_st.verdict);
compat_st.target.u.user.target_size =
sizeof(struct compat_ipt_standard_target);
if (__copy_to_user(*dstptr, &compat_st,
sizeof(struct compat_ipt_standard_target)))
ret = -EFAULT;
*size -= IPT_ST_OFFSET;
*dstptr += sizeof(struct compat_ipt_standard_target);
break;
case COMPAT_FROM_USER:
pcompat_st =
(struct compat_ipt_standard_target *)target;
memcpy(&st.target, &pcompat_st->target,
sizeof(struct ipt_entry_target));
st.verdict = pcompat_st->verdict;
if (st.verdict > 0)
st.verdict += compat_calc_jump(st.verdict);
st.target.u.user.target_size =
sizeof(struct ipt_standard_target);
memcpy(*dstptr, &st,
sizeof(struct ipt_standard_target));
*size += IPT_ST_OFFSET;
*dstptr += sizeof(struct ipt_standard_target);
break;
case COMPAT_CALC_SIZE:
*size += IPT_ST_OFFSET;
break;
default:
ret = -ENOPROTOOPT;
break;
}
return ret;
}
static inline int
compat_calc_match(struct ipt_entry_match *m, int * size)
{
if (m->u.kernel.match->compat)
m->u.kernel.match->compat(m, NULL, size, COMPAT_CALC_SIZE);
else
xt_compat_match(m, NULL, size, COMPAT_CALC_SIZE);
return 0;
}
static int compat_calc_entry(struct ipt_entry *e, struct xt_table_info *info,
void *base, struct xt_table_info *newinfo)
{
struct ipt_entry_target *t;
u_int16_t entry_offset;
int off, i, ret;
off = 0;
entry_offset = (void *)e - base;
IPT_MATCH_ITERATE(e, compat_calc_match, &off);
t = ipt_get_target(e);
if (t->u.kernel.target->compat)
t->u.kernel.target->compat(t, NULL, &off, COMPAT_CALC_SIZE);
else
xt_compat_target(t, NULL, &off, COMPAT_CALC_SIZE);
newinfo->size -= off;
ret = compat_add_offset(entry_offset, off);
if (ret)
return ret;
for (i = 0; i< NF_IP_NUMHOOKS; i++) {
if (info->hook_entry[i] && (e < (struct ipt_entry *)
(base + info->hook_entry[i])))
newinfo->hook_entry[i] -= off;
if (info->underflow[i] && (e < (struct ipt_entry *)
(base + info->underflow[i])))
newinfo->underflow[i] -= off;
}
return 0;
}
static int compat_table_info(struct xt_table_info *info,
struct xt_table_info *newinfo)
{
void *loc_cpu_entry;
int i;
if (!newinfo || !info)
return -EINVAL;
memset(newinfo, 0, sizeof(struct xt_table_info));
newinfo->size = info->size;
newinfo->number = info->number;
for (i = 0; i < NF_IP_NUMHOOKS; i++) {
newinfo->hook_entry[i] = info->hook_entry[i];
newinfo->underflow[i] = info->underflow[i];
}
loc_cpu_entry = info->entries[raw_smp_processor_id()];
return IPT_ENTRY_ITERATE(loc_cpu_entry, info->size,
compat_calc_entry, info, loc_cpu_entry, newinfo);
}
#endif
static int get_info(void __user *user, int *len, int compat)
{
char name[IPT_TABLE_MAXNAMELEN];
struct ipt_table *t;
int ret;
if (*len != sizeof(struct ipt_getinfo)) {
duprintf("length %u != %u\n", *len,
(unsigned int)sizeof(struct ipt_getinfo));
return -EINVAL;
}
if (copy_from_user(name, user, sizeof(name)) != 0)
return -EFAULT;
name[IPT_TABLE_MAXNAMELEN-1] = '\0';
#ifdef CONFIG_COMPAT
if (compat)
xt_compat_lock(AF_INET);
#endif
t = try_then_request_module(xt_find_table_lock(AF_INET, name),
"iptable_%s", name);
if (t && !IS_ERR(t)) {
struct ipt_getinfo info;
struct xt_table_info *private = t->private;
#ifdef CONFIG_COMPAT
if (compat) {
struct xt_table_info tmp;
ret = compat_table_info(private, &tmp);
compat_flush_offsets();
private = &tmp;
}
#endif
info.valid_hooks = t->valid_hooks;
memcpy(info.hook_entry, private->hook_entry,
sizeof(info.hook_entry));
memcpy(info.underflow, private->underflow,
sizeof(info.underflow));
info.num_entries = private->number;
info.size = private->size;
strcpy(info.name, name);
if (copy_to_user(user, &info, *len) != 0)
ret = -EFAULT;
else
ret = 0;
xt_table_unlock(t);
module_put(t->me);
} else
ret = t ? PTR_ERR(t) : -ENOENT;
#ifdef CONFIG_COMPAT
if (compat)
xt_compat_unlock(AF_INET);
#endif
return ret;
}
static int
get_entries(struct ipt_get_entries __user *uptr, int *len)
{
int ret;
struct ipt_get_entries get;
struct ipt_table *t;
if (*len < sizeof(get)) {
duprintf("get_entries: %u < %d\n", *len,
(unsigned int)sizeof(get));
return -EINVAL;
}
if (copy_from_user(&get, uptr, sizeof(get)) != 0)
return -EFAULT;
if (*len != sizeof(struct ipt_get_entries) + get.size) {
duprintf("get_entries: %u != %u\n", *len,
(unsigned int)(sizeof(struct ipt_get_entries) +
get.size));
return -EINVAL;
}
t = xt_find_table_lock(AF_INET, get.name);
if (t && !IS_ERR(t)) {
struct xt_table_info *private = t->private;
duprintf("t->private->number = %u\n",
private->number);
if (get.size == private->size)
ret = copy_entries_to_user(private->size,
t, uptr->entrytable);
else {
duprintf("get_entries: I've got %u not %u!\n",
private->size,
get.size);
ret = -EINVAL;
}
module_put(t->me);
xt_table_unlock(t);
} else
ret = t ? PTR_ERR(t) : -ENOENT;
return ret;
}
static int
__do_replace(const char *name, unsigned int valid_hooks,
struct xt_table_info *newinfo, unsigned int num_counters,
void __user *counters_ptr)
{
int ret;
struct ipt_table *t;
struct xt_table_info *oldinfo;
struct xt_counters *counters;
void *loc_cpu_old_entry;
ret = 0;
counters = vmalloc(num_counters * sizeof(struct xt_counters));
if (!counters) {
ret = -ENOMEM;
goto out;
}
t = try_then_request_module(xt_find_table_lock(AF_INET, name),
"iptable_%s", name);
if (!t || IS_ERR(t)) {
ret = t ? PTR_ERR(t) : -ENOENT;
goto free_newinfo_counters_untrans;
}
/* You lied! */
if (valid_hooks != t->valid_hooks) {
duprintf("Valid hook crap: %08X vs %08X\n",
valid_hooks, t->valid_hooks);
ret = -EINVAL;
goto put_module;
}
oldinfo = xt_replace_table(t, num_counters, newinfo, &ret);
if (!oldinfo)
goto put_module;
/* Update module usage count based on number of rules */
duprintf("do_replace: oldnum=%u, initnum=%u, newnum=%u\n",
oldinfo->number, oldinfo->initial_entries, newinfo->number);
if ((oldinfo->number > oldinfo->initial_entries) ||
(newinfo->number <= oldinfo->initial_entries))
module_put(t->me);
if ((oldinfo->number > oldinfo->initial_entries) &&
(newinfo->number <= oldinfo->initial_entries))
module_put(t->me);
/* Get the old counters. */
get_counters(oldinfo, counters);
/* Decrease module usage counts and free resource */
loc_cpu_old_entry = oldinfo->entries[raw_smp_processor_id()];
IPT_ENTRY_ITERATE(loc_cpu_old_entry, oldinfo->size, cleanup_entry,NULL);
xt_free_table_info(oldinfo);
if (copy_to_user(counters_ptr, counters,
sizeof(struct xt_counters) * num_counters) != 0)
ret = -EFAULT;
vfree(counters);
xt_table_unlock(t);
return ret;
put_module:
module_put(t->me);
xt_table_unlock(t);
free_newinfo_counters_untrans:
vfree(counters);
out:
return ret;
}
static int
do_replace(void __user *user, unsigned int len)
{
int ret;
struct ipt_replace tmp;
struct xt_table_info *newinfo;
void *loc_cpu_entry;
if (copy_from_user(&tmp, user, sizeof(tmp)) != 0)
return -EFAULT;
/* Hack: Causes ipchains to give correct error msg --RR */
if (len != sizeof(tmp) + tmp.size)
return -ENOPROTOOPT;
/* overflow check */
if (tmp.size >= (INT_MAX - sizeof(struct xt_table_info)) / NR_CPUS -
SMP_CACHE_BYTES)
return -ENOMEM;
if (tmp.num_counters >= INT_MAX / sizeof(struct xt_counters))
return -ENOMEM;
newinfo = xt_alloc_table_info(tmp.size);
if (!newinfo)
return -ENOMEM;
/* choose the copy that is our node/cpu */
loc_cpu_entry = newinfo->entries[raw_smp_processor_id()];
if (copy_from_user(loc_cpu_entry, user + sizeof(tmp),
tmp.size) != 0) {
ret = -EFAULT;
goto free_newinfo;
}
ret = translate_table(tmp.name, tmp.valid_hooks,
newinfo, loc_cpu_entry, tmp.size, tmp.num_entries,
tmp.hook_entry, tmp.underflow);
if (ret != 0)
goto free_newinfo;
duprintf("ip_tables: Translated table\n");
ret = __do_replace(tmp.name, tmp.valid_hooks,
newinfo, tmp.num_counters,
tmp.counters);
if (ret)
goto free_newinfo_untrans;
return 0;
free_newinfo_untrans:
IPT_ENTRY_ITERATE(loc_cpu_entry, newinfo->size, cleanup_entry,NULL);
free_newinfo:
xt_free_table_info(newinfo);
return ret;
}
/* We're lazy, and add to the first CPU; overflow works its fey magic
* and everything is OK. */
static inline int
add_counter_to_entry(struct ipt_entry *e,
const struct xt_counters addme[],
unsigned int *i)
{
#if 0
duprintf("add_counter: Entry %u %lu/%lu + %lu/%lu\n",
*i,
(long unsigned int)e->counters.pcnt,
(long unsigned int)e->counters.bcnt,
(long unsigned int)addme[*i].pcnt,
(long unsigned int)addme[*i].bcnt);
#endif
ADD_COUNTER(e->counters, addme[*i].bcnt, addme[*i].pcnt);
(*i)++;
return 0;
}
static int
do_add_counters(void __user *user, unsigned int len, int compat)
{
unsigned int i;
struct xt_counters_info tmp;
struct xt_counters *paddc;
unsigned int num_counters;
char *name;
int size;
void *ptmp;
struct ipt_table *t;
struct xt_table_info *private;
int ret = 0;
void *loc_cpu_entry;
#ifdef CONFIG_COMPAT
struct compat_xt_counters_info compat_tmp;
if (compat) {
ptmp = &compat_tmp;
size = sizeof(struct compat_xt_counters_info);
} else
#endif
{
ptmp = &tmp;
size = sizeof(struct xt_counters_info);
}
if (copy_from_user(ptmp, user, size) != 0)
return -EFAULT;
#ifdef CONFIG_COMPAT
if (compat) {
num_counters = compat_tmp.num_counters;
name = compat_tmp.name;
} else
#endif
{
num_counters = tmp.num_counters;
name = tmp.name;
}
if (len != size + num_counters * sizeof(struct xt_counters))
return -EINVAL;
paddc = vmalloc_node(len - size, numa_node_id());
if (!paddc)
return -ENOMEM;
if (copy_from_user(paddc, user + size, len - size) != 0) {
ret = -EFAULT;
goto free;
}
t = xt_find_table_lock(AF_INET, name);
if (!t || IS_ERR(t)) {
ret = t ? PTR_ERR(t) : -ENOENT;
goto free;
}
write_lock_bh(&t->lock);
private = t->private;
if (private->number != num_counters) {
ret = -EINVAL;
goto unlock_up_free;
}
i = 0;
/* Choose the copy that is on our node */
loc_cpu_entry = private->entries[raw_smp_processor_id()];
IPT_ENTRY_ITERATE(loc_cpu_entry,
private->size,
add_counter_to_entry,
paddc,
&i);
unlock_up_free:
write_unlock_bh(&t->lock);
xt_table_unlock(t);
module_put(t->me);
free:
vfree(paddc);
return ret;
}
#ifdef CONFIG_COMPAT
struct compat_ipt_replace {
char name[IPT_TABLE_MAXNAMELEN];
u32 valid_hooks;
u32 num_entries;
u32 size;
u32 hook_entry[NF_IP_NUMHOOKS];
u32 underflow[NF_IP_NUMHOOKS];
u32 num_counters;
compat_uptr_t counters; /* struct ipt_counters * */
struct compat_ipt_entry entries[0];
};
static inline int compat_copy_match_to_user(struct ipt_entry_match *m,
void __user **dstptr, compat_uint_t *size)
{
if (m->u.kernel.match->compat)
return m->u.kernel.match->compat(m, dstptr, size,
COMPAT_TO_USER);
else
return xt_compat_match(m, dstptr, size, COMPAT_TO_USER);
}
static int compat_copy_entry_to_user(struct ipt_entry *e,
void __user **dstptr, compat_uint_t *size)
{
struct ipt_entry_target __user *t;
struct compat_ipt_entry __user *ce;
u_int16_t target_offset, next_offset;
compat_uint_t origsize;
int ret;
ret = -EFAULT;
origsize = *size;
ce = (struct compat_ipt_entry __user *)*dstptr;
if (__copy_to_user(ce, e, sizeof(struct ipt_entry)))
goto out;
*dstptr += sizeof(struct compat_ipt_entry);
ret = IPT_MATCH_ITERATE(e, compat_copy_match_to_user, dstptr, size);
target_offset = e->target_offset - (origsize - *size);
if (ret)
goto out;
t = ipt_get_target(e);
if (t->u.kernel.target->compat)
ret = t->u.kernel.target->compat(t, dstptr, size,
COMPAT_TO_USER);
else
ret = xt_compat_target(t, dstptr, size, COMPAT_TO_USER);
if (ret)
goto out;
ret = -EFAULT;
next_offset = e->next_offset - (origsize - *size);
if (__put_user(target_offset, &ce->target_offset))
goto out;
if (__put_user(next_offset, &ce->next_offset))
goto out;
return 0;
out:
return ret;
}
static inline int
compat_check_calc_match(struct ipt_entry_match *m,
const char *name,
const struct ipt_ip *ip,
unsigned int hookmask,
int *size, int *i)
{
struct ipt_match *match;
match = try_then_request_module(xt_find_match(AF_INET, m->u.user.name,
m->u.user.revision),
"ipt_%s", m->u.user.name);
if (IS_ERR(match) || !match) {
duprintf("compat_check_calc_match: `%s' not found\n",
m->u.user.name);
return match ? PTR_ERR(match) : -ENOENT;
}
m->u.kernel.match = match;
if (m->u.kernel.match->compat)
m->u.kernel.match->compat(m, NULL, size, COMPAT_CALC_SIZE);
else
xt_compat_match(m, NULL, size, COMPAT_CALC_SIZE);
(*i)++;
return 0;
}
static inline int
check_compat_entry_size_and_hooks(struct ipt_entry *e,
struct xt_table_info *newinfo,
unsigned int *size,
unsigned char *base,
unsigned char *limit,
unsigned int *hook_entries,
unsigned int *underflows,
unsigned int *i,
const char *name)
{
struct ipt_entry_target *t;
struct ipt_target *target;
u_int16_t entry_offset;
int ret, off, h, j;
duprintf("check_compat_entry_size_and_hooks %p\n", e);
if ((unsigned long)e % __alignof__(struct compat_ipt_entry) != 0
|| (unsigned char *)e + sizeof(struct compat_ipt_entry) >= limit) {
duprintf("Bad offset %p, limit = %p\n", e, limit);
return -EINVAL;
}
if (e->next_offset < sizeof(struct compat_ipt_entry) +
sizeof(struct compat_xt_entry_target)) {
duprintf("checking: element %p size %u\n",
e, e->next_offset);
return -EINVAL;
}
if (!ip_checkentry(&e->ip)) {
duprintf("ip_tables: ip check failed %p %s.\n", e, name);
return -EINVAL;
}
off = 0;
entry_offset = (void *)e - (void *)base;
j = 0;
ret = IPT_MATCH_ITERATE(e, compat_check_calc_match, name, &e->ip,
e->comefrom, &off, &j);
if (ret != 0)
goto out;
t = ipt_get_target(e);
target = try_then_request_module(xt_find_target(AF_INET,
t->u.user.name,
t->u.user.revision),
"ipt_%s", t->u.user.name);
if (IS_ERR(target) || !target) {
duprintf("check_entry: `%s' not found\n", t->u.user.name);
ret = target ? PTR_ERR(target) : -ENOENT;
goto out;
}
t->u.kernel.target = target;
if (t->u.kernel.target->compat)
t->u.kernel.target->compat(t, NULL, &off, COMPAT_CALC_SIZE);
else
xt_compat_target(t, NULL, &off, COMPAT_CALC_SIZE);
*size += off;
ret = compat_add_offset(entry_offset, off);
if (ret)
goto out;
/* Check hooks & underflows */
for (h = 0; h < NF_IP_NUMHOOKS; h++) {
if ((unsigned char *)e - base == hook_entries[h])
newinfo->hook_entry[h] = hook_entries[h];
if ((unsigned char *)e - base == underflows[h])
newinfo->underflow[h] = underflows[h];
}
/* Clear counters and comefrom */
e->counters = ((struct ipt_counters) { 0, 0 });
e->comefrom = 0;
(*i)++;
return 0;
out:
IPT_MATCH_ITERATE(e, cleanup_match, &j);
return ret;
}
static inline int compat_copy_match_from_user(struct ipt_entry_match *m,
void **dstptr, compat_uint_t *size, const char *name,
const struct ipt_ip *ip, unsigned int hookmask)
{
struct ipt_entry_match *dm;
struct ipt_match *match;
int ret;
dm = (struct ipt_entry_match *)*dstptr;
match = m->u.kernel.match;
if (match->compat)
match->compat(m, dstptr, size, COMPAT_FROM_USER);
else
xt_compat_match(m, dstptr, size, COMPAT_FROM_USER);
ret = xt_check_match(match, AF_INET, dm->u.match_size - sizeof(*dm),
name, hookmask, ip->proto,
ip->invflags & IPT_INV_PROTO);
if (ret)
return ret;
if (m->u.kernel.match->checkentry
&& !m->u.kernel.match->checkentry(name, ip, match, dm->data,
dm->u.match_size - sizeof(*dm),
hookmask)) {
duprintf("ip_tables: check failed for `%s'.\n",
m->u.kernel.match->name);
return -EINVAL;
}
return 0;
}
static int compat_copy_entry_from_user(struct ipt_entry *e, void **dstptr,
unsigned int *size, const char *name,
struct xt_table_info *newinfo, unsigned char *base)
{
struct ipt_entry_target *t;
struct ipt_target *target;
struct ipt_entry *de;
unsigned int origsize;
int ret, h;
ret = 0;
origsize = *size;
de = (struct ipt_entry *)*dstptr;
memcpy(de, e, sizeof(struct ipt_entry));
*dstptr += sizeof(struct compat_ipt_entry);
ret = IPT_MATCH_ITERATE(e, compat_copy_match_from_user, dstptr, size,
name, &de->ip, de->comefrom);
if (ret)
goto out;
de->target_offset = e->target_offset - (origsize - *size);
t = ipt_get_target(e);
target = t->u.kernel.target;
if (target->compat)
target->compat(t, dstptr, size, COMPAT_FROM_USER);
else
xt_compat_target(t, dstptr, size, COMPAT_FROM_USER);
de->next_offset = e->next_offset - (origsize - *size);
for (h = 0; h < NF_IP_NUMHOOKS; h++) {
if ((unsigned char *)de - base < newinfo->hook_entry[h])
newinfo->hook_entry[h] -= origsize - *size;
if ((unsigned char *)de - base < newinfo->underflow[h])
newinfo->underflow[h] -= origsize - *size;
}
t = ipt_get_target(de);
target = t->u.kernel.target;
ret = xt_check_target(target, AF_INET, t->u.target_size - sizeof(*t),
name, e->comefrom, e->ip.proto,
e->ip.invflags & IPT_INV_PROTO);
if (ret)
goto out;
ret = -EINVAL;
if (t->u.kernel.target == &ipt_standard_target) {
if (!standard_check(t, *size))
goto out;
} else if (t->u.kernel.target->checkentry
&& !t->u.kernel.target->checkentry(name, de, target,
t->data, t->u.target_size - sizeof(*t),
de->comefrom)) {
duprintf("ip_tables: compat: check failed for `%s'.\n",
t->u.kernel.target->name);
goto out;
}
ret = 0;
out:
return ret;
}
static int static int
get_entries(const struct ipt_get_entries *entries, translate_compat_table(const char *name,
struct ipt_get_entries __user *uptr) unsigned int valid_hooks,
struct xt_table_info **pinfo,
void **pentry0,
unsigned int total_size,
unsigned int number,
unsigned int *hook_entries,
unsigned int *underflows)
{ {
unsigned int i;
struct xt_table_info *newinfo, *info;
void *pos, *entry0, *entry1;
unsigned int size;
int ret; int ret;
struct ipt_table *t;
t = xt_find_table_lock(AF_INET, entries->name); info = *pinfo;
if (t && !IS_ERR(t)) { entry0 = *pentry0;
struct xt_table_info *private = t->private; size = total_size;
duprintf("t->private->number = %u\n", info->number = number;
private->number);
if (entries->size == private->size) /* Init all hooks to impossible value. */
ret = copy_entries_to_user(private->size, for (i = 0; i < NF_IP_NUMHOOKS; i++) {
t, uptr->entrytable); info->hook_entry[i] = 0xFFFFFFFF;
else { info->underflow[i] = 0xFFFFFFFF;
duprintf("get_entries: I've got %u not %u!\n", }
private->size,
entries->size); duprintf("translate_compat_table: size %u\n", info->size);
ret = -EINVAL; i = 0;
xt_compat_lock(AF_INET);
/* Walk through entries, checking offsets. */
ret = IPT_ENTRY_ITERATE(entry0, total_size,
check_compat_entry_size_and_hooks,
info, &size, entry0,
entry0 + total_size,
hook_entries, underflows, &i, name);
if (ret != 0)
goto out_unlock;
ret = -EINVAL;
if (i != number) {
duprintf("translate_compat_table: %u not %u entries\n",
i, number);
goto out_unlock;
}
/* Check hooks all assigned */
for (i = 0; i < NF_IP_NUMHOOKS; i++) {
/* Only hooks which are valid */
if (!(valid_hooks & (1 << i)))
continue;
if (info->hook_entry[i] == 0xFFFFFFFF) {
duprintf("Invalid hook entry %u %u\n",
i, hook_entries[i]);
goto out_unlock;
} }
module_put(t->me); if (info->underflow[i] == 0xFFFFFFFF) {
xt_table_unlock(t); duprintf("Invalid underflow %u %u\n",
} else i, underflows[i]);
ret = t ? PTR_ERR(t) : -ENOENT; goto out_unlock;
}
}
ret = -ENOMEM;
newinfo = xt_alloc_table_info(size);
if (!newinfo)
goto out_unlock;
newinfo->number = number;
for (i = 0; i < NF_IP_NUMHOOKS; i++) {
newinfo->hook_entry[i] = info->hook_entry[i];
newinfo->underflow[i] = info->underflow[i];
}
entry1 = newinfo->entries[raw_smp_processor_id()];
pos = entry1;
size = total_size;
ret = IPT_ENTRY_ITERATE(entry0, total_size,
compat_copy_entry_from_user, &pos, &size,
name, newinfo, entry1);
compat_flush_offsets();
xt_compat_unlock(AF_INET);
if (ret)
goto free_newinfo;
ret = -ELOOP;
if (!mark_source_chains(newinfo, valid_hooks, entry1))
goto free_newinfo;
/* And one copy for every other CPU */
for_each_cpu(i)
if (newinfo->entries[i] && newinfo->entries[i] != entry1)
memcpy(newinfo->entries[i], entry1, newinfo->size);
*pinfo = newinfo;
*pentry0 = entry1;
xt_free_table_info(info);
return 0;
free_newinfo:
xt_free_table_info(newinfo);
out:
return ret; return ret;
out_unlock:
xt_compat_unlock(AF_INET);
goto out;
} }
static int static int
do_replace(void __user *user, unsigned int len) compat_do_replace(void __user *user, unsigned int len)
{ {
int ret; int ret;
struct ipt_replace tmp; struct compat_ipt_replace tmp;
struct ipt_table *t; struct xt_table_info *newinfo;
struct xt_table_info *newinfo, *oldinfo; void *loc_cpu_entry;
struct xt_counters *counters;
void *loc_cpu_entry, *loc_cpu_old_entry;
if (copy_from_user(&tmp, user, sizeof(tmp)) != 0) if (copy_from_user(&tmp, user, sizeof(tmp)) != 0)
return -EFAULT; return -EFAULT;
...@@ -949,151 +1818,201 @@ do_replace(void __user *user, unsigned int len) ...@@ -949,151 +1818,201 @@ do_replace(void __user *user, unsigned int len)
goto free_newinfo; goto free_newinfo;
} }
counters = vmalloc(tmp.num_counters * sizeof(struct xt_counters)); ret = translate_compat_table(tmp.name, tmp.valid_hooks,
if (!counters) { &newinfo, &loc_cpu_entry, tmp.size,
ret = -ENOMEM; tmp.num_entries, tmp.hook_entry, tmp.underflow);
if (ret != 0)
goto free_newinfo; goto free_newinfo;
}
ret = translate_table(tmp.name, tmp.valid_hooks, duprintf("compat_do_replace: Translated table\n");
newinfo, loc_cpu_entry, tmp.size, tmp.num_entries,
tmp.hook_entry, tmp.underflow);
if (ret != 0)
goto free_newinfo_counters;
duprintf("ip_tables: Translated table\n"); ret = __do_replace(tmp.name, tmp.valid_hooks,
newinfo, tmp.num_counters,
compat_ptr(tmp.counters));
if (ret)
goto free_newinfo_untrans;
return 0;
t = try_then_request_module(xt_find_table_lock(AF_INET, tmp.name), free_newinfo_untrans:
"iptable_%s", tmp.name); IPT_ENTRY_ITERATE(loc_cpu_entry, newinfo->size, cleanup_entry,NULL);
if (!t || IS_ERR(t)) { free_newinfo:
ret = t ? PTR_ERR(t) : -ENOENT; xt_free_table_info(newinfo);
goto free_newinfo_counters_untrans; return ret;
} }
/* You lied! */ static int
if (tmp.valid_hooks != t->valid_hooks) { compat_do_ipt_set_ctl(struct sock *sk, int cmd, void __user *user,
duprintf("Valid hook crap: %08X vs %08X\n", unsigned int len)
tmp.valid_hooks, t->valid_hooks); {
ret = -EINVAL; int ret;
goto put_module;
}
oldinfo = xt_replace_table(t, tmp.num_counters, newinfo, &ret); if (!capable(CAP_NET_ADMIN))
if (!oldinfo) return -EPERM;
goto put_module;
/* Update module usage count based on number of rules */ switch (cmd) {
duprintf("do_replace: oldnum=%u, initnum=%u, newnum=%u\n", case IPT_SO_SET_REPLACE:
oldinfo->number, oldinfo->initial_entries, newinfo->number); ret = compat_do_replace(user, len);
if ((oldinfo->number > oldinfo->initial_entries) || break;
(newinfo->number <= oldinfo->initial_entries))
module_put(t->me);
if ((oldinfo->number > oldinfo->initial_entries) &&
(newinfo->number <= oldinfo->initial_entries))
module_put(t->me);
/* Get the old counters. */ case IPT_SO_SET_ADD_COUNTERS:
get_counters(oldinfo, counters); ret = do_add_counters(user, len, 1);
/* Decrease module usage counts and free resource */ break;
loc_cpu_old_entry = oldinfo->entries[raw_smp_processor_id()];
IPT_ENTRY_ITERATE(loc_cpu_old_entry, oldinfo->size, cleanup_entry,NULL); default:
xt_free_table_info(oldinfo); duprintf("do_ipt_set_ctl: unknown request %i\n", cmd);
if (copy_to_user(tmp.counters, counters, ret = -EINVAL;
sizeof(struct xt_counters) * tmp.num_counters) != 0) }
ret = -EFAULT;
vfree(counters);
xt_table_unlock(t);
return ret;
put_module:
module_put(t->me);
xt_table_unlock(t);
free_newinfo_counters_untrans:
IPT_ENTRY_ITERATE(loc_cpu_entry, newinfo->size, cleanup_entry,NULL);
free_newinfo_counters:
vfree(counters);
free_newinfo:
xt_free_table_info(newinfo);
return ret; return ret;
} }
/* We're lazy, and add to the first CPU; overflow works its fey magic struct compat_ipt_get_entries
* and everything is OK. */
static inline int
add_counter_to_entry(struct ipt_entry *e,
const struct xt_counters addme[],
unsigned int *i)
{ {
#if 0 char name[IPT_TABLE_MAXNAMELEN];
duprintf("add_counter: Entry %u %lu/%lu + %lu/%lu\n", compat_uint_t size;
*i, struct compat_ipt_entry entrytable[0];
(long unsigned int)e->counters.pcnt, };
(long unsigned int)e->counters.bcnt,
(long unsigned int)addme[*i].pcnt,
(long unsigned int)addme[*i].bcnt);
#endif
ADD_COUNTER(e->counters, addme[*i].bcnt, addme[*i].pcnt); static int compat_copy_entries_to_user(unsigned int total_size,
struct ipt_table *table, void __user *userptr)
{
unsigned int off, num;
struct compat_ipt_entry e;
struct xt_counters *counters;
struct xt_table_info *private = table->private;
void __user *pos;
unsigned int size;
int ret = 0;
void *loc_cpu_entry;
(*i)++; counters = alloc_counters(table);
return 0; if (IS_ERR(counters))
return PTR_ERR(counters);
/* choose the copy that is on our node/cpu, ...
* This choice is lazy (because current thread is
* allowed to migrate to another cpu)
*/
loc_cpu_entry = private->entries[raw_smp_processor_id()];
pos = userptr;
size = total_size;
ret = IPT_ENTRY_ITERATE(loc_cpu_entry, total_size,
compat_copy_entry_to_user, &pos, &size);
if (ret)
goto free_counters;
/* ... then go back and fix counters and names */
for (off = 0, num = 0; off < size; off += e.next_offset, num++) {
unsigned int i;
struct ipt_entry_match m;
struct ipt_entry_target t;
ret = -EFAULT;
if (copy_from_user(&e, userptr + off,
sizeof(struct compat_ipt_entry)))
goto free_counters;
if (copy_to_user(userptr + off +
offsetof(struct compat_ipt_entry, counters),
&counters[num], sizeof(counters[num])))
goto free_counters;
for (i = sizeof(struct compat_ipt_entry);
i < e.target_offset; i += m.u.match_size) {
if (copy_from_user(&m, userptr + off + i,
sizeof(struct ipt_entry_match)))
goto free_counters;
if (copy_to_user(userptr + off + i +
offsetof(struct ipt_entry_match, u.user.name),
m.u.kernel.match->name,
strlen(m.u.kernel.match->name) + 1))
goto free_counters;
}
if (copy_from_user(&t, userptr + off + e.target_offset,
sizeof(struct ipt_entry_target)))
goto free_counters;
if (copy_to_user(userptr + off + e.target_offset +
offsetof(struct ipt_entry_target, u.user.name),
t.u.kernel.target->name,
strlen(t.u.kernel.target->name) + 1))
goto free_counters;
}
ret = 0;
free_counters:
vfree(counters);
return ret;
} }
static int static int
do_add_counters(void __user *user, unsigned int len) compat_get_entries(struct compat_ipt_get_entries __user *uptr, int *len)
{ {
unsigned int i; int ret;
struct xt_counters_info tmp, *paddc; struct compat_ipt_get_entries get;
struct ipt_table *t; struct ipt_table *t;
struct xt_table_info *private;
int ret = 0;
void *loc_cpu_entry;
if (copy_from_user(&tmp, user, sizeof(tmp)) != 0)
return -EFAULT;
if (len != sizeof(tmp) + tmp.num_counters*sizeof(struct xt_counters)) if (*len < sizeof(get)) {
duprintf("compat_get_entries: %u < %u\n",
*len, (unsigned int)sizeof(get));
return -EINVAL; return -EINVAL;
}
paddc = vmalloc_node(len, numa_node_id()); if (copy_from_user(&get, uptr, sizeof(get)) != 0)
if (!paddc) return -EFAULT;
return -ENOMEM;
if (copy_from_user(paddc, user, len) != 0) { if (*len != sizeof(struct compat_ipt_get_entries) + get.size) {
ret = -EFAULT; duprintf("compat_get_entries: %u != %u\n", *len,
goto free; (unsigned int)(sizeof(struct compat_ipt_get_entries) +
get.size));
return -EINVAL;
} }
t = xt_find_table_lock(AF_INET, tmp.name); xt_compat_lock(AF_INET);
if (!t || IS_ERR(t)) { t = xt_find_table_lock(AF_INET, get.name);
if (t && !IS_ERR(t)) {
struct xt_table_info *private = t->private;
struct xt_table_info info;
duprintf("t->private->number = %u\n",
private->number);
ret = compat_table_info(private, &info);
if (!ret && get.size == info.size) {
ret = compat_copy_entries_to_user(private->size,
t, uptr->entrytable);
} else if (!ret) {
duprintf("compat_get_entries: I've got %u not %u!\n",
private->size,
get.size);
ret = -EINVAL;
}
compat_flush_offsets();
module_put(t->me);
xt_table_unlock(t);
} else
ret = t ? PTR_ERR(t) : -ENOENT; ret = t ? PTR_ERR(t) : -ENOENT;
goto free;
}
write_lock_bh(&t->lock); xt_compat_unlock(AF_INET);
private = t->private; return ret;
if (private->number != paddc->num_counters) { }
ret = -EINVAL;
goto unlock_up_free;
}
i = 0; static int
/* Choose the copy that is on our node */ compat_do_ipt_get_ctl(struct sock *sk, int cmd, void __user *user, int *len)
loc_cpu_entry = private->entries[raw_smp_processor_id()]; {
IPT_ENTRY_ITERATE(loc_cpu_entry, int ret;
private->size,
add_counter_to_entry,
paddc->counters,
&i);
unlock_up_free:
write_unlock_bh(&t->lock);
xt_table_unlock(t);
module_put(t->me);
free:
vfree(paddc);
switch (cmd) {
case IPT_SO_GET_INFO:
ret = get_info(user, len, 1);
break;
case IPT_SO_GET_ENTRIES:
ret = compat_get_entries(user, len);
break;
default:
duprintf("compat_do_ipt_get_ctl: unknown request %i\n", cmd);
ret = -EINVAL;
}
return ret; return ret;
} }
#endif
static int static int
do_ipt_set_ctl(struct sock *sk, int cmd, void __user *user, unsigned int len) do_ipt_set_ctl(struct sock *sk, int cmd, void __user *user, unsigned int len)
...@@ -1109,7 +2028,7 @@ do_ipt_set_ctl(struct sock *sk, int cmd, void __user *user, unsigned int len) ...@@ -1109,7 +2028,7 @@ do_ipt_set_ctl(struct sock *sk, int cmd, void __user *user, unsigned int len)
break; break;
case IPT_SO_SET_ADD_COUNTERS: case IPT_SO_SET_ADD_COUNTERS:
ret = do_add_counters(user, len); ret = do_add_counters(user, len, 0);
break; break;
default: default:
...@@ -1129,65 +2048,13 @@ do_ipt_get_ctl(struct sock *sk, int cmd, void __user *user, int *len) ...@@ -1129,65 +2048,13 @@ do_ipt_get_ctl(struct sock *sk, int cmd, void __user *user, int *len)
return -EPERM; return -EPERM;
switch (cmd) { switch (cmd) {
case IPT_SO_GET_INFO: { case IPT_SO_GET_INFO:
char name[IPT_TABLE_MAXNAMELEN]; ret = get_info(user, len, 0);
struct ipt_table *t; break;
if (*len != sizeof(struct ipt_getinfo)) {
duprintf("length %u != %u\n", *len,
sizeof(struct ipt_getinfo));
ret = -EINVAL;
break;
}
if (copy_from_user(name, user, sizeof(name)) != 0) {
ret = -EFAULT;
break;
}
name[IPT_TABLE_MAXNAMELEN-1] = '\0';
t = try_then_request_module(xt_find_table_lock(AF_INET, name),
"iptable_%s", name);
if (t && !IS_ERR(t)) {
struct ipt_getinfo info;
struct xt_table_info *private = t->private;
info.valid_hooks = t->valid_hooks;
memcpy(info.hook_entry, private->hook_entry,
sizeof(info.hook_entry));
memcpy(info.underflow, private->underflow,
sizeof(info.underflow));
info.num_entries = private->number;
info.size = private->size;
memcpy(info.name, name, sizeof(info.name));
if (copy_to_user(user, &info, *len) != 0)
ret = -EFAULT;
else
ret = 0;
xt_table_unlock(t);
module_put(t->me);
} else
ret = t ? PTR_ERR(t) : -ENOENT;
}
break;
case IPT_SO_GET_ENTRIES: {
struct ipt_get_entries get;
if (*len < sizeof(get)) { case IPT_SO_GET_ENTRIES:
duprintf("get_entries: %u < %u\n", *len, sizeof(get)); ret = get_entries(user, len);
ret = -EINVAL;
} else if (copy_from_user(&get, user, sizeof(get)) != 0) {
ret = -EFAULT;
} else if (*len != sizeof(struct ipt_get_entries) + get.size) {
duprintf("get_entries: %u != %u\n", *len,
sizeof(struct ipt_get_entries) + get.size);
ret = -EINVAL;
} else
ret = get_entries(&get, user);
break; break;
}
case IPT_SO_GET_REVISION_MATCH: case IPT_SO_GET_REVISION_MATCH:
case IPT_SO_GET_REVISION_TARGET: { case IPT_SO_GET_REVISION_TARGET: {
...@@ -1336,6 +2203,9 @@ static struct ipt_target ipt_standard_target = { ...@@ -1336,6 +2203,9 @@ static struct ipt_target ipt_standard_target = {
.name = IPT_STANDARD_TARGET, .name = IPT_STANDARD_TARGET,
.targetsize = sizeof(int), .targetsize = sizeof(int),
.family = AF_INET, .family = AF_INET,
#ifdef CONFIG_COMPAT
.compat = &compat_ipt_standard_fn,
#endif
}; };
static struct ipt_target ipt_error_target = { static struct ipt_target ipt_error_target = {
...@@ -1350,9 +2220,15 @@ static struct nf_sockopt_ops ipt_sockopts = { ...@@ -1350,9 +2220,15 @@ static struct nf_sockopt_ops ipt_sockopts = {
.set_optmin = IPT_BASE_CTL, .set_optmin = IPT_BASE_CTL,
.set_optmax = IPT_SO_SET_MAX+1, .set_optmax = IPT_SO_SET_MAX+1,
.set = do_ipt_set_ctl, .set = do_ipt_set_ctl,
#ifdef CONFIG_COMPAT
.compat_set = compat_do_ipt_set_ctl,
#endif
.get_optmin = IPT_BASE_CTL, .get_optmin = IPT_BASE_CTL,
.get_optmax = IPT_SO_GET_MAX+1, .get_optmax = IPT_SO_GET_MAX+1,
.get = do_ipt_get_ctl, .get = do_ipt_get_ctl,
#ifdef CONFIG_COMPAT
.compat_get = compat_do_ipt_get_ctl,
#endif
}; };
static struct ipt_match icmp_matchstruct = { static struct ipt_match icmp_matchstruct = {
......
...@@ -38,6 +38,7 @@ struct xt_af { ...@@ -38,6 +38,7 @@ struct xt_af {
struct list_head match; struct list_head match;
struct list_head target; struct list_head target;
struct list_head tables; struct list_head tables;
struct mutex compat_mutex;
}; };
static struct xt_af *xt; static struct xt_af *xt;
...@@ -272,6 +273,54 @@ int xt_check_match(const struct xt_match *match, unsigned short family, ...@@ -272,6 +273,54 @@ int xt_check_match(const struct xt_match *match, unsigned short family,
} }
EXPORT_SYMBOL_GPL(xt_check_match); EXPORT_SYMBOL_GPL(xt_check_match);
#ifdef CONFIG_COMPAT
int xt_compat_match(void *match, void **dstptr, int *size, int convert)
{
struct xt_match *m;
struct compat_xt_entry_match *pcompat_m;
struct xt_entry_match *pm;
u_int16_t msize;
int off, ret;
ret = 0;
m = ((struct xt_entry_match *)match)->u.kernel.match;
off = XT_ALIGN(m->matchsize) - COMPAT_XT_ALIGN(m->matchsize);
switch (convert) {
case COMPAT_TO_USER:
pm = (struct xt_entry_match *)match;
msize = pm->u.user.match_size;
if (__copy_to_user(*dstptr, pm, msize)) {
ret = -EFAULT;
break;
}
msize -= off;
if (put_user(msize, (u_int16_t *)*dstptr))
ret = -EFAULT;
*size -= off;
*dstptr += msize;
break;
case COMPAT_FROM_USER:
pcompat_m = (struct compat_xt_entry_match *)match;
pm = (struct xt_entry_match *)*dstptr;
msize = pcompat_m->u.user.match_size;
memcpy(pm, pcompat_m, msize);
msize += off;
pm->u.user.match_size = msize;
*size += off;
*dstptr += msize;
break;
case COMPAT_CALC_SIZE:
*size += off;
break;
default:
ret = -ENOPROTOOPT;
break;
}
return ret;
}
EXPORT_SYMBOL_GPL(xt_compat_match);
#endif
int xt_check_target(const struct xt_target *target, unsigned short family, int xt_check_target(const struct xt_target *target, unsigned short family,
unsigned int size, const char *table, unsigned int hook_mask, unsigned int size, const char *table, unsigned int hook_mask,
unsigned short proto, int inv_proto) unsigned short proto, int inv_proto)
...@@ -301,6 +350,54 @@ int xt_check_target(const struct xt_target *target, unsigned short family, ...@@ -301,6 +350,54 @@ int xt_check_target(const struct xt_target *target, unsigned short family,
} }
EXPORT_SYMBOL_GPL(xt_check_target); EXPORT_SYMBOL_GPL(xt_check_target);
#ifdef CONFIG_COMPAT
int xt_compat_target(void *target, void **dstptr, int *size, int convert)
{
struct xt_target *t;
struct compat_xt_entry_target *pcompat;
struct xt_entry_target *pt;
u_int16_t tsize;
int off, ret;
ret = 0;
t = ((struct xt_entry_target *)target)->u.kernel.target;
off = XT_ALIGN(t->targetsize) - COMPAT_XT_ALIGN(t->targetsize);
switch (convert) {
case COMPAT_TO_USER:
pt = (struct xt_entry_target *)target;
tsize = pt->u.user.target_size;
if (__copy_to_user(*dstptr, pt, tsize)) {
ret = -EFAULT;
break;
}
tsize -= off;
if (put_user(tsize, (u_int16_t *)*dstptr))
ret = -EFAULT;
*size -= off;
*dstptr += tsize;
break;
case COMPAT_FROM_USER:
pcompat = (struct compat_xt_entry_target *)target;
pt = (struct xt_entry_target *)*dstptr;
tsize = pcompat->u.user.target_size;
memcpy(pt, pcompat, tsize);
tsize += off;
pt->u.user.target_size = tsize;
*size += off;
*dstptr += tsize;
break;
case COMPAT_CALC_SIZE:
*size += off;
break;
default:
ret = -ENOPROTOOPT;
break;
}
return ret;
}
EXPORT_SYMBOL_GPL(xt_compat_target);
#endif
struct xt_table_info *xt_alloc_table_info(unsigned int size) struct xt_table_info *xt_alloc_table_info(unsigned int size)
{ {
struct xt_table_info *newinfo; struct xt_table_info *newinfo;
...@@ -371,6 +468,19 @@ void xt_table_unlock(struct xt_table *table) ...@@ -371,6 +468,19 @@ void xt_table_unlock(struct xt_table *table)
} }
EXPORT_SYMBOL_GPL(xt_table_unlock); EXPORT_SYMBOL_GPL(xt_table_unlock);
#ifdef CONFIG_COMPAT
void xt_compat_lock(int af)
{
mutex_lock(&xt[af].compat_mutex);
}
EXPORT_SYMBOL_GPL(xt_compat_lock);
void xt_compat_unlock(int af)
{
mutex_unlock(&xt[af].compat_mutex);
}
EXPORT_SYMBOL_GPL(xt_compat_unlock);
#endif
struct xt_table_info * struct xt_table_info *
xt_replace_table(struct xt_table *table, xt_replace_table(struct xt_table *table,
...@@ -671,6 +781,9 @@ static int __init xt_init(void) ...@@ -671,6 +781,9 @@ static int __init xt_init(void)
for (i = 0; i < NPROTO; i++) { for (i = 0; i < NPROTO; i++) {
mutex_init(&xt[i].mutex); mutex_init(&xt[i].mutex);
#ifdef CONFIG_COMPAT
mutex_init(&xt[i].compat_mutex);
#endif
INIT_LIST_HEAD(&xt[i].target); INIT_LIST_HEAD(&xt[i].target);
INIT_LIST_HEAD(&xt[i].match); INIT_LIST_HEAD(&xt[i].match);
INIT_LIST_HEAD(&xt[i].tables); INIT_LIST_HEAD(&xt[i].tables);
......
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