[PATCH 1/6] Core changes for better error reporting to userspace.

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Hi,

For better error reporting to userspace, a new getsockopt operation, 
*GET_REPLACE is introduced. The new operation makes us possible to push 
back fine grained error reporting from the extensions to userspace. If 
there was an extension error, the new 'xt_error_entry' structure and 
following that the *entry_match or *entry_target structures are sent back 
to the userspace. The 'xt_error_entry' structure carries the 
extension-specific error code and a flag to signal the type of the next 
data block (match, target [or watcher with ebtables]). For the sake of the 
complete error reporting, the table, hook and protocol checkings are moved 
from the xt_check_match|target functions to the checkentry functions.

A sample error reporting with the patched kernel and iptables:

root@teg~# iptables -A FORWARD -j TCPMSS --clamp-mss-to-pmtu
iptables v1.4.3.2: Error in target extension `TCPMSS':
Target is only valid for protocol TCP.
Try `iptables -h' or 'iptables --help' for more information.

Signed-off-by: Jozsef Kadlecsik <kadlec@xxxxxxxxxxxxxxxxx>
---

 include/linux/netfilter/x_tables.h        |   47 +++--
 include/linux/netfilter_arp/arp_tables.h  |    3 
 include/linux/netfilter_bridge/ebtables.h |    3 
 include/linux/netfilter_ipv4/ip_tables.h  |   10 +
 include/linux/netfilter_ipv6/ip6_tables.h |   10 +
 net/bridge/netfilter/ebtables.c           |  299 +++++++++++++++++++++++++++--
 net/ipv4/netfilter/arp_tables.c           |  161 ++++++++++++++--
 net/ipv4/netfilter/ip_tables.c            |  204 +++++++++++++++++---
 net/ipv6/netfilter/ip6_tables.c           |  196 ++++++++++++++++---
 net/netfilter/x_tables.c                  |   86 +-------
 10 files changed, 825 insertions(+), 194 deletions(-)

diff --git a/include/linux/netfilter/x_tables.h b/include/linux/netfilter/x_tables.h
index 1030b75..8583eab 100644
--- a/include/linux/netfilter/x_tables.h
+++ b/include/linux/netfilter/x_tables.h
@@ -2,6 +2,7 @@
 #define _X_TABLES_H
 
 #include <linux/types.h>
+#include <linux/err.h>
 
 #define XT_FUNCTION_MAXNAMELEN 30
 #define XT_TABLE_MAXNAMELEN 32
@@ -70,6 +71,14 @@ struct xt_standard_target
 	int verdict;
 };
 
+struct xt_error_entry {
+	__u8 errcode;
+	__u8 match;
+	unsigned char data[0];
+};
+
+#define XT_PTR_ERR	(MAX_ERRNO+1)
+
 /* The argument to IPT_SO_GET_REVISION_*.  Returns highest revision
  * kernel supports, if >= revision. */
 struct xt_get_revision
@@ -210,6 +219,9 @@ struct xt_match_param {
  * @match:	struct xt_match through which this function was invoked
  * @matchinfo:	per-match data
  * @hook_mask:	via which hooks the new rule is reachable
+ * @family:	protocol family
+ * @proto:	transport protocol
+ * @inverted:	transport protocol check is inverted
  */
 struct xt_mtchk_param {
 	const char *table;
@@ -217,7 +229,10 @@ struct xt_mtchk_param {
 	const struct xt_match *match;
 	void *matchinfo;
 	unsigned int hook_mask;
+	u_int16_t proto;
 	u_int8_t family;
+	bool inverted;
+	bool compat_log;
 };
 
 /* Match destructor parameters */
@@ -259,7 +274,10 @@ struct xt_tgchk_param {
 	const struct xt_target *target;
 	void *targinfo;
 	unsigned int hook_mask;
+	u_int16_t proto;
 	u_int8_t family;
+	bool inverted;
+	bool compat_log;
 };
 
 /* Target destructor parameters */
@@ -284,8 +302,9 @@ struct xt_match
 	bool (*match)(const struct sk_buff *skb,
 		      const struct xt_match_param *);
 
-	/* Called when user tries to insert an entry of this type. */
-	bool (*checkentry)(const struct xt_mtchk_param *);
+	/* Called when user tries to insert an entry of this type.
+	 * Returns zero or a positive errno. */
+	unsigned int (*checkentry)(const struct xt_mtchk_param *);
 
 	/* Called when entry of this type deleted. */
 	void (*destroy)(const struct xt_mtdtor_param *);
@@ -300,11 +319,8 @@ struct xt_match
 	/* Free to use by each match */
 	unsigned long data;
 
-	const char *table;
 	unsigned int matchsize;
 	unsigned int compatsize;
-	unsigned int hooks;
-	unsigned short proto;
 
 	unsigned short family;
 };
@@ -322,11 +338,9 @@ struct xt_target
 	unsigned int (*target)(struct sk_buff *skb,
 			       const struct xt_target_param *);
 
-	/* Called when user tries to insert an entry of this type:
-           hook_mask is a bitmask of hooks from which it can be
-           called. */
-	/* Should return true or false. */
-	bool (*checkentry)(const struct xt_tgchk_param *);
+	/* Called when user tries to insert an entry of this type.
+	 * Returns 0 or a positive errno */
+	unsigned int (*checkentry)(const struct xt_tgchk_param *);
 
 	/* Called when entry of this type deleted. */
 	void (*destroy)(const struct xt_tgdtor_param *);
@@ -338,11 +352,8 @@ struct xt_target
 	/* Set this to THIS_MODULE if you are a module, otherwise NULL */
 	struct module *me;
 
-	const char *table;
 	unsigned int targetsize;
 	unsigned int compatsize;
-	unsigned int hooks;
-	unsigned short proto;
 
 	unsigned short family;
 	u_int8_t revision;
@@ -401,10 +412,8 @@ extern void xt_unregister_match(struct xt_match *target);
 extern int xt_register_matches(struct xt_match *match, unsigned int n);
 extern void xt_unregister_matches(struct xt_match *match, unsigned int n);
 
-extern int xt_check_match(struct xt_mtchk_param *,
-			  unsigned int size, u_int8_t proto, bool inv_proto);
-extern int xt_check_target(struct xt_tgchk_param *,
-			   unsigned int size, u_int8_t proto, bool inv_proto);
+extern int xt_check_match(struct xt_mtchk_param *, unsigned int size);
+extern int xt_check_target(struct xt_tgchk_param *, unsigned int size);
 
 extern struct xt_table *xt_register_table(struct net *net,
 					  struct xt_table *table,
@@ -525,6 +534,10 @@ static inline unsigned long ifname_compare_aligned(const char *_a,
 	return ret;
 }
 
+#define xt_compat_log(par, fmt, args...) \
+	if ((par)->compat_log)		 \
+		printk(KERN_ERR fmt "\n", ## args)
+
 #ifdef CONFIG_COMPAT
 #include <net/compat.h>
 
diff --git a/include/linux/netfilter_arp/arp_tables.h b/include/linux/netfilter_arp/arp_tables.h
index 590ac3d..913b65a 100644
--- a/include/linux/netfilter_arp/arp_tables.h
+++ b/include/linux/netfilter_arp/arp_tables.h
@@ -123,7 +123,8 @@ struct arpt_entry
 #define ARPT_SO_GET_ENTRIES		(ARPT_BASE_CTL + 1)
 /* #define ARPT_SO_GET_REVISION_MATCH	(APRT_BASE_CTL + 2) */
 #define ARPT_SO_GET_REVISION_TARGET	(ARPT_BASE_CTL + 3)
-#define ARPT_SO_GET_MAX			(ARPT_SO_GET_REVISION_TARGET)
+#define ARPT_SO_GET_REPLACE		(ARPT_BASE_CTL + 4)
+#define ARPT_SO_GET_MAX			(ARPT_SO_GET_REPLACE)
 
 /* CONTINUE verdict for targets */
 #define ARPT_CONTINUE XT_CONTINUE
diff --git a/include/linux/netfilter_bridge/ebtables.h b/include/linux/netfilter_bridge/ebtables.h
index e40ddb9..b5b04fc 100644
--- a/include/linux/netfilter_bridge/ebtables.h
+++ b/include/linux/netfilter_bridge/ebtables.h
@@ -198,7 +198,8 @@ struct ebt_entry {
 #define EBT_SO_GET_ENTRIES      (EBT_SO_GET_INFO+1)
 #define EBT_SO_GET_INIT_INFO    (EBT_SO_GET_ENTRIES+1)
 #define EBT_SO_GET_INIT_ENTRIES (EBT_SO_GET_INIT_INFO+1)
-#define EBT_SO_GET_MAX          (EBT_SO_GET_INIT_ENTRIES+1)
+#define EBT_SO_GET_REPLACE	(EBT_SO_GET_INIT_ENTRIES+1)
+#define EBT_SO_GET_MAX          (EBT_SO_GET_REPLACE+1)
 
 #ifdef __KERNEL__
 
diff --git a/include/linux/netfilter_ipv4/ip_tables.h b/include/linux/netfilter_ipv4/ip_tables.h
index 092bd50..37b6805 100644
--- a/include/linux/netfilter_ipv4/ip_tables.h
+++ b/include/linux/netfilter_ipv4/ip_tables.h
@@ -115,7 +115,8 @@ struct ipt_entry
 #define IPT_SO_GET_ENTRIES		(IPT_BASE_CTL + 1)
 #define IPT_SO_GET_REVISION_MATCH	(IPT_BASE_CTL + 2)
 #define IPT_SO_GET_REVISION_TARGET	(IPT_BASE_CTL + 3)
-#define IPT_SO_GET_MAX			IPT_SO_GET_REVISION_TARGET
+#define IPT_SO_GET_REPLACE		(IPT_BASE_CTL + 4)
+#define IPT_SO_GET_MAX			IPT_SO_GET_REPLACE
 
 #define IPT_CONTINUE XT_CONTINUE
 #define IPT_RETURN XT_RETURN
@@ -134,6 +135,13 @@ struct ipt_entry
 #define IPT_UDP_INV_DSTPT	XT_UDP_INV_DSTPT
 #define IPT_UDP_INV_MASK	XT_UDP_INV_MASK
 
+enum {
+	IPT_ICMP_ERR_NONE,
+	IPT_ICMP_ERR_PROTO,
+	IPT_ICMP_ERR_FLAGS,
+	IPT_ICMP_ERR_MAX,
+};
+
 /* ICMP matching stuff */
 struct ipt_icmp
 {
diff --git a/include/linux/netfilter_ipv6/ip6_tables.h b/include/linux/netfilter_ipv6/ip6_tables.h
index 1089e33..140d48c 100644
--- a/include/linux/netfilter_ipv6/ip6_tables.h
+++ b/include/linux/netfilter_ipv6/ip6_tables.h
@@ -168,7 +168,8 @@ struct ip6t_error
 #define IP6T_SO_GET_ENTRIES		(IP6T_BASE_CTL + 1)
 #define IP6T_SO_GET_REVISION_MATCH	(IP6T_BASE_CTL + 4)
 #define IP6T_SO_GET_REVISION_TARGET	(IP6T_BASE_CTL + 5)
-#define IP6T_SO_GET_MAX			IP6T_SO_GET_REVISION_TARGET
+#define IP6T_SO_GET_REPLACE		(IP6T_BASE_CTL + 6)
+#define IP6T_SO_GET_MAX			IP6T_SO_GET_REPLACE
 
 /* CONTINUE verdict for targets */
 #define IP6T_CONTINUE XT_CONTINUE
@@ -194,6 +195,13 @@ struct ip6t_error
 #define IP6T_UDP_INV_DSTPT	XT_UDP_INV_DSTPT
 #define IP6T_UDP_INV_MASK	XT_UDP_INV_MASK
 
+enum {
+	IP6T_ICMPV6_ERR_NONE,
+	IP6T_ICMPV6_ERR_PROTO,
+	IP6T_ICMPV6_ERR_FLAGS,
+	IP6T_ICMPV6_ERR_MAX,
+};
+
 /* ICMP matching stuff */
 struct ip6t_icmp
 {
diff --git a/net/bridge/netfilter/ebtables.c b/net/bridge/netfilter/ebtables.c
index 37928d5..668bee8 100644
--- a/net/bridge/netfilter/ebtables.c
+++ b/net/bridge/netfilter/ebtables.c
@@ -328,7 +328,7 @@ find_table_lock(struct net *net, const char *name, int *error,
 
 static inline int
 ebt_check_match(struct ebt_entry_match *m, struct xt_mtchk_param *par,
-		unsigned int *cnt)
+		unsigned int *cnt, unsigned int *error_offset)
 {
 	const struct ebt_entry *e = par->entryinfo;
 	struct xt_match *match;
@@ -342,27 +342,28 @@ ebt_check_match(struct ebt_entry_match *m, struct xt_mtchk_param *par,
 	match = try_then_request_module(xt_find_match(NFPROTO_BRIDGE,
 		m->u.name, 0), "ebt_%s", m->u.name);
 	if (IS_ERR(match))
-		return PTR_ERR(match);
+		return -XT_PTR_ERR;
 	if (match == NULL)
 		return -ENOENT;
 	m->u.match = match;
 
 	par->match     = match;
 	par->matchinfo = m->data;
-	ret = xt_check_match(par, m->match_size,
-	      e->ethproto, e->invflags & EBT_IPROTO);
-	if (ret < 0) {
+	par->proto     = e->ethproto;
+	par->inverted  = e->invflags & EBT_IPROTO;
+	ret = xt_check_match(par, m->match_size);
+	if (ret) {
 		module_put(match->me);
 		return ret;
 	}
-
+	*error_offset += m->match_size;
 	(*cnt)++;
 	return 0;
 }
 
 static inline int
 ebt_check_watcher(struct ebt_entry_watcher *w, struct xt_tgchk_param *par,
-		  unsigned int *cnt)
+		  unsigned int *cnt, unsigned int *error_offset)
 {
 	const struct ebt_entry *e = par->entryinfo;
 	struct xt_target *watcher;
@@ -377,20 +378,21 @@ ebt_check_watcher(struct ebt_entry_watcher *w, struct xt_tgchk_param *par,
 		  xt_find_target(NFPROTO_BRIDGE, w->u.name, 0),
 		  "ebt_%s", w->u.name);
 	if (IS_ERR(watcher))
-		return PTR_ERR(watcher);
+		return -XT_PTR_ERR;
 	if (watcher == NULL)
 		return -ENOENT;
 	w->u.watcher = watcher;
 
 	par->target   = watcher;
 	par->targinfo = w->data;
-	ret = xt_check_target(par, w->watcher_size,
-	      e->ethproto, e->invflags & EBT_IPROTO);
-	if (ret < 0) {
+	par->proto    = e->ethproto;
+	par->inverted = e->invflags & EBT_IPROTO;
+	ret = xt_check_target(par, w->watcher_size);
+	if (ret) {
 		module_put(watcher->me);
 		return ret;
 	}
-
+	*error_offset += w->watcher_size;
 	(*cnt)++;
 	return 0;
 }
@@ -621,7 +623,8 @@ ebt_cleanup_entry(struct ebt_entry *e, unsigned int *cnt)
 static inline int
 ebt_check_entry(struct ebt_entry *e, struct ebt_table_info *newinfo,
    const char *name, unsigned int *cnt,
-   struct ebt_cl_stack *cl_s, unsigned int udc_cnt)
+   struct ebt_cl_stack *cl_s, unsigned int udc_cnt,
+   unsigned int *error_offset, bool compat_log)
 {
 	struct ebt_entry_target *t;
 	struct xt_target *target;
@@ -675,11 +678,14 @@ ebt_check_entry(struct ebt_entry *e, struct ebt_table_info *newinfo,
 	mtpar.entryinfo = tgpar.entryinfo = e;
 	mtpar.hook_mask = tgpar.hook_mask = hookmask;
 	mtpar.family    = tgpar.family    = NFPROTO_BRIDGE;
-	ret = EBT_MATCH_ITERATE(e, ebt_check_match, &mtpar, &i);
+	mtpar.compat_log = tgpar.compat_log = compat_log;
+	ret = EBT_MATCH_ITERATE(e, ebt_check_match, &mtpar, &i,
+				error_offset);
 	if (ret != 0)
 		goto cleanup_matches;
 	j = 0;
-	ret = EBT_WATCHER_ITERATE(e, ebt_check_watcher, &tgpar, &j);
+	ret = EBT_WATCHER_ITERATE(e, ebt_check_watcher, &tgpar, &j,
+				  error_offset);
 	if (ret != 0)
 		goto cleanup_watchers;
 	t = (struct ebt_entry_target *)(((char *)e) + e->target_offset);
@@ -689,7 +695,7 @@ ebt_check_entry(struct ebt_entry *e, struct ebt_table_info *newinfo,
 		 xt_find_target(NFPROTO_BRIDGE, t->u.name, 0),
 		 "ebt_%s", t->u.name);
 	if (IS_ERR(target)) {
-		ret = PTR_ERR(target);
+		ret = -XT_PTR_ERR;
 		goto cleanup_watchers;
 	} else if (target == NULL) {
 		ret = -ENOENT;
@@ -717,9 +723,12 @@ ebt_check_entry(struct ebt_entry *e, struct ebt_table_info *newinfo,
 
 	tgpar.target   = target;
 	tgpar.targinfo = t->data;
-	ret = xt_check_target(&tgpar, t->target_size,
-	      e->ethproto, e->invflags & EBT_IPROTO);
-	if (ret < 0) {
+	tgpar.proto    = e->ethproto;
+	tgpar.inverted = e->invflags & EBT_IPROTO;
+	tgpar.compat_log = compat_log;
+	ret = xt_check_target(&tgpar, t->target_size);
+	if (ret) {
+		*error_offset = e->target_offset;
 		module_put(target->me);
 		goto cleanup_watchers;
 	}
@@ -808,7 +817,9 @@ letscontinue:
 }
 
 /* do the parsing of the table/chains/entries/matches/watchers/targets, heh */
-static int translate_table(char *name, struct ebt_table_info *newinfo)
+static int translate_table(char *name, struct ebt_table_info *newinfo,
+			   unsigned int *rulenum, unsigned int *error_offset,
+			   bool compat_log)
 {
 	unsigned int i, j, k, udc_cnt;
 	int ret;
@@ -916,9 +927,12 @@ static int translate_table(char *name, struct ebt_table_info *newinfo)
 
 	/* used to know what we need to clean up if something goes wrong */
 	i = 0;
+	*error_offset = sizeof(struct ebt_entry);
 	ret = EBT_ENTRY_ITERATE(newinfo->entries, newinfo->entries_size,
-	   ebt_check_entry, newinfo, name, &i, cl_s, udc_cnt);
+	   ebt_check_entry, newinfo, name, &i, cl_s, udc_cnt,
+	   error_offset, compat_log);
 	if (ret != 0) {
+		*rulenum = i;
 		EBT_ENTRY_ITERATE(newinfo->entries, newinfo->entries_size,
 		   ebt_cleanup_entry, &i);
 	}
@@ -953,6 +967,7 @@ static void get_counters(struct ebt_counter *oldcounters,
 static int do_replace(struct net *net, void __user *user, unsigned int len)
 {
 	int ret, i, countersize;
+	unsigned int rulenum, error_offset;
 	struct ebt_table_info *newinfo;
 	struct ebt_replace tmp;
 	struct ebt_table *t;
@@ -1017,7 +1032,7 @@ static int do_replace(struct net *net, void __user *user, unsigned int len)
 	if (ret != 0)
 		goto free_counterstmp;
 
-	ret = translate_table(tmp.name, newinfo);
+	ret = translate_table(tmp.name, newinfo, &rulenum, &error_offset, true);
 
 	if (ret != 0)
 		goto free_counterstmp;
@@ -1103,12 +1118,246 @@ free_newinfo:
 	return ret;
 }
 
+static int
+find_failed(struct ebt_entry *e, struct ebt_entry **failed,
+	    unsigned int *which)
+{
+	if (which && (*which)-- == 0) {
+		*failed = e;
+		return 1;
+	}
+	return 0;
+}
+
+/* replace the table */
+static int get_replace(struct net *net, void __user *user, int *len)
+{
+	int ret, i, countersize;
+	unsigned int rulenum, error_offset;
+	struct ebt_table_info *newinfo;
+	struct ebt_replace tmp;
+	struct ebt_table *t;
+	struct ebt_counter *counterstmp = NULL;
+	/* used to be able to unlock earlier */
+	struct ebt_table_info *table;
+
+	if (copy_from_user(&tmp, user, sizeof(tmp)) != 0)
+		return -EFAULT;
+
+	if (*len != sizeof(tmp) + tmp.entries_size) {
+		BUGPRINT("Wrong len argument\n");
+		return -EINVAL;
+	}
+
+	if (tmp.entries_size == 0) {
+		BUGPRINT("Entries_size never zero\n");
+		return -EINVAL;
+	}
+	/* overflow check */
+	if (tmp.nentries >= ((INT_MAX - sizeof(struct ebt_table_info)) / NR_CPUS -
+			SMP_CACHE_BYTES) / sizeof(struct ebt_counter))
+		return -ENOMEM;
+	if (tmp.num_counters >= INT_MAX / sizeof(struct ebt_counter))
+		return -ENOMEM;
+
+	countersize = COUNTER_OFFSET(tmp.nentries) * nr_cpu_ids;
+	newinfo = vmalloc(sizeof(*newinfo) + countersize);
+	if (!newinfo)
+		return -ENOMEM;
+
+	if (countersize)
+		memset(newinfo->counters, 0, countersize);
+
+	newinfo->entries = vmalloc(tmp.entries_size);
+	if (!newinfo->entries) {
+		ret = -ENOMEM;
+		goto free_newinfo;
+	}
+	if (copy_from_user(
+	   newinfo->entries, tmp.entries, tmp.entries_size) != 0) {
+		BUGPRINT("Couldn't copy entries from userspace\n");
+		ret = -EFAULT;
+		goto free_entries;
+	}
+
+	/* the user wants counters back
+	   the check on the size is done later, when we have the lock */
+	if (tmp.num_counters) {
+		counterstmp = vmalloc(tmp.num_counters * sizeof(*counterstmp));
+		if (!counterstmp) {
+			ret = -ENOMEM;
+			goto free_entries;
+		}
+	}
+	else
+		counterstmp = NULL;
+
+	/* this can get initialized by translate_table() */
+	newinfo->chainstack = NULL;
+	ret = ebt_verify_pointers(&tmp, newinfo);
+	if (ret != 0)
+		goto free_counterstmp;
+
+	*len = 0;
+	ret = translate_table(tmp.name, newinfo, &rulenum, &error_offset, false);
+
+	if (ret < 0) {
+		/* Core failure */
+		goto free_counterstmp;
+	} else if (ret > 0) {
+		/* Extension checkentry failure
+		 * newinfo->nentries: rule num
+		 * newinfo->entries_size: offset to match/target
+		 */
+		struct ebt_entry *e = NULL;
+		struct xt_error_entry *x;
+
+		EBT_ENTRY_ITERATE(newinfo->entries, newinfo->entries_size,
+				  find_failed, &e, &rulenum);
+
+		if (!e) {
+			ret = -EFAULT;
+			goto free_counterstmp;
+		}
+		x = (void *) e + error_offset - sizeof(*x);
+		x->errcode = ret;
+		x->match = error_offset < e->watchers_offset ? 1 :
+			   error_offset < e->target_offset ? 2 : 0;
+		ret = 0;
+		if (x->match == 1) {
+			const struct ebt_entry_match *m = 
+			 	(void *) e + error_offset;
+			if (copy_to_user(user, x,
+					 sizeof(*x) + m->match_size) != 0
+			    || copy_to_user(user + sizeof(*x)
+					 + offsetof(struct ebt_entry_match,
+					 	    u.name),
+					m->u.match->name,
+					strlen(m->u.match->name)+1)
+			    != 0)
+			    	ret = -EFAULT;
+			else
+				*len = sizeof(*x) + m->match_size;
+		} else if (x->match == 2) {
+			const struct ebt_entry_watcher *w = 
+			 	(void *) e + error_offset;
+			if (copy_to_user(user, x,
+					 sizeof(*x) + w->watcher_size) != 0
+			    || copy_to_user(user + sizeof(*x)
+					 + offsetof(struct ebt_entry_watcher,
+					 	    u.name),
+					w->u.watcher->name,
+					strlen(w->u.watcher->name)+1)
+			    != 0)
+			    	ret = -EFAULT;
+			else
+				*len = sizeof(*x) + w->watcher_size;
+		} else {
+			const struct ebt_entry_target *t = 
+			 	(void *) e + error_offset;
+			if (copy_to_user(user, x,
+					 sizeof(*x) + t->target_size) != 0
+			    || copy_to_user(user + sizeof(*x)
+					 + offsetof(struct ebt_entry_target,
+					 	    u.name),
+					t->u.target->name,
+					strlen(t->u.target->name)+1)
+			    != 0)
+			    	ret = -EFAULT;
+			else
+				*len = sizeof(*x) + t->target_size;
+		}
+		goto free_counterstmp;
+	}
+
+	t = find_table_lock(net, tmp.name, &ret, &ebt_mutex);
+	if (!t) {
+		ret = -ENOENT;
+		goto free_iterate;
+	}
+
+	/* the table doesn't like it */
+	if (t->check && (ret = t->check(newinfo, tmp.valid_hooks)))
+		goto free_unlock;
+
+	if (tmp.num_counters && tmp.num_counters != t->private->nentries) {
+		BUGPRINT("Wrong nr. of counters requested\n");
+		ret = -EINVAL;
+		goto free_unlock;
+	}
+
+	/* we have the mutex lock, so no danger in reading this pointer */
+	table = t->private;
+	/* make sure the table can only be rmmod'ed if it contains no rules */
+	if (!table->nentries && newinfo->nentries && !try_module_get(t->me)) {
+		ret = -ENOENT;
+		goto free_unlock;
+	} else if (table->nentries && !newinfo->nentries)
+		module_put(t->me);
+	/* we need an atomic snapshot of the counters */
+	write_lock_bh(&t->lock);
+	if (tmp.num_counters)
+		get_counters(t->private->counters, counterstmp,
+		   t->private->nentries);
+
+	t->private = newinfo;
+	write_unlock_bh(&t->lock);
+	mutex_unlock(&ebt_mutex);
+	/* so, a user can change the chains while having messed up her counter
+	   allocation. Only reason why this is done is because this way the lock
+	   is held only once, while this doesn't bring the kernel into a
+	   dangerous state. */
+	if (tmp.num_counters &&
+	   copy_to_user(tmp.counters, counterstmp,
+	   tmp.num_counters * sizeof(struct ebt_counter))) {
+		BUGPRINT("Couldn't copy counters to userspace\n");
+		ret = -EFAULT;
+	}
+	else
+		ret = 0;
+
+	/* decrease module count and free resources */
+	EBT_ENTRY_ITERATE(table->entries, table->entries_size,
+	   ebt_cleanup_entry, NULL);
+
+	vfree(table->entries);
+	if (table->chainstack) {
+		for_each_possible_cpu(i)
+			vfree(table->chainstack[i]);
+		vfree(table->chainstack);
+	}
+	vfree(table);
+
+	vfree(counterstmp);
+	return ret;
+
+free_unlock:
+	mutex_unlock(&ebt_mutex);
+free_iterate:
+	EBT_ENTRY_ITERATE(newinfo->entries, newinfo->entries_size,
+	   ebt_cleanup_entry, NULL);
+free_counterstmp:
+	vfree(counterstmp);
+	/* can be initialized in translate_table() */
+	if (newinfo->chainstack) {
+		for_each_possible_cpu(i)
+			vfree(newinfo->chainstack[i]);
+		vfree(newinfo->chainstack);
+	}
+free_entries:
+	vfree(newinfo->entries);
+free_newinfo:
+	vfree(newinfo);
+	return ret;
+}
+
 struct ebt_table *ebt_register_table(struct net *net, struct ebt_table *table)
 {
 	struct ebt_table_info *newinfo;
 	struct ebt_table *t;
 	struct ebt_replace_kernel *repl;
 	int ret, i, countersize;
+	unsigned int rulenum, error_offset;
 	void *p;
 
 	if (!table || !(repl = table->table) || !repl->entries ||
@@ -1153,7 +1402,7 @@ struct ebt_table *ebt_register_table(struct net *net, struct ebt_table *table)
 			newinfo->hook_entry[i] = p +
 				((char *)repl->hook_entry[i] - repl->entries);
 	}
-	ret = translate_table(repl->name, newinfo);
+	ret = translate_table(repl->name, newinfo, &rulenum, &error_offset, true);
 	if (ret != 0) {
 		BUGPRINT("Translate_table failed\n");
 		goto free_chainstack;
@@ -1463,6 +1712,10 @@ static int do_ebt_get_ctl(struct sock *sk, int cmd, void __user *user, int *len)
 		mutex_unlock(&ebt_mutex);
 		break;
 
+	case EBT_SO_GET_REPLACE:
+		ret = get_replace(sock_net(sk), user, len);
+		break;
+
 	default:
 		mutex_unlock(&ebt_mutex);
 		ret = -EINVAL;
diff --git a/net/ipv4/netfilter/arp_tables.c b/net/ipv4/netfilter/arp_tables.c
index 7505dff..a677016 100644
--- a/net/ipv4/netfilter/arp_tables.c
+++ b/net/ipv4/netfilter/arp_tables.c
@@ -479,7 +479,8 @@ static inline int check_entry(struct arpt_entry *e, const char *name)
 	return 0;
 }
 
-static inline int check_target(struct arpt_entry *e, const char *name)
+static inline int check_target(struct arpt_entry *e, const char *name,
+			       bool compat_log)
 {
 	struct arpt_entry_target *t = arpt_get_target(e);
 	int ret;
@@ -490,10 +491,13 @@ static inline int check_target(struct arpt_entry *e, const char *name)
 		.targinfo  = t->data,
 		.hook_mask = e->comefrom,
 		.family    = NFPROTO_ARP,
+		.proto	   = 0,
+		.inverted  = false,
+		.compat_log = compat_log,
 	};
 
-	ret = xt_check_target(&par, t->u.target_size - sizeof(*t), 0, false);
-	if (ret < 0) {
+	ret = xt_check_target(&par, t->u.target_size - sizeof(*t));
+	if (ret) {
 		duprintf("arp_tables: check failed for `%s'.\n",
 			 t->u.kernel.target->name);
 		return ret;
@@ -503,7 +507,8 @@ static inline int check_target(struct arpt_entry *e, const char *name)
 
 static inline int
 find_check_entry(struct arpt_entry *e, const char *name, unsigned int size,
-		 unsigned int *i)
+		 unsigned int *i, unsigned int *error_offset,
+		 bool compat_log)
 {
 	struct arpt_entry_target *t;
 	struct xt_target *target;
@@ -520,14 +525,16 @@ find_check_entry(struct arpt_entry *e, const char *name, unsigned int size,
 					 "arpt_%s", t->u.user.name);
 	if (IS_ERR(target) || !target) {
 		duprintf("find_check_entry: `%s' not found\n", t->u.user.name);
-		ret = target ? PTR_ERR(target) : -ENOENT;
+		ret = target ? -XT_PTR_ERR : -ENOENT;
 		goto out;
 	}
 	t->u.kernel.target = target;
 
-	ret = check_target(e, name);
-	if (ret)
+	ret = check_target(e, name, compat_log);
+	if (ret) {
+		*error_offset = e->target_offset;
 		goto err;
+	}
 
 	(*i)++;
 	return 0;
@@ -607,9 +614,10 @@ static int translate_table(const char *name,
 			   unsigned int size,
 			   unsigned int number,
 			   const unsigned int *hook_entries,
-			   const unsigned int *underflows)
+			   const unsigned int *underflows,
+			   bool compat_log)
 {
-	unsigned int i;
+	unsigned int i, error_offset;
 	int ret;
 
 	newinfo->size = size;
@@ -665,10 +673,16 @@ static int translate_table(const char *name,
 
 	/* Finally, each sanity check must pass */
 	i = 0;
+	error_offset = sizeof(struct arpt_entry);
 	ret = ARPT_ENTRY_ITERATE(entry0, newinfo->size,
-				 find_check_entry, name, size, &i);
+				 find_check_entry, name, size, &i,
+				 &error_offset, compat_log);
 
 	if (ret != 0) {
+		/* Reuse members */
+		newinfo->hook_entry[0] = i;
+		newinfo->underflow[0] = error_offset;
+		
 		ARPT_ENTRY_ITERATE(entry0, newinfo->size,
 				cleanup_entry, &i);
 		return ret;
@@ -1083,7 +1097,7 @@ static int do_replace(struct net *net, void __user *user, unsigned int len)
 
 	ret = translate_table(tmp.name, tmp.valid_hooks,
 			      newinfo, loc_cpu_entry, tmp.size, tmp.num_entries,
-			      tmp.hook_entry, tmp.underflow);
+			      tmp.hook_entry, tmp.underflow, true);
 	if (ret != 0)
 		goto free_newinfo;
 
@@ -1102,6 +1116,122 @@ static int do_replace(struct net *net, void __user *user, unsigned int len)
 	return ret;
 }
 
+static int
+find_failed(struct arpt_entry *e, struct arpt_entry **failed,
+	    unsigned int *which)
+{
+	if (which && (*which)-- == 0) {
+		*failed = e;
+		return 1;
+	}
+	return 0;
+}
+
+static int get_replace(struct net *net, void __user *user, int *len)
+{
+	int ret;
+	struct arpt_replace tmp;
+	struct xt_table_info *newinfo;
+	void *loc_cpu_entry;
+
+	if (copy_from_user(&tmp, user, sizeof(tmp)) != 0)
+		return -EFAULT;
+
+	/* overflow check */
+	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 on 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;
+	}
+
+	*len = 0;
+	ret = translate_table(tmp.name, tmp.valid_hooks,
+			      newinfo, loc_cpu_entry, tmp.size, tmp.num_entries,
+			      tmp.hook_entry, tmp.underflow, false);
+	if (ret < 0) {
+		/* Core failure */
+		goto free_newinfo;
+	} else if (ret > 0) {
+		/* Extension checkentry failure
+		 * newinfo->hook_entry[0]: rule num
+		 * newinfo->underflow[0]: offset to match/target
+		 */
+		struct arpt_entry *e = NULL;
+		struct xt_error_entry *x;
+		unsigned int offset = newinfo->underflow[0];
+		unsigned int rulenum = newinfo->hook_entry[0];
+
+		ARPT_ENTRY_ITERATE(loc_cpu_entry, newinfo->size,
+				   find_failed, &e, &rulenum);
+
+		if (!e) {
+			ret = -EFAULT;
+			goto free_newinfo;
+		}
+		x = (void *) e + offset - sizeof(*x);
+		x->errcode = ret;
+		x->match = !!(offset < e->target_offset);
+		ret = 0;
+		if (x->match) {
+#ifndef ARPT_ENTRY_MATCH_INTRODUCED
+			ret = -EFAULT;
+#else
+			const struct arpt_entry_match *m = 
+			 	(void *) e + offset;
+			if (copy_to_user(user, x,
+					 sizeof(*x) + m->u.match_size) != 0
+			    || copy_to_user(user + sizeof(*x)
+					 + offsetof(struct arpt_entry_match,
+					 	    u.user.name),
+					m->u.kernel.match->name,
+					strlen(m->u.kernel.match->name)+1)
+			    != 0)
+			    	ret = -EFAULT;
+			else
+				*len = sizeof(*x) + m->u.match_size;
+#endif
+		} else {
+			const struct arpt_entry_target *t = 
+			 	(void *) e + offset;
+			if (copy_to_user(user, x,
+					 sizeof(*x) + t->u.target_size) != 0
+			    || copy_to_user(user + sizeof(*x)
+					 + offsetof(struct arpt_entry_target,
+					 	    u.user.name),
+					t->u.kernel.target->name,
+					strlen(t->u.kernel.target->name)+1)
+			    != 0)
+			    	ret = -EFAULT;
+			else
+				*len = sizeof(*x) + t->u.target_size;
+		}
+		goto free_newinfo;
+	}
+
+	duprintf("arp_tables: Translated table\n");
+
+	ret = __do_replace(net, tmp.name, tmp.valid_hooks, newinfo,
+			   tmp.num_counters, tmp.counters);
+	if (ret)
+		goto free_newinfo_untrans;
+	return 0;
+
+ free_newinfo_untrans:
+	ARPT_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 int
@@ -1334,7 +1464,7 @@ static inline int compat_check_entry(struct arpt_entry *e, const char *name,
 {
 	int ret;
 
-	ret = check_target(e, name);
+	ret = check_target(e, name, true);
 	if (ret)
 		return ret;
 
@@ -1751,6 +1881,10 @@ static int do_arpt_get_ctl(struct sock *sk, int cmd, void __user *user, int *len
 					"arpt_%s", rev.name);
 		break;
 	}
+	
+	case ARPT_SO_GET_REPLACE:
+		ret = get_replace(sock_net(sk), user, len);
+		break;
 
 	default:
 		duprintf("do_arpt_get_ctl: unknown request %i\n", cmd);
@@ -1784,7 +1918,8 @@ struct xt_table *arpt_register_table(struct net *net, struct xt_table *table,
 			      newinfo, loc_cpu_entry, repl->size,
 			      repl->num_entries,
 			      repl->hook_entry,
-			      repl->underflow);
+			      repl->underflow,
+			      true);
 
 	duprintf("arpt_register_table: translate table gives %d\n", ret);
 	if (ret != 0)
diff --git a/net/ipv4/netfilter/ip_tables.c b/net/ipv4/netfilter/ip_tables.c
index fdefae6..954e90e 100644
--- a/net/ipv4/netfilter/ip_tables.c
+++ b/net/ipv4/netfilter/ip_tables.c
@@ -600,28 +600,30 @@ check_entry(struct ipt_entry *e, const char *name)
 
 static int
 check_match(struct ipt_entry_match *m, struct xt_mtchk_param *par,
-	    unsigned int *i)
+	    unsigned int *i, unsigned int *error_offset)
 {
 	const struct ipt_ip *ip = par->entryinfo;
 	int ret;
 
 	par->match     = m->u.kernel.match;
 	par->matchinfo = m->data;
+	par->proto     = ip->proto;
+	par->inverted  = ip->invflags & IPT_INV_PROTO;
 
-	ret = xt_check_match(par, m->u.match_size - sizeof(*m),
-	      ip->proto, ip->invflags & IPT_INV_PROTO);
-	if (ret < 0) {
+	ret = xt_check_match(par, m->u.match_size - sizeof(*m));
+	if (ret) {
 		duprintf("ip_tables: check failed for `%s'.\n",
 			 par.match->name);
 		return ret;
 	}
+	*error_offset += m->u.match_size;
 	++*i;
 	return 0;
 }
 
 static int
 find_check_match(struct ipt_entry_match *m, struct xt_mtchk_param *par,
-		 unsigned int *i)
+		 unsigned int *i, unsigned int *error_offset)
 {
 	struct xt_match *match;
 	int ret;
@@ -631,11 +633,11 @@ find_check_match(struct ipt_entry_match *m, struct xt_mtchk_param *par,
 					"ipt_%s", m->u.user.name);
 	if (IS_ERR(match) || !match) {
 		duprintf("find_check_match: `%s' not found\n", m->u.user.name);
-		return match ? PTR_ERR(match) : -ENOENT;
+		return match ? -XT_PTR_ERR : -ENOENT;
 	}
 	m->u.kernel.match = match;
 
-	ret = check_match(m, par, i);
+	ret = check_match(m, par, i, error_offset);
 	if (ret)
 		goto err;
 
@@ -645,7 +647,8 @@ err:
 	return ret;
 }
 
-static int check_target(struct ipt_entry *e, const char *name)
+static int check_target(struct ipt_entry *e, const char *name,
+			bool compat_log)
 {
 	struct ipt_entry_target *t = ipt_get_target(e);
 	struct xt_tgchk_param par = {
@@ -655,12 +658,14 @@ static int check_target(struct ipt_entry *e, const char *name)
 		.targinfo  = t->data,
 		.hook_mask = e->comefrom,
 		.family    = NFPROTO_IPV4,
+		.proto     = e->ip.proto,
+		.inverted  = e->ip.invflags & IPT_INV_PROTO,
+		.compat_log = compat_log,
 	};
 	int ret;
 
-	ret = xt_check_target(&par, t->u.target_size - sizeof(*t),
-	      e->ip.proto, e->ip.invflags & IPT_INV_PROTO);
-	if (ret < 0) {
+	ret = xt_check_target(&par, t->u.target_size - sizeof(*t));
+	if (ret) {
 		duprintf("ip_tables: check failed for `%s'.\n",
 			 t->u.kernel.target->name);
 		return ret;
@@ -670,7 +675,8 @@ static int check_target(struct ipt_entry *e, const char *name)
 
 static int
 find_check_entry(struct ipt_entry *e, const char *name, unsigned int size,
-		 unsigned int *i)
+		 unsigned int *i, unsigned int *error_offset,
+		 bool compat_log)
 {
 	struct ipt_entry_target *t;
 	struct xt_target *target;
@@ -687,10 +693,11 @@ find_check_entry(struct ipt_entry *e, const char *name, unsigned int size,
 	mtpar.entryinfo = &e->ip;
 	mtpar.hook_mask = e->comefrom;
 	mtpar.family    = NFPROTO_IPV4;
-	ret = IPT_MATCH_ITERATE(e, find_check_match, &mtpar, &j);
-	if (ret != 0)
+	mtpar.compat_log = compat_log;
+	ret = IPT_MATCH_ITERATE(e, find_check_match, &mtpar, &j, error_offset);
+	if (ret != 0) {
 		goto cleanup_matches;
-
+	}
 	t = ipt_get_target(e);
 	target = try_then_request_module(xt_find_target(AF_INET,
 							t->u.user.name,
@@ -698,14 +705,16 @@ find_check_entry(struct ipt_entry *e, const char *name, unsigned int size,
 					 "ipt_%s", t->u.user.name);
 	if (IS_ERR(target) || !target) {
 		duprintf("find_check_entry: `%s' not found\n", t->u.user.name);
-		ret = target ? PTR_ERR(target) : -ENOENT;
+		ret = target ? -XT_PTR_ERR : -ENOENT;
 		goto cleanup_matches;
 	}
 	t->u.kernel.target = target;
 
-	ret = check_target(e, name);
-	if (ret)
+	ret = check_target(e, name, compat_log);
+	if (ret) {
+		*error_offset = e->target_offset;
 		goto err;
+	}
 
 	(*i)++;
 	return 0;
@@ -791,9 +800,10 @@ translate_table(const char *name,
 		unsigned int size,
 		unsigned int number,
 		const unsigned int *hook_entries,
-		const unsigned int *underflows)
+		const unsigned int *underflows,
+		bool compat_log)
 {
-	unsigned int i;
+	unsigned int i, error_offset;
 	int ret;
 
 	newinfo->size = size;
@@ -845,12 +855,19 @@ translate_table(const char *name,
 
 	/* Finally, each sanity check must pass */
 	i = 0;
+	error_offset = sizeof(struct ipt_entry);
 	ret = IPT_ENTRY_ITERATE(entry0, newinfo->size,
-				find_check_entry, name, size, &i);
+				find_check_entry, name, size,
+				&i, &error_offset, compat_log);
 
 	if (ret != 0) {
+		/* Reuse members */
+		newinfo->hook_entry[0] = i;
+		newinfo->underflow[0] = error_offset;
+
 		IPT_ENTRY_ITERATE(entry0, newinfo->size,
 				cleanup_entry, &i);
+
 		return ret;
 	}
 
@@ -1291,7 +1308,7 @@ do_replace(struct net *net, void __user *user, unsigned int len)
 
 	ret = translate_table(tmp.name, tmp.valid_hooks,
 			      newinfo, loc_cpu_entry, tmp.size, tmp.num_entries,
-			      tmp.hook_entry, tmp.underflow);
+			      tmp.hook_entry, tmp.underflow, true);
 	if (ret != 0)
 		goto free_newinfo;
 
@@ -1310,6 +1327,120 @@ do_replace(struct net *net, void __user *user, unsigned int len)
 	return ret;
 }
 
+static int
+find_failed(struct ipt_entry *e, struct ipt_entry **failed,
+	    unsigned int *which)
+{
+	if (which && (*which)-- == 0) {
+		*failed = e;
+		return 1;
+	}
+	return 0;
+}
+
+static int
+get_replace(struct net *net, void __user *user, 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;
+
+	/* overflow check */
+	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 on 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;
+	}
+
+	*len = 0;
+	ret = translate_table(tmp.name, tmp.valid_hooks,
+			      newinfo, loc_cpu_entry, tmp.size, tmp.num_entries,
+			      tmp.hook_entry, tmp.underflow, false);
+	if (ret < 0) {
+		/* Core failure */
+		goto free_newinfo;
+	} else if (ret > 0) {
+		/* Extension checkentry failure
+		 * newinfo->hook_entry[0]: rule num
+		 * newinfo->underflow[0]: offset to match/target
+		 */
+		struct ipt_entry *e = NULL;
+		struct xt_error_entry *x;
+		unsigned int offset = newinfo->underflow[0];
+		unsigned int rulenum = newinfo->hook_entry[0];
+
+		IPT_ENTRY_ITERATE(loc_cpu_entry, newinfo->size,
+				  find_failed, &e, &rulenum);
+
+		if (!e) {
+			ret = -EFAULT;
+			goto free_newinfo;
+		}
+		x = (void *) e + offset - sizeof(*x);
+		x->errcode = ret;
+		x->match = !!(offset < e->target_offset);
+		ret = 0;
+		if (x->match) {
+			const struct ipt_entry_match *m = 
+			 	(void *) e + offset;
+			if (copy_to_user(user, x,
+					 sizeof(*x) + m->u.match_size) != 0
+			    || copy_to_user(user + sizeof(*x)
+					 + offsetof(struct ipt_entry_match,
+					 	    u.user.name),
+					m->u.kernel.match->name,
+					strlen(m->u.kernel.match->name)+1)
+			    != 0)
+			    	ret = -EFAULT;
+			else
+				*len = sizeof(*x) + m->u.match_size;
+		} else {
+			const struct ipt_entry_target *t = 
+			 	(void *) e + offset;
+			if (copy_to_user(user, x,
+					 sizeof(*x) + t->u.target_size) != 0
+			    || copy_to_user(user + sizeof(*x)
+					 + offsetof(struct ipt_entry_target,
+					 	    u.user.name),
+					t->u.kernel.target->name,
+					strlen(t->u.kernel.target->name)+1)
+			    != 0)
+			    	ret = -EFAULT;
+			else
+				*len = sizeof(*x) + t->u.target_size;
+		}
+		goto free_newinfo;
+	}
+
+	duprintf("ip_tables: Translated table\n");
+
+	*len = 0;
+	ret = __do_replace(net, 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 int
@@ -1645,7 +1776,7 @@ compat_check_entry(struct ipt_entry *e, const char *name,
 				     unsigned int *i)
 {
 	struct xt_mtchk_param mtpar;
-	unsigned int j;
+	unsigned int j, error_offset;
 	int ret;
 
 	j = 0;
@@ -1653,11 +1784,12 @@ compat_check_entry(struct ipt_entry *e, const char *name,
 	mtpar.entryinfo = &e->ip;
 	mtpar.hook_mask = e->comefrom;
 	mtpar.family    = NFPROTO_IPV4;
-	ret = IPT_MATCH_ITERATE(e, check_match, &mtpar, &j);
+	mtpar.compat_log = true;
+	ret = IPT_MATCH_ITERATE(e, check_match, &mtpar, &j, &error_offset);
 	if (ret)
 		goto cleanup_matches;
 
-	ret = check_target(e, name);
+	ret = check_target(e, name, true);
 	if (ret)
 		goto cleanup_matches;
 
@@ -2043,6 +2175,10 @@ do_ipt_get_ctl(struct sock *sk, int cmd, void __user *user, int *len)
 		break;
 	}
 
+	case IPT_SO_GET_REPLACE:
+		ret = get_replace(sock_net(sk), user, len);
+		break;
+
 	default:
 		duprintf("do_ipt_get_ctl: unknown request %i\n", cmd);
 		ret = -EINVAL;
@@ -2075,7 +2211,8 @@ struct xt_table *ipt_register_table(struct net *net, struct xt_table *table,
 			      newinfo, loc_cpu_entry, repl->size,
 			      repl->num_entries,
 			      repl->hook_entry,
-			      repl->underflow);
+			      repl->underflow,
+			      true);
 	if (ret != 0)
 		goto out_free;
 
@@ -2148,12 +2285,20 @@ icmp_match(const struct sk_buff *skb, const struct xt_match_param *par)
 				    !!(icmpinfo->invflags&IPT_ICMP_INV));
 }
 
-static bool icmp_checkentry(const struct xt_mtchk_param *par)
+static unsigned int icmp_checkentry(const struct xt_mtchk_param *par)
 {
-	const struct ipt_icmp *icmpinfo = par->matchinfo;
+	struct ipt_icmp *icmpinfo = par->matchinfo;
 
+	if (par->proto != IPPROTO_ICMP || par->inverted) {
+		xt_compat_log(par, "icmp match: only valid for protocol ICMP");
+		return IPT_ICMP_ERR_PROTO;
+	}
 	/* Must specify no unknown invflags */
-	return !(icmpinfo->invflags & ~IPT_ICMP_INV);
+	if (icmpinfo->invflags & ~IPT_ICMP_INV) {
+		icmpinfo->invflags &= ~IPT_ICMP_INV;
+		return IPT_ICMP_ERR_FLAGS;
+	}
+	return IPT_ICMP_ERR_NONE;
 }
 
 /* The built-in targets: standard (NULL) and error. */
@@ -2197,7 +2342,6 @@ static struct xt_match icmp_matchstruct __read_mostly = {
 	.match		= icmp_match,
 	.matchsize	= sizeof(struct ipt_icmp),
 	.checkentry	= icmp_checkentry,
-	.proto		= IPPROTO_ICMP,
 	.family		= NFPROTO_IPV4,
 };
 
diff --git a/net/ipv6/netfilter/ip6_tables.c b/net/ipv6/netfilter/ip6_tables.c
index ced1f2c..907235b 100644
--- a/net/ipv6/netfilter/ip6_tables.c
+++ b/net/ipv6/netfilter/ip6_tables.c
@@ -628,28 +628,30 @@ check_entry(struct ip6t_entry *e, const char *name)
 }
 
 static int check_match(struct ip6t_entry_match *m, struct xt_mtchk_param *par,
-		       unsigned int *i)
+		       unsigned int *i, unsigned int *error_offset)
 {
 	const struct ip6t_ip6 *ipv6 = par->entryinfo;
 	int ret;
 
 	par->match     = m->u.kernel.match;
 	par->matchinfo = m->data;
+	par->proto     = ipv6->proto;
+	par->inverted  = ipv6->invflags & IP6T_INV_PROTO;
 
-	ret = xt_check_match(par, m->u.match_size - sizeof(*m),
-			     ipv6->proto, ipv6->invflags & IP6T_INV_PROTO);
-	if (ret < 0) {
+	ret = xt_check_match(par, m->u.match_size - sizeof(*m));
+	if (ret) {
 		duprintf("ip_tables: check failed for `%s'.\n",
 			 par.match->name);
 		return ret;
 	}
+	*error_offset += m->u.match_size;
 	++*i;
 	return 0;
 }
 
 static int
 find_check_match(struct ip6t_entry_match *m, struct xt_mtchk_param *par,
-		 unsigned int *i)
+		 unsigned int *i, unsigned int *error_offset)
 {
 	struct xt_match *match;
 	int ret;
@@ -659,11 +661,11 @@ find_check_match(struct ip6t_entry_match *m, struct xt_mtchk_param *par,
 					"ip6t_%s", m->u.user.name);
 	if (IS_ERR(match) || !match) {
 		duprintf("find_check_match: `%s' not found\n", m->u.user.name);
-		return match ? PTR_ERR(match) : -ENOENT;
+		return match ? -XT_PTR_ERR : -ENOENT;
 	}
 	m->u.kernel.match = match;
 
-	ret = check_match(m, par, i);
+	ret = check_match(m, par, i, error_offset);
 	if (ret)
 		goto err;
 
@@ -673,7 +675,8 @@ err:
 	return ret;
 }
 
-static int check_target(struct ip6t_entry *e, const char *name)
+static int check_target(struct ip6t_entry *e, const char *name,
+			bool compat_log)
 {
 	struct ip6t_entry_target *t = ip6t_get_target(e);
 	struct xt_tgchk_param par = {
@@ -683,13 +686,15 @@ static int check_target(struct ip6t_entry *e, const char *name)
 		.targinfo  = t->data,
 		.hook_mask = e->comefrom,
 		.family    = NFPROTO_IPV6,
+		.proto     = e->ipv6.proto,
+		.inverted  = e->ipv6.invflags & IP6T_INV_PROTO,
+		.compat_log = compat_log,
 	};
 	int ret;
 
 	t = ip6t_get_target(e);
-	ret = xt_check_target(&par, t->u.target_size - sizeof(*t),
-	      e->ipv6.proto, e->ipv6.invflags & IP6T_INV_PROTO);
-	if (ret < 0) {
+	ret = xt_check_target(&par, t->u.target_size - sizeof(*t));
+	if (ret) {
 		duprintf("ip_tables: check failed for `%s'.\n",
 			 t->u.kernel.target->name);
 		return ret;
@@ -699,7 +704,8 @@ static int check_target(struct ip6t_entry *e, const char *name)
 
 static int
 find_check_entry(struct ip6t_entry *e, const char *name, unsigned int size,
-		 unsigned int *i)
+		 unsigned int *i, unsigned int *error_offset,
+		 bool compat_log)
 {
 	struct ip6t_entry_target *t;
 	struct xt_target *target;
@@ -716,7 +722,8 @@ find_check_entry(struct ip6t_entry *e, const char *name, unsigned int size,
 	mtpar.entryinfo = &e->ipv6;
 	mtpar.hook_mask = e->comefrom;
 	mtpar.family    = NFPROTO_IPV6;
-	ret = IP6T_MATCH_ITERATE(e, find_check_match, &mtpar, &j);
+	mtpar.compat_log = compat_log;
+	ret = IP6T_MATCH_ITERATE(e, find_check_match, &mtpar, &j, error_offset);
 	if (ret != 0)
 		goto cleanup_matches;
 
@@ -727,14 +734,16 @@ find_check_entry(struct ip6t_entry *e, const char *name, unsigned int size,
 					 "ip6t_%s", t->u.user.name);
 	if (IS_ERR(target) || !target) {
 		duprintf("find_check_entry: `%s' not found\n", t->u.user.name);
-		ret = target ? PTR_ERR(target) : -ENOENT;
+		ret = target ? -XT_PTR_ERR : -ENOENT;
 		goto cleanup_matches;
 	}
 	t->u.kernel.target = target;
 
-	ret = check_target(e, name);
-	if (ret)
+	ret = check_target(e, name, compat_log);
+	if (ret) {
+		*error_offset = e->target_offset;
 		goto err;
+	}
 
 	(*i)++;
 	return 0;
@@ -820,9 +829,10 @@ translate_table(const char *name,
 		unsigned int size,
 		unsigned int number,
 		const unsigned int *hook_entries,
-		const unsigned int *underflows)
+		const unsigned int *underflows,
+		bool compat_log)
 {
-	unsigned int i;
+	unsigned int i, error_offset;
 	int ret;
 
 	newinfo->size = size;
@@ -874,12 +884,19 @@ translate_table(const char *name,
 
 	/* Finally, each sanity check must pass */
 	i = 0;
+	error_offset = sizeof(struct ip6t_entry);
 	ret = IP6T_ENTRY_ITERATE(entry0, newinfo->size,
-				find_check_entry, name, size, &i);
+				find_check_entry, name, size,
+				&i, &error_offset, compat_log);
 
 	if (ret != 0) {
+		/* Reuse members */
+		newinfo->hook_entry[0] = i;
+		newinfo->underflow[0] = error_offset;
+
 		IP6T_ENTRY_ITERATE(entry0, newinfo->size,
 				   cleanup_entry, &i);
+				   
 		return ret;
 	}
 
@@ -1321,7 +1338,7 @@ do_replace(struct net *net, void __user *user, unsigned int len)
 
 	ret = translate_table(tmp.name, tmp.valid_hooks,
 			      newinfo, loc_cpu_entry, tmp.size, tmp.num_entries,
-			      tmp.hook_entry, tmp.underflow);
+			      tmp.hook_entry, tmp.underflow, true);
 	if (ret != 0)
 		goto free_newinfo;
 
@@ -1340,6 +1357,120 @@ do_replace(struct net *net, void __user *user, unsigned int len)
 	return ret;
 }
 
+static int
+find_failed(struct ip6t_entry *e, struct ip6t_entry **failed,
+	    unsigned int *which)
+{
+	if (which && (*which)-- == 0) {
+		*failed = e;
+		return 1;
+	}
+	return 0;
+}
+
+static int
+get_replace(struct net *net, void __user *user, int *len)
+{
+	int ret;
+	struct ip6t_replace tmp;
+	struct xt_table_info *newinfo;
+	void *loc_cpu_entry;
+
+	if (copy_from_user(&tmp, user, sizeof(tmp)) != 0)
+		return -EFAULT;
+
+	/* overflow check */
+	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 on 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;
+	}
+
+	*len = 0;
+	ret = translate_table(tmp.name, tmp.valid_hooks,
+			      newinfo, loc_cpu_entry, tmp.size, tmp.num_entries,
+			      tmp.hook_entry, tmp.underflow, false);
+	if (ret < 0) {
+		/* Core failure */
+		goto free_newinfo;
+ 	} else if (ret > 0) {
+ 		/* Extension checkentry failure:
+ 		 * newinfo->hook_entry[0]: rule num
+ 		 * newinfo->underflow[0]: offset to match/target
+ 		 */
+ 		struct ip6t_entry *e = NULL;
+ 		struct xt_error_entry *x;
+ 		unsigned int offset = newinfo->underflow[0];
+ 		unsigned int rulenum = newinfo->hook_entry[0];
+ 		
+ 		IP6T_ENTRY_ITERATE(loc_cpu_entry, newinfo->size,
+ 				   find_failed, &e, &rulenum);
+
+		if (!e) {
+			ret = -EFAULT;
+			goto free_newinfo;
+		}
+		x = (void *) e + offset - sizeof(*x);
+		x->errcode = ret;
+		x->match = !!(offset < e->target_offset);
+		ret = 0;
+		if (x->match) {
+			const struct ip6t_entry_match *m = 
+			 	(void *) e + offset;
+			if (copy_to_user(user, x,
+					 sizeof(*x) + m->u.match_size) != 0
+			    || copy_to_user(user + sizeof(*x)
+					 + offsetof(struct ip6t_entry_match,
+					 	    u.user.name),
+					m->u.kernel.match->name,
+					strlen(m->u.kernel.match->name)+1)
+			    != 0)
+			    	ret = -EFAULT;
+			else
+				*len = sizeof(*x) + m->u.match_size;
+		} else {
+			const struct ip6t_entry_target *t = 
+			 	(void *) e + offset;
+			*len = sizeof(*x) + t->u.target_size;
+			if (copy_to_user(user, x,
+					 sizeof(*x) + t->u.target_size) != 0
+			    || copy_to_user(user + sizeof(*x)
+					 + offsetof(struct ip6t_entry_target,
+					 	    u.user.name),
+					t->u.kernel.target->name,
+					strlen(t->u.kernel.target->name)+1)
+			    != 0)
+			    	ret = -EFAULT;
+			else
+				*len = sizeof(*x) + t->u.target_size;
+		}
+		goto free_newinfo;
+	}
+
+	duprintf("ip_tables: Translated table\n");
+
+	ret = __do_replace(net, tmp.name, tmp.valid_hooks, newinfo,
+			   tmp.num_counters, tmp.counters);
+	if (ret)
+		goto free_newinfo_untrans;
+	return 0;
+
+ free_newinfo_untrans:
+	IP6T_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 int
@@ -1676,7 +1807,7 @@ compat_copy_entry_from_user(struct compat_ip6t_entry *e, void **dstptr,
 static int compat_check_entry(struct ip6t_entry *e, const char *name,
 				     unsigned int *i)
 {
-	unsigned int j;
+	unsigned int j, error_offset;
 	int ret;
 	struct xt_mtchk_param mtpar;
 
@@ -1685,11 +1816,12 @@ static int compat_check_entry(struct ip6t_entry *e, const char *name,
 	mtpar.entryinfo = &e->ipv6;
 	mtpar.hook_mask = e->comefrom;
 	mtpar.family    = NFPROTO_IPV6;
-	ret = IP6T_MATCH_ITERATE(e, check_match, &mtpar, &j);
+	mtpar.compat_log = true;
+	ret = IP6T_MATCH_ITERATE(e, check_match, &mtpar, &j, &error_offset);
 	if (ret)
 		goto cleanup_matches;
 
-	ret = check_target(e, name);
+	ret = check_target(e, name, true);
 	if (ret)
 		goto cleanup_matches;
 
@@ -2075,6 +2207,10 @@ do_ip6t_get_ctl(struct sock *sk, int cmd, void __user *user, int *len)
 		break;
 	}
 
+	case IP6T_SO_GET_REPLACE:
+		ret = get_replace(sock_net(sk), user, len);
+		break;
+
 	default:
 		duprintf("do_ip6t_get_ctl: unknown request %i\n", cmd);
 		ret = -EINVAL;
@@ -2107,7 +2243,8 @@ struct xt_table *ip6t_register_table(struct net *net, struct xt_table *table,
 			      newinfo, loc_cpu_entry, repl->size,
 			      repl->num_entries,
 			      repl->hook_entry,
-			      repl->underflow);
+			      repl->underflow,
+			      true);
 	if (ret != 0)
 		goto out_free;
 
@@ -2179,12 +2316,18 @@ icmp6_match(const struct sk_buff *skb, const struct xt_match_param *par)
 }
 
 /* Called when user tries to insert an entry of this type. */
-static bool icmp6_checkentry(const struct xt_mtchk_param *par)
+static unsigned int icmp6_checkentry(const struct xt_mtchk_param *par)
 {
 	const struct ip6t_icmp *icmpinfo = par->matchinfo;
 
+	if (par->proto != IPPROTO_ICMPV6 || par->inverted) {
+		xt_compat_log(par, "icmpv6 match: only valid for protocol ICMPV6");
+		return IP6T_ICMPV6_ERR_PROTO;
+	}
 	/* Must specify no unknown invflags */
-	return !(icmpinfo->invflags & ~IP6T_ICMP_INV);
+	if (icmpinfo->invflags & ~IP6T_ICMP_INV)
+		return IP6T_ICMPV6_ERR_FLAGS;
+	return IP6T_ICMPV6_ERR_NONE;
 }
 
 /* The built-in targets: standard (NULL) and error. */
@@ -2228,7 +2371,6 @@ static struct xt_match icmp6_matchstruct __read_mostly = {
 	.match		= icmp6_match,
 	.matchsize	= sizeof(struct ip6t_icmp),
 	.checkentry	= icmp6_checkentry,
-	.proto		= IPPROTO_ICMPV6,
 	.family		= NFPROTO_IPV6,
 };
 
diff --git a/net/netfilter/x_tables.c b/net/netfilter/x_tables.c
index 025d1a0..59465ca 100644
--- a/net/netfilter/x_tables.c
+++ b/net/netfilter/x_tables.c
@@ -329,34 +329,7 @@ int xt_find_revision(u8 af, const char *name, u8 revision, int target,
 }
 EXPORT_SYMBOL_GPL(xt_find_revision);
 
-static char *textify_hooks(char *buf, size_t size, unsigned int mask)
-{
-	static const char *const names[] = {
-		"PREROUTING", "INPUT", "FORWARD",
-		"OUTPUT", "POSTROUTING", "BROUTING",
-	};
-	unsigned int i;
-	char *p = buf;
-	bool np = false;
-	int res;
-
-	*p = '\0';
-	for (i = 0; i < ARRAY_SIZE(names); ++i) {
-		if (!(mask & (1 << i)))
-			continue;
-		res = snprintf(p, size, "%s%s", np ? "/" : "", names[i]);
-		if (res > 0) {
-			size -= res;
-			p += res;
-		}
-		np = true;
-	}
-
-	return buf;
-}
-
-int xt_check_match(struct xt_mtchk_param *par,
-		   unsigned int size, u_int8_t proto, bool inv_proto)
+int xt_check_match(struct xt_mtchk_param *par, unsigned int size)
 {
 	if (XT_ALIGN(par->match->matchsize) != size &&
 	    par->match->matchsize != -1) {
@@ -369,31 +342,8 @@ int xt_check_match(struct xt_mtchk_param *par,
 		       XT_ALIGN(par->match->matchsize), size);
 		return -EINVAL;
 	}
-	if (par->match->table != NULL &&
-	    strcmp(par->match->table, par->table) != 0) {
-		pr_err("%s_tables: %s match: only valid in %s table, not %s\n",
-		       xt_prefix[par->family], par->match->name,
-		       par->match->table, par->table);
-		return -EINVAL;
-	}
-	if (par->match->hooks && (par->hook_mask & ~par->match->hooks) != 0) {
-		char used[64], allow[64];
-
-		pr_err("%s_tables: %s match: used from hooks %s, but only "
-		       "valid from %s\n",
-		       xt_prefix[par->family], par->match->name,
-		       textify_hooks(used, sizeof(used), par->hook_mask),
-		       textify_hooks(allow, sizeof(allow), par->match->hooks));
-		return -EINVAL;
-	}
-	if (par->match->proto && (par->match->proto != proto || inv_proto)) {
-		pr_err("%s_tables: %s match: only valid for protocol %u\n",
-		       xt_prefix[par->family], par->match->name,
-		       par->match->proto);
-		return -EINVAL;
-	}
-	if (par->match->checkentry != NULL && !par->match->checkentry(par))
-		return -EINVAL;
+	if (par->match->checkentry != NULL)
+		return par->match->checkentry(par);
 	return 0;
 }
 EXPORT_SYMBOL_GPL(xt_check_match);
@@ -510,8 +460,7 @@ int xt_compat_match_to_user(struct xt_entry_match *m, void __user **dstptr,
 EXPORT_SYMBOL_GPL(xt_compat_match_to_user);
 #endif /* CONFIG_COMPAT */
 
-int xt_check_target(struct xt_tgchk_param *par,
-		    unsigned int size, u_int8_t proto, bool inv_proto)
+int xt_check_target(struct xt_tgchk_param *par, unsigned int size)
 {
 	if (XT_ALIGN(par->target->targetsize) != size) {
 		pr_err("%s_tables: %s target: invalid size %Zu != %u\n",
@@ -519,31 +468,8 @@ int xt_check_target(struct xt_tgchk_param *par,
 		       XT_ALIGN(par->target->targetsize), size);
 		return -EINVAL;
 	}
-	if (par->target->table != NULL &&
-	    strcmp(par->target->table, par->table) != 0) {
-		pr_err("%s_tables: %s target: only valid in %s table, not %s\n",
-		       xt_prefix[par->family], par->target->name,
-		       par->target->table, par->table);
-		return -EINVAL;
-	}
-	if (par->target->hooks && (par->hook_mask & ~par->target->hooks) != 0) {
-		char used[64], allow[64];
-
-		pr_err("%s_tables: %s target: used from hooks %s, but only "
-		       "usable from %s\n",
-		       xt_prefix[par->family], par->target->name,
-		       textify_hooks(used, sizeof(used), par->hook_mask),
-		       textify_hooks(allow, sizeof(allow), par->target->hooks));
-		return -EINVAL;
-	}
-	if (par->target->proto && (par->target->proto != proto || inv_proto)) {
-		pr_err("%s_tables: %s target: only valid for protocol %u\n",
-		       xt_prefix[par->family], par->target->name,
-		       par->target->proto);
-		return -EINVAL;
-	}
-	if (par->target->checkentry != NULL && !par->target->checkentry(par))
-		return -EINVAL;
+	if (par->target->checkentry != NULL)
+		return par->target->checkentry(par);
 	return 0;
 }
 EXPORT_SYMBOL_GPL(xt_check_target);


Best regards,
Jozsef
-
E-mail  : kadlec@xxxxxxxxxxxxxxxxx, kadlec@xxxxxxxxxxxx
PGP key : http://www.kfki.hu/~kadlec/pgp_public_key.txt
Address : KFKI Research Institute for Particle and Nuclear Physics
          H-1525 Budapest 114, POB. 49, Hungary
--
To unsubscribe from this list: send the line "unsubscribe netfilter-devel" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html

[Index of Archives]     [Netfitler Users]     [LARTC]     [Bugtraq]     [Yosemite Forum]

  Powered by Linux