crypto: some hardening against AES cache-timing attacks
Subsystems (2)
v3 cover crypto: some hardening against AES cache-timing attacks
This series makes the "aes-fixed-time" and "aes-arm" implementations of AES more resistant to cache-timing attacks.
Note that even after these changes, the implementations still aren't necessarily guaranteed to be constant-time; see
https://cr.yp.to/antiforgery/cachetiming-20050414.pdf for a discussion of the many difficulties involved in writing truly constant-time AES software. But it's valuable to make such attacks more difficult.
Changed since v2:
- In aes-arm, move the IRQ disable/enable into the assembly file.
- Other aes-arm tweaks.
- Add Kconfig help text.
Thanks to Ard Biesheuvel for the suggestions.
Eric Biggers (2):
crypto: aes_ti - disable interrupts while accessing S-box
crypto: arm/aes - add some hardening against cache-timing attacks
arch/arm/crypto/Kconfig | 9 +++++ arch/arm/crypto/aes-cipher-core.S | 62 ++++++++++++++++++++++++++----- crypto/Kconfig | 3 +- crypto/aes_generic.c | 9 +++-- crypto/aes_ti.c | 18 +++++++++ 5 files changed, 86 insertions(+), 15 deletions(-)
Signature · 1 line
2.19.1
PATCH v3 1/2 crypto: aes_ti - disable interrupts while accessing S-box
From: Eric Biggers <[email protected]>
In the "aes-fixed-time" AES implementation, disable interrupts while accessing the S-box, in order to make cache-timing attacks more difficult. Previously it was possible for the CPU to be interrupted while the S-box was loaded into L1 cache, potentially evicting the cachelines and causing later table lookups to be time-variant.
In tests I did on x86 and ARM, this doesn't affect performance significantly. Responsiveness is potentially a concern, but interrupts are only disabled for a single AES block.
Note that even after this change, the implementation still isn't necessarily guaranteed to be constant-time; see
https://cr.yp.to/antiforgery/cachetiming-20050414.pdf for a discussion of the many difficulties involved in writing truly constant-time AES software. But it's valuable to make such attacks more difficult.
--- crypto/Kconfig | 3 ++- crypto/aes_ti.c | 18 ++++++++++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-)
crypto/Kconfig +2 -1
@@ -1006,7 +1006,8 @@ config CRYPTO_AES_TI 8 for decryption), this implementation only uses just two S-boxes of 256 bytes each, and attempts to eliminate data dependent latencies by prefetching the entire table into the cache at the start of each - block. + block. Interrupts are also disabled to avoid races where cachelines + are evicted when the CPU is interrupted to do something else. config CRYPTO_AES_586 tristate "AES cipher algorithms (i586)"
crypto/aes_ti.c +18 -0
@@ -269,6 +269,7 @@ static void aesti_encrypt(struct crypto_tfm *tfm, u8 *out, const u8 *in)const u32 *rkp = ctx->key_enc +4 ;int rounds =6 + ctx->key_length /4 ;u32 st0[4 ], st1[4 ]; +unsigned long flags;int round; st0[0 ] = ctx->key_enc[0 ] ^get_unaligned_le32 (in);
@@ -276,6 +277,12 @@ static void aesti_encrypt(struct crypto_tfm *tfm, u8 *out, const u8 *in) st0[2 ] = ctx->key_enc[2 ] ^get_unaligned_le32 (in +8 ); st0[3 ] = ctx->key_enc[3 ] ^get_unaligned_le32 (in +12 ); +/* +* Temporarily disable interrupts to avoid races where cachelines are +* evicted when the CPU is interrupted to do something else. +*/ +local_irq_save (flags); + st0[0 ] ^= __aesti_sbox[0 ] ^ __aesti_sbox[128 ]; st0[1 ] ^= __aesti_sbox[32 ] ^ __aesti_sbox[160 ]; st0[2 ] ^= __aesti_sbox[64 ] ^ __aesti_sbox[192 ];
@@ -300,6 +307,8 @@ static void aesti_encrypt(struct crypto_tfm *tfm, u8 *out, const u8 *in)put_unaligned_le32 (subshift (st1,1 ) ^ rkp[5 ], out +4 );put_unaligned_le32 (subshift (st1,2 ) ^ rkp[6 ], out +8 );put_unaligned_le32 (subshift (st1,3 ) ^ rkp[7 ], out +12 ); + +local_irq_restore (flags); }static void aesti_decrypt (struct crypto_tfm *tfm,u8 *out,const u8 *in)
@@ -308,6 +317,7 @@ static void aesti_decrypt(struct crypto_tfm *tfm, u8 *out, const u8 *in)const u32 *rkp = ctx->key_dec +4 ;int rounds =6 + ctx->key_length /4 ;u32 st0[4 ], st1[4 ]; +unsigned long flags;int round; st0[0 ] = ctx->key_dec[0 ] ^get_unaligned_le32 (in);
@@ -315,6 +325,12 @@ static void aesti_decrypt(struct crypto_tfm *tfm, u8 *out, const u8 *in) st0[2 ] = ctx->key_dec[2 ] ^get_unaligned_le32 (in +8 ); st0[3 ] = ctx->key_dec[3 ] ^get_unaligned_le32 (in +12 ); +/* +* Temporarily disable interrupts to avoid races where cachelines are +* evicted when the CPU is interrupted to do something else. +*/ +local_irq_save (flags); + st0[0 ] ^= __aesti_inv_sbox[0 ] ^ __aesti_inv_sbox[128 ]; st0[1 ] ^= __aesti_inv_sbox[32 ] ^ __aesti_inv_sbox[160 ]; st0[2 ] ^= __aesti_inv_sbox[64 ] ^ __aesti_inv_sbox[192 ];
@@ -339,6 +355,8 @@ static void aesti_decrypt(struct crypto_tfm *tfm, u8 *out, const u8 *in)put_unaligned_le32 (inv_subshift (st1,1 ) ^ rkp[5 ], out +4 );put_unaligned_le32 (inv_subshift (st1,2 ) ^ rkp[6 ], out +8 );put_unaligned_le32 (inv_subshift (st1,3 ) ^ rkp[7 ], out +12 ); + +local_irq_restore (flags); }static struct crypto_alg aes_alg = {
Signature · 1 line
2.19.1
PATCH v3 2/2 crypto: arm/aes - add some hardening against cache-timing attacks
In reply to Eric BiggersFrom: Eric Biggers <[email protected]>
Make the ARM scalar AES implementation closer to constant-time by disabling interrupts and prefetching the tables into L1 cache. This is feasible because due to ARM's "free" rotations, the main tables are only 1024 bytes instead of the usual 4096 used by most AES implementations.
On ARM Cortex-A7, the speed loss is only about 5%. The resulting code is still over twice as fast as aes_ti.c. Responsiveness is potentially a concern, but interrupts are only disabled for a single AES block.
Note that even after these changes, the implementation still isn't necessarily guaranteed to be constant-time; see
https://cr.yp.to/antiforgery/cachetiming-20050414.pdf for a discussion of the many difficulties involved in writing truly constant-time AES software. But it's valuable to make such attacks more difficult.
Much of this patch is based on patches suggested by Ard Biesheuvel.
--- arch/arm/crypto/Kconfig | 9 +++++ arch/arm/crypto/aes-cipher-core.S | 62 ++++++++++++++++++++++++++----- crypto/aes_generic.c | 9 +++-- 3 files changed, 66 insertions(+), 14 deletions(-)
arch/arm/crypto/Kconfig +9 -0
@@ -69,6 +69,15 @@ config CRYPTO_AES_ARM help Use optimized AES assembler routines for ARM platforms. + On ARM processors without the Crypto Extensions, this is the + fastest AES implementation for single blocks. For multiple + blocks, the NEON bit-sliced implementation is usually faster. + + This implementation may be vulnerable to cache timing attacks, + since it uses lookup tables. However, as countermeasures it + disables IRQs and preloads the tables; it is hoped this makes + such attacks very difficult. + config CRYPTO_AES_ARM_BS tristate "Bit sliced AES using NEON instructions" depends on KERNEL_MODE_NEON
arch/arm/crypto/aes-cipher-core.S +52 -10
119 lines not on this page · raw message
crypto/aes_generic.c +5 -4
@@ -63,7 +63,8 @@ static inline u8 byte(const u32 x, const unsigned n)static const u32 rco_tab[10 ] = {1 ,2 ,4 ,8 ,16 ,32 ,64 ,128 ,27 ,54 }; -__visible const u32 crypto_ft_tab[4][256] = { +/* cacheline-aligned to facilitate prefetching into cache */ +__visible const u32 crypto_ft_tab[4][256] __cacheline_aligned = { {0xa56363c6 ,0x847c7cf8 ,0x997777ee ,0x8d7b7bf6 ,0x0df2f2ff ,0xbd6b6bd6 ,0xb16f6fde ,0x54c5c591 ,
@@ -327,7 +328,7 @@ __visible const u32 crypto_ft_tab[4][256] = { } }; -__visible const u32 crypto_fl_tab[4][256] = { +__visible const u32 crypto_fl_tab[4][256] __cacheline_aligned = { {0x00000063 ,0x0000007c ,0x00000077 ,0x0000007b ,0x000000f2 ,0x0000006b ,0x0000006f ,0x000000c5 ,
@@ -591,7 +592,7 @@ __visible const u32 crypto_fl_tab[4][256] = { } }; -__visible const u32 crypto_it_tab[4][256] = { +__visible const u32 crypto_it_tab[4][256] __cacheline_aligned = { {0x50a7f451 ,0x5365417e ,0xc3a4171a ,0x965e273a ,0xcb6bab3b ,0xf1459d1f ,0xab58faac ,0x9303e34b ,
@@ -855,7 +856,7 @@ __visible const u32 crypto_it_tab[4][256] = { } }; -__visible const u32 crypto_il_tab[4][256] = { +__visible const u32 crypto_il_tab[4][256] __cacheline_aligned = { {0x00000052 ,0x00000009 ,0x0000006a ,0x000000d5 ,0x00000030 ,0x00000036 ,0x000000a5 ,0x00000038 ,
Signature · 1 line
2.19.1
Re: [PATCH v3 2/2] crypto: arm/aes - add some hardening against cache-timing attacks
Quoted 11 lines · Eric Biggers
From: Eric Biggers <[email protected]>
Make the ARM scalar AES implementation closer to constant-time by disabling interrupts and prefetching the tables into L1 cache. This is feasible because due to ARM's "free" rotations, the main tables are only 1024 bytes instead of the usual 4096 used by most AES implementations.
On ARM Cortex-A7, the speed loss is only about 5%. The resulting code is still over twice as fast as
aes_ti.c. Responsiveness is potentially a concern, but interrupts are only disabled for a single AES block.
So that would be in the order of 700 cycles, based on the numbers you shared in v1 of the aes_ti.c patch. Does that sound about right? So that would be around 1 microsecond, which is really not a number to obsess about imo.
I considered another option, which is to detect whether an interrupt has been taken (by writing some canary value below that stack pointer in the location where the exception handler will preserve the value of sp, and checking at the end whether it has been modified) and doing a usleep_range(x, y) if that is the case.
But this is much simpler so let's only go there if we must.
Quoted 10 lines
Note that even after these changes, the implementation still isn't necessarily guaranteed to be constant-time; see
https://cr.yp.to/antiforgery/cachetiming-20050414.pdf for a discussion of the many difficulties involved in writing truly constant-time AES software. But it's valuable to make such attacks more difficult.Much of this patch is based on patches suggested by Ard Biesheuvel.
Suggested-by Ard Biesheuvel <[email protected]>Signed-off-by Eric Biggers <[email protected]>
Quoted 201 lines
--- arch/arm/crypto/Kconfig | 9 +++++ arch/arm/crypto/aes-cipher-core.S | 62 ++++++++++++++++++++++++++----- crypto/aes_generic.c | 9 +++-- 3 files changed, 66 insertions(+), 14 deletions(-)arch/arm/crypto/Kconfig +9
-0@@ -69,6 +69,15 @@ config CRYPTO_AES_ARM help Use optimized AES assembler routines for ARM platforms. + On ARM processors without the Crypto Extensions, this is the + fastest AES implementation for single blocks. For multiple + blocks, the NEON bit-sliced implementation is usually faster. + + This implementation may be vulnerable to cache timing attacks, + since it uses lookup tables. However, as countermeasures it + disables IRQs and preloads the tables; it is hoped this makes + such attacks very difficult. + config CRYPTO_AES_ARM_BS tristate "Bit sliced AES using NEON instructions" depends on KERNEL_MODE_NEONarch/arm/crypto/aes-cipher-core.S +52
-10@@ -10,6 +10,7 @@ */ #include <linux/linkage.h> +#include <asm/assembler.h> #include <asm/cache.h> .text@@ -41,7 +42,7 @@ .endif .endm - .macro __hround, out0, out1, in0, in1, in2, in3, t3, t4, enc, sz, op + .macro __hround, out0, out1, in0, in1, in2, in3, t3, t4, enc, sz, op, oldcpsr __select \out0, \in0, 0 __select t0, \in1, 1 __load \out0, \out0, 0, \sz, \op@@ -73,6 +74,14 @@ __load t0, t0, 3, \sz, \op __load \t4, \t4, 3, \sz, \op + .ifnb \oldcpsr + /* + * This is the final round and we're done with all data-dependent table + * lookups, so we can safely re-enable interrupts. + */ + restore_irqs \oldcpsr + .endif + eor \out1, \out1, t1, ror #24 eor \out0, \out0, t2, ror #16 ldm rk!, {t1, t2}@@ -83,14 +92,14 @@ eor \out1, \out1, t2 .endm - .macro fround, out0, out1, out2, out3, in0, in1, in2, in3, sz=2, op + .macro fround, out0, out1, out2, out3, in0, in1, in2, in3, sz=2, op, oldcpsr __hround \out0, \out1, \in0, \in1, \in2, \in3, \out2, \out3, 1, \sz, \op - __hround \out2, \out3, \in2, \in3, \in0, \in1, \in1, \in2, 1, \sz, \op + __hround \out2, \out3, \in2, \in3, \in0, \in1, \in1, \in2, 1, \sz, \op, \oldcpsr .endm - .macro iround, out0, out1, out2, out3, in0, in1, in2, in3, sz=2, op + .macro iround, out0, out1, out2, out3, in0, in1, in2, in3, sz=2, op, oldcpsr __hround \out0, \out1, \in0, \in3, \in2, \in1, \out2, \out3, 0, \sz, \op - __hround \out2, \out3, \in2, \in1, \in0, \in3, \in1, \in0, 0, \sz, \op + __hround \out2, \out3, \in2, \in1, \in0, \in3, \in1, \in0, 0, \sz, \op, \oldcpsr .endm .macro __rev, out, in@@ -118,13 +127,14 @@ .macro do_crypt, round, ttab, ltab, bsz push {r3-r11, lr} + // Load keys first, to reduce latency in case they're not cached yet. + ldm rk!, {r8-r11} + ldr r4, [in] ldr r5, [in, #4] ldr r6, [in, #8] ldr r7, [in, #12] - ldm rk!, {r8-r11} - #ifdef CONFIG_CPU_BIG_ENDIAN __rev r4, r4 __rev r5, r5@@ -138,6 +148,25 @@ eor r7, r7, r11 __adrl ttab, \ttab + /* + * Disable interrupts and prefetch the 1024-byte 'ft' or 'it' table into + * L1 cache, assuming cacheline size >= 32. This is a hardening measure + * intended to make cache-timing attacks more difficult. They may not + * be fully prevented, however; see the paper + * https://cr.yp.to/antiforgery/cachetiming-20050414.pdf + * ("Cache-timing attacks on AES") for a discussion of the many + * difficulties involved in writing truly constant-time AES software. + */ + save_and_disable_irqs t0 + .set i, 0 + .rept 1024 / 128 + ldr r8, [ttab, #i + 0] + ldr r9, [ttab, #i + 32] + ldr r10, [ttab, #i + 64] + ldr r11, [ttab, #i + 96] + .set i, i + 128 + .endr + push {t0} // oldcpsr tst rounds, #2 bne 1f@@ -151,8 +180,21 @@ \round r4, r5, r6, r7, r8, r9, r10, r11 b 0b -2: __adrl ttab, \ltab - \round r4, r5, r6, r7, r8, r9, r10, r11, \bsz, b +2: .ifb \ltab + add ttab, ttab, #1 + .else + __adrl ttab, \ltab + // Prefetch inverse S-box for final round; see explanation above + .set i, 0 + .rept 256 / 64 + ldr t0, [ttab, #i + 0] + ldr t1, [ttab, #i + 32] + .set i, i + 64 + .endr + .endif + + pop {rounds} // oldcpsr + \round r4, r5, r6, r7, r8, r9, r10, r11, \bsz, b, rounds #ifdef CONFIG_CPU_BIG_ENDIAN __rev r4, r4@@ -175,7 +217,7 @@ .endm ENTRY(__aes_arm_encrypt) - do_crypt fround, crypto_ft_tab, crypto_ft_tab + 1, 2 + do_crypt fround, crypto_ft_tab,, 2 ENDPROC(__aes_arm_encrypt) .align 5crypto/aes_generic.c +5
-5@@ -63,7 +63,8 @@ static inline u8 byte(const u32 x, const unsigned n)static const u32 rco_tab[10 ] = {1 ,2 ,4 ,8 ,16 ,32 ,64 ,128 ,27 ,54 }; -__visible const u32 crypto_ft_tab[4][256] = { +/* cacheline-aligned to facilitate prefetching into cache */ +__visible const u32 crypto_ft_tab[4][256] __cacheline_aligned = { {0xa56363c6 ,0x847c7cf8 ,0x997777ee ,0x8d7b7bf6 ,0x0df2f2ff ,0xbd6b6bd6 ,0xb16f6fde ,0x54c5c591 ,@@ -327,7 +328,7 @@ __visible const u32 crypto_ft_tab[4][256] = { } }; -__visible const u32 crypto_fl_tab[4][256] = { +__visible const u32 crypto_fl_tab[4][256] __cacheline_aligned = { {0x00000063 ,0x0000007c ,0x00000077 ,0x0000007b ,0x000000f2 ,0x0000006b ,0x0000006f ,0x000000c5 ,@@ -591,7 +592,7 @@ __visible const u32 crypto_fl_tab[4][256] = { } }; -__visible const u32 crypto_it_tab[4][256] = { +__visible const u32 crypto_it_tab[4][256] __cacheline_aligned = { {0x50a7f451 ,0x5365417e ,0xc3a4171a ,0x965e273a ,0xcb6bab3b ,0xf1459d1f ,0xab58faac ,0x9303e34b ,@@ -855,7 +856,7 @@ __visible const u32 crypto_it_tab[4][256] = { } }; -__visible const u32 crypto_il_tab[4][256] = { +__visible const u32 crypto_il_tab[4][256] __cacheline_aligned = { {0x00000052 ,0x00000009 ,0x0000006a ,0x000000d5 ,0x00000030 ,0x00000036 ,0x000000a5 ,0x00000038 , --2.19.1
Re: [PATCH v3 2/2] crypto: arm/aes - add some hardening against cache-timing attacks
Quoted 18 lines · Ard Biesheuvel
Quoted 11 lines · Eric Biggers
From: Eric Biggers <[email protected]>
Make the ARM scalar AES implementation closer to constant-time by disabling interrupts and prefetching the tables into L1 cache. This is feasible because due to ARM's "free" rotations, the main tables are only 1024 bytes instead of the usual 4096 used by most AES implementations.
On ARM Cortex-A7, the speed loss is only about 5%. The resulting code is still over twice as fast as
aes_ti.c. Responsiveness is potentially a concern, but interrupts are only disabled for a single AES block.So that would be in the order of 700 cycles, based on the numbers you shared in v1 of the
aes_ti.cpatch. Does that sound about right? So that would be around 1 microsecond, which is really not a number to obsess about imo.
Quoted 8 lines
I considered another option, which is to detect whether an interrupt has been taken (by writing some canary value below that stack pointer in the location where the exception handler will preserve the value of sp, and checking at the end whether it has been modified) and doing a
usleep_range(x, y)if that is the case.But this is much simpler so let's only go there if we must.
I played around a bit and implemented it for discussion purposes, but restarting the operation if it gets interrupted, as suggested in the paper (whitespace corruption courtesy of Gmail)
(quoted diff) +0 -0
b/arch/arm/crypto/aes-cipher-core.S
index 184d6c2d15d5..2e8a84a47784 100644
arch/arm/crypto/aes-cipher-core.S +31 -0
@@ -10,6 +10,7 @@ */ #include <linux/linkage.h> +#include <asm/asm-offsets.h> #include <asm/cache.h> .text
@@ -139,6 +140,34 @@ __adrl ttab, \ttab + /* + * Set a canary that will allow us to tell whether any + * interrupts were taken while this function was executing. + * The zero value will be overwritten with the process counter + * value at the point where the IRQ exception is taken. + */ + mov t0, #0 + str t0, [sp, #-(SVC_REGS_SIZE - S_PC)] + + /* + * Prefetch the 1024-byte 'ft' or 'it' table into L1 cache, + * assuming cacheline size >= 32. This is a hardening measure + * intended to make cache-timing attacks more difficult. + * They may not be fully prevented, however; see the paper + * https://cr.yp.to/antiforgery/cachetiming-20050414.pdf + * ("Cache-timing attacks on AES") for a discussion of the many + * difficulties involved in writing truly constant-time AES + * software. + */ + .set i, 0 + .rept 1024 / 128 + ldr r8, [ttab, #i + 0] + ldr r9, [ttab, #i + 32] + ldr r10, [ttab, #i + 64] + ldr r11, [ttab, #i + 96] + .set i, i + 128 + .endr + tst rounds, #2 bne 1f
@@ -154,6 +183,8 @@ 2: __adrl ttab, \ltab \round r4, r5, r6, r7, r8, r9, r10, r11, \bsz, b + ldr r0, [sp, #-(SVC_REGS_SIZE - S_PC)] // check canary + #ifdef CONFIG_CPU_BIG_ENDIAN __rev r4, r4 __rev r5, r5
(quoted diff) +0 -0
b/arch/arm/crypto/aes-cipher-glue.c
index c222f6e072ad..de8f32121511 100644
arch/arm/crypto/aes-cipher-glue.c +15 -4
@@ -11,28 +11,39 @@#include <crypto/aes.h> #include <linux/crypto.h> +#include <linux/delay.h> #include <linux/module.h> -asmlinkage void __aes_arm_encrypt(u32 *rk, int rounds, const u8 *in, u8 *out); +asmlinkage int __aes_arm_encrypt(u32 *rk, int rounds, const u8 *in, u8 *out);EXPORT_SYMBOL (__aes_arm_encrypt); -asmlinkage void __aes_arm_decrypt(u32 *rk, int rounds, const u8 *in, u8 *out); +asmlinkage int __aes_arm_decrypt(u32 *rk, int rounds, const u8 *in, u8 *out);EXPORT_SYMBOL (__aes_arm_decrypt);static void aes_encrypt (struct crypto_tfm *tfm,u8 *out,const u8 *in) {struct crypto_aes_ctx *ctx =crypto_tfm_ctx (tfm);int rounds =6 + ctx->key_length /4 ; +u8 buf[AES_BLOCK_SIZE ]; -__aes_arm_encrypt (ctx->key_enc, rounds, in, out); +if (out == in) + in =memcpy (buf, in,AES_BLOCK_SIZE ); + +while (unlikely (__aes_arm_encrypt (ctx->key_enc, rounds, in, out))) +cpu_relax (); }static void aes_decrypt (struct crypto_tfm *tfm,u8 *out,const u8 *in) {struct crypto_aes_ctx *ctx =crypto_tfm_ctx (tfm);int rounds =6 + ctx->key_length /4 ; +u8 buf[AES_BLOCK_SIZE ]; + +if (out == in) + in =memcpy (buf, in,AES_BLOCK_SIZE ); -__aes_arm_decrypt (ctx->key_dec, rounds, in, out); +while (unlikely (__aes_arm_decrypt (ctx->key_dec, rounds, in, out))) +cpu_relax (); }static struct crypto_alg aes_alg = {
Re: [PATCH v3 2/2] crypto: arm/aes - add some hardening against cache-timing attacks
Quoted 129 lines · Ard Biesheuvel
Quoted 18 lines · Ard Biesheuvel
Quoted 11 lines · Eric Biggers
From: Eric Biggers <[email protected]>
Make the ARM scalar AES implementation closer to constant-time by disabling interrupts and prefetching the tables into L1 cache. This is feasible because due to ARM's "free" rotations, the main tables are only 1024 bytes instead of the usual 4096 used by most AES implementations.
On ARM Cortex-A7, the speed loss is only about 5%. The resulting code is still over twice as fast as
aes_ti.c. Responsiveness is potentially a concern, but interrupts are only disabled for a single AES block.So that would be in the order of 700 cycles, based on the numbers you shared in v1 of the
aes_ti.cpatch. Does that sound about right? So that would be around 1 microsecond, which is really not a number to obsess about imo.Quoted 8 lines
I considered another option, which is to detect whether an interrupt has been taken (by writing some canary value below that stack pointer in the location where the exception handler will preserve the value of sp, and checking at the end whether it has been modified) and doing a
usleep_range(x, y)if that is the case.But this is much simpler so let's only go there if we must.
I played around a bit and implemented it for discussion purposes, but restarting the operation if it gets interrupted, as suggested in the paper (whitespace corruption courtesy of Gmail)
arch/arm/crypto/aes-cipher-core.S +31
-0@@ -10,6 +10,7 @@ */ #include <linux/linkage.h> +#include <asm/asm-offsets.h> #include <asm/cache.h> .text@@ -139,6 +140,34 @@ __adrl ttab, \ttab + /* + * Set a canary that will allow us to tell whether any + * interrupts were taken while this function was executing. + * The zero value will be overwritten with the process counter + * value at the point where the IRQ exception is taken. + */ + mov t0, #0 + str t0, [sp, #-(SVC_REGS_SIZE - S_PC)] + + /* + * Prefetch the 1024-byte 'ft' or 'it' table into L1 cache, + * assuming cacheline size >= 32. This is a hardening measure + * intended to make cache-timing attacks more difficult. + * They may not be fully prevented, however; see the paper + * https://cr.yp.to/antiforgery/cachetiming-20050414.pdf + * ("Cache-timing attacks on AES") for a discussion of the many + * difficulties involved in writing truly constant-time AES + * software. + */ + .set i, 0 + .rept 1024 / 128 + ldr r8, [ttab, #i + 0] + ldr r9, [ttab, #i + 32] + ldr r10, [ttab, #i + 64] + ldr r11, [ttab, #i + 96] + .set i, i + 128 + .endr + tst rounds, #2 bne 1f@@ -154,6 +183,8 @@ 2: __adrl ttab, \ltab \round r4, r5, r6, r7, r8, r9, r10, r11, \bsz, b + ldr r0, [sp, #-(SVC_REGS_SIZE - S_PC)] // check canary + #ifdef CONFIG_CPU_BIG_ENDIAN __rev r4, r4 __rev r5, r5arch/arm/crypto/aes-cipher-glue.c +10
-3@@ -11,28 +11,39 @@#include <crypto/aes.h> #include <linux/crypto.h> +#include <linux/delay.h> #include <linux/module.h> -asmlinkage void __aes_arm_encrypt(u32 *rk, int rounds, const u8 *in, u8 *out); +asmlinkage int __aes_arm_encrypt(u32 *rk, int rounds, const u8 *in, u8 *out);EXPORT_SYMBOL (__aes_arm_encrypt); -asmlinkage void __aes_arm_decrypt(u32 *rk, int rounds, const u8 *in, u8 *out); +asmlinkage int __aes_arm_decrypt(u32 *rk, int rounds, const u8 *in, u8 *out);EXPORT_SYMBOL (__aes_arm_decrypt);static void aes_encrypt (struct crypto_tfm *tfm,u8 *out,const u8 *in) {struct crypto_aes_ctx *ctx =crypto_tfm_ctx (tfm);int rounds =6 + ctx->key_length /4 ; +u8 buf[AES_BLOCK_SIZE ]; -__aes_arm_encrypt (ctx->key_enc, rounds, in, out); +if (out == in) + in =memcpy (buf, in,AES_BLOCK_SIZE ); + +while (unlikely (__aes_arm_encrypt (ctx->key_enc, rounds, in, out))) +cpu_relax (); }static void aes_decrypt (struct crypto_tfm *tfm,u8 *out,const u8 *in) {struct crypto_aes_ctx *ctx =crypto_tfm_ctx (tfm);int rounds =6 + ctx->key_length /4 ; +u8 buf[AES_BLOCK_SIZE ];
arch/arm/crypto/aes-cipher-glue.c line 49
Quoted 10 lines
arch/arm/crypto/aes-cipher-glue.c +5
-1+ +if (out == in) + in =memcpy (buf, in,AES_BLOCK_SIZE ); -__aes_arm_decrypt (ctx->key_dec, rounds, in, out); +while (unlikely (__aes_arm_decrypt (ctx->key_dec, rounds, in, out))) +cpu_relax (); }static struct crypto_alg aes_alg = {
It's an interesting idea, but the main thing I don't like about this is that the time it takes to do the encryption/decryption is unbounded, since it could get livelocked with a high rate of interrupts. To fix this you'd have to fall back to a truly constant-time implementation (e.g. implementing the S-box by simulating a hardware circuit) if the fast implementation gets interrupted too many times.
It's also less obviously correct since it relies on the canary reliably being overwritten by the interrupt handler, *and* being overwritten with a different value than it had before.
So as long as it doesn't cause problems in practice, I prefer the solution that just disables interrupts.
- Eric
Re: [PATCH v3 2/2] crypto: arm/aes - add some hardening against cache-timing attacks
Quoted 139 lines · Eric Biggers
Quoted 138 lines · Ard Biesheuvel
Quoted 18 lines · Ard Biesheuvel
Quoted 11 lines · Eric Biggers
From: Eric Biggers <[email protected]>
Make the ARM scalar AES implementation closer to constant-time by disabling interrupts and prefetching the tables into L1 cache. This is feasible because due to ARM's "free" rotations, the main tables are only 1024 bytes instead of the usual 4096 used by most AES implementations.
On ARM Cortex-A7, the speed loss is only about 5%. The resulting code is still over twice as fast as
aes_ti.c. Responsiveness is potentially a concern, but interrupts are only disabled for a single AES block.So that would be in the order of 700 cycles, based on the numbers you shared in v1 of the
aes_ti.cpatch. Does that sound about right? So that would be around 1 microsecond, which is really not a number to obsess about imo.Quoted 8 lines
I considered another option, which is to detect whether an interrupt has been taken (by writing some canary value below that stack pointer in the location where the exception handler will preserve the value of sp, and checking at the end whether it has been modified) and doing a
usleep_range(x, y)if that is the case.But this is much simpler so let's only go there if we must.
I played around a bit and implemented it for discussion purposes, but restarting the operation if it gets interrupted, as suggested in the paper (whitespace corruption courtesy of Gmail)
arch/arm/crypto/aes-cipher-core.S +31
-0@@ -10,6 +10,7 @@ */ #include <linux/linkage.h> +#include <asm/asm-offsets.h> #include <asm/cache.h> .text@@ -139,6 +140,34 @@ __adrl ttab, \ttab + /* + * Set a canary that will allow us to tell whether any + * interrupts were taken while this function was executing. + * The zero value will be overwritten with the process counter + * value at the point where the IRQ exception is taken. + */ + mov t0, #0 + str t0, [sp, #-(SVC_REGS_SIZE - S_PC)] + + /* + * Prefetch the 1024-byte 'ft' or 'it' table into L1 cache, + * assuming cacheline size >= 32. This is a hardening measure + * intended to make cache-timing attacks more difficult. + * They may not be fully prevented, however; see the paper + * https://cr.yp.to/antiforgery/cachetiming-20050414.pdf + * ("Cache-timing attacks on AES") for a discussion of the many + * difficulties involved in writing truly constant-time AES + * software. + */ + .set i, 0 + .rept 1024 / 128 + ldr r8, [ttab, #i + 0] + ldr r9, [ttab, #i + 32] + ldr r10, [ttab, #i + 64] + ldr r11, [ttab, #i + 96] + .set i, i + 128 + .endr + tst rounds, #2 bne 1f@@ -154,6 +183,8 @@ 2: __adrl ttab, \ltab \round r4, r5, r6, r7, r8, r9, r10, r11, \bsz, b + ldr r0, [sp, #-(SVC_REGS_SIZE - S_PC)] // check canary + #ifdef CONFIG_CPU_BIG_ENDIAN __rev r4, r4 __rev r5, r5arch/arm/crypto/aes-cipher-glue.c +15
-4@@ -11,28 +11,39 @@#include <crypto/aes.h> #include <linux/crypto.h> +#include <linux/delay.h> #include <linux/module.h> -asmlinkage void __aes_arm_encrypt(u32 *rk, int rounds, const u8 *in, u8 *out); +asmlinkage int __aes_arm_encrypt(u32 *rk, int rounds, const u8 *in, u8 *out);EXPORT_SYMBOL (__aes_arm_encrypt); -asmlinkage void __aes_arm_decrypt(u32 *rk, int rounds, const u8 *in, u8 *out); +asmlinkage int __aes_arm_decrypt(u32 *rk, int rounds, const u8 *in, u8 *out);EXPORT_SYMBOL (__aes_arm_decrypt);static void aes_encrypt (struct crypto_tfm *tfm,u8 *out,const u8 *in) {struct crypto_aes_ctx *ctx =crypto_tfm_ctx (tfm);int rounds =6 + ctx->key_length /4 ; +u8 buf[AES_BLOCK_SIZE ]; -__aes_arm_encrypt (ctx->key_enc, rounds, in, out); +if (out == in) + in =memcpy (buf, in,AES_BLOCK_SIZE ); + +while (unlikely (__aes_arm_encrypt (ctx->key_enc, rounds, in, out))) +cpu_relax (); }static void aes_decrypt (struct crypto_tfm *tfm,u8 *out,const u8 *in) {struct crypto_aes_ctx *ctx =crypto_tfm_ctx (tfm);int rounds =6 + ctx->key_length /4 ; +u8 buf[AES_BLOCK_SIZE ]; + +if (out == in) + in =memcpy (buf, in,AES_BLOCK_SIZE ); -__aes_arm_decrypt (ctx->key_dec, rounds, in, out); +while (unlikely (__aes_arm_decrypt (ctx->key_dec, rounds, in, out))) +cpu_relax (); }
Quoted 9 lines
arch/arm/crypto/aes-cipher-glue.c line 49
Quoted 1 line
arch/arm/crypto/aes-cipher-glue.c +0
-0static struct crypto_alg aes_alg = {It's an interesting idea, but the main thing I don't like about this is that the time it takes to do the encryption/decryption is unbounded, since it could get livelocked with a high rate of interrupts. To fix this you'd have to fall back to a truly constant-time implementation (e.g. implementing the S-box by simulating a hardware circuit) if the fast implementation gets interrupted too many times.
Yeah. I'm surprised that this is what the paper suggests, given that multiple interruptions only increase the time variance.
Quoted 4 lines
It's also less obviously correct since it relies on the canary reliably being overwritten by the interrupt handler, *and* being overwritten with a different value than it had before.
Indeed. That is why I am using the value of PC rather than SP (which was my original idea). In a previous approach, I did something like
ret = __aes_arm_encrypt(...);
if (unlikely(ret))
udelay((ret & 0xff) >> 2);
to insert an arbitrary delay in the range of 0 .. 63 microseconds, depending on where the interruption took place.
Quoted 3 lines
So as long as it doesn't cause problems in practice, I prefer the solution that just disables interrupts.
I agree, but I am anticipating some pushback, so we should make sure we have exhausted all other options.
Re: [PATCH v3 2/2] crypto: arm/aes - add some hardening against cache-timing attacks
In reply to Ard BiesheuvelQuoted 13 lines · Ard Biesheuvel
Quoted 11 lines · Eric Biggers
From: Eric Biggers <[email protected]>
Make the ARM scalar AES implementation closer to constant-time by disabling interrupts and prefetching the tables into L1 cache. This is feasible because due to ARM's "free" rotations, the main tables are only 1024 bytes instead of the usual 4096 used by most AES implementations.
On ARM Cortex-A7, the speed loss is only about 5%. The resulting code is still over twice as fast as
aes_ti.c. Responsiveness is potentially a concern, but interrupts are only disabled for a single AES block.
Quoted 5 lines
So that would be in the order of 700 cycles, based on the numbers you shared in v1 of the
aes_ti.cpatch. Does that sound about right? So that would be around 1 microsecond, which is really not a number to obsess about imo.
Correct, on ARM Cortex-A7 I'm seeing slightly over 700 cycles per block encrypted or decrypted, including the prefetching.
- Eric
Herbert Xu
maintainerQuoted 20 lines · Eric Biggers
This series makes the "aes-fixed-time" and "aes-arm" implementations of AES more resistant to cache-timing attacks.
Note that even after these changes, the implementations still aren't necessarily guaranteed to be constant-time; see
https://cr.yp.to/antiforgery/cachetiming-20050414.pdf for a discussion of the many difficulties involved in writing truly constant-time AES software. But it's valuable to make such attacks more difficult.Changed since v2:
- In aes-arm, move the IRQ disable/enable into the assembly file.
- Other aes-arm tweaks.
- AddKconfighelp text.Thanks to Ard Biesheuvel for the suggestions.
Eric Biggers (2):
crypto:aes_ti- disable interrupts while accessing S-box
crypto: arm/aes - add some hardening against cache-timing attacks
Quoted 6 lines
arch/arm/crypto/Kconfig | 9 +++++ arch/arm/crypto/aes-cipher-core.S | 62 ++++++++++++++++++++++++++----- crypto/Kconfig | 3 +- crypto/aes_generic.c | 9 +++-- crypto/aes_ti.c | 18 +++++++++ 5 files changed, 86 insertions(+), 15 deletions(-)
All applied. Thanks.
Signature · 3 lines
Email: Herbert Xu <[email protected]> Home Page: http://gondor.apana.org.au/~herbert/ PGP Key: http://gondor.apana.org.au/~herbert/pubkey.txt