[NETFILTER] x_tables: fix compat related crash on non-x86
When iptables userspace adds an ipt_standard_target, it calculates the size of the entire entry as: sizeof(struct ipt_entry) + XT_ALIGN(sizeof(struct ipt_standard_target)) ipt_standard_target looks like this: struct xt_standard_target { struct xt_entry_target target; int verdict; }; xt_entry_target contains a pointer, so when compiled for 64 bit the structure gets an extra 4 byte of padding at the end. On 32 bit architectures where iptables aligns to 8 byte it will also have 4 byte padding at the end because it is only 36 bytes large. The compat_ipt_standard_fn in the kernel adjusts the offsets by sizeof(struct ipt_standard_target) - sizeof(struct compat_ipt_standard_target), which will always result in 4, even if the structure from userspace was already padded to a multiple of 8. On x86 this works out by accident because userspace only aligns to 4, on all other architectures this is broken and causes incorrect adjustments to the size and following offsets. Thanks to Linus for lots of debugging help and testing. Signed-off-by: Patrick McHardy <kaber@trash.net> Signed-off-by: Linus Torvalds <torvalds@osdl.org>
This commit is contained in:
parent
9817d207dc
commit
46c5ea3c9a
|
@ -337,6 +337,10 @@ struct compat_xt_entry_match
|
||||||
char name[XT_FUNCTION_MAXNAMELEN - 1];
|
char name[XT_FUNCTION_MAXNAMELEN - 1];
|
||||||
u_int8_t revision;
|
u_int8_t revision;
|
||||||
} user;
|
} user;
|
||||||
|
struct {
|
||||||
|
u_int16_t match_size;
|
||||||
|
compat_uptr_t match;
|
||||||
|
} kernel;
|
||||||
u_int16_t match_size;
|
u_int16_t match_size;
|
||||||
} u;
|
} u;
|
||||||
unsigned char data[0];
|
unsigned char data[0];
|
||||||
|
@ -350,6 +354,10 @@ struct compat_xt_entry_target
|
||||||
char name[XT_FUNCTION_MAXNAMELEN - 1];
|
char name[XT_FUNCTION_MAXNAMELEN - 1];
|
||||||
u_int8_t revision;
|
u_int8_t revision;
|
||||||
} user;
|
} user;
|
||||||
|
struct {
|
||||||
|
u_int16_t target_size;
|
||||||
|
compat_uptr_t target;
|
||||||
|
} kernel;
|
||||||
u_int16_t target_size;
|
u_int16_t target_size;
|
||||||
} u;
|
} u;
|
||||||
unsigned char data[0];
|
unsigned char data[0];
|
||||||
|
|
|
@ -956,15 +956,16 @@ struct compat_ipt_standard_target
|
||||||
compat_int_t verdict;
|
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_standard
|
||||||
{
|
{
|
||||||
struct compat_ipt_entry entry;
|
struct compat_ipt_entry entry;
|
||||||
struct compat_ipt_standard_target target;
|
struct compat_ipt_standard_target target;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#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)
|
||||||
|
|
||||||
static int compat_ipt_standard_fn(void *target,
|
static int compat_ipt_standard_fn(void *target,
|
||||||
void **dstptr, int *size, int convert)
|
void **dstptr, int *size, int convert)
|
||||||
{
|
{
|
||||||
|
@ -975,35 +976,29 @@ static int compat_ipt_standard_fn(void *target,
|
||||||
ret = 0;
|
ret = 0;
|
||||||
switch (convert) {
|
switch (convert) {
|
||||||
case COMPAT_TO_USER:
|
case COMPAT_TO_USER:
|
||||||
pst = (struct ipt_standard_target *)target;
|
pst = target;
|
||||||
memcpy(&compat_st.target, &pst->target,
|
memcpy(&compat_st.target, &pst->target,
|
||||||
sizeof(struct ipt_entry_target));
|
sizeof(compat_st.target));
|
||||||
compat_st.verdict = pst->verdict;
|
compat_st.verdict = pst->verdict;
|
||||||
if (compat_st.verdict > 0)
|
if (compat_st.verdict > 0)
|
||||||
compat_st.verdict -=
|
compat_st.verdict -=
|
||||||
compat_calc_jump(compat_st.verdict);
|
compat_calc_jump(compat_st.verdict);
|
||||||
compat_st.target.u.user.target_size =
|
compat_st.target.u.user.target_size = IPT_ST_COMPAT_LEN;
|
||||||
sizeof(struct compat_ipt_standard_target);
|
if (copy_to_user(*dstptr, &compat_st, IPT_ST_COMPAT_LEN))
|
||||||
if (__copy_to_user(*dstptr, &compat_st,
|
|
||||||
sizeof(struct compat_ipt_standard_target)))
|
|
||||||
ret = -EFAULT;
|
ret = -EFAULT;
|
||||||
*size -= IPT_ST_OFFSET;
|
*size -= IPT_ST_OFFSET;
|
||||||
*dstptr += sizeof(struct compat_ipt_standard_target);
|
*dstptr += IPT_ST_COMPAT_LEN;
|
||||||
break;
|
break;
|
||||||
case COMPAT_FROM_USER:
|
case COMPAT_FROM_USER:
|
||||||
pcompat_st =
|
pcompat_st = target;
|
||||||
(struct compat_ipt_standard_target *)target;
|
memcpy(&st.target, &pcompat_st->target, IPT_ST_COMPAT_LEN);
|
||||||
memcpy(&st.target, &pcompat_st->target,
|
|
||||||
sizeof(struct ipt_entry_target));
|
|
||||||
st.verdict = pcompat_st->verdict;
|
st.verdict = pcompat_st->verdict;
|
||||||
if (st.verdict > 0)
|
if (st.verdict > 0)
|
||||||
st.verdict += compat_calc_jump(st.verdict);
|
st.verdict += compat_calc_jump(st.verdict);
|
||||||
st.target.u.user.target_size =
|
st.target.u.user.target_size = IPT_ST_LEN;
|
||||||
sizeof(struct ipt_standard_target);
|
memcpy(*dstptr, &st, IPT_ST_LEN);
|
||||||
memcpy(*dstptr, &st,
|
|
||||||
sizeof(struct ipt_standard_target));
|
|
||||||
*size += IPT_ST_OFFSET;
|
*size += IPT_ST_OFFSET;
|
||||||
*dstptr += sizeof(struct ipt_standard_target);
|
*dstptr += IPT_ST_LEN;
|
||||||
break;
|
break;
|
||||||
case COMPAT_CALC_SIZE:
|
case COMPAT_CALC_SIZE:
|
||||||
*size += IPT_ST_OFFSET;
|
*size += IPT_ST_OFFSET;
|
||||||
|
|
Loading…
Reference in New Issue