diff mbox

arm64: bpf: fix div-by-zero case

Message ID 1446620204-4228-1-git-send-email-zlim.lnx@gmail.com (mailing list archive)
State New, archived
Headers show

Commit Message

Zi Shen Lim Nov. 4, 2015, 6:56 a.m. UTC
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 <yang.shi@linaro.org>
CC: Xi Wang <xi.wang@gmail.com>
CC: Alexei Starovoitov <ast@plumgrid.com>
CC: Catalin Marinas <catalin.marinas@arm.com>
CC: linux-arm-kernel@lists.infradead.org
CC: linux-kernel@vger.kernel.org
Fixes: e54bcde3d69d ("arm64: eBPF JIT compiler")
Cc: <stable@vger.kernel.org> # 3.18+
Signed-off-by: Zi Shen Lim <zlim.lnx@gmail.com>
---
 arch/arm64/net/bpf_jit.h      |  3 ++-
 arch/arm64/net/bpf_jit_comp.c | 37 +++++++++++++++++++++++++------------
 2 files changed, 27 insertions(+), 13 deletions(-)

Comments

Xi Wang Nov. 4, 2015, 7:04 a.m. UTC | #1
On Tue, Nov 3, 2015 at 10:56 PM, Zi Shen Lim <zlim.lnx@gmail.com> 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.
Yang Shi Nov. 4, 2015, 6:03 p.m. UTC | #2
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 <yang.shi@linaro.org>

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

Tested with the latest 4.3 kernel.
Tested-by: Yang Shi <yang.shi@linaro.org>

Yang

> CC: Xi Wang <xi.wang@gmail.com>
> CC: Alexei Starovoitov <ast@plumgrid.com>
> CC: Catalin Marinas <catalin.marinas@arm.com>
> CC: linux-arm-kernel@lists.infradead.org
> CC: linux-kernel@vger.kernel.org
> Fixes: e54bcde3d69d ("arm64: eBPF JIT compiler")
> Cc: <stable@vger.kernel.org> # 3.18+
> Signed-off-by: Zi Shen Lim <zlim.lnx@gmail.com>
> ---
>   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 <zlim.lnx@gmail.com>
> + * Copyright (C) 2014-2015 Zi Shen Lim <zlim.lnx@gmail.com>
>    *
>    * 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 <zlim.lnx@gmail.com>
> + * Copyright (C) 2014-2015 Zi Shen Lim <zlim.lnx@gmail.com>
>    *
>    * 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);
>
Yang Shi Nov. 4, 2015, 6:21 p.m. UTC | #3
On 11/3/2015 11:04 PM, Xi Wang wrote:
> On Tue, Nov 3, 2015 at 10:56 PM, Zi Shen Lim <zlim.lnx@gmail.com> 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
> linux-arm-kernel@lists.infradead.org
> http://lists.infradead.org/mailman/listinfo/linux-arm-kernel
>
Zi Shen Lim Nov. 4, 2015, 6:25 p.m. UTC | #4
On Wed, Nov 4, 2015 at 10:21 AM, Shi, Yang <yang.shi@linaro.org> wrote:
> On 11/3/2015 11:04 PM, Xi Wang wrote:
>>
>> On Tue, Nov 3, 2015 at 10:56 PM, Zi Shen Lim <zlim.lnx@gmail.com> 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
>> linux-arm-kernel@lists.infradead.org
>> http://lists.infradead.org/mailman/listinfo/linux-arm-kernel
>>
>
Yang Shi Nov. 4, 2015, 6:41 p.m. UTC | #5
On 11/4/2015 10:25 AM, Z Lim wrote:
> On Wed, Nov 4, 2015 at 10:21 AM, Shi, Yang <yang.shi@linaro.org> wrote:
>> On 11/3/2015 11:04 PM, Xi Wang wrote:
>>>
>>> On Tue, Nov 3, 2015 at 10:56 PM, Zi Shen Lim <zlim.lnx@gmail.com> 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
>>> linux-arm-kernel@lists.infradead.org
>>> http://lists.infradead.org/mailman/listinfo/linux-arm-kernel
>>>
>>
Daniel Borkmann Nov. 4, 2015, 7:39 p.m. UTC | #6
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
diff mbox

Patch

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 <zlim.lnx@gmail.com>
+ * Copyright (C) 2014-2015 Zi Shen Lim <zlim.lnx@gmail.com>
  *
  * 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 <zlim.lnx@gmail.com>
+ * Copyright (C) 2014-2015 Zi Shen Lim <zlim.lnx@gmail.com>
  *
  * 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);