Cached EC curves leak when the Linux module is unloaded after composite key allocation
Dieses Issue hat noch niemand übernommen.
Bewertung
- Schwierigkeit
- 4/5
- Geschätzter Aufwand
- 3-5 Tage
- Anfängerfreundlichkeit
- 55/100
- Issue-Typ
- Bug
- Klarheit
- Größtenteils klar
- Aktivitätsstatus
- Aktiv
- Tech-Stack
- c, linux
- Bereich
- cryptography, operating-systems
Rechercherichtung
Start with SymCryptCompositeMlDsakeyAllocate in lib/composite_mldsa.c, then trace SymCryptGetCachedEcurve and the static cache in lib/ec_internal_curves.c. Compare this with SymCryptModuleDestructor in modules/posix/common/module.c and run the ASan allocate and repeat modes from the reproducer. Done means repeated unloads no longer report the cached-curve allocations, or the supported module lifetime is explicitly documented.
Vom Indexierungsmodell aus dem Issue-Text verfasst.
Beschreibung
Summary
On Linux, allocating and freeing composite ML-DSA keys initializes cached EC curves that are not released when libsymcrypt.so is subsequently unloaded with dlclose.
A standalone reproducer using only SymCrypt's public APIs leaks 10,240 bytes in two allocations per load/unload cycle after using the P-256 and P-384 variants. Three cycles leak 30,720 bytes in six allocations. No OpenSSL or SCOSSL dependency is required, and key generation/signing is not necessary to reproduce it.
All caller-owned composite keys are freed before unloading the module. This report concerns module-lifetime resource cleanup, not a cryptographic correctness finding.
Environment
- Public
maincommit:286762b7730e2b780678f5ab11fef2b1bad639e0(SymCrypt 103.13.0). - Ubuntu 24.04.4 LTS, x86-64, running under WSL.
- GCC 13.3.0.
- Normal
RelWithDebInfoLinux generic shared-module build. SYMCRYPT_FIPS_BUILD=ONandSYMCRYPT_FIPS_POSTPROCESS=ON; no self-tests or integrity checks disabled.- Only the reproducer is ASan-instrumented. LeakSanitizer intercepts the shared library's allocations; the SymCrypt library itself does not need sanitizer instrumentation.
Reproduction
Build the pinned revision using the normal repository prerequisites, including scripts/requirements.txt:
git checkout 286762b7730e2b780678f5ab11fef2b1bad639e0
cmake -S . -B build-unload -DCMAKE_BUILD_TYPE=RelWithDebInfo -DSYMCRYPT_FIPS_BUILD=ON -DSYMCRYPT_FIPS_POSTPROCESS=ON
cmake --build build-unload -j4
Save the following as symcrypt-unload-repro.c in the repository root:
#include <dlfcn.h>
#include <stdio.h>
#include <string.h>
#include "symcrypt.h"
typedef VOID (SYMCRYPT_CALL *init_fn)(UINT32, UINT32);
typedef PSYMCRYPT_COMPOSITE_MLDSAKEY (SYMCRYPT_CALL *allocate_fn)(
SYMCRYPT_COMPOSITE_MLDSA_PARAMS);
typedef VOID (SYMCRYPT_CALL *free_fn)(PSYMCRYPT_COMPOSITE_MLDSAKEY);
int main(int argc, char **argv)
{
if (argc != 3 || (strcmp(argv[2], "allocate") &&
strcmp(argv[2], "repeat") && strcmp(argv[2], "load-only") &&
strcmp(argv[2], "keep-loaded")))
{
fprintf(stderr, "Usage: %s LIBRARY allocate|repeat|load-only|keep-loaded\n", argv[0]);
return 2;
}
int cycles = strcmp(argv[2], "repeat") == 0 ? 3 : 1;
for (int i = 0; i < cycles; ++i)
{
void *module = dlopen(argv[1], RTLD_NOW | RTLD_LOCAL);
if (module == NULL)
{
fprintf(stderr, "dlopen: %s\n", dlerror());
return 2;
}
init_fn init = (init_fn)dlsym(module, "SymCryptModuleInit");
allocate_fn allocate = (allocate_fn)dlsym(module, "SymCryptCompositeMlDsakeyAllocate");
free_fn release = (free_fn)dlsym(module, "SymCryptCompositeMlDsakeyFree");
if (init == NULL || allocate == NULL || release == NULL)
{
fprintf(stderr, "Required exported function missing\n");
dlclose(module);
return 2;
}
init(103, 12);
if (strcmp(argv[2], "load-only") != 0)
{
PSYMCRYPT_COMPOSITE_MLDSAKEY a = allocate(
SYMCRYPT_COMPOSITE_MLDSA_PARAMS_MLDSA44_ECDSA_P256_SHA256);
PSYMCRYPT_COMPOSITE_MLDSAKEY b = allocate(
SYMCRYPT_COMPOSITE_MLDSA_PARAMS_MLDSA65_ECDSA_P384_SHA512);
int ok = a != NULL && b != NULL;
if (a != NULL)
release(a);
if (b != NULL)
release(b);
if (!ok)
{
fprintf(stderr, "Key allocation failed\n");
dlclose(module);
return 2;
}
}
if (strcmp(argv[2], "keep-loaded") != 0 && dlclose(module) != 0)
{
fprintf(stderr, "dlclose: %s\n", dlerror());
return 2;
}
printf("Completed cycle %d (%s)\n", i + 1, argv[2]);
}
return 0;
}
Compile without linking SymCrypt directly, so that dlclose can unload it:
gcc -std=c11 -Wall -Wextra -Werror -Wno-unknown-pragmas -g -O0 -fsanitize=address -fno-omit-frame-pointer -Iinc symcrypt-unload-repro.c -ldl -o symcrypt-unload-repro
Run each mode in a separate process, without preloading SymCrypt:
ASAN_OPTIONS=detect_leaks=1:fast_unwind_on_malloc=0 ./symcrypt-unload-repro ./build-unload/module/generic/libsymcrypt.so load-only
ASAN_OPTIONS=detect_leaks=1:fast_unwind_on_malloc=0 ./symcrypt-unload-repro ./build-unload/module/generic/libsymcrypt.so allocate
ASAN_OPTIONS=detect_leaks=1:fast_unwind_on_malloc=0 ./symcrypt-unload-repro ./build-unload/module/generic/libsymcrypt.so repeat
ASAN_OPTIONS=detect_leaks=1:fast_unwind_on_malloc=0 ./symcrypt-unload-repro ./build-unload/module/generic/libsymcrypt.so keep-loaded
Observed results
| Mode | Behavior | LeakSanitizer result | Exit status |
|---|---|---|---|
load-only |
Load/init/unload, no composite allocation | No leak reported | 0 |
allocate |
Allocate P-256/P-384 composite keys, free both, unload | 10,240 bytes in 2 allocations | 1 |
repeat |
Repeat the allocate/free/unload sequence three times | 30,720 bytes in 6 allocations | 1 |
keep-loaded |
Allocate/free both keys, retain module until process exit | No leak reported | 0 |
The allocate report contains two direct leaks of 5,120 bytes each, with allocation stacks through aligned_alloc and the two calls to SymCryptCompositeMlDsakeyAllocate:
SUMMARY: AddressSanitizer: 10240 byte(s) leaked in 2 allocation(s).
The repeated-unload case reports:
SUMMARY: AddressSanitizer: 30720 byte(s) leaked in 6 allocation(s).
SymCrypt stack frames appear as <unknown module> after dlclose. Keeping the module resident leaves the caches reachable and avoids the report; that is a lifetime workaround, not an unload-cleanup fix.
Relevant source
SymCryptCompositeMlDsakeyAllocateobtains its curve throughSymCryptGetCachedEcurve.lib/ec_internal_curves.clazily allocates curves and stores them in staticrgpCachedCurves. It frees a losing allocation during concurrent initialization, but has no cache teardown.SymCryptModuleDestructorcallsSymCryptRngUninit()but does not release the cached curves.
Expected behavior
Once all caller-owned keys have been freed, unloading the module should release module-owned curve caches, rather than accumulating allocations across repeated loads.
If unloading after composite use is intentionally unsupported, please clarify/document that lifetime requirement. Otherwise, a module-owned teardown path appears appropriate; callers should not free internal shared curve pointers.
- Vorherrschende Sprache
- C
- Sterne
- 890
- Forks
- 91
- PR-Merge-Kennzahlen
- Keine gemergten PRs in 30 T.
Entwicklungsumgebung
Die Einrichtungsdateien dieses Projekts haben wir noch nicht geprüft. Beginnen Sie mit der README; die allgemeinen Schritte stehen in unserem Leitfaden für den ersten Beitrag.
Erste Schritte
- Lesen Sie das ganze Issue und danach den Beitragsleitfaden des Projekts.
- Schreiben Sie ins Issue, dass Sie es übernehmen — das erspart doppelte Arbeit.
- Forken Sie das Repository und arbeiten Sie in einem Branch.
- Öffnen Sie einen Pull Request, der die Issue-Nummer nennt.
Mehr aus microsoft/SymCrypt
-
Schwierigkeit 1/5 Unter einer Stunde Anfängerfreundlichkeit 85/100
-
bug compiler-support
Schwierigkeit 4/5 3-5 Tage Anfängerfreundlichkeit 42/100
-
enhancement
Schwierigkeit 5/5 Über eine Woche Anfängerfreundlichkeit 25/100
-
Schwierigkeit 5/5 Über eine Woche Anfängerfreundlichkeit 25/100
-
Schwierigkeit 5/5 Über eine Woche Anfängerfreundlichkeit 25/100
Alle Issues in microsoft/SymCrypt
Ähnliche Issues
-
Schwierigkeit 2/5 1-3 Stunden Anfängerfreundlichkeit 84/100
-
Schwierigkeit 1/5 Unter einer Stunde Anfängerfreundlichkeit 92/100
-
[Issue]: Headers - vx_ext_amd.h does not compile as C (enum types used without the enum keyword)Offen
Schwierigkeit 1/5 Unter einer Stunde Anfängerfreundlichkeit 92/100
Maintainer antworten meist innerhalb von 1 Tag
-
Schwierigkeit 2/5 1-3 Stunden Anfängerfreundlichkeit 84/100
-
Schwierigkeit 2/5 1-3 Stunden Anfängerfreundlichkeit 88/100
Qiskit/qiskit#17079 · 1 Kommentar ·
Maintainer antworten meist innerhalb von 1 Tag