Skip to content

valgrind reporting multiple memory leaks with libtls #1200

@TheTechsTech

Description

@TheTechsTech

This was initial to report a much bigger memory leak report, but after reading #762 #704 #687 and #730 it seems to be a known issue with no real solution.

Using OPENSSL_cleanup() reduced it to the following:

valgrind -s --leak-check=full --show-leak-kinds=all ./example
==13514== Memcheck, a memory error detector
==13514== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==13514== Using Valgrind-3.18.1 and LibVEX; rerun with -h for copyright info
==13514== Command: ./example
==13514==
HTTP/1.0 200 OK
Date: Sun, 05 Oct 2025 18:30:53 GMT
Expires: -1
Cache-Control: private, max-age=0
Content-Type: text/html; charset=ISO-8859-1
Content-Security-Policy-Report-Only: object-src 'none';base-uri 'self';script-src 'nonce-zJrL9sfw63uri4JWt6c1jA' 'strict-dynamic' 'report-sample' 'unsafe-eval' 'unsafe-inline' https: http:;report-uri https://csp.withgoogle.com/csp/gws/other-hp
Accept-CH: Sec-CH-Prefers-Color-Scheme
P3P: CP="This is not a P3P policy! See g.co/p3phelp for more info."
Server: gws
X-XSS-Protection: 0
X-Frame-Options: SAMEORIGIN
Set-Cookie: AEC=AaJma5uQAHJb-Pz6FK-qk1FvM64gbU3m2M7paKE-bSyQ2NDO5s11vxjuzT4; expires=Fri, 03-Apr-2026 18:30:53 GMT; path=/; domain=.google.com; Secure; HttpOnly; SameSite=lax
Set-Cookie: NID=525=MVeXBL7kgOHxWV5MUV8N-CmH31FnsVwA6Q7LMZys-_m4jjfWSYptvRXB2YvhVcLQYOtQJTJuoA9nzSn9GpvDpMLvVWO8NnPeXMwlP3Frvnehr4yuFH2thBLBdXA5ejGdTW5Q6YpXnjWbTZaSrF2DzUrSdtsYUE9HXFC_Iwv_x1ce9X8rWHK9kU95mocbTWF5O5kwFuanGfJd_XnvsUvucK8; expires=Mon, 06-Apr-2026 18:30:53 GMT; path=/; domain=.google.com; HttpOnly
Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000
Accept-Ranges: none
Vary: Accept-Encoding

<!doctype html><html itemscope="" itemtype="http://schema.org/WebPage" lang="en"><head><meta content="Search the world's information, including webpages, images, videos and more. Google has many special features to help

==13514== 
==13514== HEAP SUMMARY:
==13514==     in use at exit: 833 bytes in 5 blocks
==13514==   total heap usage: 3,208 allocs, 3,203 frees, 504,542 bytes allocated
==13514==
==13514== 17 bytes in 1 blocks are still reachable in loss record 1 of 5
==13514==    at 0x4848899: malloc (in /usr/libexec/valgrind/vgpreload_memcheck-amd64-linux.so)
==13514==    by 0x48ED157: __vasprintf_internal (vasprintf.c:71)
==13514==    by 0x48C59E9: asprintf (asprintf.c:31)
==13514==    by 0x4916751: strerror_l (strerror_l.c:45)
==13514==    by 0x1BF9A1: err_build_SYS_str_reasons (err.c:499)
==13514==    by 0x1BFFDD: ERR_load_ERR_strings_internal (err.c:684)
==13514==    by 0x1C0E8C: ERR_load_crypto_strings_internal (err_all.c:104)
==13514==    by 0x48FEEE7: __pthread_once_slow (pthread_once.c:116)
==13514==    by 0x1C0F28: ERR_load_crypto_strings (err_all.c:149)
==13514==    by 0x18F438: OPENSSL_init_crypto_internal (crypto_init.c:53)
==13514==    by 0x48FEEE7: __pthread_once_slow (pthread_once.c:116)
==13514==    by 0x18F4C3: OPENSSL_init_crypto (crypto_init.c:67)
==13514==
==13514== 24 bytes in 1 blocks are still reachable in loss record 2 of 5
==13514==    at 0x4848899: malloc (in /usr/libexec/valgrind/vgpreload_memcheck-amd64-linux.so)
==13514==    by 0x1D247C: lh_insert (lhash.c:273)
==13514==    by 0x1BF770: err_thread_set_item (err.c:422)
==13514==    by 0x1BFC5C: ERR_get_state (err.c:574)
==13514==    by 0x1C0331: ERR_clear_error (err.c:816)
==13514==    by 0x14131B: tls_handshake_client (tls_client.c:473)
==13514==    by 0x13FF63: tls_handshake (tls.c:854)
==13514==    by 0x140144: tls_write (tls.c:913)
==13514==    by 0x13BEDF: main (example.c:35)
==13514==
==13514== 72 bytes in 1 blocks are still reachable in loss record 3 of 5
==13514==    at 0x484DA83: calloc (in /usr/libexec/valgrind/vgpreload_memcheck-amd64-linux.so)
==13514==    by 0x1D2277: lh_new (lhash.c:212)
==13514==    by 0x1BF593: err_thread_get (err.c:369)
==13514==    by 0x1BF72B: err_thread_set_item (err.c:417)
==13514==    by 0x1BFC5C: ERR_get_state (err.c:574)
==13514==    by 0x1C0331: ERR_clear_error (err.c:816)
==13514==    by 0x14131B: tls_handshake_client (tls_client.c:473)
==13514==    by 0x13FF63: tls_handshake (tls.c:854)
==13514==    by 0x140144: tls_write (tls.c:913)
==13514==    by 0x13BEDF: main (example.c:35)
==13514==
==13514== 128 bytes in 1 blocks are still reachable in loss record 4 of 5
==13514==    at 0x484DA83: calloc (in /usr/libexec/valgrind/vgpreload_memcheck-amd64-linux.so)
==13514==    by 0x1D229B: lh_new (lhash.c:214)
==13514==    by 0x1BF593: err_thread_get (err.c:369)
==13514==    by 0x1BF72B: err_thread_set_item (err.c:417)
==13514==    by 0x1BFC5C: ERR_get_state (err.c:574)
==13514==    by 0x1C0331: ERR_clear_error (err.c:816)
==13514==    by 0x14131B: tls_handshake_client (tls_client.c:473)
==13514==    by 0x13FF63: tls_handshake (tls.c:854)
==13514==    by 0x140144: tls_write (tls.c:913)
==13514==    by 0x13BEDF: main (example.c:35)
==13514==
==13514== 592 bytes in 1 blocks are still reachable in loss record 5 of 5
==13514==    at 0x4848899: malloc (in /usr/libexec/valgrind/vgpreload_memcheck-amd64-linux.so)
==13514==    by 0x1BFBAA: ERR_get_state (err.c:564)
==13514==    by 0x1C0331: ERR_clear_error (err.c:816)
==13514==    by 0x14131B: tls_handshake_client (tls_client.c:473)
==13514==    by 0x13FF63: tls_handshake (tls.c:854)
==13514==    by 0x140144: tls_write (tls.c:913)
==13514==    by 0x13BEDF: main (example.c:35)
==13514==
==13514== LEAK SUMMARY:
==13514==    definitely lost: 0 bytes in 0 blocks
==13514==    indirectly lost: 0 bytes in 0 blocks
==13514==      possibly lost: 0 bytes in 0 blocks
==13514==    still reachable: 833 bytes in 5 blocks
==13514==         suppressed: 0 bytes in 0 blocks
==13514==
==13514== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)

The example:

#include <stdio.h>
#include <tls.h>
#include <openssl/crypto.h>

int main()
{
	struct tls *tls;
	struct tls_config *tls_config;
	ssize_t written, read;
	char buf[4096];

	if (tls_init() != 0) {
		fprintf(stderr, "tls_init failed");
		return 1;
	}

	if ((tls = tls_client()) == NULL)
		goto err;

	if ((tls_config = tls_config_new()) == NULL)
		goto err;

	if (tls_config_set_ciphers(tls_config, "compat") != 0)
		goto err;

	tls_config_insecure_noverifycert(tls_config);
	tls_config_insecure_noverifyname(tls_config);

	if (tls_configure(tls, tls_config) != 0)
		goto err;

	if (tls_connect(tls, "google.com", "443") != 0)
		goto err;

	if ((written = tls_write(tls, "GET /\r\n", 7)) < 0)
		goto err;

	if ((read = tls_read(tls, buf, sizeof(buf))) < 0)
		goto err;

	buf[read - 1] = '\0';
	puts(buf);

	if (tls_close(tls) != 0)
		goto err;

	tls_config_free(tls_config);
	tls_free(tls);

	OPENSSL_cleanup();
	return 0;

err:
	fprintf(stderr, "Error: %s\n", tls_error(tls));
	return 1;
}

I'm working on openTLS building your libtls only for the installed OpenSSL version, it's being used to develop c-asio.

Adding a atexit() routine for the never freed tls_config_default in tls.c solved my issue without using OPENSSL_cleanup.

The same example produces:

valgrind -s --leak-check=full --show-leak-kinds=all ./example

==6788== Memcheck, a memory error detector
==6788== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==6788== Using Valgrind-3.18.1 and LibVEX; rerun with -h for copyright info
==6788== Command: ./example
==6788==
HTTP/1.0 200 OK
Date: Sun, 05 Oct 2025 16:49:25 GMT
Expires: -1
...
...
...
...

==6788== 
==6788== HEAP SUMMARY:
==6788==     in use at exit: 0 bytes in 0 blocks
==6788==   total heap usage: 14,865 allocs, 14,865 frees, 2,158,115 bytes allocated
==6788==
==6788== All heap blocks were freed -- no leaks are possible
==6788==
==6788== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions