[NETFILTER]: x_tables: simplify compat API
Patrick McHardy [Wed, 20 Sep 2006 19:05:37 +0000 (12:05 -0700)]
Split the xt_compat_match/xt_compat_target into smaller type-safe functions
performing just one operation. Handle all alignment and size-related
conversions centrally in these function instead of requiring each module to
implement a full-blown conversion function. Replace ->compat callback by
->compat_from_user and ->compat_to_user callbacks, responsible for
converting just a single private structure.

Signed-off-by: Patrick McHardy <kaber@trash.net>
Signed-off-by: David S. Miller <davem@davemloft.net>

include/linux/netfilter/x_tables.h
net/ipv4/netfilter/ip_tables.c
net/netfilter/x_tables.c

index c832295..739a98e 100644 (file)
@@ -138,12 +138,6 @@ struct xt_counters_info
 
 #include <linux/netdevice.h>
 
-#ifdef CONFIG_COMPAT
-#define COMPAT_TO_USER         1
-#define COMPAT_FROM_USER       -1
-#define COMPAT_CALC_SIZE       0
-#endif
-
 struct xt_match
 {
        struct list_head list;
@@ -176,7 +170,8 @@ struct xt_match
        void (*destroy)(const struct xt_match *match, void *matchinfo);
 
        /* Called when userspace align differs from kernel space one */
-       int (*compat)(void *match, void **dstptr, int *size, int convert);
+       void (*compat_from_user)(void *dst, void *src);
+       int (*compat_to_user)(void __user *dst, void *src);
 
        /* Set this to THIS_MODULE if you are a module, otherwise NULL */
        struct module *me;
@@ -186,6 +181,7 @@ struct xt_match
 
        char *table;
        unsigned int matchsize;
+       unsigned int compatsize;
        unsigned int hooks;
        unsigned short proto;
 
@@ -224,13 +220,15 @@ struct xt_target
        void (*destroy)(const struct xt_target *target, void *targinfo);
 
        /* Called when userspace align differs from kernel space one */
-       int (*compat)(void *target, void **dstptr, int *size, int convert);
+       void (*compat_from_user)(void *dst, void *src);
+       int (*compat_to_user)(void __user *dst, void *src);
 
        /* Set this to THIS_MODULE if you are a module, otherwise NULL */
        struct module *me;
 
        char *table;
        unsigned int targetsize;
+       unsigned int compatsize;
        unsigned int hooks;
        unsigned short proto;
 
@@ -387,9 +385,18 @@ struct compat_xt_counters_info
 
 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);
+
+extern int xt_compat_match_offset(struct xt_match *match);
+extern void xt_compat_match_from_user(struct xt_entry_match *m,
+                                     void **dstptr, int *size);
+extern int xt_compat_match_to_user(struct xt_entry_match *m,
+                                  void * __user *dstptr, int *size);
+
+extern int xt_compat_target_offset(struct xt_target *target);
+extern void xt_compat_target_from_user(struct xt_entry_target *t,
+                                      void **dstptr, int *size);
+extern int xt_compat_target_to_user(struct xt_entry_target *t,
+                                   void * __user *dstptr, int *size);
 
 #endif /* CONFIG_COMPAT */
 #endif /* __KERNEL__ */
index 673581d..800067d 100644 (file)
@@ -942,73 +942,28 @@ static short compat_calc_jump(u_int16_t offset)
        return delta;
 }
 
-struct compat_ipt_standard_target
+static void compat_standard_from_user(void *dst, void *src)
 {
-       struct compat_xt_entry_target target;
-       compat_int_t verdict;
-};
-
-struct compat_ipt_standard
-{
-       struct compat_ipt_entry entry;
-       struct compat_ipt_standard_target target;
-};
+       int v = *(compat_int_t *)src;
 
-#define IPT_ST_LEN             XT_ALIGN(sizeof(struct ipt_standard_target))
-#define IPT_ST_COMPAT_LEN      COMPAT_XT_ALIGN(sizeof(struct compat_ipt_standard_target))
-#define IPT_ST_OFFSET          (IPT_ST_LEN - IPT_ST_COMPAT_LEN)
+       if (v > 0)
+               v += compat_calc_jump(v);
+       memcpy(dst, &v, sizeof(v));
+}
 
-static int compat_ipt_standard_fn(void *target,
-               void **dstptr, int *size, int convert)
+static int compat_standard_to_user(void __user *dst, void *src)
 {
-       struct compat_ipt_standard_target compat_st, *pcompat_st;
-       struct ipt_standard_target st, *pst;
-       int ret;
+       compat_int_t cv = *(int *)src;
 
-       ret = 0;
-       switch (convert) {
-               case COMPAT_TO_USER:
-                       pst = target;
-                       memcpy(&compat_st.target, &pst->target,
-                               sizeof(compat_st.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 = IPT_ST_COMPAT_LEN;
-                       if (copy_to_user(*dstptr, &compat_st, IPT_ST_COMPAT_LEN))
-                               ret = -EFAULT;
-                       *size -= IPT_ST_OFFSET;
-                       *dstptr += IPT_ST_COMPAT_LEN;
-                       break;
-               case COMPAT_FROM_USER:
-                       pcompat_st = target;
-                       memcpy(&st.target, &pcompat_st->target, IPT_ST_COMPAT_LEN);
-                       st.verdict = pcompat_st->verdict;
-                       if (st.verdict > 0)
-                               st.verdict += compat_calc_jump(st.verdict);
-                       st.target.u.user.target_size = IPT_ST_LEN;
-                       memcpy(*dstptr, &st, IPT_ST_LEN);
-                       *size += IPT_ST_OFFSET;
-                       *dstptr += IPT_ST_LEN;
-                       break;
-               case COMPAT_CALC_SIZE:
-                       *size += IPT_ST_OFFSET;
-                       break;
-               default:
-                       ret = -ENOPROTOOPT;
-                       break;
-       }
-       return ret;
+       if (cv > 0)
+               cv -= compat_calc_jump(cv);
+       return copy_to_user(dst, &cv, sizeof(cv)) ? -EFAULT : 0;
 }
 
 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);
+       *size += xt_compat_match_offset(m->u.kernel.match);
        return 0;
 }
 
@@ -1023,10 +978,7 @@ static int compat_calc_entry(struct ipt_entry *e, struct xt_table_info *info,
        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);
+       off += xt_compat_target_offset(t->u.kernel.target);
        newinfo->size -= off;
        ret = compat_add_offset(entry_offset, off);
        if (ret)
@@ -1412,17 +1364,13 @@ struct compat_ipt_replace {
 };
 
 static inline int compat_copy_match_to_user(struct ipt_entry_match *m,
-               void __user **dstptr, compat_uint_t *size)
+               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);
+       return xt_compat_match_to_user(m, dstptr, size);
 }
 
 static int compat_copy_entry_to_user(struct ipt_entry *e,
-               void __user **dstptr, compat_uint_t *size)
+               void * __user *dstptr, compat_uint_t *size)
 {
        struct ipt_entry_target __user *t;
        struct compat_ipt_entry __user *ce;
@@ -1442,11 +1390,7 @@ static int compat_copy_entry_to_user(struct ipt_entry *e,
        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);
+       ret = xt_compat_target_to_user(t, dstptr, size);
        if (ret)
                goto out;
        ret = -EFAULT;
@@ -1478,11 +1422,7 @@ compat_check_calc_match(struct ipt_entry_match *m,
                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);
+       *size += xt_compat_match_offset(match);
 
        (*i)++;
        return 0;
@@ -1543,10 +1483,7 @@ check_compat_entry_size_and_hooks(struct ipt_entry *e,
        }
        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);
+       off += xt_compat_target_offset(target);
        *size += off;
        ret = compat_add_offset(entry_offset, off);
        if (ret)
@@ -1584,10 +1521,7 @@ static inline int compat_copy_match_from_user(struct ipt_entry_match *m,
 
        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);
+       xt_compat_match_from_user(m, dstptr, size);
 
        ret = xt_check_match(match, AF_INET, dm->u.match_size - sizeof(*dm),
                             name, hookmask, ip->proto,
@@ -1635,10 +1569,7 @@ static int compat_copy_entry_from_user(struct ipt_entry *e, void **dstptr,
        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);
+       xt_compat_target_from_user(t, dstptr, size);
 
        de->next_offset = e->next_offset - (origsize - *size);
        for (h = 0; h < NF_IP_NUMHOOKS; h++) {
@@ -2205,7 +2136,9 @@ static struct ipt_target ipt_standard_target = {
        .targetsize     = sizeof(int),
        .family         = AF_INET,
 #ifdef CONFIG_COMPAT
-       .compat         = &compat_ipt_standard_fn,
+       .compatsize     = sizeof(compat_int_t),
+       .compat_from_user = compat_standard_from_user,
+       .compat_to_user = compat_standard_to_user,
 #endif
 };
 
index be7baf4..58522fc 100644 (file)
@@ -333,52 +333,65 @@ int xt_check_match(const struct xt_match *match, unsigned short family,
 EXPORT_SYMBOL_GPL(xt_check_match);
 
 #ifdef CONFIG_COMPAT
-int xt_compat_match(void *match, void **dstptr, int *size, int convert)
+int xt_compat_match_offset(struct xt_match *match)
 {
-       struct xt_match *m;
-       struct compat_xt_entry_match *pcompat_m;
-       struct xt_entry_match *pm;
-       u_int16_t msize;
-       int off, ret;
+       u_int16_t csize = match->compatsize ? : match->matchsize;
+       return XT_ALIGN(match->matchsize) - COMPAT_XT_ALIGN(csize);
+}
+EXPORT_SYMBOL_GPL(xt_compat_match_offset);
 
-       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;
+void xt_compat_match_from_user(struct xt_entry_match *m, void **dstptr,
+                              int *size)
+{
+       struct xt_match *match = m->u.kernel.match;
+       struct compat_xt_entry_match *cm = (struct compat_xt_entry_match *)m;
+       int pad, off = xt_compat_match_offset(match);
+       u_int16_t msize = cm->u.user.match_size;
+
+       m = *dstptr;
+       memcpy(m, cm, sizeof(*cm));
+       if (match->compat_from_user)
+               match->compat_from_user(m->data, cm->data);
+       else
+               memcpy(m->data, cm->data, msize - sizeof(*cm));
+       pad = XT_ALIGN(match->matchsize) - match->matchsize;
+       if (pad > 0)
+               memset(m->data + match->matchsize, 0, pad);
+
+       msize += off;
+       m->u.user.match_size = msize;
+
+       *size += off;
+       *dstptr += msize;
+}
+EXPORT_SYMBOL_GPL(xt_compat_match_from_user);
+
+int xt_compat_match_to_user(struct xt_entry_match *m, void __user **dstptr,
+                           int *size)
+{
+       struct xt_match *match = m->u.kernel.match;
+       struct compat_xt_entry_match __user *cm = *dstptr;
+       int off = xt_compat_match_offset(match);
+       u_int16_t msize = m->u.user.match_size - off;
+
+       if (copy_to_user(cm, m, sizeof(*cm)) ||
+           put_user(msize, &cm->u.user.match_size))
+               return -EFAULT;
+
+       if (match->compat_to_user) {
+               if (match->compat_to_user((void __user *)cm->data, m->data))
+                       return -EFAULT;
+       } else {
+               if (copy_to_user(cm->data, m->data, msize - sizeof(*cm)))
+                       return -EFAULT;
        }
-       return ret;
+
+       *size -= off;
+       *dstptr += msize;
+       return 0;
 }
-EXPORT_SYMBOL_GPL(xt_compat_match);
-#endif
+EXPORT_SYMBOL_GPL(xt_compat_match_to_user);
+#endif /* CONFIG_COMPAT */
 
 int xt_check_target(const struct xt_target *target, unsigned short family,
                    unsigned int size, const char *table, unsigned int hook_mask,
@@ -410,51 +423,64 @@ int xt_check_target(const struct xt_target *target, unsigned short family,
 EXPORT_SYMBOL_GPL(xt_check_target);
 
 #ifdef CONFIG_COMPAT
-int xt_compat_target(void *target, void **dstptr, int *size, int convert)
+int xt_compat_target_offset(struct xt_target *target)
 {
-       struct xt_target *t;
-       struct compat_xt_entry_target *pcompat;
-       struct xt_entry_target *pt;
-       u_int16_t tsize;
-       int off, ret;
+       u_int16_t csize = target->compatsize ? : target->targetsize;
+       return XT_ALIGN(target->targetsize) - COMPAT_XT_ALIGN(csize);
+}
+EXPORT_SYMBOL_GPL(xt_compat_target_offset);
 
-       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;
+void xt_compat_target_from_user(struct xt_entry_target *t, void **dstptr,
+                               int *size)
+{
+       struct xt_target *target = t->u.kernel.target;
+       struct compat_xt_entry_target *ct = (struct compat_xt_entry_target *)t;
+       int pad, off = xt_compat_target_offset(target);
+       u_int16_t tsize = ct->u.user.target_size;
+
+       t = *dstptr;
+       memcpy(t, ct, sizeof(*ct));
+       if (target->compat_from_user)
+               target->compat_from_user(t->data, ct->data);
+       else
+               memcpy(t->data, ct->data, tsize - sizeof(*ct));
+       pad = XT_ALIGN(target->targetsize) - target->targetsize;
+       if (pad > 0)
+               memset(t->data + target->targetsize, 0, pad);
+
+       tsize += off;
+       t->u.user.target_size = tsize;
+
+       *size += off;
+       *dstptr += tsize;
+}
+EXPORT_SYMBOL_GPL(xt_compat_target_from_user);
+
+int xt_compat_target_to_user(struct xt_entry_target *t, void __user **dstptr,
+                            int *size)
+{
+       struct xt_target *target = t->u.kernel.target;
+       struct compat_xt_entry_target __user *ct = *dstptr;
+       int off = xt_compat_target_offset(target);
+       u_int16_t tsize = t->u.user.target_size - off;
+
+       if (copy_to_user(ct, t, sizeof(*ct)) ||
+           put_user(tsize, &ct->u.user.target_size))
+               return -EFAULT;
+
+       if (target->compat_to_user) {
+               if (target->compat_to_user((void __user *)ct->data, t->data))
+                       return -EFAULT;
+       } else {
+               if (copy_to_user(ct->data, t->data, tsize - sizeof(*ct)))
+                       return -EFAULT;
        }
-       return ret;
+
+       *size -= off;
+       *dstptr += tsize;
+       return 0;
 }
-EXPORT_SYMBOL_GPL(xt_compat_target);
+EXPORT_SYMBOL_GPL(xt_compat_target_to_user);
 #endif
 
 struct xt_table_info *xt_alloc_table_info(unsigned int size)