Hacktoberfest 2026 : les issues que les mainteneurs ont marquées pour octobre, ouvertes et accessibles aux débutants. Parcourir les issues Hacktoberfest

Cached EC curves leak when the Linux module is unloaded after composite key allocation

Ouverte
#63 1 commentaire 0 réactions 0 personnes assignées Voir sur GitHub

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

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 main commit: 286762b7730e2b780678f5ab11fef2b1bad639e0 (SymCrypt 103.13.0).
  • Ubuntu 24.04.4 LTS, x86-64, running under WSL.
  • GCC 13.3.0.
  • Normal RelWithDebInfo Linux generic shared-module build.
  • SYMCRYPT_FIPS_BUILD=ON and SYMCRYPT_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

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

  1. Lisez l'issue en entier, puis le guide de contribution du projet.
  2. Signalez en commentaire que vous la prenez — cela évite que deux personnes fassent le même travail.
  3. Forkez le dépôt et travaillez sur une branche.
  4. Ouvrez une pull request qui référence le numéro de l'issue.

Autres issues de microsoft/SymCrypt

Toutes les issues de microsoft/SymCrypt

Issues similaires

Plus d'issues C

Recevez les nouvelles issues par e-mail

Un résumé court des issues GitHub adaptées aux débutants.