From ff6eb2213bca878cdcef0e9cec20e524d8d9f944 Mon Sep 17 00:00:00 2001 From: Nicolas Iooss Date: Tue, 2 Dec 2025 16:19:37 +0100 Subject: [PATCH 1/2] Fix disassembly of eBPF atomic instructions eBPF ISA v3 introduced atomic instructions: https://www.kernel.org/doc/html/v6.0/bpf/instruction-set.html#atomic-operations These instructions are encoded using BPF_ATOMIC | BPF_W | BPF_STX and BPF_ATOMIC | BPF_DW | BPF_STX for 32-bit and 64-bit operations, with: BPF_ATOMIC = 0xc0 BPF_DW = 0x18 BPF_W = 0 BPF_STX = 0x03 While Ghidra's semantic section is constructed correctly (atomic add uses an addition ; atomic or uses or ; ...), the disassembly always displays STXXADDW and STXXADDDW. These mnemonics come from the deprecated name BPF_XADD = BPF_ATOMIC | BPF_ADD = 0xc0. Replace the confusing mnemonics with the ones used by binutils and documented in https://sourceware.org/git/?p=binutils-gdb.git;a=blob;f=gas/doc/c-bpf.texi;h=003cb92a457985038a9abc1ffbf347f636eb0586;hb=2bc7af1ff7732451b6a7b09462a815c3284f9613#l745 --- .../Processors/eBPF/data/languages/eBPF.sinc | 44 +++++++++---------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/Ghidra/Processors/eBPF/data/languages/eBPF.sinc b/Ghidra/Processors/eBPF/data/languages/eBPF.sinc index 62a77a1065..67aa5aee2d 100644 --- a/Ghidra/Processors/eBPF/data/languages/eBPF.sinc +++ b/Ghidra/Processors/eBPF/data/languages/eBPF.sinc @@ -272,38 +272,38 @@ DST4: dst is dst { local tmp:4 = dst:4; export tmp; } # BPF_ADD: # BPF_STX | BPF_ATOMIC | BPF_W -:STXXADDW [dst + off], src is imm=0x0 & off & src & dst & op_ld_st_mode=0x6 & op_ld_st_size=0x0 & op_insn_class=0x3 { *:4 (dst + off) = *:4 (dst + off) + src:4; } +:AADD32 [dst + off], src is imm=0x0 & off & src & dst & op_ld_st_mode=0x6 & op_ld_st_size=0x0 & op_insn_class=0x3 { *:4 (dst + off) = *:4 (dst + off) + src:4; } # BPF_STX | BPF_ATOMIC | BPF_DW -:STXXADDDW [dst + off], src is imm=0x0 & off & src & dst & op_ld_st_mode=0x6 & op_ld_st_size=0x3 & op_insn_class=0x3 { *:8 (dst + off) = *:8 (dst + off) + src; } +:AADD [dst + off], src is imm=0x0 & off & src & dst & op_ld_st_mode=0x6 & op_ld_st_size=0x3 & op_insn_class=0x3 { *:8 (dst + off) = *:8 (dst + off) + src; } # BPF_OR: -:STXXADDW [dst + off], src is imm=0x40 & off & src & dst & op_ld_st_mode=0x6 & op_ld_st_size=0x0 & op_insn_class=0x3 { *:4 (dst + off) = *:4 (dst + off) | src:4; } +:AOR32 [dst + off], src is imm=0x40 & off & src & dst & op_ld_st_mode=0x6 & op_ld_st_size=0x0 & op_insn_class=0x3 { *:4 (dst + off) = *:4 (dst + off) | src:4; } -:STXXADDDW [dst + off], src is imm=0x40 & off & src & dst & op_ld_st_mode=0x6 & op_ld_st_size=0x3 & op_insn_class=0x3 { *:8 (dst + off) = *:8 (dst + off) | src; } +:AOR [dst + off], src is imm=0x40 & off & src & dst & op_ld_st_mode=0x6 & op_ld_st_size=0x3 & op_insn_class=0x3 { *:8 (dst + off) = *:8 (dst + off) | src; } # BPF_AND: -:STXXADDW [dst + off], src is imm=0x50 & off & src & dst & op_ld_st_mode=0x6 & op_ld_st_size=0x0 & op_insn_class=0x3 { *:4 (dst + off) = *:4 (dst + off) & src:4; } +:AAND32 [dst + off], src is imm=0x50 & off & src & dst & op_ld_st_mode=0x6 & op_ld_st_size=0x0 & op_insn_class=0x3 { *:4 (dst + off) = *:4 (dst + off) & src:4; } -:STXXADDDW [dst + off], src is imm=0x50 & off & src & dst & op_ld_st_mode=0x6 & op_ld_st_size=0x3 & op_insn_class=0x3 { *:8 (dst + off) = *:8 (dst + off) & src; } +:AAND [dst + off], src is imm=0x50 & off & src & dst & op_ld_st_mode=0x6 & op_ld_st_size=0x3 & op_insn_class=0x3 { *:8 (dst + off) = *:8 (dst + off) & src; } # BPF_XOR: -:STXXADDW [dst + off], src is imm=0xa0 & off & src & dst & op_ld_st_mode=0x6 & op_ld_st_size=0x0 & op_insn_class=0x3 { *:4 (dst + off) = *:4 (dst + off) ^ src:4; } +:AXOR32 [dst + off], src is imm=0xa0 & off & src & dst & op_ld_st_mode=0x6 & op_ld_st_size=0x0 & op_insn_class=0x3 { *:4 (dst + off) = *:4 (dst + off) ^ src:4; } -:STXXADDDW [dst + off], src is imm=0xa0 & off & src & dst & op_ld_st_mode=0x6 & op_ld_st_size=0x3 & op_insn_class=0x3 { *:8 (dst + off) = *:8 (dst + off) ^ src; } +:AXOR [dst + off], src is imm=0xa0 & off & src & dst & op_ld_st_mode=0x6 & op_ld_st_size=0x3 & op_insn_class=0x3 { *:8 (dst + off) = *:8 (dst + off) ^ src; } # BPF_ADD | BPF_FETCH -> src = atomic_fetch_add(dst + off, src): -:STXXADDW [dst + off], src is imm=0x1 & off & src & dst & op_ld_st_mode=0x6 & op_ld_st_size=0x0 & op_insn_class=0x3 { +:AFADD32 [dst + off], src is imm=0x1 & off & src & dst & op_ld_st_mode=0x6 & op_ld_st_size=0x0 & op_insn_class=0x3 { local tmp:4 = *:4 (dst + off); *:4 (dst + off) = *:4 (dst + off) + src:4; src = zext(tmp); } -:STXXADDDW [dst + off], src is imm=0x1 & off & src & dst & op_ld_st_mode=0x6 & op_ld_st_size=0x3 & op_insn_class=0x3 { +:AFADD [dst + off], src is imm=0x1 & off & src & dst & op_ld_st_mode=0x6 & op_ld_st_size=0x3 & op_insn_class=0x3 { local tmp:8 = *:8 (dst + off); *:8 (dst + off) = *:8 (dst + off) + src; src = tmp; @@ -311,13 +311,13 @@ DST4: dst is dst { local tmp:4 = dst:4; export tmp; } # BPF_OR | BPF_FETCH -> src = atomic_fetch_or(dst + off, src): -:STXXADDW [dst + off], src is imm=0x41 & off & src & dst & op_ld_st_mode=0x6 & op_ld_st_size=0x0 & op_insn_class=0x3 { +:AFOR32 [dst + off], src is imm=0x41 & off & src & dst & op_ld_st_mode=0x6 & op_ld_st_size=0x0 & op_insn_class=0x3 { local tmp:4 = *:4 (dst + off); *:4 (dst + off) = *:4 (dst + off) | src:4; src = zext(tmp); } -:STXXADDDW [dst + off], src is imm=0x41 & off & src & dst & op_ld_st_mode=0x6 & op_ld_st_size=0x3 & op_insn_class=0x3 { +:AFOR [dst + off], src is imm=0x41 & off & src & dst & op_ld_st_mode=0x6 & op_ld_st_size=0x3 & op_insn_class=0x3 { local tmp:8 = *:8 (dst + off); *:8 (dst + off) = *:8 (dst + off) | src; src = tmp; @@ -325,13 +325,13 @@ DST4: dst is dst { local tmp:4 = dst:4; export tmp; } # BPF_AND | BPF_FETCH -> src = atomic_fetch_and(dst + off, src): -:STXXADDW [dst + off], src is imm=0x51 & off & src & dst & op_ld_st_mode=0x6 & op_ld_st_size=0x0 & op_insn_class=0x3 { +:AFAND32 [dst + off], src is imm=0x51 & off & src & dst & op_ld_st_mode=0x6 & op_ld_st_size=0x0 & op_insn_class=0x3 { local tmp:4 = *:4 (dst + off); *:4 (dst + off) = *:4 (dst + off) & src:4; src = zext(tmp); } -:STXXADDDW [dst + off], src is imm=0x51 & off & src & dst & op_ld_st_mode=0x6 & op_ld_st_size=0x3 & op_insn_class=0x3 { +:AFAND [dst + off], src is imm=0x51 & off & src & dst & op_ld_st_mode=0x6 & op_ld_st_size=0x3 & op_insn_class=0x3 { local tmp:8 = *:8 (dst + off); *:8 (dst + off) = *:8 (dst + off) & src; src = tmp; @@ -339,13 +339,13 @@ DST4: dst is dst { local tmp:4 = dst:4; export tmp; } # BPF_XOR | BPF_FETCH -> src = atomic_fetch_xor(dst + off, src): -:STXXADDW [dst + off], src is imm=0xa1 & off & src & dst & op_ld_st_mode=0x6 & op_ld_st_size=0x0 & op_insn_class=0x3 { +:AFXOR32 [dst + off], src is imm=0xa1 & off & src & dst & op_ld_st_mode=0x6 & op_ld_st_size=0x0 & op_insn_class=0x3 { local tmp:4 = *:4 (dst + off); *:4 (dst + off) = *:4 (dst + off) ^ src:4; src = zext(tmp); } -:STXXADDDW [dst + off], src is imm=0xa1 & off & src & dst & op_ld_st_mode=0x6 & op_ld_st_size=0x3 & op_insn_class=0x3 { +:AFXOR [dst + off], src is imm=0xa1 & off & src & dst & op_ld_st_mode=0x6 & op_ld_st_size=0x3 & op_insn_class=0x3 { local tmp:8 = *:8 (dst + off); *:8 (dst + off) = *:8 (dst + off) ^ src; src = tmp; @@ -353,13 +353,13 @@ DST4: dst is dst { local tmp:4 = dst:4; export tmp; } # BPF_XCHG -> src_reg = atomic_xchg(dst + off, src): -:STXXADDW [dst + off], src is imm=0xe1 & off & src & dst & op_ld_st_mode=0x6 & op_ld_st_size=0x0 & op_insn_class=0x3 { +:AXCHG32 [dst + off], src is imm=0xe1 & off & src & dst & op_ld_st_mode=0x6 & op_ld_st_size=0x0 & op_insn_class=0x3 { local tmp:4 = *:4 (dst + off); *:4 (dst + off) = src:4; src = zext(tmp); } -:STXXADDDW [dst + off], src is imm=0xe1 & off & src & dst & op_ld_st_mode=0x6 & op_ld_st_size=0x3 & op_insn_class=0x3 { +:AXCHG [dst + off], src is imm=0xe1 & off & src & dst & op_ld_st_mode=0x6 & op_ld_st_size=0x3 & op_insn_class=0x3 { local tmp:8 = *:8 (dst + off); *:8 (dst + off) = src; src = tmp; @@ -367,18 +367,18 @@ DST4: dst is dst { local tmp:4 = dst:4; export tmp; } # BPF_CMPXCHG -> R0 = atomic_cmpxchg(dst + off, R0, src): -:STXXADDW [dst + off], src is imm=0xf1 & off & src & dst & op_ld_st_mode=0x6 & op_ld_st_size=0x0 & op_insn_class=0x3 { +:ACMP32 [dst + off], src is imm=0xf1 & off & src & dst & op_ld_st_mode=0x6 & op_ld_st_size=0x0 & op_insn_class=0x3 { local tmp:4 = *:4 (dst + off); if (R0:4 == tmp) goto ; - R0 = zext(tmp); + R0 = zext(tmp); *:4 (dst + off) = src:4; } -:STXXADDDW [dst + off], src is imm=0xf1 & off & src & dst & op_ld_st_mode=0x6 & op_ld_st_size=0x3 & op_insn_class=0x3 { +:ACMP [dst + off], src is imm=0xf1 & off & src & dst & op_ld_st_mode=0x6 & op_ld_st_size=0x3 & op_insn_class=0x3 { local tmp:8 = *:8 (dst + off); if (R0 == tmp) goto ; - R0 = tmp; + R0 = tmp; *:8 (dst + off) = src; } From af8a3704212b157d21bfddad834f6485fdfabeb1 Mon Sep 17 00:00:00 2001 From: Nicolas Iooss Date: Tue, 2 Dec 2025 22:00:49 +0100 Subject: [PATCH 2/2] Fix eBPF compare-and-exchange instruction Linux kernel's documentation tells in https://www.kernel.org/doc/html/v6.0/bpf/instruction-set.html#atomic-operations > The BPF_CMPXCHG operation atomically compares the value addressed by > dst_reg + off with R0. If they match, the value addressed by > dst_reg + off is replaced with src_reg. In either case, the value that > was at dst_reg + off before the operation is zero-extended and loaded > back to R0. If the values don't match, *(dst_reg + off) is not supposed to be modified. Moreover, register R0 is always modified and the 32-bit instruction truncates its value (with a zero-extension). This is also clear in the implementation of BPF_CMPXCHG in https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/kernel/bpf/core.c?h=v6.18#n2186 case BPF_CMPXCHG: if (BPF_SIZE(insn->code) == BPF_W) BPF_R0 = (u32) atomic_cmpxchg( (atomic_t *)(unsigned long) (DST + insn->off), (u32) BPF_R0, (u32) SRC); else if (BPF_SIZE(insn->code) == BPF_DW) BPF_R0 = (u64) atomic64_cmpxchg( (atomic64_t *)(unsigned long) (DST + insn->off), (u64) BPF_R0, (u64) SRC); Fix the semantic of the compare-and-exchange instruction accordingly. --- Ghidra/Processors/eBPF/data/languages/eBPF.sinc | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/Ghidra/Processors/eBPF/data/languages/eBPF.sinc b/Ghidra/Processors/eBPF/data/languages/eBPF.sinc index 67aa5aee2d..32cbc7ee8a 100644 --- a/Ghidra/Processors/eBPF/data/languages/eBPF.sinc +++ b/Ghidra/Processors/eBPF/data/languages/eBPF.sinc @@ -369,18 +369,19 @@ DST4: dst is dst { local tmp:4 = dst:4; export tmp; } :ACMP32 [dst + off], src is imm=0xf1 & off & src & dst & op_ld_st_mode=0x6 & op_ld_st_size=0x0 & op_insn_class=0x3 { local tmp:4 = *:4 (dst + off); - if (R0:4 == tmp) goto ; - R0 = zext(tmp); - + if (R0:4 != tmp) goto ; *:4 (dst + off) = src:4; + + R0 = zext(tmp); } :ACMP [dst + off], src is imm=0xf1 & off & src & dst & op_ld_st_mode=0x6 & op_ld_st_size=0x3 & op_insn_class=0x3 { local tmp:8 = *:8 (dst + off); - if (R0 == tmp) goto ; + if (R0 != tmp) goto ; + *:8 (dst + off) = src; + goto inst_next; + R0 = tmp; - - *:8 (dst + off) = src; } #Jump instructions (BPF_JMP, BPF_JMP32)