Coverage Report

Created: 2026-09-12 06:55

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/openssl/crypto/aes/aes_cbc_vaes_intrinsic.c
Line
Count
Source
1
/*
2
 * Copyright 2025-2026 The OpenSSL Project Authors. All Rights Reserved.
3
 * Copyright (C) 2026, Advanced Micro Devices, all rights reserved.
4
 *
5
 * Licensed under the Apache License 2.0 (the "License").  You may not use
6
 * this file except in compliance with the License.  You can obtain a copy
7
 * in the file LICENSE in the source distribution or at
8
 * https://www.openssl.org/source/license.html
9
 *
10
 * Implements AES-CBC128/192/256 decryption with VAES (AVX-512)
11
 *
12
 * CBC encryption is inherently serial (each ciphertext block depends
13
 * on the previous one), so VAES provides no benefit there -- the
14
 * encrypt path falls back to the aesni_cbc_encrypt assembly routine.
15
 *
16
 * CBC decryption IS parallel: all blocks can be independently decrypted,
17
 * then XORed with the preceding ciphertext block (or IV for the first).
18
 * This implementation processes 4x4=16 blocks per iteration using four
19
 * ZMM registers, falling back to 8, 4, then single-block processing.
20
 */
21
22
#include "internal/deprecated.h"
23
24
#include <openssl/opensslconf.h>
25
#include "internal/cryptlib.h"
26
#include <openssl/aes.h>
27
#include "crypto/modes.h"
28
#include "crypto/aes_platform.h"
29
30
#if VAES_CBC_ELIGIBLE
31
32
/* Function prototypes */
33
void ossl_aes_cbc_vaes_decrypt(const unsigned char *in, unsigned char *out,
34
    size_t len, const void *key,
35
    unsigned char ivec[16], int enc);
36
int ossl_aes_cbc_vaes_eligible(void);
37
38
#include <openssl/modes.h>
39
40
/* Forward declarations -- defined in aesni-x86_64.pl assembly        */
41
void aesni_cbc_encrypt(const unsigned char *in, unsigned char *out,
42
    size_t len, const AES_KEY *key,
43
    unsigned char *ivec, int enc);
44
void aesni_decrypt(const unsigned char *in, unsigned char *out,
45
    const AES_KEY *key);
46
47
/*
48
 * Keep the cleanup out of line in assembly. Its opacity to C and LTO
49
 * optimizers is what prevents the cleanup call and stores from being
50
 * eliminated as dead. Do not replace it with a compiler-visible C function.
51
 */
52
void ossl_aes_cbc_vaes_cleanup(void *key_schedule, size_t num_keys);
53
int ossl_aes_cbc_vaes_cleanup_eligible(void);
54
55
/* Portable compiler abstractions for inlining and ISA target selection */
56
#define STRINGIFY_IMPL_(a) #a
57
#define STRINGIFY_(a) STRINGIFY_IMPL_(a)
58
59
#ifdef __clang__
60
#define OPENSSL_TARGET_VAES512                                         \
61
    _Pragma(STRINGIFY_(clang attribute push(                           \
62
        __attribute__((target("avx512f,avx512dq,avx512bw,vaes,aes"))), \
63
        apply_to = function)))
64
#define OPENSSL_UNTARGET_VAES512 _Pragma("clang attribute pop")
65
#elif defined(__GNUC__)
66
#define OPENSSL_TARGET_VAES512  \
67
    _Pragma("GCC push_options") \
68
        _Pragma(STRINGIFY_(GCC target("avx512f,avx512dq,avx512bw,vaes,aes")))
69
#define OPENSSL_UNTARGET_VAES512 _Pragma("GCC pop_options")
70
#else
71
/* MSVC: all intrinsics are always available via <immintrin.h>. */
72
#define OPENSSL_TARGET_VAES512
73
#define OPENSSL_UNTARGET_VAES512
74
#endif
75
76
#if defined(__GNUC__) || defined(__clang__)
77
#define OSSL_FUNC_ALWAYS_INLINE static inline __attribute__((always_inline))
78
#define OSSL_FUNC_NOINLINE __attribute__((noinline))
79
#elif defined(_MSC_VER)
80
#define OSSL_FUNC_ALWAYS_INLINE static __forceinline
81
#define OSSL_FUNC_NOINLINE __declspec(noinline)
82
#else
83
#define OSSL_FUNC_ALWAYS_INLINE static inline
84
#define OSSL_FUNC_NOINLINE
85
#endif
86
87
#include <immintrin.h>
88
89
OPENSSL_TARGET_VAES512
90
91
/* ------------------------------------------------------------------ */
92
/* AES decryption helpers: 1x, 2x, 4x parallel 512-bit blocks         */
93
/* Each 512-bit register holds 4 independent 128-bit AES blocks.      */
94
/* always_inline guarantees the compiler keeps keys in ZMM regs.      */
95
/* ------------------------------------------------------------------ */
96
97
#define DEFINE_AES_DECRYPT_FUNCS(ROUNDS)                    \
98
    OSSL_FUNC_ALWAYS_INLINE                                 \
99
    void AesDec_4x512_##ROUNDS(                             \
100
        __m512i *b1, __m512i *b2, __m512i *b3, __m512i *b4, \
101
        const __m512i *rk)                                  \
102
0
    {                                                       \
103
0
        *b1 = _mm512_xor_si512(*b1, rk[0]);                 \
104
0
        *b2 = _mm512_xor_si512(*b2, rk[0]);                 \
105
0
        *b3 = _mm512_xor_si512(*b3, rk[0]);                 \
106
0
        *b4 = _mm512_xor_si512(*b4, rk[0]);                 \
107
0
        for (int i = 1; i < ROUNDS; i++) {                  \
108
0
            *b1 = _mm512_aesdec_epi128(*b1, rk[i]);         \
109
0
            *b2 = _mm512_aesdec_epi128(*b2, rk[i]);         \
110
0
            *b3 = _mm512_aesdec_epi128(*b3, rk[i]);         \
111
0
            *b4 = _mm512_aesdec_epi128(*b4, rk[i]);         \
112
0
        }                                                   \
113
0
        *b1 = _mm512_aesdeclast_epi128(*b1, rk[ROUNDS]);    \
114
0
        *b2 = _mm512_aesdeclast_epi128(*b2, rk[ROUNDS]);    \
115
0
        *b3 = _mm512_aesdeclast_epi128(*b3, rk[ROUNDS]);    \
116
0
        *b4 = _mm512_aesdeclast_epi128(*b4, rk[ROUNDS]);    \
117
0
    }                                                       \
Unexecuted instantiation: aes_cbc_vaes_intrinsic.c:AesDec_4x512_10
Unexecuted instantiation: aes_cbc_vaes_intrinsic.c:AesDec_4x512_12
Unexecuted instantiation: aes_cbc_vaes_intrinsic.c:AesDec_4x512_14
118
                                                            \
119
    OSSL_FUNC_ALWAYS_INLINE                                 \
120
    void AesDec_2x512_##ROUNDS(                             \
121
        __m512i *b1, __m512i *b2, const __m512i *rk)        \
122
0
    {                                                       \
123
0
        *b1 = _mm512_xor_si512(*b1, rk[0]);                 \
124
0
        *b2 = _mm512_xor_si512(*b2, rk[0]);                 \
125
0
        for (int i = 1; i < ROUNDS; i++) {                  \
126
0
            *b1 = _mm512_aesdec_epi128(*b1, rk[i]);         \
127
0
            *b2 = _mm512_aesdec_epi128(*b2, rk[i]);         \
128
0
        }                                                   \
129
0
        *b1 = _mm512_aesdeclast_epi128(*b1, rk[ROUNDS]);    \
130
0
        *b2 = _mm512_aesdeclast_epi128(*b2, rk[ROUNDS]);    \
131
0
    }                                                       \
Unexecuted instantiation: aes_cbc_vaes_intrinsic.c:AesDec_2x512_10
Unexecuted instantiation: aes_cbc_vaes_intrinsic.c:AesDec_2x512_12
Unexecuted instantiation: aes_cbc_vaes_intrinsic.c:AesDec_2x512_14
132
                                                            \
133
    OSSL_FUNC_ALWAYS_INLINE                                 \
134
    void AesDec_1x512_##ROUNDS(                             \
135
        __m512i *b1, const __m512i *rk)                     \
136
0
    {                                                       \
137
0
        *b1 = _mm512_xor_si512(*b1, rk[0]);                 \
138
0
        for (int i = 1; i < ROUNDS; i++)                    \
139
0
            *b1 = _mm512_aesdec_epi128(*b1, rk[i]);         \
140
0
        *b1 = _mm512_aesdeclast_epi128(*b1, rk[ROUNDS]);    \
141
0
    }
Unexecuted instantiation: aes_cbc_vaes_intrinsic.c:AesDec_1x512_10
Unexecuted instantiation: aes_cbc_vaes_intrinsic.c:AesDec_1x512_12
Unexecuted instantiation: aes_cbc_vaes_intrinsic.c:AesDec_1x512_14
142
143
DEFINE_AES_DECRYPT_FUNCS(10) /* AES-128 */
144
DEFINE_AES_DECRYPT_FUNCS(12) /* AES-192 */
145
DEFINE_AES_DECRYPT_FUNCS(14) /* AES-256 */
146
147
/* ------------------------------------------------------------------ */
148
/* CBC-mode decryption -- templated per round count                   */
149
/*                                                                    */
150
/* Processes as many full blocks as possible:                         */
151
/*   16 blocks at a time (4 x zmm = 4 x 4 = 16 blocks)                */
152
/*    8 blocks at a time (2 x zmm)                                    */
153
/*    4 blocks at a time (1 x zmm)                                    */
154
/*    1 block at a time for the remaining 0-3 blocks                  */
155
/*                                                                    */
156
/* The chaining vector b1 packs [prev_ct[last] | ct[0] | ct[1]        */
157
/* | ct[2]] so that a single XOR after decryption applies the CBC     */
158
/* feedback to all four lanes simultaneously.                         */
159
/* ------------------------------------------------------------------ */
160
161
#define DEFINE_CBC_DECRYPT(NR)                                                      \
162
    OSSL_FUNC_NOINLINE                                                              \
163
    static void cbc_decrypt_##NR(                                                   \
164
        const unsigned char *in, unsigned char *out, size_t len,                    \
165
        const AES_KEY *key, unsigned char *iv)                                      \
166
0
    {                                                                               \
167
0
        const unsigned char *rk_bytes = (const unsigned char *)key->rd_key;         \
168
0
        __m512i rk[NR + 1];                                                         \
169
0
        for (int i = 0; i <= NR; i++) {                                             \
170
0
            __m128i t = _mm_loadu_si128((const __m128i *)(rk_bytes + i * 16));      \
171
0
            rk[i] = _mm512_broadcast_i32x4(t);                                      \
172
0
        }                                                                           \
173
0
                                                                                    \
174
0
        __m512i a1, a2, a3, a4;                                                     \
175
0
        __m512i b1, b2, b3, b4;                                                     \
176
0
                                                                                    \
177
0
        const __m128i *pa = (const __m128i *)in;                                    \
178
0
        __m512i *po = (__m512i *)out;                                               \
179
0
        size_t blocks = len / AES_BLOCK_SIZE;                                       \
180
0
                                                                                    \
181
0
        /* Save last ciphertext block for IV update (in-place safe)             */  \
182
0
        __m128i saved_iv = _mm_setzero_si128();                                     \
183
0
        int has_blocks = (blocks > 0);                                              \
184
0
        if (has_blocks)                                                             \
185
0
            saved_iv = _mm_loadu_si128(pa + blocks - 1);                            \
186
0
                                                                                    \
187
0
        if (blocks >= 4) {                                                          \
188
0
            /* Build b1 = [IV | ct[0] | ct[1] | ct[2]]                          */  \
189
0
            __m512i idx = _mm512_set_epi64(5, 4, 3, 2, 1, 0, 0, 0);                 \
190
0
            __m512i ct0;                                                            \
191
0
                                                                                    \
192
0
            /* CBC C[0]=IV; 0x03 loads one 128-bit block (two 64-bit lanes). */     \
193
0
            b1 = _mm512_maskz_loadu_epi64(0x03, iv);                                \
194
0
            ct0 = _mm512_loadu_si512(pa);                                           \
195
0
            ct0 = _mm512_permutexvar_epi64(idx, ct0);                               \
196
0
            b1 = _mm512_mask_blend_epi64(0xFC, b1, ct0);                            \
197
0
                                                                                    \
198
0
            /* --- 16-block (4 x zmm) main loop ---                             */  \
199
0
            while (blocks >= 16) {                                                  \
200
0
                __m128i last;                                                       \
201
0
                                                                                    \
202
0
                a1 = _mm512_loadu_si512(pa);                                        \
203
0
                a2 = _mm512_loadu_si512(pa + 4);                                    \
204
0
                a3 = _mm512_loadu_si512(pa + 8);                                    \
205
0
                a4 = _mm512_loadu_si512(pa + 12);                                   \
206
0
                                                                                    \
207
0
                b2 = _mm512_loadu_si512(pa + 3);                                    \
208
0
                b3 = _mm512_loadu_si512(pa + 7);                                    \
209
0
                b4 = _mm512_loadu_si512(pa + 11);                                   \
210
0
                                                                                    \
211
0
                last = _mm_loadu_si128(pa + 15);                                    \
212
0
                                                                                    \
213
0
                AesDec_4x512_##NR(&a1, &a2, &a3, &a4, rk);                          \
214
0
                                                                                    \
215
0
                a1 = _mm512_xor_si512(a1, b1);                                      \
216
0
                a2 = _mm512_xor_si512(a2, b2);                                      \
217
0
                a3 = _mm512_xor_si512(a3, b3);                                      \
218
0
                a4 = _mm512_xor_si512(a4, b4);                                      \
219
0
                                                                                    \
220
0
                _mm512_storeu_si512(po, a1);                                        \
221
0
                _mm512_storeu_si512(po + 1, a2);                                    \
222
0
                _mm512_storeu_si512(po + 2, a3);                                    \
223
0
                _mm512_storeu_si512(po + 3, a4);                                    \
224
0
                                                                                    \
225
0
                /* Build next b1 from last ciphertext block                     */  \
226
0
                b1 = _mm512_maskz_loadu_epi64(0x03, &last);                         \
227
0
                if (blocks > 16) {                                                  \
228
0
                    size_t rem = blocks - 16;                                       \
229
0
                    /* Load only available lookahead blocks to avoid OOB read. */   \
230
0
                    /* One AES block is 16 bytes, it maps to two 64-bit lanes. */   \
231
0
                    /* 0x03 (00000011) for 1 block (2 lanes), */                    \
232
0
                    /* 0x0F (00001111) for 2 blocks (4 lanes), */                   \
233
0
                    /* 0x3F (00111111) for 3 or more blocks (6 lanes). */           \
234
0
                    __mmask8 nxmask = (rem >= 3) ? 0x3F : (rem == 2 ? 0x0F : 0x03); \
235
0
                    __m512i nx = _mm512_maskz_loadu_epi64(nxmask, pa + 16);         \
236
0
                    nx = _mm512_permutexvar_epi64(idx, nx);                         \
237
0
                    b1 = _mm512_mask_blend_epi64(0xFC, b1, nx);                     \
238
0
                }                                                                   \
239
0
                                                                                    \
240
0
                pa += 16;                                                           \
241
0
                po += 4;                                                            \
242
0
                blocks -= 16;                                                       \
243
0
            }                                                                       \
244
0
                                                                                    \
245
0
            /* --- 8-block (2 x zmm) ---                                        */  \
246
0
            if (blocks >= 8) {                                                      \
247
0
                __m128i last8;                                                      \
248
0
                                                                                    \
249
0
                a1 = _mm512_loadu_si512(pa);                                        \
250
0
                a2 = _mm512_loadu_si512(pa + 4);                                    \
251
0
                b2 = _mm512_loadu_si512(pa + 3);                                    \
252
0
                last8 = _mm_loadu_si128(pa + 7);                                    \
253
0
                                                                                    \
254
0
                AesDec_2x512_##NR(&a1, &a2, rk);                                    \
255
0
                a1 = _mm512_xor_si512(a1, b1);                                      \
256
0
                a2 = _mm512_xor_si512(a2, b2);                                      \
257
0
                                                                                    \
258
0
                _mm512_storeu_si512(po, a1);                                        \
259
0
                _mm512_storeu_si512(po + 1, a2);                                    \
260
0
                                                                                    \
261
0
                b1 = _mm512_maskz_loadu_epi64(0x03, &last8);                        \
262
0
                pa += 8;                                                            \
263
0
                po += 2;                                                            \
264
0
                blocks -= 8;                                                        \
265
0
                                                                                    \
266
0
                if (blocks >= 4) {                                                  \
267
0
                    __m512i nx = _mm512_loadu_si512(pa);                            \
268
0
                    nx = _mm512_permutexvar_epi64(idx, nx);                         \
269
0
                    b1 = _mm512_mask_blend_epi64(0xFC, b1, nx);                     \
270
0
                }                                                                   \
271
0
            }                                                                       \
272
0
                                                                                    \
273
0
            /* --- 4-block (1 x zmm) ---                                        */  \
274
0
            if (blocks >= 4) {                                                      \
275
0
                __m128i last4;                                                      \
276
0
                                                                                    \
277
0
                a1 = _mm512_loadu_si512(pa);                                        \
278
0
                last4 = _mm_loadu_si128(pa + 3);                                    \
279
0
                                                                                    \
280
0
                AesDec_1x512_##NR(&a1, rk);                                         \
281
0
                a1 = _mm512_xor_si512(a1, b1);                                      \
282
0
                _mm512_storeu_si512(po, a1);                                        \
283
0
                                                                                    \
284
0
                b1 = _mm512_maskz_loadu_epi64(0x03, &last4);                        \
285
0
                pa += 4;                                                            \
286
0
                po += 1;                                                            \
287
0
                blocks -= 4;                                                        \
288
0
            }                                                                       \
289
0
                                                                                    \
290
0
            /* --- Remaining 1-3 blocks ---                                     */  \
291
0
            {                                                                       \
292
0
                __m128i *po128 = (__m128i *)po;                                     \
293
0
                while (blocks > 0) {                                                \
294
0
                    __m128i ct = _mm_loadu_si128(pa);                               \
295
0
                    a1 = _mm512_maskz_loadu_epi64(0x03, pa);                        \
296
0
                    AesDec_1x512_##NR(&a1, rk);                                     \
297
0
                    a1 = _mm512_xor_si512(a1, b1);                                  \
298
0
                    _mm512_mask_storeu_epi64(po128, 0x03, a1);                      \
299
0
                    b1 = _mm512_maskz_loadu_epi64(0x03, &ct);                       \
300
0
                    pa++;                                                           \
301
0
                    po128++;                                                        \
302
0
                    blocks--;                                                       \
303
0
                }                                                                   \
304
0
            }                                                                       \
305
0
        } else {                                                                    \
306
0
            /* Less than 4 blocks -- process individually                       */  \
307
0
            __m128i *po128 = (__m128i *)po;                                         \
308
0
            b1 = _mm512_maskz_loadu_epi64(0x03, iv);                                \
309
0
            while (blocks > 0) {                                                    \
310
0
                __m128i ct = _mm_loadu_si128(pa);                                   \
311
0
                a1 = _mm512_maskz_loadu_epi64(0x03, pa);                            \
312
0
                AesDec_1x512_##NR(&a1, rk);                                         \
313
0
                a1 = _mm512_xor_si512(a1, b1);                                      \
314
0
                _mm512_mask_storeu_epi64(po128, 0x03, a1);                          \
315
0
                b1 = _mm512_maskz_loadu_epi64(0x03, &ct);                           \
316
0
                pa++;                                                               \
317
0
                po128++;                                                            \
318
0
                blocks--;                                                           \
319
0
            }                                                                       \
320
0
        }                                                                           \
321
0
                                                                                    \
322
0
        if (has_blocks)                                                             \
323
0
            _mm_storeu_si128((__m128i *)iv, saved_iv);                              \
324
0
                                                                                    \
325
0
        /* Erase the broadcast schedule and the volatile vector register bank. */   \
326
0
        ossl_aes_cbc_vaes_cleanup(rk, NR + 1);                                      \
327
0
    }
Unexecuted instantiation: aes_cbc_vaes_intrinsic.c:cbc_decrypt_10
Unexecuted instantiation: aes_cbc_vaes_intrinsic.c:cbc_decrypt_12
Unexecuted instantiation: aes_cbc_vaes_intrinsic.c:cbc_decrypt_14
328
329
DEFINE_CBC_DECRYPT(10) /* AES-128 */
330
DEFINE_CBC_DECRYPT(12) /* AES-192 */
331
DEFINE_CBC_DECRYPT(14) /* AES-256 */
332
333
/* ------------------------------------------------------------------ */
334
/* Public entry point                                                 */
335
/* ------------------------------------------------------------------ */
336
337
void ossl_aes_cbc_vaes_decrypt(const unsigned char *in, unsigned char *out,
338
    size_t len, const void *key,
339
    unsigned char ivec[16], int enc)
340
0
{
341
0
    size_t full_bytes;
342
0
    int nr = ((const AES_KEY *)key)->rounds + 1;
343
344
0
    if (len == 0)
345
0
        return;
346
347
    /* VAES path only optimises decryption; encrypt falls back to asm */
348
0
    if (enc) {
349
0
        aesni_cbc_encrypt(in, out, len, (const AES_KEY *)key, ivec, enc);
350
0
        return;
351
0
    }
352
353
0
    full_bytes = (len / AES_BLOCK_SIZE) * AES_BLOCK_SIZE;
354
0
    if (full_bytes > 0) {
355
0
        switch (nr) {
356
0
        case 10:
357
0
            cbc_decrypt_10(in, out, full_bytes, (const AES_KEY *)key, ivec);
358
0
            break;
359
0
        case 12:
360
0
            cbc_decrypt_12(in, out, full_bytes, (const AES_KEY *)key, ivec);
361
0
            break;
362
0
        case 14:
363
0
            cbc_decrypt_14(in, out, full_bytes, (const AES_KEY *)key, ivec);
364
0
            break;
365
0
        default: /* invalid key size */
366
0
            aesni_cbc_encrypt(in, out, len, (const AES_KEY *)key, ivec, 0);
367
0
            break;
368
0
        }
369
0
    }
370
0
}
371
372
/* ------------------------------------------------------------------ */
373
/* CPU feature check                                                  */
374
/* ------------------------------------------------------------------ */
375
376
int ossl_aes_cbc_vaes_eligible(void)
377
879
{
378
879
    return ossl_aes_cbc_vaes_cleanup_eligible()
379
879
        && (OPENSSL_ia32cap_P[2] & (1 << 16)) /* AVX512F            */
380
0
        && (OPENSSL_ia32cap_P[2] & (1 << 17)) /* AVX512DQ           */
381
0
        && (OPENSSL_ia32cap_P[2] & (1 << 30)) /* AVX512BW           */
382
0
        && (OPENSSL_ia32cap_P[3] & (1 << 9)); /* AVX512VAES         */
383
879
}
384
385
OPENSSL_UNTARGET_VAES512
386
387
#undef OPENSSL_TARGET_VAES512
388
#undef OPENSSL_UNTARGET_VAES512
389
#undef STRINGIFY_IMPL_
390
#undef STRINGIFY_
391
#undef OSSL_FUNC_ALWAYS_INLINE
392
#undef OSSL_FUNC_NOINLINE
393
#endif /* VAES_CBC_ELIGIBLE */