Hacktoberfest 2026:维护者为十月标记出来的 issue,仍然开放、适合新手。 浏览 Hacktoberfest issue

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

未关闭
#63 1 条评论 0 个 reaction 已指派 0 人 在 GitHub 查看

还没有人认领这个 Issue。

评估

难度
4/5
预计耗时
3-5 天
新手友好度
55/100
Issue 类型
缺陷
描述清晰度
基本清楚
活跃度
活跃
技术栈
c, linux

调研方向

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.

由索引模型根据 Issue 内容生成。

描述

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.

主要语言
C
星标
890
派生
91
PR 合并指标
30 天内没有已合并 PR

环境准备

我们还没有检查这个项目的环境配置文件。先看它的 README,通用步骤见我们的新手贡献指南。

从这里开始

  1. 先读完整个 Issue,再读项目的贡献指南。
  2. 在 Issue 下留言说明你要接手 —— 这能避免两个人做同样的事。
  3. Fork 仓库,在一个分支上完成修改。
  4. 提交 Pull Request,并在描述里引用这个 Issue 编号。

microsoft/SymCrypt 的其他 Issue

查看 microsoft/SymCrypt 的全部 Issue

相似的 Issue

更多 C Issue

把新 issue 发到你的邮箱

精选适合新手参与的 GitHub issue 摘要。