aws / aws/aws-lc

AES-GCM seal/open scatter paths redundantly zero-initialize the full GCM128_CONTEXT on every call (~2.7% seal throughput at QUIC packet sizes)

Open Beginner friendly
#3,334 1 comment 0 reactions 0 assignees View on GitHub
Dominant language
Assembly
Stars
830
Forks
212
Avg merge
2d 22h
Merged PRs (30d)
61

Description

## Summary

`aead_aes_gcm_seal_scatter_impl` and `aead_aes_gcm_open_gather_impl` (`crypto/fipsmodule/cipher/e_aes.c`) do the following on **every** AEAD call:

```c
GCM128_CONTEXT gcm;
OPENSSL_memset(&gcm, 0, sizeof(gcm)); // ~360 bytes
OPENSSL_memcpy(&gcm.gcm_key, &gcm_ctx->gcm_key, sizeof(gcm.gcm_key)); // ~290 bytes
CRYPTO_gcm128_setiv(&gcm, key, nonce, nonce_len);
```

The full-struct memset appears to be redundant: `CRYPTO_gcm128_setiv` already initializes all per-message state (`Yi`, `Xi`, `len.aad`, `len.msg`, `ares`, `mres`, and `EK0` via the block call), and the only remaining field, `EKi`, is scratch keystream that is always written before being read (its reads are guarded by `mres`/`ares`, which `setiv` sets to 0). `gcm_key` is overwritten by the memcpy on the next line.

That is ~650 bytes of redundant memory traffic per sealed/opened message, which is measurable for small-message workloads — e.g. QUIC, where every packet is one seal/open and internet-MTU packets are ~1200-1450 bytes.

## Measurements

AES-128-GCM `EVP_AEAD_CTX_seal_scatter` via aws-lc-rs `seal_in_place_separate_tag`, Apple M1 Max, `aws-lc-sys` 0.42.0 symbol prefix, interleaved A/B (baseline vs memset removed), medians:

| message size | before | after | delta |
|---|---|---|---|
| 1200 B | 5.59 GB/s | 5.74 GB/s | **+2.7%** (3/3 pairs) |
| 1450 B | 5.44 GB/s | 5.59 GB/s | **+2.7%** (6/6 pairs) |
| 8950 B | 8.05 GB/s | 8.03 GB/s | ~0 (fixed cost amortized) |

The delta pattern (visible at small sizes, gone at large) matches a fixed per-call cost of roughly 7 ns. The open/gather path has the identical structure and should see the same benefit.

Context for why we noticed: profiling s2n-quic on Linux shows AES-GCM at 46-48% of endpoint CPU, so per-call overheads in this path are material for QUIC fleets running at ~1500 MTU.

## Caveats

- Numbers above are from an arm64 (M1) machine; we have not yet validated on Graviton or x86_64 (including the `gcm_setiv_avx512` early-return path, which is upstream of the removed memset and unaffected structurally).
- If the zero-init is intentionally defensive (e.g. for MSAN cleanliness or as a hedge against future fields being read before write), a narrower fix would be to memset only `EKi`, which still avoids most of the per-call cost.

All aws-lc-rs AEAD KATs and downstream s2n-quic crypto tests pass with the memset removed. Happy to send a PR — the change is ~4 lines plus comments in the two call sites.

cc @andrewhop

Contributor guide

Open the contributing guide

Research direction

Read crypto/fipsmodule/cipher/e_aes.c at aead_aes_gcm_seal_scatter_impl and aead_aes_gcm_open_gather_impl, then inspect CRYPTO_gcm128_setiv and the GCM128_CONTEXT fields it initializes. Run the existing AES-GCM AEAD KATs and downstream s2n-quic crypto tests. Done means the redundant per-call initialization is removed or narrowed without changing behavior or test results.

Written by the indexing model from the issue text.

Assessment

Tech stack
c
Domain
cryptography, performance
Issue type
Refactor
Difficulty
2/5
Estimated time
1-3 hours
Activity status
Quiet
Clarity
Clearly specified
Newbie friendliness
68/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.