• Andrii Nakryiko's avatar
    libbpf: Support safe subset of load/store instruction resizing with CO-RE · a66345bc
    Andrii Nakryiko authored
    Add support for patching instructions of the following form:
      - rX = *(T *)(rY + <off>);
      - *(T *)(rX + <off>) = rY;
      - *(T *)(rX + <off>) = <imm>, where T is one of {u8, u16, u32, u64}.
    
    For such instructions, if the actual kernel field recorded in CO-RE relocation
    has a different size than the one recorded locally (e.g., from vmlinux.h),
    then libbpf will adjust T to an appropriate 1-, 2-, 4-, or 8-byte loads.
    
    In general, such transformation is not always correct and could lead to
    invalid final value being loaded or stored. But two classes of cases are
    always safe:
      - if both local and target (kernel) types are unsigned integers, but of
      different sizes, then it's OK to adjust load/store instruction according to
      the necessary memory size. Zero-extending nature of such instructions and
      unsignedness make sure that the final value is always correct;
      - pointer size mismatch between BPF target architecture (which is always
      64-bit) and 32-bit host kernel architecture can be similarly resolved
      automatically, because pointer is essentially an unsigned integer. Loading
      32-bit pointer into 64-bit BPF register with zero extension will leave
      correct pointer in the register.
    
    Both cases are necessary to support CO-RE on 32-bit kernels, as `unsigned
    long` in vmlinux.h generated from 32-bit kernel is 32-bit, but when compiled
    with BPF program for BPF target it will be treated by compiler as 64-bit
    integer. Similarly, pointers in vmlinux.h are 32-bit for kernel, but treated
    as 64-bit values by compiler for BPF target. Both problems are now resolved by
    libbpf for direct memory reads.
    
    But similar transformations are useful in general when kernel fields are
    "resized" from, e.g., unsigned int to unsigned long (or vice versa).
    
    Now, similar transformations for signed integers are not safe to perform as
    they will result in incorrect sign extension of the value. If such situation
    is detected, libbpf will emit helpful message and will poison the instruction.
    Not failing immediately means that it's possible to guard the instruction
    based on kernel version (or other conditions) and make sure it's not
    reachable.
    
    If there is a need to read signed integers that change sizes between different
    kernels, it's possible to use BPF_CORE_READ_BITFIELD() macro, which works both
    with bitfields and non-bitfield integers of any signedness and handles
    sign-extension properly. Also, bpf_core_read() with proper size and/or use of
    bpf_core_field_size() relocation could allow to deal with such complicated
    situations explicitly, if not so conventiently as direct memory reads.
    
    Selftests added in a separate patch in progs/test_core_autosize.c demonstrate
    both direct memory and probed use cases.
    
    BPF_CORE_READ() is not changed and it won't deal with such situations as
    automatically as direct memory reads due to the signedness integer
    limitations, which are much harder to detect and control with compiler macro
    magic. So it's encouraged to utilize direct memory reads as much as possible.
    Signed-off-by: default avatarAndrii Nakryiko <andrii@kernel.org>
    Signed-off-by: default avatarAlexei Starovoitov <ast@kernel.org>
    Link: https://lore.kernel.org/bpf/20201008001025.292064-3-andrii@kernel.org
    a66345bc
libbpf.c 280 KB