BPF List
 help / color / mirror / Atom feed
From: Yonghong Song <yhs@fb.com>
To: Alexei Starovoitov <alexei.starovoitov@gmail.com>
Cc: bpf <bpf@vger.kernel.org>, Alexei Starovoitov <ast@kernel.org>,
	Andrii Nakryiko <andrii@kernel.org>,
	Daniel Borkmann <daniel@iogearbox.net>,
	Kernel Team <kernel-team@fb.com>, Tejun Heo <tj@kernel.org>
Subject: Re: [PATCH bpf-next 2/3] bpf: Perform necessary sign/zero extension for kfunc return values
Date: Tue, 9 Aug 2022 10:21:39 -0700	[thread overview]
Message-ID: <ae74f476-be27-162a-e004-7bc112505473@fb.com> (raw)
In-Reply-To: <CAADnVQK__BPkmgsjuLmBxZ3a=kDTA_cGR8oWLCvjLVDYYW6hfw@mail.gmail.com>



On 8/9/22 10:02 AM, Alexei Starovoitov wrote:
> On Sun, Aug 7, 2022 at 10:51 AM Yonghong Song <yhs@fb.com> wrote:
>>
>> Tejun reported a bpf program kfunc return value mis-handling which
>> may cause incorrect result. The following is an example to show
>> the problem.
>>    $ cat t.c
>>    unsigned char bar();
>>    int foo() {
>>          if (bar() != 10) return 0; else return 1;
>>    }
>>    $ clang -target bpf -O2 -c t.c
>>    $ llvm-objdump -d t.o
>>    ...
>>    0000000000000000 <foo>:
>>         0:       85 10 00 00 ff ff ff ff call -1
>>         1:       bf 01 00 00 00 00 00 00 r1 = r0
>>         2:       b7 00 00 00 01 00 00 00 r0 = 1
>>         3:       15 01 01 00 0a 00 00 00 if r1 == 10 goto +1 <LBB0_2>
>>         4:       b7 00 00 00 00 00 00 00 r0 = 0
>>
>>    0000000000000028 <LBB0_2>:
>>         5:       95 00 00 00 00 00 00 00 exit
>>    $
>>
>> In the above example, the return type for bar() is 'unsigned char'.
>> But in the disassembly code, the whole register 'r1' is used to
>> compare to 10 without truncating upper 56 bits.
>>
>> If function bar() is implemented as a bpf function, everything
>> should be okay since bpf ABI will make sure the caller do
>> proper truncation of upper 56 bits.
>>
>> But if function bar() is implemented as a non-bpf kfunc,
>> there could a mismatch between bar() implementation and bpf program.
>> For example, if the host arch is x86_64, the bar() function
>> may just put the return value in lower 8-bit subregister and all
>> upper 56 bits could contain garbage. This is not a problem
>> if bar() is called in x86_64 context as the caller will use
>> %al to get the value.
>>
>> But this could be a problem if bar() is called in bpf context
>> and there is a mismatch expectation between bpf and native architecture.
>> Currently, bpf programs use the default llvm ABI ([1], function
>> isPromotableIntegerTypeForABI()) such that if an integer type size
>> is less than int type size, it is assumed proper sign or zero
>> extension has been done to the return value. There will be a problem
>> if the kfunc return value type is u8/s8/u16/s16.
>>
>> This patch intends to address this issue by doing proper sign or zero
>> extension for the kfunc return value before it is used later.
>>
>>   [1] https://github.com/llvm/llvm-project/blob/main/clang/lib/CodeGen/TargetInfo.cpp
>>
>> Reported-by: Tejun Heo <tj@kernel.org>
>> Signed-off-by: Yonghong Song <yhs@fb.com>
>> ---
>>   include/linux/bpf.h   |  2 ++
>>   kernel/bpf/btf.c      |  9 +++++++++
>>   kernel/bpf/verifier.c | 35 +++++++++++++++++++++++++++++++++--
>>   3 files changed, 44 insertions(+), 2 deletions(-)
>>
>> diff --git a/include/linux/bpf.h b/include/linux/bpf.h
>> index 20c26aed7896..b6f6bb1b707d 100644
>> --- a/include/linux/bpf.h
>> +++ b/include/linux/bpf.h
>> @@ -727,6 +727,8 @@ enum bpf_cgroup_storage_type {
>>   #define MAX_BPF_FUNC_REG_ARGS 5
>>
>>   struct btf_func_model {
>> +       u8 ret_integer:1;
>> +       u8 ret_integer_signed:1;
>>          u8 ret_size;
>>          u8 nr_args;
>>          u8 arg_size[MAX_BPF_FUNC_ARGS];
>> diff --git a/kernel/bpf/btf.c b/kernel/bpf/btf.c
>> index 8119dc3994db..f30a02018701 100644
>> --- a/kernel/bpf/btf.c
>> +++ b/kernel/bpf/btf.c
>> @@ -5897,6 +5897,7 @@ int btf_distill_func_proto(struct bpf_verifier_log *log,
>>          u32 i, nargs;
>>          int ret;
>>
>> +       m->ret_integer = false;
>>          if (!func) {
>>                  /* BTF function prototype doesn't match the verifier types.
>>                   * Fall back to MAX_BPF_FUNC_REG_ARGS u64 args.
>> @@ -5923,6 +5924,14 @@ int btf_distill_func_proto(struct bpf_verifier_log *log,
>>                  return -EINVAL;
>>          }
>>          m->ret_size = ret;
>> +       if (btf_type_is_int(t)) {
>> +               m->ret_integer = true;
>> +               /* BTF_INT_BOOL is considered as unsigned */
>> +               if (BTF_INT_ENCODING(btf_type_int(t)) == BTF_INT_SIGNED)
>> +                       m->ret_integer_signed = true;
>> +               else
>> +                       m->ret_integer_signed = false;
>> +       }
>>
>>          for (i = 0; i < nargs; i++) {
>>                  if (i == nargs - 1 && args[i].type == 0) {
>> diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
>> index 096fdac70165..684f8606f341 100644
>> --- a/kernel/bpf/verifier.c
>> +++ b/kernel/bpf/verifier.c
>> @@ -13834,8 +13834,9 @@ static int fixup_call_args(struct bpf_verifier_env *env)
>>   }
>>
>>   static int fixup_kfunc_call(struct bpf_verifier_env *env,
>> -                           struct bpf_insn *insn)
>> +                           struct bpf_insn *insn, struct bpf_insn *insn_buf, int *cnt)
>>   {
>> +       u8 ret_size, shift_cnt, rshift_opcode;
>>          const struct bpf_kfunc_desc *desc;
>>
>>          if (!insn->imm) {
>> @@ -13855,6 +13856,26 @@ static int fixup_kfunc_call(struct bpf_verifier_env *env,
>>
>>          insn->imm = desc->imm;
>>
>> +       *cnt = 0;
>> +       ret_size = desc->func_model.ret_size;
>> +
>> +       /* If the kfunc return type is an integer and the type size is one byte or two
>> +        * bytes, currently llvm/bpf assumes proper sign/zero extension has been done
>> +        * in the caller. But such an asumption may not hold for non-bpf architectures.
>> +        * For example, for x86_64, if the return type is 'u8', it is possible that only
>> +        * %al register is set properly and upper 56 bits of %rax register may contain
>> +        * garbage. To resolve this case, Let us do a necessary truncation to zero-out
>> +        * or properly sign-extend upper 56 bits.
>> +        */
>> +       if (desc->func_model.ret_integer && ret_size < sizeof(int)) {
> 
> Few questions...
> Do we really need 'ret_integer' here?
> and is it x86 specific?
> afaik only x86 has 8 and 16-bit subregisters.
> On all other archs the hw cannot write such quantities into
> a register and don't touch the upper bits.
> At the same time such return values from kfunc are rare.
> I don't think we have such a case in the current set of kfuncs.
> So being safe than sorry is a reasonable trade off and
> gating by x86 only is unnecessary.
> So how about just if (ret_size < sizeof(int)) here?

good questions. yes, we don't need ret_integer here with the
current code base.

I added ret_integer because my kfunc struct argument/return value
support work (in RFC stage). In that case, we could have
a 1-byte or 2-bytes structure as return value in which case,
we should not do sign/extension. With checking ret_integer,
we can avoid the churn later.

But it is not clear whether we will support kfunc return struct
as we don't have a request so far. So I will remove ret_integer
in the next revision.

> 
>> +               shift_cnt = (sizeof(u64) - ret_size) * 8;
>> +               rshift_opcode = desc->func_model.ret_integer_signed ? BPF_ARSH : BPF_RSH;
>> +               insn_buf[0] = *insn;
>> +               insn_buf[1] = BPF_ALU64_IMM(BPF_LSH, BPF_REG_0, shift_cnt);
>> +               insn_buf[2] = BPF_ALU64_IMM(rshift_opcode, BPF_REG_0, shift_cnt);
>> +               *cnt = 3;
>> +       }
>> +
>>          return 0;
>>   }
>>
>> @@ -13996,9 +14017,19 @@ static int do_misc_fixups(struct bpf_verifier_env *env)
>>                  if (insn->src_reg == BPF_PSEUDO_CALL)
>>                          continue;
>>                  if (insn->src_reg == BPF_PSEUDO_KFUNC_CALL) {
>> -                       ret = fixup_kfunc_call(env, insn);
>> +                       ret = fixup_kfunc_call(env, insn, insn_buf, &cnt);
>>                          if (ret)
>>                                  return ret;
>> +                       if (cnt == 0)
>> +                               continue;
>> +
>> +                       new_prog = bpf_patch_insn_data(env, i + delta, insn_buf, cnt);
>> +                       if (!new_prog)
>> +                               return -ENOMEM;
>> +
>> +                       delta    += cnt - 1;
>> +                       env->prog = prog = new_prog;
>> +                       insn      = new_prog->insnsi + i + delta;
>>                          continue;
>>                  }
>>
>> --
>> 2.30.2
>>

  reply	other threads:[~2022-08-09 17:22 UTC|newest]

Thread overview: 12+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2022-08-07 17:51 [PATCH bpf-next 0/3] bpf: Perform necessary sign/zero extension for kfunc return values Yonghong Song
2022-08-07 17:51 ` [PATCH bpf-next 1/3] bpf: Always return corresponding btf_type in __get_type_size() Yonghong Song
2022-08-07 17:51 ` [PATCH bpf-next 2/3] bpf: Perform necessary sign/zero extension for kfunc return values Yonghong Song
2022-08-08 23:25   ` Andrii Nakryiko
2022-08-09  6:36     ` Yonghong Song
2022-08-09 17:02   ` Alexei Starovoitov
2022-08-09 17:21     ` Yonghong Song [this message]
2022-08-07 17:51 ` [PATCH bpf-next 3/3] selftests/bpf: Add tests with u8/s16 kfunc return types Yonghong Song
2022-08-08 23:25   ` Andrii Nakryiko
2022-08-09  6:41     ` Yonghong Song
2022-08-08 23:22 ` [PATCH bpf-next 0/3] bpf: Perform necessary sign/zero extension for kfunc return values Andrii Nakryiko
2022-08-09 17:40 ` patchwork-bot+netdevbpf

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=ae74f476-be27-162a-e004-7bc112505473@fb.com \
    --to=yhs@fb.com \
    --cc=alexei.starovoitov@gmail.com \
    --cc=andrii@kernel.org \
    --cc=ast@kernel.org \
    --cc=bpf@vger.kernel.org \
    --cc=daniel@iogearbox.net \
    --cc=kernel-team@fb.com \
    --cc=tj@kernel.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox