Skip to content

Commit 3c49fe7

Browse files
authored
Remove dead P-256 nistz asm under OPENSSL_SMALL and extend the size-check harness (#3350)
1 parent 5388775 commit 3c49fe7

3 files changed

Lines changed: 139 additions & 5 deletions

File tree

.github/workflows/openssl-small.yml

Lines changed: 66 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,19 @@ jobs:
6565
cmake -GNinja -B build-small -DCMAKE_BUILD_TYPE=Release \
6666
-DBUILD_SHARED_LIBS=0 -DBUILD_TESTING=OFF -DOPENSSL_SMALL=ON
6767
cmake --build build-small --target crypto
68+
- name: Build libcrypto.a (OPENSSL_SMALL, section-GC)
69+
# Same as build-small but compiled with -ffunction-sections
70+
# -fdata-sections, so --gc-sections can strip at function granularity
71+
# inside bcm.c.o. This mirrors how aws-lc-rs (CcBuilder) compiles the
72+
# library, and is the configuration where anchor-driven retention is
73+
# measurable: without it, any reference into bcm.c.o retains the whole
74+
# object and masks what the anchors actually pin.
75+
run: |
76+
cmake -GNinja -B build-small-gc -DCMAKE_BUILD_TYPE=Release \
77+
-DBUILD_SHARED_LIBS=0 -DBUILD_TESTING=OFF -DOPENSSL_SMALL=ON \
78+
-DCMAKE_C_FLAGS="-ffunction-sections -fdata-sections" \
79+
-DCMAKE_CXX_FLAGS="-ffunction-sections -fdata-sections"
80+
cmake --build build-small-gc --target crypto
6881
- name: Link consumer against each libcrypto and compare
6982
env:
7083
EXPECTED_REDUCTION_PERCENT: ${{ matrix.expected_reduction_percent }}
@@ -78,6 +91,8 @@ jobs:
7891
./consumer-small
7992
size_default=$(stat -c %s consumer-default)
8093
size_small=$(stat -c %s consumer-small)
94+
archive_default=$(stat -c %s build-default/crypto/libcrypto.a)
95+
archive_small=$(stat -c %s build-small/crypto/libcrypto.a)
8196
if [ "$size_default" -le 0 ]; then
8297
echo "ERROR: default consumer binary has zero size"
8398
exit 1
@@ -86,18 +101,64 @@ jobs:
86101
{
87102
echo "## OPENSSL_SMALL static-link size (${{ matrix.name }})"
88103
echo ""
89-
echo "| Build | Consumer binary |"
90-
echo "|-------|-----------------|"
91-
echo "| Default | $(numfmt --to=iec "$size_default") (${size_default} B) |"
92-
echo "| OPENSSL_SMALL | $(numfmt --to=iec "$size_small") (${size_small} B) |"
93-
echo "| **Reduction** | **${reduction}%** |"
104+
echo "| Build | libcrypto.a | Consumer binary |"
105+
echo "|-------|-------------|-----------------|"
106+
echo "| Default | $(numfmt --to=iec "$archive_default") | $(numfmt --to=iec "$size_default") (${size_default} B) |"
107+
echo "| OPENSSL_SMALL | $(numfmt --to=iec "$archive_small") | $(numfmt --to=iec "$size_small") (${size_small} B) |"
108+
echo "| **Reduction** | | **${reduction}%** |"
94109
} >> "$GITHUB_STEP_SUMMARY"
95110
echo "default=${size_default} small=${size_small} reduction=${reduction}%"
96111
if [ "$reduction" -lt "$EXPECTED_REDUCTION_PERCENT" ]; then
97112
echo "FAIL: expected >= ${EXPECTED_REDUCTION_PERCENT}% reduction, got ${reduction}%"
98113
exit 1
99114
fi
100115
echo "PASS: ${reduction}% reduction meets the ${EXPECTED_REDUCTION_PERCENT}% threshold"
116+
- name: Measure GC-enabled consumer and EVP key-parsing anchor
117+
# Intentionally single-toolchain (clang + GNU ld on Linux, like the
118+
# whole job): the matrix legs vary only by architecture, so the
119+
# reported numbers are per-arch, not a gcc cross-check. The anchor
120+
# cost is a reachability property and ~compiler-independent anyway.
121+
run: |
122+
set -euo pipefail
123+
# Link against the section-GC library build (the aws-lc-rs-like
124+
# configuration). The EVP-parse variant adds one
125+
# EVP_parse_public_key call, which anchors asn1_evp_pkey_methods[]
126+
# (crypto/evp_extra/p_methods.c) and with it every key type's ASN.1
127+
# machinery -- currently including all of ML-KEM/ML-DSA -- even
128+
# under full section GC. The delta between the two binaries
129+
# quantifies what key-parsing consumers inherit. Advisory only; not
130+
# thresholded (yet).
131+
common="-O2 -I include -ffunction-sections -fdata-sections"
132+
libs="-lpthread -ldl -lm -Wl,--gc-sections"
133+
clang $common tests/binary-size/main.c build-small-gc/crypto/libcrypto.a $libs -o consumer-gc
134+
clang $common -DAWSLC_SIZE_CHECK_EVP_PARSE tests/binary-size/main.c \
135+
build-small-gc/crypto/libcrypto.a $libs -o consumer-gc-evp
136+
echo "Sanity: run the GC-enabled consumers"
137+
./consumer-gc
138+
./consumer-gc-evp
139+
size_gc=$(stat -c %s consumer-gc)
140+
size_gc_evp=$(stat -c %s consumer-gc-evp)
141+
evp_overhead=$(( size_gc_evp - size_gc ))
142+
{
143+
echo "### GC-enabled consumer (OPENSSL_SMALL + section GC, ${{ matrix.name }})"
144+
echo ""
145+
echo "| Consumer | Binary size |"
146+
echo "|----------|-------------|"
147+
echo "| base | $(numfmt --to=iec "$size_gc") (${size_gc} B) |"
148+
echo "| base + \`EVP_parse_public_key\` | $(numfmt --to=iec "$size_gc_evp") (${size_gc_evp} B) |"
149+
echo "| **EVP key-parsing anchor cost** | **$(numfmt --to=iec "$evp_overhead") (${evp_overhead} B)** |"
150+
} >> "$GITHUB_STEP_SUMMARY"
151+
echo "gc=${size_gc} gc_evp=${size_gc_evp} evp_overhead=${evp_overhead}"
152+
for bin in consumer-gc consumer-gc-evp; do
153+
{
154+
echo "<details><summary>Largest retained symbols: ${bin}</summary>"
155+
echo ""
156+
echo '```'
157+
nm -S --size-sort --radix=d "$bin" | tail -25 | tac
158+
echo '```'
159+
echo "</details>"
160+
} >> "$GITHUB_STEP_SUMMARY"
161+
done
101162
102163
small-tests:
103164
if: github.repository_owner == 'aws'

crypto/fipsmodule/CMakeLists.txt

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,26 @@ if(ARCH STREQUAL "ppc64le")
117117
)
118118
endif()
119119

120+
# Under OPENSSL_SMALL, the P-256 nistz implementation (p256-nistz.c) is
121+
# compiled out entirely -- ec.c dispatches P-256 to the fiat-crypto
122+
# implementation (p256.c) instead -- leaving the nistz assembly fully
123+
# unreferenced on both x86_64 and aarch64. Drop it from the build. (The
124+
# large precomputed base-point table is in p256-nistz-table.h, included
125+
# from C code that is already gated, not in the assembly.)
126+
if(OPENSSL_SMALL)
127+
if(ARCH STREQUAL "x86_64")
128+
list(REMOVE_ITEM BCM_ASM_SOURCES
129+
p256-x86_64-asm.${ASM_EXT}
130+
p256_beeu-x86_64-asm.${ASM_EXT}
131+
)
132+
elseif(ARCH STREQUAL "aarch64")
133+
list(REMOVE_ITEM BCM_ASM_SOURCES
134+
p256-armv8-asm.${ASM_EXT}
135+
p256_beeu-armv8-asm.${ASM_EXT}
136+
)
137+
endif()
138+
endif()
139+
120140
if(PERL_EXECUTABLE)
121141
perlasm(aesni-gcm-x86_64.${ASM_EXT} modes/asm/aesni-gcm-x86_64.pl)
122142
perlasm(aesni-gcm-avx512.${ASM_EXT} modes/asm/aesni-gcm-avx512.pl)

tests/binary-size/main.c

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,14 @@
88
// workflow builds this twice -- against a default libcrypto and against one
99
// built with OPENSSL_SMALL -- and compares the resulting binary sizes.
1010
//
11+
// When AWSLC_SIZE_CHECK_EVP_PARSE is defined, the consumer additionally
12+
// marshals and parses an EVP public key. Parsing any EVP key references the
13+
// asn1_evp_pkey_methods[] table (crypto/evp_extra/p_methods.c), which
14+
// transitively retains every linked-in key type's ASN.1 machinery --
15+
// including, at present, all of ML-KEM and ML-DSA -- even under
16+
// --gc-sections. The workflow builds this variant too and reports the size
17+
// delta, quantifying what key-parsing consumers inherit.
18+
//
1119
// It is intentionally small and standalone (compiled directly by the workflow,
1220
// not part of the main CMake build).
1321

@@ -22,6 +30,12 @@
2230
#include <openssl/nid.h>
2331
#include <openssl/sha.h>
2432

33+
#if defined(AWSLC_SIZE_CHECK_EVP_PARSE)
34+
#include <openssl/bytestring.h>
35+
#include <openssl/evp.h>
36+
#include <openssl/mem.h>
37+
#endif
38+
2539
static int do_sha256(void) {
2640
uint8_t in[64], out[SHA256_DIGEST_LENGTH];
2741
memset(in, 7, sizeof(in));
@@ -71,12 +85,51 @@ static int do_ed25519(void) {
7185
return ED25519_verify(msg, sizeof(msg), sig, public_key);
7286
}
7387

88+
#if defined(AWSLC_SIZE_CHECK_EVP_PARSE)
89+
static int do_evp_parse(void) {
90+
uint8_t public_key[32], private_key[64];
91+
ED25519_keypair(public_key, private_key);
92+
EVP_PKEY *pkey = EVP_PKEY_new_raw_public_key(EVP_PKEY_ED25519, NULL,
93+
public_key, sizeof(public_key));
94+
if (pkey == NULL) {
95+
return 0;
96+
}
97+
// Marshal to a DER SubjectPublicKeyInfo and parse it back.
98+
// |EVP_parse_public_key| is the call that anchors the full EVP method
99+
// table (see the file comment).
100+
int ok = 0;
101+
uint8_t *der = NULL;
102+
size_t der_len;
103+
CBB cbb;
104+
if (CBB_init(&cbb, 64) && EVP_marshal_public_key(&cbb, pkey) &&
105+
CBB_finish(&cbb, &der, &der_len)) {
106+
CBS cbs;
107+
CBS_init(&cbs, der, der_len);
108+
EVP_PKEY *parsed = EVP_parse_public_key(&cbs);
109+
ok = parsed != NULL && CBS_len(&cbs) == 0 &&
110+
EVP_PKEY_cmp(pkey, parsed) == 1;
111+
EVP_PKEY_free(parsed);
112+
} else {
113+
CBB_cleanup(&cbb);
114+
}
115+
OPENSSL_free(der);
116+
EVP_PKEY_free(pkey);
117+
return ok;
118+
}
119+
#endif
120+
74121
int main(void) {
75122
if (!do_sha256() || !do_aes_256_gcm() || !do_ecdsa_p256() || !do_x25519() ||
76123
!do_ed25519()) {
77124
fprintf(stderr, "crypto self-check failed\n");
78125
return 1;
79126
}
127+
#if defined(AWSLC_SIZE_CHECK_EVP_PARSE)
128+
if (!do_evp_parse()) {
129+
fprintf(stderr, "EVP parse self-check failed\n");
130+
return 1;
131+
}
132+
#endif
80133
printf("ok\n");
81134
return 0;
82135
}

0 commit comments

Comments
 (0)