Cached EC curves leak when the Linux module is unloaded after composite key allocation
Personne n'a encore pris cette issue.
Évaluation
- Difficulté
- 4/5
- Temps estimé
- 3-5 jours
- Accessibilité débutants
- 55/100
- Type d'issue
- Bug
- Clarté
- Plutôt claire
- Activité
- Active
- Stack technique
- c, linux
- Domaine
- cryptography, operating-systems
Piste de recherche
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.
Rédigé par le modèle d'indexation à partir du texte de l'issue.
Description
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.
- Langage dominant
- C
- Étoiles
- 890
- Forks
- 91
- Métriques de merge des PR
- Aucune PR mergée en 30 j
Préparer son environnement
Nous n'avons pas encore vérifié les fichiers d'installation de ce projet. Commencez par son README, et consultez notre guide de la première contribution pour les étapes générales.
Par où commencer
- Lisez l'issue en entier, puis le guide de contribution du projet.
- Signalez en commentaire que vous la prenez — cela évite que deux personnes fassent le même travail.
- Forkez le dépôt et travaillez sur une branche.
- Ouvrez une pull request qui référence le numéro de l'issue.
Autres issues de microsoft/SymCrypt
-
Difficulté 1/5 Moins d'une heure Accessibilité débutants 85/100
-
bug compiler-support
Difficulté 4/5 3-5 jours Accessibilité débutants 42/100
-
enhancement
Difficulté 5/5 Plus d'une semaine Accessibilité débutants 25/100
-
Difficulté 5/5 Plus d'une semaine Accessibilité débutants 25/100
-
Difficulté 5/5 Plus d'une semaine Accessibilité débutants 25/100
Toutes les issues de microsoft/SymCrypt
Issues similaires
-
Difficulté 2/5 1-3 heures Accessibilité débutants 85/100
microsoft/ebpf-for-windows#5604 ·
Les mainteneurs répondent en général sous 3 jours
-
Difficulté 2/5 1-3 heures Accessibilité débutants 84/100
Les mainteneurs répondent en général sous 1 jour
-
Difficulté 2/5 1-3 heures Accessibilité débutants 70/100
Les mainteneurs répondent en général sous 1 jour
-
Difficulté 2/5 1-3 heures Accessibilité débutants 78/100
Les mainteneurs répondent en général sous 1 jour
-
Update OPENEXR_IMATH_TAGOuverte
Difficulté 1/5 Moins d'une heure Accessibilité débutants 84/100
AcademySoftwareFoundation/openexr#2683 ·
Les mainteneurs répondent en général sous 1 jour