filter: prevent nla extensions to peek beyond the end of the message

The BPF_S_ANC_NLATTR and BPF_S_ANC_NLATTR_NEST extensions fail to check
for a minimal message length before testing the supplied offset to be
within the bounds of the message. This allows the subtraction of the nla
header to underflow and therefore -- as the data type is unsigned --
allowing far to big offset and length values for the search of the
netlink attribute.

The remainder calculation for the BPF_S_ANC_NLATTR_NEST extension is
also wrong. It has the minuend and subtrahend mixed up, therefore
calculates a huge length value, allowing to overrun the end of the
message while looking for the netlink attribute.

The following three BPF snippets will trigger the bugs when attached to
a UNIX datagram socket and parsing a message with length 1, 2 or 3.

 ,-[ PoC for missing size check in BPF_S_ANC_NLATTR ]--
 | ld	#0x87654321
 | ldx	#42
 | ld	#nla
 | ret	a
 `---

 ,-[ PoC for the same bug in BPF_S_ANC_NLATTR_NEST ]--
 | ld	#0x87654321
 | ldx	#42
 | ld	#nlan
 | ret	a
 `---

 ,-[ PoC for wrong remainder calculation in BPF_S_ANC_NLATTR_NEST ]--
 | ; (needs a fake netlink header at offset 0)
 | ld	#0
 | ldx	#42
 | ld	#nlan
 | ret	a
 `---

Fix the first issue by ensuring the message length fulfills the minimal
size constrains of a nla header. Fix the second bug by getting the math
for the remainder calculation right.

Fixes: 4738c1db15 ("[SKFILTER]: Add SKF_ADF_NLATTR instruction")
Fixes: d214c7537b ("filter: add SKF_AD_NLATTR_NEST to look for nested..")
Cc: Patrick McHardy <kaber@trash.net>
Cc: Pablo Neira Ayuso <pablo@netfilter.org>
Signed-off-by: Mathias Krause <minipli@googlemail.com>
Acked-by: Daniel Borkmann <dborkman@redhat.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
static bool convert_bpf_extensions(struct sock_filter *fp,
				   struct sock_filter_int **insnp)
{
	struct sock_filter_int *insn = *insnp;

	switch (fp->k) {
	case SKF_AD_OFF + SKF_AD_PROTOCOL:
		BUILD_BUG_ON(FIELD_SIZEOF(struct sk_buff, protocol) != 2);

		insn->code = BPF_LDX | BPF_MEM | BPF_H;
		insn->a_reg = A_REG;
		insn->x_reg = CTX_REG;
		insn->off = offsetof(struct sk_buff, protocol);
		insn++;

		/* A = ntohs(A) [emitting a nop or swap16] */
		insn->code = BPF_ALU | BPF_END | BPF_FROM_BE;
		insn->a_reg = A_REG;
		insn->imm = 16;
		break;

	case SKF_AD_OFF + SKF_AD_PKTTYPE:
		insn->code = BPF_LDX | BPF_MEM | BPF_B;
		insn->a_reg = A_REG;
		insn->x_reg = CTX_REG;
		insn->off = pkt_type_offset();
		if (insn->off < 0)
			return false;
		insn++;

		insn->code = BPF_ALU | BPF_AND | BPF_K;
		insn->a_reg = A_REG;
		insn->imm = PKT_TYPE_MAX;
		break;

	case SKF_AD_OFF + SKF_AD_IFINDEX:
	case SKF_AD_OFF + SKF_AD_HATYPE:
		if (FIELD_SIZEOF(struct sk_buff, dev) == 8)
			insn->code = BPF_LDX | BPF_MEM | BPF_DW;
		else
			insn->code = BPF_LDX | BPF_MEM | BPF_W;
		insn->a_reg = TMP_REG;
		insn->x_reg = CTX_REG;
		insn->off = offsetof(struct sk_buff, dev);
		insn++;

		insn->code = BPF_JMP | BPF_JNE | BPF_K;
		insn->a_reg = TMP_REG;
		insn->imm = 0;
		insn->off = 1;
		insn++;

		insn->code = BPF_JMP | BPF_EXIT;
		insn++;

		BUILD_BUG_ON(FIELD_SIZEOF(struct net_device, ifindex) != 4);
		BUILD_BUG_ON(FIELD_SIZEOF(struct net_device, type) != 2);

		insn->a_reg = A_REG;
		insn->x_reg = TMP_REG;

		if (fp->k == SKF_AD_OFF + SKF_AD_IFINDEX) {
			insn->code = BPF_LDX | BPF_MEM | BPF_W;
			insn->off = offsetof(struct net_device, ifindex);
		} else {
			insn->code = BPF_LDX | BPF_MEM | BPF_H;
			insn->off = offsetof(struct net_device, type);
		}
		break;

	case SKF_AD_OFF + SKF_AD_MARK:
		BUILD_BUG_ON(FIELD_SIZEOF(struct sk_buff, mark) != 4);

		insn->code = BPF_LDX | BPF_MEM | BPF_W;
		insn->a_reg = A_REG;
		insn->x_reg = CTX_REG;
		insn->off = offsetof(struct sk_buff, mark);
		break;

	case SKF_AD_OFF + SKF_AD_RXHASH:
		BUILD_BUG_ON(FIELD_SIZEOF(struct sk_buff, hash) != 4);

		insn->code = BPF_LDX | BPF_MEM | BPF_W;
		insn->a_reg = A_REG;
		insn->x_reg = CTX_REG;
		insn->off = offsetof(struct sk_buff, hash);
		break;

	case SKF_AD_OFF + SKF_AD_QUEUE:
		BUILD_BUG_ON(FIELD_SIZEOF(struct sk_buff, queue_mapping) != 2);

		insn->code = BPF_LDX | BPF_MEM | BPF_H;
		insn->a_reg = A_REG;
		insn->x_reg = CTX_REG;
		insn->off = offsetof(struct sk_buff, queue_mapping);
		break;

	case SKF_AD_OFF + SKF_AD_VLAN_TAG:
	case SKF_AD_OFF + SKF_AD_VLAN_TAG_PRESENT:
		BUILD_BUG_ON(FIELD_SIZEOF(struct sk_buff, vlan_tci) != 2);

		insn->code = BPF_LDX | BPF_MEM | BPF_H;
		insn->a_reg = A_REG;
		insn->x_reg = CTX_REG;
		insn->off = offsetof(struct sk_buff, vlan_tci);
		insn++;

		BUILD_BUG_ON(VLAN_TAG_PRESENT != 0x1000);

		if (fp->k == SKF_AD_OFF + SKF_AD_VLAN_TAG) {
			insn->code = BPF_ALU | BPF_AND | BPF_K;
			insn->a_reg = A_REG;
			insn->imm = ~VLAN_TAG_PRESENT;
		} else {
			insn->code = BPF_ALU | BPF_RSH | BPF_K;
			insn->a_reg = A_REG;
			insn->imm = 12;
			insn++;

			insn->code = BPF_ALU | BPF_AND | BPF_K;
			insn->a_reg = A_REG;
			insn->imm = 1;
		}
		break;

	case SKF_AD_OFF + SKF_AD_PAY_OFFSET:
	case SKF_AD_OFF + SKF_AD_NLATTR:
	case SKF_AD_OFF + SKF_AD_NLATTR_NEST:
	case SKF_AD_OFF + SKF_AD_CPU:
		/* arg1 = ctx */
		insn->code = BPF_ALU64 | BPF_MOV | BPF_X;
		insn->a_reg = ARG1_REG;
		insn->x_reg = CTX_REG;
		insn++;

		/* arg2 = A */
		insn->code = BPF_ALU64 | BPF_MOV | BPF_X;
		insn->a_reg = ARG2_REG;
		insn->x_reg = A_REG;
		insn++;

		/* arg3 = X */
		insn->code = BPF_ALU64 | BPF_MOV | BPF_X;
		insn->a_reg = ARG3_REG;
		insn->x_reg = X_REG;
		insn++;

		/* Emit call(ctx, arg2=A, arg3=X) */
		insn->code = BPF_JMP | BPF_CALL;
		switch (fp->k) {
		case SKF_AD_OFF + SKF_AD_PAY_OFFSET:
			insn->imm = __skb_get_pay_offset - __bpf_call_base;
			break;
		case SKF_AD_OFF + SKF_AD_NLATTR:
			insn->imm = __skb_get_nlattr - __bpf_call_base;
			break;
		case SKF_AD_OFF + SKF_AD_NLATTR_NEST:
			insn->imm = __skb_get_nlattr_nest - __bpf_call_base;
			break;
		case SKF_AD_OFF + SKF_AD_CPU:
			insn->imm = __get_raw_cpu_id - __bpf_call_base;
			break;
		}
		break;

	case SKF_AD_OFF + SKF_AD_ALU_XOR_X:
		insn->code = BPF_ALU | BPF_XOR | BPF_X;
		insn->a_reg = A_REG;
		insn->x_reg = X_REG;
		break;

	default:
		/* This is just a dummy call to avoid letting the compiler
		 * evict __bpf_call_base() as an optimization. Placed here
		 * where no-one bothers.
		 */
		BUG_ON(__bpf_call_base(0, 0, 0, 0, 0) != 0);
		return false;
	}

	*insnp = insn;
	return true;
}

static bool convert_bpf_extensions(struct sock_filter *fp,
				   struct sock_filter_int **insnp)
{
	struct sock_filter_int *insn = *insnp;

	switch (fp->k) {
	case SKF_AD_OFF + SKF_AD_PROTOCOL:
		BUILD_BUG_ON(FIELD_SIZEOF(struct sk_buff, protocol) != 2);

		insn->code = BPF_LDX | BPF_MEM | BPF_H;
		insn->a_reg = A_REG;
		insn->x_reg = CTX_REG;
		insn->off = offsetof(struct sk_buff, protocol);
		insn++;

		/* A = ntohs(A) [emitting a nop or swap16] */
		insn->code = BPF_ALU | BPF_END | BPF_FROM_BE;
		insn->a_reg = A_REG;
		insn->imm = 16;
		break;

	case SKF_AD_OFF + SKF_AD_PKTTYPE:
		insn->code = BPF_LDX | BPF_MEM | BPF_B;
		insn->a_reg = A_REG;
		insn->x_reg = CTX_REG;
		insn->off = pkt_type_offset();
		if (insn->off < 0)
			return false;
		insn++;

		insn->code = BPF_ALU | BPF_AND | BPF_K;
		insn->a_reg = A_REG;
		insn->imm = PKT_TYPE_MAX;
		break;

	case SKF_AD_OFF + SKF_AD_IFINDEX:
	case SKF_AD_OFF + SKF_AD_HATYPE:
		if (FIELD_SIZEOF(struct sk_buff, dev) == 8)
			insn->code = BPF_LDX | BPF_MEM | BPF_DW;
		else
			insn->code = BPF_LDX | BPF_MEM | BPF_W;
		insn->a_reg = TMP_REG;
		insn->x_reg = CTX_REG;
		insn->off = offsetof(struct sk_buff, dev);
		insn++;

		insn->code = BPF_JMP | BPF_JNE | BPF_K;
		insn->a_reg = TMP_REG;
		insn->imm = 0;
		insn->off = 1;
		insn++;

		insn->code = BPF_JMP | BPF_EXIT;
		insn++;

		BUILD_BUG_ON(FIELD_SIZEOF(struct net_device, ifindex) != 4);
		BUILD_BUG_ON(FIELD_SIZEOF(struct net_device, type) != 2);

		insn->a_reg = A_REG;
		insn->x_reg = TMP_REG;

		if (fp->k == SKF_AD_OFF + SKF_AD_IFINDEX) {
			insn->code = BPF_LDX | BPF_MEM | BPF_W;
			insn->off = offsetof(struct net_device, ifindex);
		} else {
			insn->code = BPF_LDX | BPF_MEM | BPF_H;
			insn->off = offsetof(struct net_device, type);
		}
		break;

	case SKF_AD_OFF + SKF_AD_MARK:
		BUILD_BUG_ON(FIELD_SIZEOF(struct sk_buff, mark) != 4);

		insn->code = BPF_LDX | BPF_MEM | BPF_W;
		insn->a_reg = A_REG;
		insn->x_reg = CTX_REG;
		insn->off = offsetof(struct sk_buff, mark);
		break;

	case SKF_AD_OFF + SKF_AD_RXHASH:
		BUILD_BUG_ON(FIELD_SIZEOF(struct sk_buff, hash) != 4);

		insn->code = BPF_LDX | BPF_MEM | BPF_W;
		insn->a_reg = A_REG;
		insn->x_reg = CTX_REG;
		insn->off = offsetof(struct sk_buff, hash);
		break;

	case SKF_AD_OFF + SKF_AD_QUEUE:
		BUILD_BUG_ON(FIELD_SIZEOF(struct sk_buff, queue_mapping) != 2);

		insn->code = BPF_LDX | BPF_MEM | BPF_H;
		insn->a_reg = A_REG;
		insn->x_reg = CTX_REG;
		insn->off = offsetof(struct sk_buff, queue_mapping);
		break;

	case SKF_AD_OFF + SKF_AD_VLAN_TAG:
	case SKF_AD_OFF + SKF_AD_VLAN_TAG_PRESENT:
		BUILD_BUG_ON(FIELD_SIZEOF(struct sk_buff, vlan_tci) != 2);

		insn->code = BPF_LDX | BPF_MEM | BPF_H;
		insn->a_reg = A_REG;
		insn->x_reg = CTX_REG;
		insn->off = offsetof(struct sk_buff, vlan_tci);
		insn++;

		BUILD_BUG_ON(VLAN_TAG_PRESENT != 0x1000);

		if (fp->k == SKF_AD_OFF + SKF_AD_VLAN_TAG) {
			insn->code = BPF_ALU | BPF_AND | BPF_K;
			insn->a_reg = A_REG;
			insn->imm = ~VLAN_TAG_PRESENT;
		} else {
			insn->code = BPF_ALU | BPF_RSH | BPF_K;
			insn->a_reg = A_REG;
			insn->imm = 12;
			insn++;

			insn->code = BPF_ALU | BPF_AND | BPF_K;
			insn->a_reg = A_REG;
			insn->imm = 1;
		}
		break;

	case SKF_AD_OFF + SKF_AD_PAY_OFFSET:
	case SKF_AD_OFF + SKF_AD_NLATTR:
	case SKF_AD_OFF + SKF_AD_NLATTR_NEST:
	case SKF_AD_OFF + SKF_AD_CPU:
		/* arg1 = ctx */
		insn->code = BPF_ALU64 | BPF_MOV | BPF_X;
		insn->a_reg = ARG1_REG;
		insn->x_reg = CTX_REG;
		insn++;

		/* arg2 = A */
		insn->code = BPF_ALU64 | BPF_MOV | BPF_X;
		insn->a_reg = ARG2_REG;
		insn->x_reg = A_REG;
		insn++;

		/* arg3 = X */
		insn->code = BPF_ALU64 | BPF_MOV | BPF_X;
		insn->a_reg = ARG3_REG;
		insn->x_reg = X_REG;
		insn++;

		/* Emit call(ctx, arg2=A, arg3=X) */
		insn->code = BPF_JMP | BPF_CALL;
		switch (fp->k) {
		case SKF_AD_OFF + SKF_AD_PAY_OFFSET:
			insn->imm = __skb_get_pay_offset - __bpf_call_base;
			break;
		case SKF_AD_OFF + SKF_AD_NLATTR:
			insn->imm = __skb_get_nlattr - __bpf_call_base;
			break;
		case SKF_AD_OFF + SKF_AD_NLATTR_NEST:
			insn->imm = __skb_get_nlattr_nest - __bpf_call_base;
			break;
		case SKF_AD_OFF + SKF_AD_CPU:
			insn->imm = __get_raw_cpu_id - __bpf_call_base;
			break;
		}
		break;

	case SKF_AD_OFF + SKF_AD_ALU_XOR_X:
		insn->code = BPF_ALU | BPF_XOR | BPF_X;
		insn->a_reg = A_REG;
		insn->x_reg = X_REG;
		break;

	default:
		/* This is just a dummy call to avoid letting the compiler
		 * evict __bpf_call_base() as an optimization. Placed here
		 * where no-one bothers.
		 */
		BUG_ON(__bpf_call_base(0, 0, 0, 0, 0) != 0);
		return false;
	}

	*insnp = insn;
	return true;
}
