bpf, x86: Fix bpf mapping of atomic fetch implementation
authorJohan Almbladh <johan.almbladh@anyfinetworks.com>
Mon, 27 Sep 2021 13:11:57 +0000 (13:11 +0000)
committerDaniel Borkmann <daniel@iogearbox.net>
Tue, 28 Sep 2021 10:10:29 +0000 (12:10 +0200)
Fix the case where the dst register maps to %rax as otherwise this produces
an incorrect mapping with the implementation in 981f94c3e921 ("bpf: Add
bitwise atomic instructions") as %rax is clobbered given it's part of the
cmpxchg as operand.

The issue is similar to b29dd96b905f ("bpf, x86: Fix BPF_FETCH atomic and/or/
xor with r0 as src") just that the case of dst register was missed.

Before, dst=r0 (%rax) src=r2 (%rsi):

  [...]
  c5:   mov    %rax,%r10
  c8:   mov    0x0(%rax),%rax       <---+ (broken)
  cc:   mov    %rax,%r11                |
  cf:   and    %rsi,%r11                |
  d2:   lock cmpxchg %r11,0x0(%rax) <---+
  d8:   jne    0x00000000000000c8       |
  da:   mov    %rax,%rsi                |
  dd:   mov    %r10,%rax                |
  [...]                                 |
                                        |
After, dst=r0 (%rax) src=r2 (%rsi):     |
                                        |
  [...]                                 |
  da: mov    %rax,%r10                |
  dd: mov    0x0(%r10),%rax       <---+ (fixed)
  e1: mov    %rax,%r11                |
  e4: and    %rsi,%r11                |
  e7: lock cmpxchg %r11,0x0(%r10) <---+
  ed: jne    0x00000000000000dd
  ef: mov    %rax,%rsi
  f2: mov    %r10,%rax
  [...]

The remaining combinations were fine as-is though:

After, dst=r9 (%r15) src=r0 (%rax):

  [...]
  dc: mov    %rax,%r10
  df: mov    0x0(%r15),%rax
  e3: mov    %rax,%r11
  e6: and    %r10,%r11
  e9: lock cmpxchg %r11,0x0(%r15)
  ef: jne    0x00000000000000df      _
  f1: mov    %rax,%r10                | (unneeded, but
  f4: mov    %r10,%rax               _|  not a problem)
  [...]

After, dst=r9 (%r15) src=r4 (%rcx):

  [...]
  de: mov    %rax,%r10
  e1: mov    0x0(%r15),%rax
  e5: mov    %rax,%r11
  e8: and    %rcx,%r11
  eb: lock cmpxchg %r11,0x0(%r15)
  f1: jne    0x00000000000000e1
  f3: mov    %rax,%rcx
  f6: mov    %r10,%rax
  [...]

The case of dst == src register is rejected by the verifier and
therefore not supported, but x86 JIT also handles this case just
fine.

After, dst=r0 (%rax) src=r0 (%rax):

  [...]
  eb: mov    %rax,%r10
  ee: mov    0x0(%r10),%rax
  f2: mov    %rax,%r11
  f5: and    %r10,%r11
  f8: lock cmpxchg %r11,0x0(%r10)
  fe: jne    0x00000000000000ee
 100: mov    %rax,%r10
 103: mov    %r10,%rax
  [...]

Fixes: 981f94c3e921 ("bpf: Add bitwise atomic instructions")
Reported-by: Johan Almbladh <johan.almbladh@anyfinetworks.com>
Signed-off-by: Johan Almbladh <johan.almbladh@anyfinetworks.com>
Co-developed-by: Daniel Borkmann <daniel@iogearbox.net>
Signed-off-by: Daniel Borkmann <daniel@iogearbox.net>
Reviewed-by: Brendan Jackman <jackmanb@google.com>
Acked-by: Alexei Starovoitov <ast@kernel.org>
arch/x86/net/bpf_jit_comp.c

index d24a512..9ea5738 100644 (file)
@@ -1341,9 +1341,10 @@ st:                      if (is_imm8(insn->off))
                        if (insn->imm == (BPF_AND | BPF_FETCH) ||
                            insn->imm == (BPF_OR | BPF_FETCH) ||
                            insn->imm == (BPF_XOR | BPF_FETCH)) {
-                               u8 *branch_target;
                                bool is64 = BPF_SIZE(insn->code) == BPF_DW;
                                u32 real_src_reg = src_reg;
+                               u32 real_dst_reg = dst_reg;
+                               u8 *branch_target;
 
                                /*
                                 * Can't be implemented with a single x86 insn.
@@ -1354,11 +1355,13 @@ st:                     if (is_imm8(insn->off))
                                emit_mov_reg(&prog, true, BPF_REG_AX, BPF_REG_0);
                                if (src_reg == BPF_REG_0)
                                        real_src_reg = BPF_REG_AX;
+                               if (dst_reg == BPF_REG_0)
+                                       real_dst_reg = BPF_REG_AX;
 
                                branch_target = prog;
                                /* Load old value */
                                emit_ldx(&prog, BPF_SIZE(insn->code),
-                                        BPF_REG_0, dst_reg, insn->off);
+                                        BPF_REG_0, real_dst_reg, insn->off);
                                /*
                                 * Perform the (commutative) operation locally,
                                 * put the result in the AUX_REG.
@@ -1369,7 +1372,8 @@ st:                       if (is_imm8(insn->off))
                                      add_2reg(0xC0, AUX_REG, real_src_reg));
                                /* Attempt to swap in new value */
                                err = emit_atomic(&prog, BPF_CMPXCHG,
-                                                 dst_reg, AUX_REG, insn->off,
+                                                 real_dst_reg, AUX_REG,
+                                                 insn->off,
                                                  BPF_SIZE(insn->code));
                                if (WARN_ON(err))
                                        return err;
@@ -1383,11 +1387,10 @@ st:                     if (is_imm8(insn->off))
                                /* Restore R0 after clobbering RAX */
                                emit_mov_reg(&prog, true, BPF_REG_0, BPF_REG_AX);
                                break;
-
                        }
 
                        err = emit_atomic(&prog, insn->imm, dst_reg, src_reg,
-                                                 insn->off, BPF_SIZE(insn->code));
+                                         insn->off, BPF_SIZE(insn->code));
                        if (err)
                                return err;
                        break;