From fb3fbf30975291685f03eae1b9ca9e7370719eb0 Mon Sep 17 00:00:00 2001 From: Daniel Roethlisberger Date: Mon, 17 Nov 2025 23:24:09 +0100 Subject: [PATCH 1/8] Make __ctl_wr work for an arbitrary number of parameters --- bindings/python/unicorn/unicorn_py3/unicorn.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/bindings/python/unicorn/unicorn_py3/unicorn.py b/bindings/python/unicorn/unicorn_py3/unicorn.py index 6f50eab8a0..1f770e74a7 100644 --- a/bindings/python/unicorn/unicorn_py3/unicorn.py +++ b/bindings/python/unicorn/unicorn_py3/unicorn.py @@ -1253,16 +1253,15 @@ def __ctl_w(self, ctl: int, *args: Arg): self.ctl(ctl, uc.UC_CTL_IO_WRITE, *cargs) - def __ctl_wr(self, ctl: int, arg0: Arg, arg1: Arg): - atype, avalue = arg0 - carg0 = atype(avalue) + def __ctl_wr(self, ctl: int, *args: Arg): + cargs = (atype(avalue) for atype, avalue in args[:-1]) - atype, _ = arg1 - carg1 = atype() + atype, _ = args[-1] + cretv = atype() - self.ctl(ctl, uc.UC_CTL_IO_READ_WRITE, carg0, ctypes.byref(carg1)) + self.ctl(ctl, uc.UC_CTL_IO_READ_WRITE, *cargs, ctypes.byref(cretv)) - return carg1.value + return cretv.value def ctl_get_mode(self) -> int: """Retrieve current processor mode. From d93ecc98656f2923abbe84d88802de5fef3aebd9 Mon Sep 17 00:00:00 2001 From: Daniel Roethlisberger Date: Mon, 17 Nov 2025 23:25:52 +0100 Subject: [PATCH 2/8] Rename test_arm64_v8_pac to test_arm64_v8_cas as it tests CAS not PAC --- tests/unit/test_arm64.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/unit/test_arm64.c b/tests/unit/test_arm64.c index 86f81595fe..f490c91637 100644 --- a/tests/unit/test_arm64.c +++ b/tests/unit/test_arm64.c @@ -122,7 +122,7 @@ static void test_arm64_code_patching_count(void) OK(uc_close(uc)); } -static void test_arm64_v8_pac(void) +static void test_arm64_v8_cas(void) { uc_engine *uc; char code[] = "\x28\xfd\xea\xc8"; // casal x10, x8, [x9] @@ -698,7 +698,7 @@ static void test_arm64_pc_guarantee(void) TEST_LIST = {{"test_arm64_until", test_arm64_until}, {"test_arm64_code_patching", test_arm64_code_patching}, {"test_arm64_code_patching_count", test_arm64_code_patching_count}, - {"test_arm64_v8_pac", test_arm64_v8_pac}, + {"test_arm64_v8_cas", test_arm64_v8_cas}, {"test_arm64_read_sctlr", test_arm64_read_sctlr}, {"test_arm64_hook_insn_mrs", test_arm64_hook_insn_mrs}, {"test_arm64_hook_insn_wfi", test_arm64_hook_insn_wfi}, From 36cd5df49ca9807be868718a68437c4df3a1ffff Mon Sep 17 00:00:00 2001 From: Daniel Roethlisberger Date: Mon, 17 Nov 2025 23:26:51 +0100 Subject: [PATCH 3/8] Add PAuth helpers for signing, stripping and authentication of pointers Add UC_CTL_PAUTH_SIGN, UC_CTL_PAUTH_STRIP and UC_CTL_PAUTH_AUTH as an architecture-independent interface to pointer authentication operations without having to execute instructions on the virtual CPU. This is useful in many scenarios, for instance when rebasing signed pointers as part of loading code that runs with PAuth. The C interfaces are covered by a unit test. The Python interfaces have been tested in a real world project. --- bindings/dotnet/UnicornEngine/Const/Arm64.fs | 9 ++ bindings/dotnet/UnicornEngine/Const/Common.fs | 3 + bindings/go/unicorn/arm64_const.go | 9 ++ bindings/go/unicorn/unicorn_const.go | 3 + .../src/main/java/unicorn/Arm64Const.java | 9 ++ .../src/main/java/unicorn/UnicornConst.java | 3 + bindings/pascal/unicorn/Arm64Const.pas | 9 ++ bindings/pascal/unicorn/UnicornConst.pas | 3 + bindings/python/unicorn/arm64_const.py | 9 ++ bindings/python/unicorn/unicorn_const.py | 3 + .../python/unicorn/unicorn_py3/unicorn.py | 52 +++++++ .../lib/unicorn_engine/arm64_const.rb | 9 ++ .../lib/unicorn_engine/unicorn_const.rb | 3 + bindings/zig/unicorn/arm64_const.zig | 9 ++ bindings/zig/unicorn/unicorn_const.zig | 3 + include/uc_priv.h | 8 + include/unicorn/arm64.h | 10 ++ include/unicorn/unicorn.h | 46 ++++++ qemu/target/arm/pauth_helper.c | 30 ++++ qemu/target/arm/unicorn_aarch64.c | 75 ++++++++++ tests/unit/test_arm64.c | 138 ++++++++++++++++++ uc.c | 65 +++++++++ 22 files changed, 508 insertions(+) diff --git a/bindings/dotnet/UnicornEngine/Const/Arm64.fs b/bindings/dotnet/UnicornEngine/Const/Arm64.fs index ba5da9a225..eb6d3a06d9 100644 --- a/bindings/dotnet/UnicornEngine/Const/Arm64.fs +++ b/bindings/dotnet/UnicornEngine/Const/Arm64.fs @@ -340,3 +340,12 @@ module Arm64 = let UC_ARM64_INS_WFI = 5 let UC_ARM64_INS_ENDING = 6 + // ARM64 PAuth keys + + let UC_ARM64_PAUTH_KEY_IA = 0 + let UC_ARM64_PAUTH_KEY_IB = 1 + let UC_ARM64_PAUTH_KEY_DA = 2 + let UC_ARM64_PAUTH_KEY_DB = 3 + let UC_ARM64_PAUTH_KEY_GA = 4 + let UC_ARM64_PAUTH_KEY_ENDING = 5 + diff --git a/bindings/dotnet/UnicornEngine/Const/Common.fs b/bindings/dotnet/UnicornEngine/Const/Common.fs index 4de99f01a4..d42495c3f0 100644 --- a/bindings/dotnet/UnicornEngine/Const/Common.fs +++ b/bindings/dotnet/UnicornEngine/Const/Common.fs @@ -152,6 +152,9 @@ module Common = let UC_CTL_TLB_TYPE = 12 let UC_CTL_TCG_BUFFER_SIZE = 13 let UC_CTL_CONTEXT_MODE = 14 + let UC_CTL_PAUTH_SIGN = 15 + let UC_CTL_PAUTH_STRIP = 16 + let UC_CTL_PAUTH_AUTH = 17 let UC_CTL_CONTEXT_CPU = 1 let UC_CTL_CONTEXT_MEMORY = 2 diff --git a/bindings/go/unicorn/arm64_const.go b/bindings/go/unicorn/arm64_const.go index 992a1f2bbe..471dcff852 100644 --- a/bindings/go/unicorn/arm64_const.go +++ b/bindings/go/unicorn/arm64_const.go @@ -334,4 +334,13 @@ const ( ARM64_INS_SYSL = 4 ARM64_INS_WFI = 5 ARM64_INS_ENDING = 6 + +// ARM64 PAuth keys + + ARM64_PAUTH_KEY_IA = 0 + ARM64_PAUTH_KEY_IB = 1 + ARM64_PAUTH_KEY_DA = 2 + ARM64_PAUTH_KEY_DB = 3 + ARM64_PAUTH_KEY_GA = 4 + ARM64_PAUTH_KEY_ENDING = 5 ) \ No newline at end of file diff --git a/bindings/go/unicorn/unicorn_const.go b/bindings/go/unicorn/unicorn_const.go index 6f7da98d43..03a92f720b 100644 --- a/bindings/go/unicorn/unicorn_const.go +++ b/bindings/go/unicorn/unicorn_const.go @@ -147,6 +147,9 @@ const ( CTL_TLB_TYPE = 12 CTL_TCG_BUFFER_SIZE = 13 CTL_CONTEXT_MODE = 14 + CTL_PAUTH_SIGN = 15 + CTL_PAUTH_STRIP = 16 + CTL_PAUTH_AUTH = 17 CTL_CONTEXT_CPU = 1 CTL_CONTEXT_MEMORY = 2 ) \ No newline at end of file diff --git a/bindings/java/src/main/java/unicorn/Arm64Const.java b/bindings/java/src/main/java/unicorn/Arm64Const.java index a5bc2e4757..2f187e87ea 100644 --- a/bindings/java/src/main/java/unicorn/Arm64Const.java +++ b/bindings/java/src/main/java/unicorn/Arm64Const.java @@ -337,4 +337,13 @@ public interface Arm64Const { public static final int UC_ARM64_INS_WFI = 5; public static final int UC_ARM64_INS_ENDING = 6; + // ARM64 PAuth keys + + public static final int UC_ARM64_PAUTH_KEY_IA = 0; + public static final int UC_ARM64_PAUTH_KEY_IB = 1; + public static final int UC_ARM64_PAUTH_KEY_DA = 2; + public static final int UC_ARM64_PAUTH_KEY_DB = 3; + public static final int UC_ARM64_PAUTH_KEY_GA = 4; + public static final int UC_ARM64_PAUTH_KEY_ENDING = 5; + } diff --git a/bindings/java/src/main/java/unicorn/UnicornConst.java b/bindings/java/src/main/java/unicorn/UnicornConst.java index b562494e61..c46675e17f 100644 --- a/bindings/java/src/main/java/unicorn/UnicornConst.java +++ b/bindings/java/src/main/java/unicorn/UnicornConst.java @@ -149,6 +149,9 @@ public interface UnicornConst { public static final int UC_CTL_TLB_TYPE = 12; public static final int UC_CTL_TCG_BUFFER_SIZE = 13; public static final int UC_CTL_CONTEXT_MODE = 14; + public static final int UC_CTL_PAUTH_SIGN = 15; + public static final int UC_CTL_PAUTH_STRIP = 16; + public static final int UC_CTL_PAUTH_AUTH = 17; public static final int UC_CTL_CONTEXT_CPU = 1; public static final int UC_CTL_CONTEXT_MEMORY = 2; diff --git a/bindings/pascal/unicorn/Arm64Const.pas b/bindings/pascal/unicorn/Arm64Const.pas index e98f24d7f4..ab3228d703 100644 --- a/bindings/pascal/unicorn/Arm64Const.pas +++ b/bindings/pascal/unicorn/Arm64Const.pas @@ -338,5 +338,14 @@ interface UC_ARM64_INS_WFI = 5; UC_ARM64_INS_ENDING = 6; +// ARM64 PAuth keys + + UC_ARM64_PAUTH_KEY_IA = 0; + UC_ARM64_PAUTH_KEY_IB = 1; + UC_ARM64_PAUTH_KEY_DA = 2; + UC_ARM64_PAUTH_KEY_DB = 3; + UC_ARM64_PAUTH_KEY_GA = 4; + UC_ARM64_PAUTH_KEY_ENDING = 5; + implementation end. \ No newline at end of file diff --git a/bindings/pascal/unicorn/UnicornConst.pas b/bindings/pascal/unicorn/UnicornConst.pas index 07e781c42c..1ac1a92f5d 100644 --- a/bindings/pascal/unicorn/UnicornConst.pas +++ b/bindings/pascal/unicorn/UnicornConst.pas @@ -150,6 +150,9 @@ interface UC_CTL_TLB_TYPE = 12; UC_CTL_TCG_BUFFER_SIZE = 13; UC_CTL_CONTEXT_MODE = 14; + UC_CTL_PAUTH_SIGN = 15; + UC_CTL_PAUTH_STRIP = 16; + UC_CTL_PAUTH_AUTH = 17; UC_CTL_CONTEXT_CPU = 1; UC_CTL_CONTEXT_MEMORY = 2; diff --git a/bindings/python/unicorn/arm64_const.py b/bindings/python/unicorn/arm64_const.py index 5a613623c1..71ae036a86 100644 --- a/bindings/python/unicorn/arm64_const.py +++ b/bindings/python/unicorn/arm64_const.py @@ -332,3 +332,12 @@ UC_ARM64_INS_SYSL = 4 UC_ARM64_INS_WFI = 5 UC_ARM64_INS_ENDING = 6 + +# ARM64 PAuth keys + +UC_ARM64_PAUTH_KEY_IA = 0 +UC_ARM64_PAUTH_KEY_IB = 1 +UC_ARM64_PAUTH_KEY_DA = 2 +UC_ARM64_PAUTH_KEY_DB = 3 +UC_ARM64_PAUTH_KEY_GA = 4 +UC_ARM64_PAUTH_KEY_ENDING = 5 diff --git a/bindings/python/unicorn/unicorn_const.py b/bindings/python/unicorn/unicorn_const.py index 82476d20a8..3f280c77d4 100644 --- a/bindings/python/unicorn/unicorn_const.py +++ b/bindings/python/unicorn/unicorn_const.py @@ -145,5 +145,8 @@ UC_CTL_TLB_TYPE = 12 UC_CTL_TCG_BUFFER_SIZE = 13 UC_CTL_CONTEXT_MODE = 14 +UC_CTL_PAUTH_SIGN = 15 +UC_CTL_PAUTH_STRIP = 16 +UC_CTL_PAUTH_AUTH = 17 UC_CTL_CONTEXT_CPU = 1 UC_CTL_CONTEXT_MEMORY = 2 diff --git a/bindings/python/unicorn/unicorn_py3/unicorn.py b/bindings/python/unicorn/unicorn_py3/unicorn.py index 1f770e74a7..c86d72a6c3 100644 --- a/bindings/python/unicorn/unicorn_py3/unicorn.py +++ b/bindings/python/unicorn/unicorn_py3/unicorn.py @@ -1474,6 +1474,58 @@ def ctl_set_tcg_buffer_size(self, size: int) -> None: (ctypes.c_uint32, size) ) + def ctl_pauth_sign(self, ptr: int, key: int, diversifier: int) -> int: + """Sign a pointer with a key and discriminator. + + Args: + ptr: pointer to sign + key: architecture-specific constant indicating the key to use + diversifier: discriminator to use + + Returns: the signed pointer + """ + + return self.__ctl_wr(uc.UC_CTL_PAUTH_SIGN, + (ctypes.c_uint64, ptr), + (ctypes.c_uint32, key), + (ctypes.c_uint64, diversifier), + (ctypes.c_uint64, None), + ) + + def ctl_pauth_strip(self, ptr: int, key: int) -> int: + """Strip the PAC from a possibly signed pointer. + + Args: + ptr: pointer to strip PAC from + key: architecture-specific constant indicating the key to use + + Returns: the stripped pointer + """ + + return self.__ctl_wr(uc.UC_CTL_PAUTH_STRIP, + (ctypes.c_uint64, ptr), + (ctypes.c_uint32, key), + (ctypes.c_uint64, None), + ) + + def ctl_pauth_auth(self, ptr: int, key: int, diversifier: int) -> bool: + """Authenticate a pointer with a key and discriminator. + + Args: + ptr: signed pointer to authenticate + key: architecture-specific constant indicating the key to use + diversifier: discriminator to use + + Returns: True if signature is valid, False othewise + """ + + return self.__ctl_wr(uc.UC_CTL_PAUTH_AUTH, + (ctypes.c_uint64, ptr), + (ctypes.c_uint32, key), + (ctypes.c_uint64, diversifier), + (ctypes.c_bool, None), + ) + class UcContext(RegStateManager): """Unicorn internal context. diff --git a/bindings/ruby/unicorn_gem/lib/unicorn_engine/arm64_const.rb b/bindings/ruby/unicorn_gem/lib/unicorn_engine/arm64_const.rb index 8aadaf9806..9bf1ccd7f4 100644 --- a/bindings/ruby/unicorn_gem/lib/unicorn_engine/arm64_const.rb +++ b/bindings/ruby/unicorn_gem/lib/unicorn_engine/arm64_const.rb @@ -334,4 +334,13 @@ module UnicornEngine UC_ARM64_INS_SYSL = 4 UC_ARM64_INS_WFI = 5 UC_ARM64_INS_ENDING = 6 + +# ARM64 PAuth keys + + UC_ARM64_PAUTH_KEY_IA = 0 + UC_ARM64_PAUTH_KEY_IB = 1 + UC_ARM64_PAUTH_KEY_DA = 2 + UC_ARM64_PAUTH_KEY_DB = 3 + UC_ARM64_PAUTH_KEY_GA = 4 + UC_ARM64_PAUTH_KEY_ENDING = 5 end \ No newline at end of file diff --git a/bindings/ruby/unicorn_gem/lib/unicorn_engine/unicorn_const.rb b/bindings/ruby/unicorn_gem/lib/unicorn_engine/unicorn_const.rb index d190233c06..f39650f081 100644 --- a/bindings/ruby/unicorn_gem/lib/unicorn_engine/unicorn_const.rb +++ b/bindings/ruby/unicorn_gem/lib/unicorn_engine/unicorn_const.rb @@ -147,6 +147,9 @@ module UnicornEngine UC_CTL_TLB_TYPE = 12 UC_CTL_TCG_BUFFER_SIZE = 13 UC_CTL_CONTEXT_MODE = 14 + UC_CTL_PAUTH_SIGN = 15 + UC_CTL_PAUTH_STRIP = 16 + UC_CTL_PAUTH_AUTH = 17 UC_CTL_CONTEXT_CPU = 1 UC_CTL_CONTEXT_MEMORY = 2 end \ No newline at end of file diff --git a/bindings/zig/unicorn/arm64_const.zig b/bindings/zig/unicorn/arm64_const.zig index 77ca1c8421..b44d63ccf3 100644 --- a/bindings/zig/unicorn/arm64_const.zig +++ b/bindings/zig/unicorn/arm64_const.zig @@ -335,4 +335,13 @@ pub const arm64Const = enum(c_int) { ARM64_INS_WFI = 5, ARM64_INS_ENDING = 6, +// ARM64 PAuth keys + + ARM64_PAUTH_KEY_IA = 0, + ARM64_PAUTH_KEY_IB = 1, + ARM64_PAUTH_KEY_DA = 2, + ARM64_PAUTH_KEY_DB = 3, + ARM64_PAUTH_KEY_GA = 4, + ARM64_PAUTH_KEY_ENDING = 5, + }; diff --git a/bindings/zig/unicorn/unicorn_const.zig b/bindings/zig/unicorn/unicorn_const.zig index 7a24f55d41..fc94d2e452 100644 --- a/bindings/zig/unicorn/unicorn_const.zig +++ b/bindings/zig/unicorn/unicorn_const.zig @@ -147,6 +147,9 @@ pub const unicornConst = enum(c_int) { CTL_TLB_TYPE = 12, CTL_TCG_BUFFER_SIZE = 13, CTL_CONTEXT_MODE = 14, + CTL_PAUTH_SIGN = 15, + CTL_PAUTH_STRIP = 16, + CTL_PAUTH_AUTH = 17, CTL_CONTEXT_CPU = 1, CTL_CONTEXT_MEMORY = 2, diff --git a/include/uc_priv.h b/include/uc_priv.h index c3132dccec..6a7cda8c58 100644 --- a/include/uc_priv.h +++ b/include/uc_priv.h @@ -157,6 +157,11 @@ typedef uc_tcg_flush_tlb uc_tb_flush_t; typedef uc_err (*uc_set_tlb_t)(struct uc_struct *uc, int mode); +// PAuth sign and strip +typedef uc_err (*uc_pauth_sign_t)(struct uc_struct *uc, uint64_t ptr, int key, uint64_t diversifier, uint64_t *signed_ptr); +typedef uc_err (*uc_pauth_strip_t)(struct uc_struct *uc, uint64_t ptr, int key, uint64_t *stripped_ptr); +typedef uc_err (*uc_pauth_auth_t)(struct uc_struct *uc, uint64_t ptr, int key, uint64_t diversifier, bool *valid); + struct hook { int type; // UC_HOOK_* int insn; // instruction for HOOK_INSN @@ -313,6 +318,9 @@ struct uc_struct { uc_tb_flush_t tb_flush; uc_add_inline_hook_t add_inline_hook; uc_del_inline_hook_t del_inline_hook; + uc_pauth_sign_t pauth_sign; + uc_pauth_strip_t pauth_strip; + uc_pauth_auth_t pauth_auth; uc_context_size_t context_size; uc_context_save_t context_save; diff --git a/include/unicorn/arm64.h b/include/unicorn/arm64.h index 7b6d25d56e..30c534d2c5 100644 --- a/include/unicorn/arm64.h +++ b/include/unicorn/arm64.h @@ -387,6 +387,16 @@ typedef enum uc_arm64_insn { UC_ARM64_INS_ENDING } uc_arm64_insn; +//> ARM64 PAuth keys +typedef enum uc_arm64_pauth_key { + UC_ARM64_PAUTH_KEY_IA = 0, + UC_ARM64_PAUTH_KEY_IB, + UC_ARM64_PAUTH_KEY_DA, + UC_ARM64_PAUTH_KEY_DB, + UC_ARM64_PAUTH_KEY_GA, + UC_ARM64_PAUTH_KEY_ENDING, +} uc_arm64_pauth_key; + #ifdef __cplusplus } #endif diff --git a/include/unicorn/unicorn.h b/include/unicorn/unicorn.h index b4ffc6bc75..02b259a308 100644 --- a/include/unicorn/unicorn.h +++ b/include/unicorn/unicorn.h @@ -605,6 +605,46 @@ typedef enum uc_control_type { // controle if context_save/restore should work with snapshots // Write: @args = (int) UC_CTL_CONTEXT_MODE, + + // Sign a pointer with a given architecture-specific key and a diversifier + // (also known as modifier, extra data, or discriminator). If the key is + // currently disabled, the operation returns the unsigned ptr. + // ABI-specific string hashing or address blending is up to the caller to + // implement. + // + // Pointer authentication needs to have been set up properly beforehand. + // Depending on architecture, current CPU state may determine pointer + // layout and other aspects of the operation not explicitly specified as + // parameters. + // + // Read/write: @args = (uint64_t ptr, int key, uint64_t diversifier, + // uint64_t *signed_ptr) + UC_CTL_PAUTH_SIGN, + + // Strip a possibly signed pointer of all PAC bits without authenticating, + // returning an unsigned pointer. + // + // Pointer authentication needs to have been set up properly beforehand. + // Depending on architecture, current CPU state may determine pointer + // layout and other aspects of the operation not explicitly specified as + // parameters. + // + // Read/write: @args = (uint64_t ptr, int key, uint64_t *stripped_ptr) + UC_CTL_PAUTH_STRIP, + + // Authenticate a signed pointer with a given architecture-specific key and + // diversifier (also known as modifier, extra data, or discriminator). + // ABI-specific string hashing or address blending is up to the caller to + // implement. + // + // Pointer authentication needs to have been set up properly beforehand. + // Depending on architecture, current CPU state may determine pointer + // layout and other aspects of the operation not explicitly specified as + // parameters. + // + // Read/write: @args = (uint64_t ptr, int key, uint64_t diversifier, + // bool *valid) + UC_CTL_PAUTH_AUTH, } uc_control_type; /* @@ -688,6 +728,12 @@ See sample_ctl.c for a detailed example. uc_ctl(uc, UC_CTL_WRITE(UC_CTL_TCG_BUFFER_SIZE, 1), (size)) #define uc_ctl_context_mode(uc, mode) \ uc_ctl(uc, UC_CTL_WRITE(UC_CTL_CONTEXT_MODE, 1), (mode)) +#define uc_ctl_pauth_sign(uc, ptr, key, diversifier, signed_ptr) \ + uc_ctl(uc, UC_CTL_READ_WRITE(UC_CTL_PAUTH_SIGN, 4), (ptr), (key), (diversifier), (signed_ptr)) +#define uc_ctl_pauth_strip(uc, ptr, key, stripped_ptr) \ + uc_ctl(uc, UC_CTL_READ_WRITE(UC_CTL_PAUTH_STRIP, 3), (ptr), (key), (stripped_ptr)) +#define uc_ctl_pauth_auth(uc, ptr, key, diversifier, valid) \ + uc_ctl(uc, UC_CTL_READ_WRITE(UC_CTL_PAUTH_AUTH, 4), (ptr), (key), (diversifier), (valid)) // Opaque storage for CPU context, used with uc_context_*() struct uc_context; diff --git a/qemu/target/arm/pauth_helper.c b/qemu/target/arm/pauth_helper.c index b909630317..51116e2363 100644 --- a/qemu/target/arm/pauth_helper.c +++ b/qemu/target/arm/pauth_helper.c @@ -492,3 +492,33 @@ uint64_t HELPER(xpacd)(CPUARMState *env, uint64_t a) { return pauth_strip(env, a, true); } + +uint64_t _uc_pauth_sign(CPUARMState *env, uint64_t ptr, uint64_t diversifier, uint32_t sctlr_bit, ARMPACKey *key, bool data) +{ + int el = arm_current_el(env); + if (!pauth_key_enabled(env, el, sctlr_bit)) { + return ptr; + } else { + return pauth_addpac(env, ptr, diversifier, key, data); + } +} + +uint64_t _uc_pauth_sign_ga(CPUARMState *env, uint64_t ptr, uint64_t diversifier) +{ + return pauth_computepac(ptr, diversifier, env->keys.apga) & 0xffffffff00000000ull; +} + +uint64_t _uc_pauth_strip(CPUARMState *env, uint64_t ptr, bool data) +{ + return pauth_strip(env, ptr, data); +} + +bool _uc_pauth_auth(CPUARMState *env, uint64_t ptr, uint64_t diversifier, uint32_t sctlr_bit, ARMPACKey *key, bool data) +{ + int el = arm_current_el(env); + if (!pauth_key_enabled(env, el, sctlr_bit)) { + return false; + } + uint64_t maybe_corrupted_ptr = pauth_auth(env, ptr, diversifier, key, data, 0); + return maybe_corrupted_ptr == pauth_strip(env, ptr, data); +} diff --git a/qemu/target/arm/unicorn_aarch64.c b/qemu/target/arm/unicorn_aarch64.c index 987e1180dd..82ea40c99b 100644 --- a/qemu/target/arm/unicorn_aarch64.c +++ b/qemu/target/arm/unicorn_aarch64.c @@ -450,6 +450,78 @@ static bool arm64_insn_hook_validate(uint32_t insn_enum) return true; } +uint64_t _uc_pauth_sign(CPUARMState *env, uint64_t ptr, uint64_t diversifier, uint32_t sctlr_bit, ARMPACKey *key, bool data); +uint64_t _uc_pauth_sign_ga(CPUARMState *env, uint64_t ptr, uint64_t diversifier); +uint64_t _uc_pauth_strip(CPUARMState *env, uint64_t ptr, bool data); +bool _uc_pauth_auth(CPUARMState *env, uint64_t ptr, uint64_t diversifier, uint32_t sctlr_bit, ARMPACKey *key, bool data); + +static uc_err arm64_pauth_sign(struct uc_struct *uc, uint64_t ptr, int key, uint64_t diversifier, uint64_t *signed_ptr) +{ + CPUArchState *env = uc->cpu->env_ptr; + switch (key) { + case UC_ARM64_PAUTH_KEY_IA: + *signed_ptr = _uc_pauth_sign(env, ptr, diversifier, SCTLR_EnIA, &env->keys.apia, false); + break; + case UC_ARM64_PAUTH_KEY_IB: + *signed_ptr = _uc_pauth_sign(env, ptr, diversifier, SCTLR_EnIB, &env->keys.apib, false); + break; + case UC_ARM64_PAUTH_KEY_DA: + *signed_ptr = _uc_pauth_sign(env, ptr, diversifier, SCTLR_EnDA, &env->keys.apda, true); + break; + case UC_ARM64_PAUTH_KEY_DB: + *signed_ptr = _uc_pauth_sign(env, ptr, diversifier, SCTLR_EnDB, &env->keys.apdb, true); + break; + case UC_ARM64_PAUTH_KEY_GA: + *signed_ptr = _uc_pauth_sign_ga(env, ptr, diversifier); + break; + default: + return UC_ERR_ARG; + } + return UC_ERR_OK; +} + +static uc_err arm64_pauth_strip(struct uc_struct *uc, uint64_t ptr, int key, uint64_t *stripped_ptr) +{ + CPUArchState *env = uc->cpu->env_ptr; + switch (key) { + case UC_ARM64_PAUTH_KEY_IA: + case UC_ARM64_PAUTH_KEY_IB: + *stripped_ptr = _uc_pauth_strip(env, ptr, false); + break; + case UC_ARM64_PAUTH_KEY_DA: + case UC_ARM64_PAUTH_KEY_DB: + *stripped_ptr = _uc_pauth_strip(env, ptr, true); + break; + case UC_ARM64_PAUTH_KEY_GA: + default: + return UC_ERR_ARG; + } + return UC_ERR_OK; +} + +static uc_err arm64_pauth_auth(struct uc_struct *uc, uint64_t ptr, int key, uint64_t diversifier, bool *valid) +{ + CPUArchState *env = uc->cpu->env_ptr; + switch (key) { + case UC_ARM64_PAUTH_KEY_IA: + *valid = _uc_pauth_auth(env, ptr, diversifier, SCTLR_EnIA, &env->keys.apia, false); + break; + case UC_ARM64_PAUTH_KEY_IB: + *valid = _uc_pauth_auth(env, ptr, diversifier, SCTLR_EnIB, &env->keys.apib, false); + break; + case UC_ARM64_PAUTH_KEY_DA: + *valid = _uc_pauth_auth(env, ptr, diversifier, SCTLR_EnDA, &env->keys.apda, true); + break; + case UC_ARM64_PAUTH_KEY_DB: + *valid = _uc_pauth_auth(env, ptr, diversifier, SCTLR_EnDB, &env->keys.apdb, true); + break; + case UC_ARM64_PAUTH_KEY_GA: + default: + return UC_ERR_ARG; + } + return UC_ERR_OK; +} + DEFAULT_VISIBILITY void uc_init(struct uc_struct *uc) { @@ -462,5 +534,8 @@ void uc_init(struct uc_struct *uc) uc->cpus_init = arm64_cpus_init; uc->insn_hook_validate = arm64_insn_hook_validate; uc->cpu_context_size = offsetof(CPUARMState, cpu_watchpoint); + uc->pauth_sign = arm64_pauth_sign; + uc->pauth_strip = arm64_pauth_strip; + uc->pauth_auth = arm64_pauth_auth; uc_common_init(uc); } diff --git a/tests/unit/test_arm64.c b/tests/unit/test_arm64.c index f490c91637..62b4c7cd55 100644 --- a/tests/unit/test_arm64.c +++ b/tests/unit/test_arm64.c @@ -695,6 +695,143 @@ static void test_arm64_pc_guarantee(void) OK(uc_close(uc)); } +static uint64_t test_arm64_pauth_cp_reg_read(uc_engine *uc, const uint32_t cpregid[5]) +{ + uc_arm64_cp_reg reg = { + .op0 = cpregid[0], + .op1 = cpregid[1], + .crn = cpregid[2], + .crm = cpregid[3], + .op2 = cpregid[4], + .val = 0, + }; + OK(uc_reg_read(uc, UC_ARM64_REG_CP_REG, ®)); + return reg.val; +} + +static void test_arm64_pauth_cp_reg_write(uc_engine *uc, const uint32_t cpregid[5], uint64_t value) +{ + uc_arm64_cp_reg reg = { + .op0 = cpregid[0], + .op1 = cpregid[1], + .crn = cpregid[2], + .crm = cpregid[3], + .op2 = cpregid[4], + .val = value, + }; + OK(uc_reg_write(uc, UC_ARM64_REG_CP_REG, ®)); +} + +static bool test_arm64_pauth_cp_reg_update(uc_engine *uc, const uint32_t cpregid[5], uint64_t clearmask, uint64_t setmask) +{ + uc_arm64_cp_reg reg = { + .op0 = cpregid[0], + .op1 = cpregid[1], + .crn = cpregid[2], + .crm = cpregid[3], + .op2 = cpregid[4], + .val = 0, + }; + OK(uc_reg_read(uc, UC_ARM64_REG_CP_REG, ®)); + reg.val &= ~clearmask; + reg.val |= setmask; + OK(uc_reg_write(uc, UC_ARM64_REG_CP_REG, ®)); + OK(uc_reg_read(uc, UC_ARM64_REG_CP_REG, ®)); + return (((reg.val & setmask) == setmask) && ((reg.val & clearmask) == 0)); +} + +static void test_arm64_pauth(void) +{ + uc_engine *uc; + const char code_paciza_x1[] = "\xe1\x23\xc1\xda"; // paciza x1 + + // We expect a PAC added somewhere in pac_mask bits in order to make the + // test agnostic of TxSZ and TBI. + + const uint64_t some_unsigned_pointer = 0x0000aaaabbbbccccULL; + const uint64_t pac_mask = 0xffff000000000000ULL & ~(1ULL << 55); + static_assert((some_unsigned_pointer & pac_mask) == 0, + "unsigned pointer must not have any PAC bits set"); + + OK(uc_open(UC_ARCH_ARM64, UC_MODE_ARM, &uc)); + OK(uc_ctl_set_cpu_model(uc, UC_CPU_ARM64_MAX)); + + // Check the CPU actually supports any form of PAuth, i.e. any APA or API + // bits are set. At the time of writing, UC_CPU_ARM64_A72 does not support + // PAuth, but UC_CPU_ARM64_MAX does. This is not required for the test, + // but helps with diagnostics when the selected CPU does not support PAuth. + + const uint32_t ID_AA64ISAR1_EL1[5] = { 0b11, 0b000, 0b0000, 0b0110, 0b001 }; + const uint64_t ID_AA64ISAR1_EL1_APA_API_MASK = (0b1111ULL << 4) | (0b1111ULL << 8); + uint64_t ID_AA64ISAR1_EL1_bits = test_arm64_pauth_cp_reg_read(uc, ID_AA64ISAR1_EL1); + TEST_CHECK((ID_AA64ISAR1_EL1_bits & ID_AA64ISAR1_EL1_APA_API_MASK) != 0); + + // Minimal PAuth setup, enabling only IA and IB. The test is agnostic to + // VA size and MTE config, so don't bother touching TCR_EL1 for now. + + const uint32_t SCR_EL3[5] = { 0b11, 0b110, 0b0001, 0b0001, 0b000 }; + const uint64_t SCR_EL3_NS_RW_API = 1ULL | (1ULL << 10) | (1ULL << 17); + TEST_CHECK(test_arm64_pauth_cp_reg_update(uc, SCR_EL3, 0, SCR_EL3_NS_RW_API)); + + const uint32_t HCR_EL2[5] = { 0b11, 0b100, 0b0001, 0b0001, 0b000 }; + const uint64_t HCR_EL2_API = 1ULL << 41; + TEST_CHECK(test_arm64_pauth_cp_reg_update(uc, HCR_EL2, 0, HCR_EL2_API)); + + const uint32_t SCTLR_EL1[5] = { 0b11, 0b000, 0b0001, 0b0000, 0b000 }; + const uint64_t SCTLR_EL1_EnIA_EnIB = (1ULL << 31) | (1ULL << 30); + TEST_CHECK(test_arm64_pauth_cp_reg_update(uc, SCTLR_EL1, 0, SCTLR_EL1_EnIA_EnIB)); + + const uint32_t APIAKeyLo_EL1[5] = { 0b11, 0b000, 0b0010, 0b0001, 0b000 }; + const uint32_t APIAKeyHi_EL1[5] = { 0b11, 0b000, 0b0010, 0b0001, 0b001 }; + const uint32_t APIBKeyLo_EL1[5] = { 0b11, 0b000, 0b0010, 0b0001, 0b010 }; + const uint32_t APIBKeyHi_EL1[5] = { 0b11, 0b000, 0b0010, 0b0001, 0b011 }; + const uint32_t APDAKeyLo_EL1[5] = { 0b11, 0b000, 0b0010, 0b0010, 0b000 }; + const uint32_t APDAKeyHi_EL1[5] = { 0b11, 0b000, 0b0010, 0b0010, 0b001 }; + test_arm64_pauth_cp_reg_write(uc, APIAKeyLo_EL1, 0xAAAAAAAAAAAAAAAA); + test_arm64_pauth_cp_reg_write(uc, APIAKeyHi_EL1, 0xBBBBBBBBBBBBBBBB); + test_arm64_pauth_cp_reg_write(uc, APIBKeyLo_EL1, 0xCCCCCCCCCCCCCCCC); // != IA + test_arm64_pauth_cp_reg_write(uc, APIBKeyHi_EL1, 0xDDDDDDDDDDDDDDDD); + test_arm64_pauth_cp_reg_write(uc, APDAKeyLo_EL1, 0xAAAAAAAAAAAAAAAA); // == IA + test_arm64_pauth_cp_reg_write(uc, APDAKeyHi_EL1, 0xBBBBBBBBBBBBBBBB); + + // Verify that paciza and uc_ctl_pauth_sign() result in the same signed + // pointer. + + uint64_t x1 = some_unsigned_pointer; + OK(uc_mem_map(uc, code_start, code_len, UC_PROT_ALL)); + OK(uc_mem_write(uc, code_start, code_paciza_x1, sizeof(code_paciza_x1))); + OK(uc_reg_write(uc, UC_ARM64_REG_X1, &x1)); + OK(uc_emu_start(uc, code_start, code_start + sizeof(code_paciza_x1) - 1, 0, 0)); + OK(uc_reg_read(uc, UC_ARM64_REG_X1, &x1)); + TEST_CHECK(x1 != some_unsigned_pointer); + TEST_CHECK((x1 & pac_mask) != 0); + + uint64_t ptr = some_unsigned_pointer; + OK(uc_ctl_pauth_sign(uc, ptr, UC_ARM64_PAUTH_KEY_IA, 0, &ptr)); + TEST_CHECK(ptr == x1); + + // Verify that stripping the PAC results in the original pointer. + + OK(uc_ctl_pauth_strip(uc, ptr, UC_ARM64_PAUTH_KEY_IA, &ptr)); + TEST_CHECK(ptr == some_unsigned_pointer); + + // Verify that authenticating works as expected. + + bool valid = false; + OK(uc_ctl_pauth_auth(uc, ptr, UC_ARM64_PAUTH_KEY_IA, 0, &valid)); + TEST_CHECK(!valid); // unsigned pointer + OK(uc_ctl_pauth_auth(uc, x1, UC_ARM64_PAUTH_KEY_IA, 0, &valid)); + TEST_CHECK(valid); // signed pointer + OK(uc_ctl_pauth_auth(uc, x1, UC_ARM64_PAUTH_KEY_IA, 1337, &valid)); + TEST_CHECK(!valid); // wrong diversifier + OK(uc_ctl_pauth_auth(uc, x1, UC_ARM64_PAUTH_KEY_IB, 0, &valid)); + TEST_CHECK(!valid); // wrong but enabled key + OK(uc_ctl_pauth_auth(uc, x1, UC_ARM64_PAUTH_KEY_DA, 0, &valid)); + TEST_CHECK(!valid); // disabled but same value key + + OK(uc_close(uc)); +} + TEST_LIST = {{"test_arm64_until", test_arm64_until}, {"test_arm64_code_patching", test_arm64_code_patching}, {"test_arm64_code_patching_count", test_arm64_code_patching_count}, @@ -714,4 +851,5 @@ TEST_LIST = {{"test_arm64_until", test_arm64_until}, {"test_arm64_mem_prot_regress", test_arm64_mem_prot_regress}, {"test_arm64_mem_hook_read_write", test_arm64_mem_hook_read_write}, {"test_arm64_pc_guarantee", test_arm64_pc_guarantee}, + {"test_arm64_pauth", test_arm64_pauth}, {NULL, NULL}}; diff --git a/uc.c b/uc.c index 75b89a0176..43f720409c 100644 --- a/uc.c +++ b/uc.c @@ -2977,6 +2977,71 @@ uc_err uc_ctl(uc_engine *uc, uc_control_type control, ...) restore_jit_state(uc); break; + case UC_CTL_PAUTH_SIGN: { + + UC_INIT(uc); + + if (rw == UC_CTL_IO_READ_WRITE) { + uint64_t ptr = va_arg(args, uint64_t); + int key = va_arg(args, int); + uint64_t diversifier = va_arg(args, uint64_t); + uint64_t *signed_ptr = va_arg(args, uint64_t *); + if (uc->pauth_sign != NULL) { + err = uc->pauth_sign(uc, ptr, key, diversifier, signed_ptr); + } else { + err = UC_ERR_ARG; + } + } else { + err = UC_ERR_ARG; + } + + restore_jit_state(uc); + break; + } + + case UC_CTL_PAUTH_STRIP: { + + UC_INIT(uc); + + if (rw == UC_CTL_IO_READ_WRITE) { + uint64_t ptr = va_arg(args, uint64_t); + int key = va_arg(args, int); + uint64_t *stripped_ptr = va_arg(args, uint64_t *); + if (uc->pauth_strip != NULL) { + err = uc->pauth_strip(uc, ptr, key, stripped_ptr); + } else { + err = UC_ERR_ARG; + } + } else { + err = UC_ERR_ARG; + } + + restore_jit_state(uc); + break; + } + + case UC_CTL_PAUTH_AUTH: { + + UC_INIT(uc); + + if (rw == UC_CTL_IO_READ_WRITE) { + uint64_t ptr = va_arg(args, uint64_t); + int key = va_arg(args, int); + uint64_t diversifier = va_arg(args, uint64_t); + bool *valid = va_arg(args, bool *); + if (uc->pauth_auth != NULL) { + err = uc->pauth_auth(uc, ptr, key, diversifier, valid); + } else { + err = UC_ERR_ARG; + } + } else { + err = UC_ERR_ARG; + } + + restore_jit_state(uc); + break; + } + default: err = UC_ERR_ARG; break; From 1b98774827dfe81df210ab1b0eca4beb02fe9601 Mon Sep 17 00:00:00 2001 From: Daniel Roethlisberger Date: Mon, 17 Nov 2025 23:38:39 +0100 Subject: [PATCH 4/8] Fifth PR, add myself to CREDITS.TXT --- CREDITS.TXT | 1 + 1 file changed, 1 insertion(+) diff --git a/CREDITS.TXT b/CREDITS.TXT index ee443858b7..11cb28e560 100644 --- a/CREDITS.TXT +++ b/CREDITS.TXT @@ -81,3 +81,4 @@ Ziqiao Kong (lazymio): uc_context_free() API and various bug fix & improvement. Sven Almgren (blindmatrix): bug fix Chenxu Wu (kabeor): Documentation Philipp Takacs: virtual tlb, memory snapshots +Daniel Roethlisberger (droe): ARM64 fixes & improvements From ce4921d41bfccaa9475bd580164376359f0285c8 Mon Sep 17 00:00:00 2001 From: Daniel Roethlisberger Date: Mon, 17 Nov 2025 23:57:59 +0100 Subject: [PATCH 5/8] Remove static_assert for better portability --- tests/unit/test_arm64.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/unit/test_arm64.c b/tests/unit/test_arm64.c index 62b4c7cd55..f9c12fc9a1 100644 --- a/tests/unit/test_arm64.c +++ b/tests/unit/test_arm64.c @@ -750,8 +750,6 @@ static void test_arm64_pauth(void) const uint64_t some_unsigned_pointer = 0x0000aaaabbbbccccULL; const uint64_t pac_mask = 0xffff000000000000ULL & ~(1ULL << 55); - static_assert((some_unsigned_pointer & pac_mask) == 0, - "unsigned pointer must not have any PAC bits set"); OK(uc_open(UC_ARCH_ARM64, UC_MODE_ARM, &uc)); OK(uc_ctl_set_cpu_model(uc, UC_CPU_ARM64_MAX)); From 6609dd866ea2dcc60988d7e605c4886dc6c5e90d Mon Sep 17 00:00:00 2001 From: Daniel Roethlisberger Date: Tue, 18 Nov 2025 00:37:16 +0100 Subject: [PATCH 6/8] Make test_arm64 slightly more robust --- tests/unit/test_arm64.c | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/tests/unit/test_arm64.c b/tests/unit/test_arm64.c index f9c12fc9a1..09b996982b 100644 --- a/tests/unit/test_arm64.c +++ b/tests/unit/test_arm64.c @@ -785,12 +785,12 @@ static void test_arm64_pauth(void) const uint32_t APIBKeyHi_EL1[5] = { 0b11, 0b000, 0b0010, 0b0001, 0b011 }; const uint32_t APDAKeyLo_EL1[5] = { 0b11, 0b000, 0b0010, 0b0010, 0b000 }; const uint32_t APDAKeyHi_EL1[5] = { 0b11, 0b000, 0b0010, 0b0010, 0b001 }; - test_arm64_pauth_cp_reg_write(uc, APIAKeyLo_EL1, 0xAAAAAAAAAAAAAAAA); - test_arm64_pauth_cp_reg_write(uc, APIAKeyHi_EL1, 0xBBBBBBBBBBBBBBBB); - test_arm64_pauth_cp_reg_write(uc, APIBKeyLo_EL1, 0xCCCCCCCCCCCCCCCC); // != IA - test_arm64_pauth_cp_reg_write(uc, APIBKeyHi_EL1, 0xDDDDDDDDDDDDDDDD); - test_arm64_pauth_cp_reg_write(uc, APDAKeyLo_EL1, 0xAAAAAAAAAAAAAAAA); // == IA - test_arm64_pauth_cp_reg_write(uc, APDAKeyHi_EL1, 0xBBBBBBBBBBBBBBBB); + test_arm64_pauth_cp_reg_write(uc, APIAKeyLo_EL1, 0xAAAAAAAAAAAAAAAAULL); + test_arm64_pauth_cp_reg_write(uc, APIAKeyHi_EL1, 0xBBBBBBBBBBBBBBBBULL); + test_arm64_pauth_cp_reg_write(uc, APIBKeyLo_EL1, 0xCCCCCCCCCCCCCCCCULL); // != IA + test_arm64_pauth_cp_reg_write(uc, APIBKeyHi_EL1, 0xDDDDDDDDDDDDDDDDULL); + test_arm64_pauth_cp_reg_write(uc, APDAKeyLo_EL1, 0xAAAAAAAAAAAAAAAAULL); // == IA + test_arm64_pauth_cp_reg_write(uc, APDAKeyHi_EL1, 0xBBBBBBBBBBBBBBBBULL); // Verify that paciza and uc_ctl_pauth_sign() result in the same signed // pointer. @@ -815,15 +815,19 @@ static void test_arm64_pauth(void) // Verify that authenticating works as expected. - bool valid = false; + bool valid = true; OK(uc_ctl_pauth_auth(uc, ptr, UC_ARM64_PAUTH_KEY_IA, 0, &valid)); TEST_CHECK(!valid); // unsigned pointer + valid = false; OK(uc_ctl_pauth_auth(uc, x1, UC_ARM64_PAUTH_KEY_IA, 0, &valid)); TEST_CHECK(valid); // signed pointer + valid = true; OK(uc_ctl_pauth_auth(uc, x1, UC_ARM64_PAUTH_KEY_IA, 1337, &valid)); TEST_CHECK(!valid); // wrong diversifier + valid = true; OK(uc_ctl_pauth_auth(uc, x1, UC_ARM64_PAUTH_KEY_IB, 0, &valid)); TEST_CHECK(!valid); // wrong but enabled key + valid = true; OK(uc_ctl_pauth_auth(uc, x1, UC_ARM64_PAUTH_KEY_DA, 0, &valid)); TEST_CHECK(!valid); // disabled but same value key From f13dd046a13bb6f277dce2ffadc95acf1bae4e2d Mon Sep 17 00:00:00 2001 From: Daniel Roethlisberger Date: Mon, 24 Nov 2025 21:59:57 +0100 Subject: [PATCH 7/8] Add test for vanilla PAuth without helpers and refactor tests --- tests/unit/test_arm64.c | 164 ++++++++++++++++++++++++++++++++-------- 1 file changed, 131 insertions(+), 33 deletions(-) diff --git a/tests/unit/test_arm64.c b/tests/unit/test_arm64.c index 09b996982b..e194121ca8 100644 --- a/tests/unit/test_arm64.c +++ b/tests/unit/test_arm64.c @@ -740,31 +740,23 @@ static bool test_arm64_pauth_cp_reg_update(uc_engine *uc, const uint32_t cpregid return (((reg.val & setmask) == setmask) && ((reg.val & clearmask) == 0)); } -static void test_arm64_pauth(void) +static void test_arm64_pauth_check_cpu_feat(uc_engine *uc) { - uc_engine *uc; - const char code_paciza_x1[] = "\xe1\x23\xc1\xda"; // paciza x1 - - // We expect a PAC added somewhere in pac_mask bits in order to make the - // test agnostic of TxSZ and TBI. - - const uint64_t some_unsigned_pointer = 0x0000aaaabbbbccccULL; - const uint64_t pac_mask = 0xffff000000000000ULL & ~(1ULL << 55); - - OK(uc_open(UC_ARCH_ARM64, UC_MODE_ARM, &uc)); - OK(uc_ctl_set_cpu_model(uc, UC_CPU_ARM64_MAX)); - // Check the CPU actually supports any form of PAuth, i.e. any APA or API // bits are set. At the time of writing, UC_CPU_ARM64_A72 does not support - // PAuth, but UC_CPU_ARM64_MAX does. This is not required for the test, - // but helps with diagnostics when the selected CPU does not support PAuth. + // PAuth, but UC_CPU_ARM64_MAX does. This check is not required for any of + // the PAuth tests to work, but helps with diagnostics when the selected + // CPU does not support PAuth. const uint32_t ID_AA64ISAR1_EL1[5] = { 0b11, 0b000, 0b0000, 0b0110, 0b001 }; const uint64_t ID_AA64ISAR1_EL1_APA_API_MASK = (0b1111ULL << 4) | (0b1111ULL << 8); uint64_t ID_AA64ISAR1_EL1_bits = test_arm64_pauth_cp_reg_read(uc, ID_AA64ISAR1_EL1); TEST_CHECK((ID_AA64ISAR1_EL1_bits & ID_AA64ISAR1_EL1_APA_API_MASK) != 0); +} - // Minimal PAuth setup, enabling only IA and IB. The test is agnostic to +static void test_arm64_pauth_setup(uc_engine *uc) +{ + // Minimal PAuth setup, enabling only IA and IB. The tests are agnostic to // VA size and MTE config, so don't bother touching TCR_EL1 for now. const uint32_t SCR_EL3[5] = { 0b11, 0b110, 0b0001, 0b0001, 0b000 }; @@ -791,44 +783,149 @@ static void test_arm64_pauth(void) test_arm64_pauth_cp_reg_write(uc, APIBKeyHi_EL1, 0xDDDDDDDDDDDDDDDDULL); test_arm64_pauth_cp_reg_write(uc, APDAKeyLo_EL1, 0xAAAAAAAAAAAAAAAAULL); // == IA test_arm64_pauth_cp_reg_write(uc, APDAKeyHi_EL1, 0xBBBBBBBBBBBBBBBBULL); +} + +static void test_arm64_pauth_vanilla(void) { + // PAuth test w/o using any uc_ctl interfaces, just PAuth on the CPU. + + uc_engine *uc; + const char code_paciza_x1[] = "\xe1\x23\xc1\xda"; // paciza x1 + const char code_autiza_x1[] = "\xe1\x33\xc1\xda"; // autiza x1 + const char code_autizb_x1[] = "\xe1\x37\xc1\xda"; // autizb x1 + const char code_autdza_x1[] = "\xe1\x3b\xc1\xda"; // autdza x1 + const char code_autia_x1_x0[] = "\x01\x10\xc1\xda"; // autia x1, x0 + const char code_xpaci_x1[] = "\xe1\x43\xc1\xda"; // xpaci x1 + + // We expect a PAC added somewhere in pac_mask bits in order to make the + // test agnostic of TxSZ and TBI. + + const uint64_t some_unsigned_pointer = 0x0000aaaabbbbccccULL; + const uint64_t pac_mask = 0xffff000000000000ULL & ~(1ULL << 55); + + OK(uc_open(UC_ARCH_ARM64, UC_MODE_ARM, &uc)); + OK(uc_ctl_set_cpu_model(uc, UC_CPU_ARM64_MAX)); + OK(uc_mem_map(uc, code_start, code_len, UC_PROT_ALL)); + + test_arm64_pauth_check_cpu_feat(uc); + test_arm64_pauth_setup(uc); + + // Verify that paciza signs a pointer. + + OK(uc_reg_write(uc, UC_ARM64_REG_X1, &some_unsigned_pointer)); + OK(uc_mem_write(uc, code_start, code_paciza_x1, sizeof(code_paciza_x1))); + OK(uc_emu_start(uc, code_start, code_start + sizeof(code_paciza_x1) - 1, 0, 0)); + uint64_t signed_pointer = 0; + OK(uc_reg_read(uc, UC_ARM64_REG_X1, &signed_pointer)); + TEST_CHECK(signed_pointer != some_unsigned_pointer); + TEST_CHECK((signed_pointer & pac_mask) != 0); + + // Verify that xpaci results in original pointer. + + OK(uc_mem_write(uc, code_start, code_xpaci_x1, sizeof(code_xpaci_x1))); + OK(uc_emu_start(uc, code_start, code_start + sizeof(code_xpaci_x1) - 1, 0, 0)); + uint64_t stripped_pointer = 0; + OK(uc_reg_read(uc, UC_ARM64_REG_X1, &stripped_pointer)); + TEST_CHECK(stripped_pointer == some_unsigned_pointer); + + // Verify autia behaviour. + + OK(uc_reg_write(uc, UC_ARM64_REG_X1, &some_unsigned_pointer)); + OK(uc_mem_write(uc, code_start, code_autiza_x1, sizeof(code_autiza_x1))); + OK(uc_emu_start(uc, code_start, code_start + sizeof(code_autiza_x1) - 1, 0, 0)); + uint64_t authenticated_pointer = 0; + OK(uc_reg_read(uc, UC_ARM64_REG_X1, &authenticated_pointer)); + TEST_CHECK((authenticated_pointer & pac_mask) != 0); // unsigned pointer is invalid + + OK(uc_reg_write(uc, UC_ARM64_REG_X1, &signed_pointer)); + OK(uc_mem_write(uc, code_start, code_autiza_x1, sizeof(code_autiza_x1))); + OK(uc_emu_start(uc, code_start, code_start + sizeof(code_autiza_x1) - 1, 0, 0)); + authenticated_pointer = 0; + OK(uc_reg_read(uc, UC_ARM64_REG_X1, &authenticated_pointer)); + TEST_CHECK((authenticated_pointer & pac_mask) == 0); // signed pointer is valid + + uint64_t diversifier = 1337; + OK(uc_reg_write(uc, UC_ARM64_REG_X1, &signed_pointer)); + OK(uc_reg_write(uc, UC_ARM64_REG_X0, &diversifier)); + OK(uc_mem_write(uc, code_start, code_autia_x1_x0, sizeof(code_autia_x1_x0))); + OK(uc_emu_start(uc, code_start, code_start + sizeof(code_autia_x1_x0) - 1, 0, 0)); + authenticated_pointer = 0; + OK(uc_reg_read(uc, UC_ARM64_REG_X1, &authenticated_pointer)); + TEST_CHECK((authenticated_pointer & pac_mask) != 0); // wrong diversifier is invalid + + OK(uc_reg_write(uc, UC_ARM64_REG_X1, &signed_pointer)); + OK(uc_mem_write(uc, code_start, code_autizb_x1, sizeof(code_autizb_x1))); + OK(uc_emu_start(uc, code_start, code_start + sizeof(code_autizb_x1) - 1, 0, 0)); + authenticated_pointer = 0; + OK(uc_reg_read(uc, UC_ARM64_REG_X1, &authenticated_pointer)); + TEST_CHECK((authenticated_pointer & pac_mask) != 0); // wrong but enabled key is invalid + + OK(uc_reg_write(uc, UC_ARM64_REG_X1, &signed_pointer)); + OK(uc_mem_write(uc, code_start, code_autdza_x1, sizeof(code_autdza_x1))); + OK(uc_emu_start(uc, code_start, code_start + sizeof(code_autdza_x1) - 1, 0, 0)); + authenticated_pointer = 0; + OK(uc_reg_read(uc, UC_ARM64_REG_X1, &authenticated_pointer)); + TEST_CHECK((authenticated_pointer & pac_mask) != 0); // disabled but same value key is invalid + + OK(uc_close(uc)); +} + +static void test_arm64_pauth_ctl(void) +{ + // PAuth test for the uc_ctl interfaces. + + uc_engine *uc; + const char code_paciza_x1[] = "\xe1\x23\xc1\xda"; // paciza x1 + + // We expect a PAC added somewhere in pac_mask bits in order to make the + // test agnostic of TxSZ and TBI. + + const uint64_t some_unsigned_pointer = 0x0000aaaabbbbccccULL; + const uint64_t pac_mask = 0xffff000000000000ULL & ~(1ULL << 55); + + OK(uc_open(UC_ARCH_ARM64, UC_MODE_ARM, &uc)); + OK(uc_ctl_set_cpu_model(uc, UC_CPU_ARM64_MAX)); + OK(uc_mem_map(uc, code_start, code_len, UC_PROT_ALL)); + + test_arm64_pauth_check_cpu_feat(uc); + test_arm64_pauth_setup(uc); // Verify that paciza and uc_ctl_pauth_sign() result in the same signed // pointer. - uint64_t x1 = some_unsigned_pointer; - OK(uc_mem_map(uc, code_start, code_len, UC_PROT_ALL)); OK(uc_mem_write(uc, code_start, code_paciza_x1, sizeof(code_paciza_x1))); - OK(uc_reg_write(uc, UC_ARM64_REG_X1, &x1)); + OK(uc_reg_write(uc, UC_ARM64_REG_X1, &some_unsigned_pointer)); OK(uc_emu_start(uc, code_start, code_start + sizeof(code_paciza_x1) - 1, 0, 0)); - OK(uc_reg_read(uc, UC_ARM64_REG_X1, &x1)); - TEST_CHECK(x1 != some_unsigned_pointer); - TEST_CHECK((x1 & pac_mask) != 0); + uint64_t signed_pointer_paciza = 0; + OK(uc_reg_read(uc, UC_ARM64_REG_X1, &signed_pointer_paciza)); + TEST_CHECK(signed_pointer_paciza != some_unsigned_pointer); + TEST_CHECK((signed_pointer_paciza & pac_mask) != 0); - uint64_t ptr = some_unsigned_pointer; - OK(uc_ctl_pauth_sign(uc, ptr, UC_ARM64_PAUTH_KEY_IA, 0, &ptr)); - TEST_CHECK(ptr == x1); + uint64_t signed_pointer = 0; + OK(uc_ctl_pauth_sign(uc, some_unsigned_pointer, UC_ARM64_PAUTH_KEY_IA, 0, &signed_pointer)); + TEST_CHECK(signed_pointer == signed_pointer_paciza); // Verify that stripping the PAC results in the original pointer. - OK(uc_ctl_pauth_strip(uc, ptr, UC_ARM64_PAUTH_KEY_IA, &ptr)); - TEST_CHECK(ptr == some_unsigned_pointer); + uint64_t stripped_pointer = 0; + OK(uc_ctl_pauth_strip(uc, signed_pointer, UC_ARM64_PAUTH_KEY_IA, &stripped_pointer)); + TEST_CHECK(stripped_pointer == some_unsigned_pointer); // Verify that authenticating works as expected. bool valid = true; - OK(uc_ctl_pauth_auth(uc, ptr, UC_ARM64_PAUTH_KEY_IA, 0, &valid)); + OK(uc_ctl_pauth_auth(uc, some_unsigned_pointer, UC_ARM64_PAUTH_KEY_IA, 0, &valid)); TEST_CHECK(!valid); // unsigned pointer valid = false; - OK(uc_ctl_pauth_auth(uc, x1, UC_ARM64_PAUTH_KEY_IA, 0, &valid)); + OK(uc_ctl_pauth_auth(uc, signed_pointer, UC_ARM64_PAUTH_KEY_IA, 0, &valid)); TEST_CHECK(valid); // signed pointer valid = true; - OK(uc_ctl_pauth_auth(uc, x1, UC_ARM64_PAUTH_KEY_IA, 1337, &valid)); + OK(uc_ctl_pauth_auth(uc, signed_pointer, UC_ARM64_PAUTH_KEY_IA, 1337, &valid)); TEST_CHECK(!valid); // wrong diversifier valid = true; - OK(uc_ctl_pauth_auth(uc, x1, UC_ARM64_PAUTH_KEY_IB, 0, &valid)); + OK(uc_ctl_pauth_auth(uc, signed_pointer, UC_ARM64_PAUTH_KEY_IB, 0, &valid)); TEST_CHECK(!valid); // wrong but enabled key valid = true; - OK(uc_ctl_pauth_auth(uc, x1, UC_ARM64_PAUTH_KEY_DA, 0, &valid)); + OK(uc_ctl_pauth_auth(uc, signed_pointer, UC_ARM64_PAUTH_KEY_DA, 0, &valid)); TEST_CHECK(!valid); // disabled but same value key OK(uc_close(uc)); @@ -853,5 +950,6 @@ TEST_LIST = {{"test_arm64_until", test_arm64_until}, {"test_arm64_mem_prot_regress", test_arm64_mem_prot_regress}, {"test_arm64_mem_hook_read_write", test_arm64_mem_hook_read_write}, {"test_arm64_pc_guarantee", test_arm64_pc_guarantee}, - {"test_arm64_pauth", test_arm64_pauth}, + {"test_arm64_pauth_vanilla", test_arm64_pauth_vanilla}, + {"test_arm64_pauth_ctl", test_arm64_pauth_ctl}, {NULL, NULL}}; From 209f87d4947b784cfcca66307558fee24a0afbb5 Mon Sep 17 00:00:00 2001 From: Daniel Roethlisberger Date: Mon, 24 Nov 2025 21:59:58 +0100 Subject: [PATCH 8/8] Typecast arguments to PAuth uc_ctl_* macros Fixes issue where literal arguments would be written to variable argument memory as an int, but subsequently read from va_list as a uint64_t, which on some platforms might be a different size and lead to corruption of later arguments, breaking PAuth helper functionality or causing segmentation faults. --- include/unicorn/unicorn.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/include/unicorn/unicorn.h b/include/unicorn/unicorn.h index 02b259a308..b6e434d596 100644 --- a/include/unicorn/unicorn.h +++ b/include/unicorn/unicorn.h @@ -729,11 +729,11 @@ See sample_ctl.c for a detailed example. #define uc_ctl_context_mode(uc, mode) \ uc_ctl(uc, UC_CTL_WRITE(UC_CTL_CONTEXT_MODE, 1), (mode)) #define uc_ctl_pauth_sign(uc, ptr, key, diversifier, signed_ptr) \ - uc_ctl(uc, UC_CTL_READ_WRITE(UC_CTL_PAUTH_SIGN, 4), (ptr), (key), (diversifier), (signed_ptr)) + uc_ctl(uc, UC_CTL_READ_WRITE(UC_CTL_PAUTH_SIGN, 4), (uint64_t)(ptr), (int)(key), (uint64_t)(diversifier), (uint64_t *)(signed_ptr)) #define uc_ctl_pauth_strip(uc, ptr, key, stripped_ptr) \ - uc_ctl(uc, UC_CTL_READ_WRITE(UC_CTL_PAUTH_STRIP, 3), (ptr), (key), (stripped_ptr)) + uc_ctl(uc, UC_CTL_READ_WRITE(UC_CTL_PAUTH_STRIP, 3), (uint64_t)(ptr), (int)(key), (uint64_t *)(stripped_ptr)) #define uc_ctl_pauth_auth(uc, ptr, key, diversifier, valid) \ - uc_ctl(uc, UC_CTL_READ_WRITE(UC_CTL_PAUTH_AUTH, 4), (ptr), (key), (diversifier), (valid)) + uc_ctl(uc, UC_CTL_READ_WRITE(UC_CTL_PAUTH_AUTH, 4), (uint64_t)(ptr), (int)(key), (uint64_t)(diversifier), (uint64_t *)(valid)) // Opaque storage for CPU context, used with uc_context_*() struct uc_context;