From 004fb58d2654f22d365f341233b29bb513e08270 Mon Sep 17 00:00:00 2001 From: Mark Rowe Date: Wed, 9 Sep 2026 16:42:56 -0700 Subject: [PATCH] [aarch64] Disassemble and lift FEAT_CPA instructions `FEAT_CPA` (Checked Pointer Arithmetic) adds pointer arithmetic instructions that, on overflow out of the address bits, force bit 54 to disagree with bit 55. This makes the result non-canonical so it faults when dereferenced. For readable decompilation these are lifted as if the instructions are supported but enforcement is disabled (i.e., as if the CPU supports `FEAT_CPA` but not `FEAT_CPA2`). --- arch/arm64/arm64test.py | 36 +++++++++++++ arch/arm64/disassembler/decode_scratchpad.c | 56 +++++++++++++++++++++ arch/arm64/il.cpp | 32 ++++++++++++ 3 files changed, 124 insertions(+) diff --git a/arch/arm64/arm64test.py b/arch/arm64/arm64test.py index e1e2c80aae..73d29f139e 100755 --- a/arch/arm64/arm64test.py +++ b/arch/arm64/arm64test.py @@ -12846,6 +12846,41 @@ (b'\x20\xb8\xe0\x4e', 'LLIL_UNIMPL()'), ] +# FEAT_CPA (Checked Pointer Arithmetic). The pointer check these perform only rewrites bits 63:54 of +# the result, and only when the arithmetic escapes the address bits with SCTLR2_ELx.CPTA/CPTM +# enabled, so we lift the integer forms as plain arithmetic -- as we do for PAC above. The SVE forms +# are unlifted, like every other SVE encoding. +tests_cpa = [ + # addpt x8, x27, x21, lsl #4 ADDPT_64_addsub_pt + (b'\x68\x33\x15\x9a', + 'LLIL_SET_REG.q(x8,LLIL_ADD.q(LLIL_REG.q(x27),LLIL_LSL.q(LLIL_REG.q(x21),LLIL_CONST.b(0x4))))'), + # addpt x8, x27, x21 ADDPT_64_addsub_pt + (b'\x68\x23\x15\x9a', 'LLIL_SET_REG.q(x8,LLIL_ADD.q(LLIL_REG.q(x27),LLIL_REG.q(x21)))'), + # addpt x8, sp, x5 (Rn==31 is SP, not XZR) ADDPT_64_addsub_pt + (b'\xe8\x23\x05\x9a', 'LLIL_SET_REG.q(x8,LLIL_ADD.q(LLIL_REG.q(sp),LLIL_REG.q(x5)))'), + # addpt x1, x2, xzr (Rm==31 is XZR, not SP) ADDPT_64_addsub_pt + (b'\x41\x20\x1f\x9a', 'LLIL_SET_REG.q(x1,LLIL_ADD.q(LLIL_REG.q(x2),LLIL_CONST.q(0x0)))'), + # subpt x17, x13, x3, lsl #7 SUBPT_64_addsub_pt + (b'\xb1\x3d\x03\xda', + 'LLIL_SET_REG.q(x17,LLIL_SUB.q(LLIL_REG.q(x13),LLIL_LSL.q(LLIL_REG.q(x3),LLIL_CONST.b(0x7))))'), + # subpt sp, sp, x5 SUBPT_64_addsub_pt + (b'\xff\x23\x05\xda', 'LLIL_SET_REG.q(sp,LLIL_SUB.q(LLIL_REG.q(sp),LLIL_REG.q(x5)))'), + # maddpt x3, x2, x1, x4 MADDPT_64A_dp_3src + (b'\x43\x10\x61\x9b', + 'LLIL_SET_REG.q(x3,LLIL_ADD.q(LLIL_REG.q(x4),LLIL_MUL.q(LLIL_REG.q(x2),LLIL_REG.q(x1))))'), + # msubpt x3, x2, x1, x4 MSUBPT_64A_dp_3src + (b'\x43\x90\x61\x9b', + 'LLIL_SET_REG.q(x3,LLIL_SUB.q(LLIL_REG.q(x4),LLIL_MUL.q(LLIL_REG.q(x2),LLIL_REG.q(x1))))'), + # addpt z3.d, z2.d, z1.d addpt_z_zz_ + (b'\x43\x08\xe1\x04', 'LLIL_UNIMPL()'), + # subpt z3.d, p1/m, z3.d, z2.d subpt_z_p_zz_ + (b'\x43\x04\xc5\x04', 'LLIL_UNIMPL()'), + # madpt z3.d, z2.d, z4.d madpt_z_zzz_ + (b'\x83\xd8\xc2\x44', 'LLIL_UNIMPL()'), + # mlapt z3.d, z2.d, z1.d mlapt_z_zzz_ + (b'\x43\xd0\xc1\x44', 'LLIL_UNIMPL()'), +] + # Apple's vendor-specific instructions, which occupy encoding space that ARM leaves unallocated. # See arch/arm64/apple_vendor.cpp. tests_apple_vendor = [ @@ -12981,6 +13016,7 @@ test_cases = \ tests_apple_vendor + \ + tests_cpa + \ tests_cssc + \ tests_shll + \ tests_udf + \ diff --git a/arch/arm64/disassembler/decode_scratchpad.c b/arch/arm64/disassembler/decode_scratchpad.c index b492613ddd..d49ef7aff1 100644 --- a/arch/arm64/disassembler/decode_scratchpad.c +++ b/arch/arm64/disassembler/decode_scratchpad.c @@ -1536,6 +1536,16 @@ static unsigned rhsdr_0123x_reg(int v) #define LAST_OPERAND_LSL_12 LAST_OPERAND_SHIFT(ShiftType_LSL, 12) +#define OPTIONAL_LSL_AMOUNT(AMOUNT) \ + if (AMOUNT) \ + { \ + LAST_OPERAND_SHIFT(ShiftType_LSL, AMOUNT); \ + } \ + else \ + { \ + instr->operands[i - 1].shiftValueUsed = 0; \ + } + #define ADD_OPERAND_OPTIONAL_PATTERN_MUL \ { \ bool print_mul = ctx->imm != 1; \ @@ -5731,6 +5741,7 @@ int decode_scratchpad(context* ctx, Instruction* instr) case ENC_MADD_64A_DP_3SRC: case ENC_MSUB_64A_DP_3SRC: case ENC_MADDPT_64A_DP_3SRC: + case ENC_MSUBPT_64A_DP_3SRC: { // ,,, ADD_OPERAND_XD; @@ -6035,6 +6046,16 @@ int decode_scratchpad(context* ctx, Instruction* instr) OPTIONAL_EXTEND_AMOUNT_64_BEHAVIOR1; break; } + case ENC_ADDPT_64_ADDSUB_PT: + case ENC_SUBPT_64_ADDSUB_PT: + { + // ,,{, LSL #} + ADD_OPERAND_XD_SP; + ADD_OPERAND_XN_SP; + ADD_OPERAND_XM; + OPTIONAL_LSL_AMOUNT(ctx->shift); + break; + } case ENC_IRG_64I_DP_2SRC: { // ,{,} @@ -6842,6 +6863,31 @@ int decode_scratchpad(context* ctx, Instruction* instr) ADD_OPERAND_CONST; break; } + case ENC_ADDPT_Z_ZZ_: + case ENC_SUBPT_Z_ZZ_: + { + // .D,.D,.D + ADD_OPERAND_ZREG_T(ctx->d, _1D) + ADD_OPERAND_ZREG_T(ctx->n, _1D) + ADD_OPERAND_ZREG_T(ctx->m, _1D) + break; + } + case ENC_MLAPT_Z_ZZZ_: + { + // .D,.D,.D + ADD_OPERAND_ZREG_T(ctx->da, _1D) + ADD_OPERAND_ZREG_T(ctx->n, _1D) + ADD_OPERAND_ZREG_T(ctx->m, _1D) + break; + } + case ENC_MADPT_Z_ZZZ_: + { + // .D,.D,.D + ADD_OPERAND_ZREG_T(ctx->dn, _1D) + ADD_OPERAND_ZREG_T(ctx->m, _1D) + ADD_OPERAND_ZREG_T(ctx->a, _1D) + break; + } case ENC_ADD_Z_ZZ_: case ENC_FADD_Z_ZZ_: case ENC_FMUL_Z_ZZ_: @@ -7303,6 +7349,16 @@ int decode_scratchpad(context* ctx, Instruction* instr) ADD_OPERAND_CONST; break; } + case ENC_ADDPT_Z_P_ZZ_: + case ENC_SUBPT_Z_P_ZZ_: + { + // .D,/M,.D,.D + ADD_OPERAND_ZREG_T(ctx->dn, _1D) + ADD_OPERAND_PRED_REG_QUAL(ctx->g, 'm'); + ADD_OPERAND_ZREG_T(ctx->dn, _1D) + ADD_OPERAND_ZREG_T(ctx->m, _1D) + break; + } case ENC_ADD_Z_P_ZZ_: case ENC_AND_Z_P_ZZ_: case ENC_ASR_Z_P_ZZ_: diff --git a/arch/arm64/il.cpp b/arch/arm64/il.cpp index 116d2b134d..9bf7096909 100644 --- a/arch/arm64/il.cpp +++ b/arch/arm64/il.cpp @@ -1378,6 +1378,21 @@ bool GetLowLevelILForInstruction( il.AddInstruction(il.Intrinsic({RegisterOrFlag::Register(REG_O(operand1))}, ARM64_INTRIN_ADDG, {ILREG_O(operand2), il.Const(REGSZ_O(operand2), IMM_O(operand3)), il.Const(1, IMM_O(operand4))})); break; + case ARM64_ADDPT: + switch (instr.encoding) + { + case ENC_ADDPT_Z_P_ZZ_: + case ENC_ADDPT_Z_ZZ_: + if (!preferIntrinsics()) + il.AddInstruction(il.Unimplemented()); + return true; + default: break; + } + // FEAT_CPA checked pointer addition, lifted as if checking is disabled + il.AddInstruction(ILSETREG_O(operand1, + il.Add(REGSZ_O(operand1), ILREG_O(operand2), + ReadILOperand(il, operand3, REGSZ_O(operand1))))); + break; case ARM64_ADC: case ARM64_ADCS: il.AddInstruction(ILSETREG_O(operand1, @@ -2927,6 +2942,7 @@ bool GetLowLevelILForInstruction( } break; case ARM64_MADD: + case ARM64_MADDPT: // FEAT_CPA checked multiply-add, lifted as if checking is disabled il.AddInstruction(ILSETREG_O(operand1, ILADDREG_O(operand4, il.Mult(REGSZ_O(operand1), ILREG_O(operand2), ILREG_O(operand3))))); break; @@ -2953,6 +2969,7 @@ bool GetLowLevelILForInstruction( break; } case ARM64_MSUB: + case ARM64_MSUBPT: // FEAT_CPA checked multiply-subtract, lifted as if checking is disabled il.AddInstruction(ILSETREG_O( operand1, il.Sub(REGSZ_O(operand1), ILREG_O(operand4), il.Mult(REGSZ_O(operand1), ILREG_O(operand2), ILREG_O(operand3))))); @@ -3755,6 +3772,21 @@ bool GetLowLevelILForInstruction( il.AddInstruction(il.Intrinsic({RegisterOrFlag::Register(REG_O(operand1))}, ARM64_INTRIN_SUBG, {ILREG_O(operand2), il.Const(REGSZ_O(operand2), IMM_O(operand3)), il.Const(1, IMM_O(operand4))})); break; + case ARM64_SUBPT: + switch (instr.encoding) + { + case ENC_SUBPT_Z_P_ZZ_: + case ENC_SUBPT_Z_ZZ_: + if (!preferIntrinsics()) + il.AddInstruction(il.Unimplemented()); + return true; + default: break; + } + // FEAT_CPA checked pointer subtraction, lifted as if checking is disabled + il.AddInstruction(ILSETREG_O(operand1, + il.Sub(REGSZ_O(operand1), ILREG_O(operand2), + ReadILOperand(il, operand3, REGSZ_O(operand1))))); + break; case ARM64_SUBP: il.AddInstruction(il.Intrinsic( {RegisterOrFlag::Register(REG_O(operand1))}, ARM64_INTRIN_SUBP, {ILREG_O(operand2), ILREG_O(operand3)}));