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 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 6f50eab8a0..c86d72a6c3 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. @@ -1475,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..b6e434d596 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), (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), (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), (uint64_t)(ptr), (int)(key), (uint64_t)(diversifier), (uint64_t *)(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 86f81595fe..e194121ca8 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] @@ -695,10 +695,246 @@ 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_check_cpu_feat(uc_engine *uc) +{ + // 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 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); +} + +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 }; + 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, 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); +} + +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. + + OK(uc_mem_write(uc, code_start, code_paciza_x1, sizeof(code_paciza_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)); + 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 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. + + 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, some_unsigned_pointer, UC_ARM64_PAUTH_KEY_IA, 0, &valid)); + TEST_CHECK(!valid); // unsigned pointer + valid = false; + 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, signed_pointer, UC_ARM64_PAUTH_KEY_IA, 1337, &valid)); + TEST_CHECK(!valid); // wrong diversifier + valid = true; + 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, signed_pointer, 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}, - {"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}, @@ -714,4 +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_vanilla", test_arm64_pauth_vanilla}, + {"test_arm64_pauth_ctl", test_arm64_pauth_ctl}, {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;