Skip to content

Commit 493b771

Browse files
authoredNov 12, 2024
feat: Reworking cleanup behavior (#4871)
1 parent 0f96548 commit 493b771

File tree

9 files changed

+56
-65
lines changed

9 files changed

+56
-65
lines changed
 

‎api/s2n.h

+2-8
Original file line numberDiff line numberDiff line change
@@ -229,8 +229,8 @@ S2N_API extern unsigned long s2n_get_openssl_version(void);
229229
S2N_API extern int s2n_init(void);
230230

231231
/**
232-
* Cleans up any internal resources used by s2n-tls. This function should be called from each thread or process
233-
* that is created subsequent to calling `s2n_init` when that thread or process is done calling other s2n-tls functions.
232+
* Cleans up thread-local resources used by s2n-tls. Does not perform a full library cleanup. To fully
233+
* clean up the library use s2n_cleanup_final().
234234
*
235235
* @returns S2N_SUCCESS on success. S2N_FAILURE on failure
236236
*/
@@ -239,12 +239,6 @@ S2N_API extern int s2n_cleanup(void);
239239
/*
240240
* Performs a complete deinitialization and cleanup of the s2n-tls library.
241241
*
242-
* s2n_cleanup_final will always perform a complete cleanup. In contrast,
243-
* s2n_cleanup will only perform a complete cleanup if the atexit handler
244-
* is disabled and s2n_cleanup is called by the thread that called s2n_init.
245-
* Therefore s2n_cleanup_final should be used instead of s2n_cleanup in cases
246-
* where the user needs full control over when the complete cleanup executes.
247-
*
248242
* @returns S2N_SUCCESS on success. S2N_FAILURE on failure
249243
*/
250244
S2N_API extern int s2n_cleanup_final(void);

‎codebuild/bin/s2n_dynamic_load_test.c

+10-5
Original file line numberDiff line numberDiff line change
@@ -37,10 +37,10 @@ static void *s2n_load_dynamic_lib(void *ctx)
3737
exit(1);
3838
}
3939

40-
int (*s2n_cleanup_dl)(void) = NULL;
41-
*(void **) (&s2n_cleanup_dl) = dlsym(s2n_so, "s2n_cleanup");
40+
int (*s2n_cleanup_final_dl)(void) = NULL;
41+
*(void **) (&s2n_cleanup_final_dl) = dlsym(s2n_so, "s2n_cleanup_final");
4242
if (dlerror()) {
43-
printf("Error dynamically loading s2n_cleanup\n");
43+
printf("Error dynamically loading s2n_cleanup_final\n");
4444
exit(1);
4545
}
4646

@@ -63,17 +63,22 @@ static void *s2n_load_dynamic_lib(void *ctx)
6363
fprintf(stderr, "Error calling s2n_init: '%s'\n", (*s2n_strerror_debug_dl)(s2n_errno, "EN"));
6464
exit(1);
6565
}
66-
if ((*s2n_cleanup_dl)()) {
66+
if ((*s2n_cleanup_final_dl)()) {
6767
int s2n_errno = (*s2n_errno_location_dl)();
68-
fprintf(stderr, "Error calling s2n_cleanup: '%s'\n", (*s2n_strerror_debug_dl)(s2n_errno, "EN"));
68+
fprintf(stderr, "Error calling s2n_cleanup_final: '%s'\n", (*s2n_strerror_debug_dl)(s2n_errno, "EN"));
6969
exit(1);
7070
}
7171

72+
/* TODO: https://github.com/aws/s2n-tls/issues/4827
73+
* This dlclose call invokes the pthread key destructor that
74+
* asserts that the s2n-tls library is initialized, which at this point
75+
* is not, due to the s2n_cleanup_final call. This is a bug.
7276
if (dlclose(s2n_so)) {
7377
printf("Error closing libs2n\n");
7478
printf("%s\n", dlerror());
7579
exit(1);
7680
}
81+
*/
7782

7883
return NULL;
7984
}

‎codebuild/bin/test_exec_leak.sh

+1-1
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ cat <<EOF > build/detect_exec_leak_finish.c
5151
5252
int main() {
5353
s2n_init();
54-
s2n_cleanup();
54+
s2n_cleanup_final();
5555
5656
/* close std* file descriptors so valgrind output is less noisy */
5757
fclose(stdin);
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,17 @@
11
# Initialization and Teardown
2-
The s2n-tls library must be initialized with `s2n_init()` before calling most library functions. `s2n_init()` MUST NOT be called more than once, even when an application uses multiple threads or processes. s2n attempts to clean up its thread-local memory at thread-exit and all other memory at process-exit. However, this may not work if you are using a thread library other than pthreads or other threads using s2n outlive the thread that called `s2n_init()`. In that case you should call `s2n_cleanup_thread()` from every thread or process created after `s2n_init()`.
32

4-
> Note: `s2n_cleanup_thread()` is currently considered unstable, meaning the API is subject to change in a future release. To access this API, include `api/unstable/cleanup.h`.
3+
## Initialization
4+
The s2n-tls library must be initialized with `s2n_init()` before calling most library functions. `s2n_init()` will error if it is called more than once, even when an application uses multiple threads.
55

66
Initialization can be modified by calling `s2n_crypto_disable_init()` or `s2n_disable_atexit()` before `s2n_init()`.
77

8-
An application can override s2n-tls’s internal memory management by calling `s2n_mem_set_callbacks` before calling s2n_init.
8+
An application can override s2n-tls’s internal memory management by calling `s2n_mem_set_callbacks()` before calling `s2n_init()`.
99

1010
If you are trying to use FIPS mode, you must enable FIPS in your libcrypto library (probably by calling `FIPS_mode_set(1)`) before calling `s2n_init()`.
11+
12+
## Teardown
13+
### Thread-local Memory
14+
We recommend calling `s2n_cleanup()` from every thread created after `s2n_init()` to ensure there are no memory leaks. s2n-tls has thread-local memory that it attempts to clean up automatically at thread-exit. However, this is done using pthread destructors and may not work if you are using a threads library other than pthreads.
15+
16+
### Library Cleanup
17+
A full cleanup and de-initialization of the library can be done by calling `s2n_cleanup_final()`. s2n-tls allocates some memory at initialization that is intended to live for the duration of the process, but can be cleaned up earlier with `s2n_cleanup_final()`. Not calling this method may cause tools like ASAN or valgrind to detect memory leaks, as the memory will still be allocated when the process exits.

‎tests/s2n_test.h

+14-13
Original file line numberDiff line numberDiff line change
@@ -15,18 +15,18 @@
1515

1616
#pragma once
1717
#include <errno.h>
18+
#include <openssl/crypto.h>
1819
#include <stdio.h>
1920
#include <stdlib.h>
2021
#include <string.h>
2122
#include <unistd.h>
2223

23-
#include <openssl/crypto.h>
24-
2524
#include "error/s2n_errno.h"
26-
#include "utils/s2n_safety.h"
27-
#include "utils/s2n_result.h"
2825
#include "tls/s2n_alerts.h"
2926
#include "tls/s2n_tls13.h"
27+
#include "utils/s2n_init.h"
28+
#include "utils/s2n_result.h"
29+
#include "utils/s2n_safety.h"
3030

3131
int test_count;
3232

@@ -64,14 +64,15 @@ bool s2n_use_color_in_output = true;
6464
* number of independent childs at the start of a unit test and where you want
6565
* each child to have its own independently initialised s2n.
6666
*/
67-
#define BEGIN_TEST_NO_INIT() \
68-
do { \
69-
test_count = 0; \
70-
fprintf(stdout, "Running %-50s ... ", __FILE__); \
71-
fflush(stdout); \
72-
EXPECT_SUCCESS_WITHOUT_COUNT(s2n_in_unit_test_set(true)); \
73-
S2N_TEST_OPTIONALLY_ENABLE_FIPS_MODE(); \
74-
} while(0)
67+
#define BEGIN_TEST_NO_INIT() \
68+
do { \
69+
test_count = 0; \
70+
fprintf(stdout, "Running %-50s ... ", __FILE__); \
71+
fflush(stdout); \
72+
EXPECT_SUCCESS_WITHOUT_COUNT(s2n_in_unit_test_set(true)); \
73+
S2N_TEST_OPTIONALLY_ENABLE_FIPS_MODE(); \
74+
EXPECT_SUCCESS(s2n_enable_atexit()); \
75+
} while (0)
7576

7677
#define END_TEST_NO_INIT() \
7778
do { \
@@ -261,7 +262,7 @@ void s2n_test__fuzz_cleanup() \
261262
if (fuzz_cleanup) { \
262263
((void (*)()) fuzz_cleanup)(); \
263264
} \
264-
s2n_cleanup(); \
265+
s2n_cleanup_final(); \
265266
} \
266267
int LLVMFuzzerInitialize(int *argc, char **argv[]) \
267268
{ \

‎tests/unit/s2n_init_test.c

+14-22
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@
1919
#include "s2n_test.h"
2020

2121
bool s2n_is_initialized(void);
22-
int s2n_enable_atexit(void);
2322

2423
static void *s2n_init_fail_cb(void *_unused_arg)
2524
{
@@ -49,24 +48,18 @@ int main(int argc, char **argv)
4948
{
5049
BEGIN_TEST_NO_INIT();
5150

52-
/* Disabling the atexit handler makes it easier for us to test s2n_init and s2n_cleanup
53-
* behavior. Otherwise we'd have to create and exit a bunch of processes to test this
54-
* interaction. */
55-
EXPECT_SUCCESS(s2n_disable_atexit());
56-
5751
/* Calling s2n_init twice in a row will cause an error */
5852
EXPECT_SUCCESS(s2n_init());
5953
EXPECT_FAILURE_WITH_ERRNO(s2n_init(), S2N_ERR_INITIALIZED);
60-
EXPECT_SUCCESS(s2n_cleanup());
54+
EXPECT_SUCCESS(s2n_cleanup_final());
6155

62-
/* Second call to s2n_cleanup will fail, since the full cleanup is not idempotent.
63-
* This behavior only exists when atexit is disabled. */
64-
EXPECT_FAILURE_WITH_ERRNO(s2n_cleanup(), S2N_ERR_NOT_INITIALIZED);
56+
/* Second call to s2n_cleanup_final will fail, since the full cleanup is not idempotent. */
57+
EXPECT_FAILURE_WITH_ERRNO(s2n_cleanup_final(), S2N_ERR_NOT_INITIALIZED);
6558

6659
/* Clean up and init multiple times */
6760
for (size_t i = 0; i < 10; i++) {
6861
EXPECT_SUCCESS(s2n_init());
69-
EXPECT_SUCCESS(s2n_cleanup());
62+
EXPECT_SUCCESS(s2n_cleanup_final());
7063
}
7164

7265
/* Calling s2n_init again after creating a process will cause an error */
@@ -78,16 +71,16 @@ int main(int argc, char **argv)
7871
EXPECT_SUCCESS(s2n_cleanup());
7972
return 0;
8073
}
81-
EXPECT_SUCCESS(s2n_cleanup());
74+
EXPECT_SUCCESS(s2n_cleanup_final());
8275

8376
/* Calling s2n_init again after creating a thread will cause an error */
8477
EXPECT_SUCCESS(s2n_init());
8578
pthread_t init_thread = { 0 };
8679
EXPECT_EQUAL(pthread_create(&init_thread, NULL, s2n_init_fail_cb, NULL), 0);
8780
EXPECT_EQUAL(pthread_join(init_thread, NULL), 0);
88-
EXPECT_SUCCESS(s2n_cleanup());
81+
EXPECT_SUCCESS(s2n_cleanup_final());
8982

90-
/* Calling s2n_init/s2n_cleanup in a different thread than s2n_cleanup_thread is called cleans up properly */
83+
/* Calling s2n_init/s2n_cleanup_final in a different thread than s2n_cleanup_thread is called cleans up properly */
9184
{
9285
EXPECT_SUCCESS(s2n_init());
9386
EXPECT_TRUE(s2n_is_initialized());
@@ -98,10 +91,10 @@ int main(int argc, char **argv)
9891
/* Calling s2n_cleanup_thread in the main thread leaves s2n initialized. */
9992
EXPECT_SUCCESS(s2n_cleanup_thread());
10093
EXPECT_TRUE(s2n_is_initialized());
101-
EXPECT_SUCCESS(s2n_cleanup());
94+
EXPECT_SUCCESS(s2n_cleanup_final());
10295
EXPECT_FALSE(s2n_is_initialized());
103-
/* Second call to s2n_cleanup will fail, since the full cleanup is not idempotent. */
104-
EXPECT_FAILURE_WITH_ERRNO(s2n_cleanup(), S2N_ERR_NOT_INITIALIZED);
96+
/* Second call to s2n_cleanup_final will fail, since the full cleanup is not idempotent. */
97+
EXPECT_FAILURE_WITH_ERRNO(s2n_cleanup_final(), S2N_ERR_NOT_INITIALIZED);
10598
}
10699

107100
/* s2n_cleanup_final */
@@ -112,11 +105,11 @@ int main(int argc, char **argv)
112105
EXPECT_SUCCESS(s2n_cleanup_final());
113106
EXPECT_FALSE(s2n_is_initialized());
114107

115-
/* s2n_cleanup fully cleans up the library when the atexit handler is disabled.
116-
* Therefore, calling s2n_cleanup_final after s2n_cleanup will error */
108+
/* s2n_cleanup only cleans up thread-local storage.
109+
* Therefore, calling s2n_cleanup_final after s2n_cleanup will succeed */
117110
EXPECT_SUCCESS(s2n_init());
118111
EXPECT_SUCCESS(s2n_cleanup());
119-
EXPECT_FAILURE_WITH_ERRNO(s2n_cleanup_final(), S2N_ERR_NOT_INITIALIZED);
112+
EXPECT_SUCCESS(s2n_cleanup_final());
120113

121114
/* s2n_cleanup_thread only cleans up thread-local storage.
122115
* Therefore calling s2n_cleanup_final after s2n_cleanup_thread will succeed */
@@ -130,8 +123,7 @@ int main(int argc, char **argv)
130123

131124
/* Initializing s2n on a child thread without calling s2n_cleanup on that
132125
* thread will not result in a memory leak. This is because we register
133-
* thread-local memory to be cleaned up at thread-exit
134-
* and then our atexit handler cleans up the rest at proccess-exit. */
126+
* thread-local memory to be cleaned up at thread-exit. */
135127
pthread_t init_success_thread = { 0 };
136128
EXPECT_EQUAL(pthread_create(&init_success_thread, NULL, s2n_init_success_cb, NULL), 0);
137129
EXPECT_EQUAL(pthread_join(init_success_thread, NULL), 0);

‎tests/unit/s2n_random_test.c

+2-5
Original file line numberDiff line numberDiff line change
@@ -782,9 +782,8 @@ static int s2n_random_noop_destructor_test_cb(struct random_test_case *test_case
782782

783783
static int s2n_random_rand_bytes_after_cleanup_cb(struct random_test_case *test_case)
784784
{
785-
s2n_disable_atexit();
786785
EXPECT_SUCCESS(s2n_init());
787-
EXPECT_SUCCESS(s2n_cleanup());
786+
EXPECT_SUCCESS(s2n_cleanup_final());
788787

789788
unsigned char rndbytes[16];
790789
EXPECT_EQUAL(RAND_bytes(rndbytes, sizeof(rndbytes)), 1);
@@ -818,8 +817,6 @@ static int s2n_random_rand_bytes_before_init(struct random_test_case *test_case)
818817

819818
static int s2n_random_invalid_urandom_fd_cb(struct random_test_case *test_case)
820819
{
821-
EXPECT_SUCCESS(s2n_disable_atexit());
822-
823820
struct s2n_rand_device *dev_urandom = NULL;
824821
EXPECT_OK(s2n_rand_get_urandom_for_test(&dev_urandom));
825822
EXPECT_NOT_NULL(dev_urandom);
@@ -871,7 +868,7 @@ static int s2n_random_invalid_urandom_fd_cb(struct random_test_case *test_case)
871868
EXPECT_TRUE(public_bytes_used > 0);
872869
}
873870

874-
EXPECT_SUCCESS(s2n_cleanup());
871+
EXPECT_SUCCESS(s2n_cleanup_final());
875872
}
876873

877874
return S2N_SUCCESS;

‎utils/s2n_init.c

+2-8
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ static void s2n_cleanup_atexit(void);
3737

3838
static pthread_t main_thread = 0;
3939
static bool initialized = false;
40-
static bool atexit_cleanup = true;
40+
static bool atexit_cleanup = false;
4141
int s2n_disable_atexit(void)
4242
{
4343
POSIX_ENSURE(!initialized, S2N_ERR_INITIALIZED);
@@ -139,13 +139,7 @@ int s2n_cleanup(void)
139139
{
140140
POSIX_GUARD(s2n_cleanup_thread());
141141

142-
/* If this is the main thread and atexit cleanup is disabled,
143-
* perform final cleanup now */
144-
if (pthread_equal(pthread_self(), main_thread) && !atexit_cleanup) {
145-
POSIX_GUARD(s2n_cleanup_final());
146-
}
147-
148-
return 0;
142+
return S2N_SUCCESS;
149143
}
150144

151145
static void s2n_cleanup_atexit(void)

‎utils/s2n_init.h

+1
Original file line numberDiff line numberDiff line change
@@ -20,3 +20,4 @@
2020
int s2n_init(void);
2121
int s2n_cleanup(void);
2222
bool s2n_is_initialized(void);
23+
int s2n_enable_atexit(void);

0 commit comments

Comments
 (0)
Failed to load comments.