2015-11-04 06:56:57

by Z Lim

[permalink] [raw]
Subject: [PATCH] arm64: bpf: fix div-by-zero case

In the case of division by zero in a BPF program:
A = A / X; (X == 0)
the expected behavior is to terminate with return value 0.

This is confirmed by the test case introduced in commit 86bf1721b226
("test_bpf: add tests checking that JIT/interpreter sets A and X to 0.").

Reported-by: Shi, Yang <[email protected]>
CC: Xi Wang <[email protected]>
CC: Alexei Starovoitov <[email protected]>
CC: Catalin Marinas <[email protected]>
CC: [email protected]
CC: [email protected]
Fixes: e54bcde3d69d ("arm64: eBPF JIT compiler")
Cc: <[email protected]> # 3.18+
Signed-off-by: Zi Shen Lim <[email protected]>
---
arch/arm64/net/bpf_jit.h | 3 ++-
arch/arm64/net/bpf_jit_comp.c | 37 +++++++++++++++++++++++++------------
2 files changed, 27 insertions(+), 13 deletions(-)

diff --git a/arch/arm64/net/bpf_jit.h b/arch/arm64/net/bpf_jit.h
index 98a26ce..aee5637 100644
--- a/arch/arm64/net/bpf_jit.h
+++ b/arch/arm64/net/bpf_jit.h
@@ -1,7 +1,7 @@
/*
* BPF JIT compiler for ARM64
*
- * Copyright (C) 2014 Zi Shen Lim <[email protected]>
+ * Copyright (C) 2014-2015 Zi Shen Lim <[email protected]>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
@@ -35,6 +35,7 @@
aarch64_insn_gen_comp_branch_imm(0, offset, Rt, A64_VARIANT(sf), \
AARCH64_INSN_BRANCH_COMP_##type)
#define A64_CBZ(sf, Rt, imm19) A64_COMP_BRANCH(sf, Rt, (imm19) << 2, ZERO)
+#define A64_CBNZ(sf, Rt, imm19) A64_COMP_BRANCH(sf, Rt, (imm19) << 2, NONZERO)

/* Conditional branch (immediate) */
#define A64_COND_BRANCH(cond, offset) \
diff --git a/arch/arm64/net/bpf_jit_comp.c b/arch/arm64/net/bpf_jit_comp.c
index c047598..9ae6f23 100644
--- a/arch/arm64/net/bpf_jit_comp.c
+++ b/arch/arm64/net/bpf_jit_comp.c
@@ -1,7 +1,7 @@
/*
* BPF JIT compiler for ARM64
*
- * Copyright (C) 2014 Zi Shen Lim <[email protected]>
+ * Copyright (C) 2014-2015 Zi Shen Lim <[email protected]>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
@@ -225,6 +225,17 @@ static int build_insn(const struct bpf_insn *insn, struct jit_ctx *ctx)
u8 jmp_cond;
s32 jmp_offset;

+#define check_imm(bits, imm) do { \
+ if ((((imm) > 0) && ((imm) >> (bits))) || \
+ (((imm) < 0) && (~(imm) >> (bits)))) { \
+ pr_info("[%2d] imm=%d(0x%x) out of range\n", \
+ i, imm, imm); \
+ return -EINVAL; \
+ } \
+} while (0)
+#define check_imm19(imm) check_imm(19, imm)
+#define check_imm26(imm) check_imm(26, imm)
+
switch (code) {
/* dst = src */
case BPF_ALU | BPF_MOV | BPF_X:
@@ -258,8 +269,21 @@ static int build_insn(const struct bpf_insn *insn, struct jit_ctx *ctx)
break;
case BPF_ALU | BPF_DIV | BPF_X:
case BPF_ALU64 | BPF_DIV | BPF_X:
+ {
+ const u8 r0 = bpf2a64[BPF_REG_0];
+
+ /* if (src == 0) return 0 */
+ jmp_offset = 3; /* skip ahead to else path */
+ check_imm19(jmp_offset);
+ emit(A64_CBNZ(is64, src, jmp_offset), ctx);
+ emit(A64_MOVZ(1, r0, 0, 0), ctx);
+ jmp_offset = epilogue_offset(ctx);
+ check_imm26(jmp_offset);
+ emit(A64_B(jmp_offset), ctx);
+ /* else */
emit(A64_UDIV(is64, dst, dst, src), ctx);
break;
+ }
case BPF_ALU | BPF_MOD | BPF_X:
case BPF_ALU64 | BPF_MOD | BPF_X:
ctx->tmp_used = 1;
@@ -393,17 +417,6 @@ emit_bswap_uxt:
emit(A64_ASR(is64, dst, dst, imm), ctx);
break;

-#define check_imm(bits, imm) do { \
- if ((((imm) > 0) && ((imm) >> (bits))) || \
- (((imm) < 0) && (~(imm) >> (bits)))) { \
- pr_info("[%2d] imm=%d(0x%x) out of range\n", \
- i, imm, imm); \
- return -EINVAL; \
- } \
-} while (0)
-#define check_imm19(imm) check_imm(19, imm)
-#define check_imm26(imm) check_imm(26, imm)
-
/* JUMP off */
case BPF_JMP | BPF_JA:
jmp_offset = bpf2a64_offset(i + off, i, ctx);
--
1.9.1


2015-11-04 07:04:48

by Xi Wang

[permalink] [raw]
Subject: Re: [PATCH] arm64: bpf: fix div-by-zero case

On Tue, Nov 3, 2015 at 10:56 PM, Zi Shen Lim <[email protected]> wrote:
> case BPF_ALU | BPF_DIV | BPF_X:
> case BPF_ALU64 | BPF_DIV | BPF_X:
> + {
> + const u8 r0 = bpf2a64[BPF_REG_0];
> +
> + /* if (src == 0) return 0 */
> + jmp_offset = 3; /* skip ahead to else path */
> + check_imm19(jmp_offset);
> + emit(A64_CBNZ(is64, src, jmp_offset), ctx);
> + emit(A64_MOVZ(1, r0, 0, 0), ctx);
> + jmp_offset = epilogue_offset(ctx);
> + check_imm26(jmp_offset);
> + emit(A64_B(jmp_offset), ctx);
> + /* else */
> emit(A64_UDIV(is64, dst, dst, src), ctx);
> break;
> + }
> case BPF_ALU | BPF_MOD | BPF_X:
> case BPF_ALU64 | BPF_MOD | BPF_X:

BPF_MOD might need the same fix.

2015-11-04 18:03:25

by Shi, Yang

[permalink] [raw]
Subject: Re: [PATCH] arm64: bpf: fix div-by-zero case

On 11/3/2015 10:56 PM, Zi Shen Lim wrote:
> In the case of division by zero in a BPF program:
> A = A / X; (X == 0)
> the expected behavior is to terminate with return value 0.
>
> This is confirmed by the test case introduced in commit 86bf1721b226
> ("test_bpf: add tests checking that JIT/interpreter sets A and X to 0.").
>
> Reported-by: Shi, Yang <[email protected]>

Thanks for coming up with the fix promptly.
s/Shi, Yang/Yang Shi

Tested with the latest 4.3 kernel.
Tested-by: Yang Shi <[email protected]>

Yang

> CC: Xi Wang <[email protected]>
> CC: Alexei Starovoitov <[email protected]>
> CC: Catalin Marinas <[email protected]>
> CC: [email protected]
> CC: [email protected]
> Fixes: e54bcde3d69d ("arm64: eBPF JIT compiler")
> Cc: <[email protected]> # 3.18+
> Signed-off-by: Zi Shen Lim <[email protected]>
> ---
> arch/arm64/net/bpf_jit.h | 3 ++-
> arch/arm64/net/bpf_jit_comp.c | 37 +++++++++++++++++++++++++------------
> 2 files changed, 27 insertions(+), 13 deletions(-)
>
> diff --git a/arch/arm64/net/bpf_jit.h b/arch/arm64/net/bpf_jit.h
> index 98a26ce..aee5637 100644
> --- a/arch/arm64/net/bpf_jit.h
> +++ b/arch/arm64/net/bpf_jit.h
> @@ -1,7 +1,7 @@
> /*
> * BPF JIT compiler for ARM64
> *
> - * Copyright (C) 2014 Zi Shen Lim <[email protected]>
> + * Copyright (C) 2014-2015 Zi Shen Lim <[email protected]>
> *
> * This program is free software; you can redistribute it and/or modify
> * it under the terms of the GNU General Public License version 2 as
> @@ -35,6 +35,7 @@
> aarch64_insn_gen_comp_branch_imm(0, offset, Rt, A64_VARIANT(sf), \
> AARCH64_INSN_BRANCH_COMP_##type)
> #define A64_CBZ(sf, Rt, imm19) A64_COMP_BRANCH(sf, Rt, (imm19) << 2, ZERO)
> +#define A64_CBNZ(sf, Rt, imm19) A64_COMP_BRANCH(sf, Rt, (imm19) << 2, NONZERO)
>
> /* Conditional branch (immediate) */
> #define A64_COND_BRANCH(cond, offset) \
> diff --git a/arch/arm64/net/bpf_jit_comp.c b/arch/arm64/net/bpf_jit_comp.c
> index c047598..9ae6f23 100644
> --- a/arch/arm64/net/bpf_jit_comp.c
> +++ b/arch/arm64/net/bpf_jit_comp.c
> @@ -1,7 +1,7 @@
> /*
> * BPF JIT compiler for ARM64
> *
> - * Copyright (C) 2014 Zi Shen Lim <[email protected]>
> + * Copyright (C) 2014-2015 Zi Shen Lim <[email protected]>
> *
> * This program is free software; you can redistribute it and/or modify
> * it under the terms of the GNU General Public License version 2 as
> @@ -225,6 +225,17 @@ static int build_insn(const struct bpf_insn *insn, struct jit_ctx *ctx)
> u8 jmp_cond;
> s32 jmp_offset;
>
> +#define check_imm(bits, imm) do { \
> + if ((((imm) > 0) && ((imm) >> (bits))) || \
> + (((imm) < 0) && (~(imm) >> (bits)))) { \
> + pr_info("[%2d] imm=%d(0x%x) out of range\n", \
> + i, imm, imm); \
> + return -EINVAL; \
> + } \
> +} while (0)
> +#define check_imm19(imm) check_imm(19, imm)
> +#define check_imm26(imm) check_imm(26, imm)
> +
> switch (code) {
> /* dst = src */
> case BPF_ALU | BPF_MOV | BPF_X:
> @@ -258,8 +269,21 @@ static int build_insn(const struct bpf_insn *insn, struct jit_ctx *ctx)
> break;
> case BPF_ALU | BPF_DIV | BPF_X:
> case BPF_ALU64 | BPF_DIV | BPF_X:
> + {
> + const u8 r0 = bpf2a64[BPF_REG_0];
> +
> + /* if (src == 0) return 0 */
> + jmp_offset = 3; /* skip ahead to else path */
> + check_imm19(jmp_offset);
> + emit(A64_CBNZ(is64, src, jmp_offset), ctx);
> + emit(A64_MOVZ(1, r0, 0, 0), ctx);
> + jmp_offset = epilogue_offset(ctx);
> + check_imm26(jmp_offset);
> + emit(A64_B(jmp_offset), ctx);
> + /* else */
> emit(A64_UDIV(is64, dst, dst, src), ctx);
> break;
> + }
> case BPF_ALU | BPF_MOD | BPF_X:
> case BPF_ALU64 | BPF_MOD | BPF_X:
> ctx->tmp_used = 1;
> @@ -393,17 +417,6 @@ emit_bswap_uxt:
> emit(A64_ASR(is64, dst, dst, imm), ctx);
> break;
>
> -#define check_imm(bits, imm) do { \
> - if ((((imm) > 0) && ((imm) >> (bits))) || \
> - (((imm) < 0) && (~(imm) >> (bits)))) { \
> - pr_info("[%2d] imm=%d(0x%x) out of range\n", \
> - i, imm, imm); \
> - return -EINVAL; \
> - } \
> -} while (0)
> -#define check_imm19(imm) check_imm(19, imm)
> -#define check_imm26(imm) check_imm(26, imm)
> -
> /* JUMP off */
> case BPF_JMP | BPF_JA:
> jmp_offset = bpf2a64_offset(i + off, i, ctx);
>

2015-11-04 18:21:19

by Shi, Yang

[permalink] [raw]
Subject: Re: [PATCH] arm64: bpf: fix div-by-zero case

On 11/3/2015 11:04 PM, Xi Wang wrote:
> On Tue, Nov 3, 2015 at 10:56 PM, Zi Shen Lim <[email protected]> wrote:
>> case BPF_ALU | BPF_DIV | BPF_X:
>> case BPF_ALU64 | BPF_DIV | BPF_X:
>> + {
>> + const u8 r0 = bpf2a64[BPF_REG_0];
>> +
>> + /* if (src == 0) return 0 */
>> + jmp_offset = 3; /* skip ahead to else path */
>> + check_imm19(jmp_offset);
>> + emit(A64_CBNZ(is64, src, jmp_offset), ctx);
>> + emit(A64_MOVZ(1, r0, 0, 0), ctx);
>> + jmp_offset = epilogue_offset(ctx);
>> + check_imm26(jmp_offset);
>> + emit(A64_B(jmp_offset), ctx);
>> + /* else */
>> emit(A64_UDIV(is64, dst, dst, src), ctx);
>> break;
>> + }
>> case BPF_ALU | BPF_MOD | BPF_X:
>> case BPF_ALU64 | BPF_MOD | BPF_X:
>
> BPF_MOD might need the same fix.

Agreed, and we may need add one more test cases in test_bpf module to
cover MOD?

Yang

>
> _______________________________________________
> linux-arm-kernel mailing list
> [email protected]
> http://lists.infradead.org/mailman/listinfo/linux-arm-kernel
>

2015-11-04 18:50:50

by Z Lim

[permalink] [raw]
Subject: Re: [PATCH] arm64: bpf: fix div-by-zero case

On Wed, Nov 4, 2015 at 10:21 AM, Shi, Yang <[email protected]> wrote:
> On 11/3/2015 11:04 PM, Xi Wang wrote:
>>
>> On Tue, Nov 3, 2015 at 10:56 PM, Zi Shen Lim <[email protected]> wrote:
>>>
>>> case BPF_ALU | BPF_DIV | BPF_X:
>>> case BPF_ALU64 | BPF_DIV | BPF_X:
>>> + {
>>> + const u8 r0 = bpf2a64[BPF_REG_0];
>>> +
>>> + /* if (src == 0) return 0 */
>>> + jmp_offset = 3; /* skip ahead to else path */
>>> + check_imm19(jmp_offset);
>>> + emit(A64_CBNZ(is64, src, jmp_offset), ctx);
>>> + emit(A64_MOVZ(1, r0, 0, 0), ctx);
>>> + jmp_offset = epilogue_offset(ctx);
>>> + check_imm26(jmp_offset);
>>> + emit(A64_B(jmp_offset), ctx);
>>> + /* else */
>>> emit(A64_UDIV(is64, dst, dst, src), ctx);
>>> break;
>>> + }
>>> case BPF_ALU | BPF_MOD | BPF_X:
>>> case BPF_ALU64 | BPF_MOD | BPF_X:
>>
>>
>> BPF_MOD might need the same fix.

I'll post a fix for this case as well.

>
>
> Agreed, and we may need add one more test cases in test_bpf module to cover
> MOD?

Let me know if you have a test case ready :)

>
> Yang
>
>>
>> _______________________________________________
>> linux-arm-kernel mailing list
>> [email protected]
>> http://lists.infradead.org/mailman/listinfo/linux-arm-kernel
>>
>

2015-11-04 18:41:59

by Shi, Yang

[permalink] [raw]
Subject: Re: [PATCH] arm64: bpf: fix div-by-zero case

On 11/4/2015 10:25 AM, Z Lim wrote:
> On Wed, Nov 4, 2015 at 10:21 AM, Shi, Yang <[email protected]> wrote:
>> On 11/3/2015 11:04 PM, Xi Wang wrote:
>>>
>>> On Tue, Nov 3, 2015 at 10:56 PM, Zi Shen Lim <[email protected]> wrote:
>>>>
>>>> case BPF_ALU | BPF_DIV | BPF_X:
>>>> case BPF_ALU64 | BPF_DIV | BPF_X:
>>>> + {
>>>> + const u8 r0 = bpf2a64[BPF_REG_0];
>>>> +
>>>> + /* if (src == 0) return 0 */
>>>> + jmp_offset = 3; /* skip ahead to else path */
>>>> + check_imm19(jmp_offset);
>>>> + emit(A64_CBNZ(is64, src, jmp_offset), ctx);
>>>> + emit(A64_MOVZ(1, r0, 0, 0), ctx);
>>>> + jmp_offset = epilogue_offset(ctx);
>>>> + check_imm26(jmp_offset);
>>>> + emit(A64_B(jmp_offset), ctx);
>>>> + /* else */
>>>> emit(A64_UDIV(is64, dst, dst, src), ctx);
>>>> break;
>>>> + }
>>>> case BPF_ALU | BPF_MOD | BPF_X:
>>>> case BPF_ALU64 | BPF_MOD | BPF_X:
>>>
>>>
>>> BPF_MOD might need the same fix.
>
> I'll post a fix for this case as well.
>
>>
>>
>> Agreed, and we may need add one more test cases in test_bpf module to cover
>> MOD?
>
> Let me know if you have a test case ready :)

Does the below change look like a valid test?

+ "MOD default X",
+ .u.insns = {
+ /*
+ * A = 0x42
+ * A = A mod X ; this halt the filter execution
if X is 0
+ * ret 0x42
+ */
+ BPF_STMT(BPF_LD | BPF_IMM, 0x42),
+ BPF_STMT(BPF_ALU | BPF_MOD | BPF_X, 0),
+ BPF_STMT(BPF_RET | BPF_K, 0x42),
+ },
+ CLASSIC | FLAG_NO_DATA,
+ {},
+ { {0x1, 0x0 } },
+ },
+ {
+ "MOD default A",
+ .u.insns = {
+ /*
+ * A = A mod 1
+ * ret A
+ */
+ BPF_STMT(BPF_ALU | BPF_MOD | BPF_K, 0x1),
+ BPF_STMT(BPF_RET | BPF_A, 0x0),
+ },
+ CLASSIC | FLAG_NO_DATA,
+ {},
+ { {0x1, 0x0 } },
+ },

My test result with it:
test_bpf: #284 MOD default X jited:1 ret 66 != 0 FAIL (1 times)
test_bpf: #285 MOD default A jited:1 102 PASS

If it looks right, I will post a patch to add the test cases.

Thanks,
Yang

>
>>
>> Yang
>>
>>>
>>> _______________________________________________
>>> linux-arm-kernel mailing list
>>> [email protected]
>>> http://lists.infradead.org/mailman/listinfo/linux-arm-kernel
>>>
>>

2015-11-04 19:39:29

by Daniel Borkmann

[permalink] [raw]
Subject: Re: [PATCH] arm64: bpf: fix div-by-zero case

On 11/04/2015 07:41 PM, Shi, Yang wrote:
...
>>> Agreed, and we may need add one more test cases in test_bpf module to cover
>>> MOD?
>>
>> Let me know if you have a test case ready :)
>
> Does the below change look like a valid test?
>
> + "MOD default X",
> + .u.insns = {
> + /*
> + * A = 0x42
> + * A = A mod X ; this halt the filter execution if X is 0
> + * ret 0x42
> + */
> + BPF_STMT(BPF_LD | BPF_IMM, 0x42),
> + BPF_STMT(BPF_ALU | BPF_MOD | BPF_X, 0),
> + BPF_STMT(BPF_RET | BPF_K, 0x42),
> + },
> + CLASSIC | FLAG_NO_DATA,
> + {},
> + { {0x1, 0x0 } },
> + },
> + {
> + "MOD default A",
> + .u.insns = {
> + /*
> + * A = A mod 1
> + * ret A
> + */
> + BPF_STMT(BPF_ALU | BPF_MOD | BPF_K, 0x1),
> + BPF_STMT(BPF_RET | BPF_A, 0x0),
> + },
> + CLASSIC | FLAG_NO_DATA,
> + {},
> + { {0x1, 0x0 } },
> + },
>
> My test result with it:
> test_bpf: #284 MOD default X jited:1 ret 66 != 0 FAIL (1 times)
> test_bpf: #285 MOD default A jited:1 102 PASS
>
> If it looks right, I will post a patch to add the test cases.

Looks good to me.

Thanks,
Daniel