From 1381b8a4a9a2803c156d14359883a9b1b365c982 Mon Sep 17 00:00:00 2001 From: Roman Janota Date: Thu, 10 Sep 2026 14:28:50 +0200 Subject: [PATCH 1/2] session BUGFIX serialize global library init The client and the server side both initialized the same process-global libraries (libcurl, the TLS backend, libssh) without any lock, so an application (or test_thread_messages) that starts a server thread and initializes the client at the same time raced inside them. With MbedTLS this corrupts the shared PSA entropy context, which was observed as a leaked 216 byte hash context, a double free, a segfault or a spurious psa_crypto_init() failure. Both sides now init and destroy the libraries through nc_global_init() and nc_global_destroy() guarded by a lock. The libraries are also initialized only once and destroyed only once the other side does not use them any longer, so nc_client_destroy() no longer tears down the crypto state of a running server and vice versa. --- src/session.c | 104 +++++++++++++++++++++++++++++++++++++++++++ src/session_client.c | 17 ++----- src/session_p.h | 15 +++++++ src/session_server.c | 29 +++++------- 4 files changed, 133 insertions(+), 32 deletions(-) diff --git a/src/session.c b/src/session.c index dbc09ccf..4af7db67 100644 --- a/src/session.c +++ b/src/session.c @@ -2512,6 +2512,110 @@ nc_session_tls_crl_verify_post_handshake(void *tls_session, void *cert_store) #endif /* NC_ENABLED_SSH_TLS */ +/** + * @brief Initialize or destroy the global state of the libraries used by libnetconf2. + * + * @param[in] side Side that is being initialized/destroyed. + * @param[in] destroy Whether to destroy the global state instead of initializing it. + * @return 0 on success, -1 on error. + */ +static int +nc_global_init_destroy(NC_SIDE side, int destroy) +{ +#ifdef NC_ENABLED_SSH_TLS + /* lock protecting the init flags and the global init of the libraries, which is not thread-safe */ + static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER; + + /* whether the client/server side has the libraries initialized */ + static int init_client = 0, init_server = 0; + + int rc = 0, curl_inited = 0, tls_inited = 0, *inited; + + inited = (side == NC_CLIENT) ? &init_client : &init_server; + + /* GLOBAL INIT LOCK */ + pthread_mutex_lock(&lock); + + if (destroy) { + if (!*inited) { + /* this side has not initialized the libraries */ + goto cleanup; + } + *inited = 0; + + if (!init_client && !init_server) { + /* the last side using the libraries, destroy them in the reverse order */ + ssh_finalize(); + nc_tls_backend_destroy_wrap(); + curl_global_cleanup(); + } + goto cleanup; + } + + if (init_client || init_server) { + /* the libraries are already initialized by the other side */ + *inited = 1; + goto cleanup; + } + + if (curl_global_init(CURL_GLOBAL_SSL | CURL_GLOBAL_ACK_EINTR)) { + ERR(NULL, "%s: failed to init CURL.", __func__); + rc = -1; + goto cleanup; + } + curl_inited = 1; + + if (nc_tls_backend_init_wrap()) { + ERR(NULL, "%s: failed to init the SSL library backend.", __func__); + rc = -1; + goto cleanup; + } + tls_inited = 1; + + /* optional for dynamic library, mandatory for static */ + if (ssh_init()) { + ERR(NULL, "%s: failed to init libssh.", __func__); + rc = -1; + goto cleanup; + } + + *inited = 1; + +cleanup: + if (rc) { + /* leave no library initialized behind */ + if (tls_inited) { + nc_tls_backend_destroy_wrap(); + } + if (curl_inited) { + curl_global_cleanup(); + } + } + + /* GLOBAL INIT UNLOCK */ + pthread_mutex_unlock(&lock); + + return rc; +#else + (void)side; + (void)destroy; + + return 0; +#endif /* NC_ENABLED_SSH_TLS */ +} + +int +nc_global_init(NC_SIDE side) +{ + return nc_global_init_destroy(side, 0); +} + +void +nc_global_destroy(NC_SIDE side) +{ + nc_global_init_destroy(side, 1); +} + API const char * nc_yang_module_dir(void) { diff --git a/src/session_client.c b/src/session_client.c index da789987..82e11bcd 100644 --- a/src/session_client.c +++ b/src/session_client.c @@ -1908,18 +1908,7 @@ nc_session_ntf_thread_running(const struct nc_session *session) API int nc_client_init(void) { -#ifdef NC_ENABLED_SSH_TLS - if (nc_tls_backend_init_wrap()) { - ERR(NULL, "%s: failed to init the SSL library backend.", __func__); - return -1; - } - if (ssh_init()) { - ERR(NULL, "%s: failed to init libssh.", __func__); - return -1; - } -#endif - - return 0; + return nc_global_init(NC_CLIENT); } API void @@ -1931,9 +1920,9 @@ nc_client_destroy(void) nc_client_ch_del_bind(NULL, 0, 0); nc_client_ssh_destroy_opts(); nc_client_tls_destroy_opts(); - nc_tls_backend_destroy_wrap(); - ssh_finalize(); #endif /* NC_ENABLED_SSH_TLS */ + + nc_global_destroy(NC_CLIENT); } static NC_MSG_TYPE diff --git a/src/session_p.h b/src/session_p.h index 6540ceb8..fefa4a05 100644 --- a/src/session_p.h +++ b/src/session_p.h @@ -1462,6 +1462,21 @@ int nc_poll(struct pollfd *pfd, uint16_t pfd_count, int timeout); */ int nc_sock_configure_ka(int sock, const struct nc_keepalives *ka); +/** + * @brief Initialize the global state of the libraries used by libnetconf2. + * + * @param[in] side Side that is being initialized. + * @return 0 on success, -1 on error. + */ +int nc_global_init(NC_SIDE side); + +/** + * @brief Destroy the global state of the libraries used by libnetconf2. + * + * @param[in] side Side that is being destroyed. + */ +void nc_global_destroy(NC_SIDE side); + struct nc_session *nc_new_session(NC_SIDE side, int shared_ti); int nc_session_rpc_lock(struct nc_session *session, int timeout, const char *func); diff --git a/src/session_server.c b/src/session_server.c index 7bfc3df5..3055a835 100644 --- a/src/session_server.c +++ b/src/session_server.c @@ -1524,6 +1524,8 @@ nc_server_init_rwlock(pthread_rwlock_t *rwlock) API int nc_server_init(void) { + int glob_inited = 0; + #ifdef NC_ENABLED_SSH_TLS int r; #endif /* NC_ENABLED_SSH_TLS */ @@ -1547,23 +1549,13 @@ nc_server_init(void) ATOMIC_STORE_RELAXED(server_opts.config->refcount, 1); ATOMIC_STORE_RELAXED(server_opts.idle_timeout, 0); -#ifdef NC_ENABLED_SSH_TLS - if (curl_global_init(CURL_GLOBAL_SSL | CURL_GLOBAL_ACK_EINTR)) { - ERR(NULL, "%s: failed to init CURL.", __func__); - goto error; - } - - if (nc_tls_backend_init_wrap()) { - ERR(NULL, "%s: failed to init the SSL library backend.", __func__); - goto error; - } - - /* optional for dynamic library, mandatory for static */ - if (ssh_init()) { - ERR(NULL, "%s: failed to init libssh.", __func__); + /* initialize the global state of the used libraries, shared with the client side */ + if (nc_global_init(NC_SERVER)) { goto error; } + glob_inited = 1; +#ifdef NC_ENABLED_SSH_TLS if ((r = pthread_mutex_init(&server_opts.cert_exp_notif.lock, NULL))) { ERR(NULL, "%s: failed to init certificate expiration notification thread lock(%s).", __func__, strerror(r)); goto error; @@ -1584,6 +1576,9 @@ nc_server_init(void) nc_server_config_release(server_opts.config); server_opts.config = NULL; ATOMIC_STORE_RELAXED(server_opts.new_session_id, 0); + if (glob_inited) { + nc_global_destroy(NC_SERVER); + } return -1; } @@ -1701,10 +1696,6 @@ nc_server_destroy(void) nc_server_config_release(config); #ifdef NC_ENABLED_SSH_TLS - curl_global_cleanup(); - nc_tls_backend_destroy_wrap(); - ssh_finalize(); - /* close the TLS keylog file */ if (server_opts.tls_keylog_file) { fclose(server_opts.tls_keylog_file); @@ -1712,6 +1703,8 @@ nc_server_destroy(void) } #endif /* NC_ENABLED_SSH_TLS */ + nc_global_destroy(NC_SERVER); + cleanup: if (opts_locked) { nc_rwlock_unlock(&server_opts.opts_lock, __func__); From 6a0bbd57a83a767ee0dfd3815311226d34dbe5b2 Mon Sep 17 00:00:00 2001 From: Roman Janota Date: Thu, 10 Sep 2026 14:28:50 +0200 Subject: [PATCH 2/2] tests UPDATE destroy the client and the server test_thread_messages never destroyed either side, leaking the global state of the used libraries on every run. --- tests/test_thread_messages.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/test_thread_messages.c b/tests/test_thread_messages.c index 82cdb802..153ddddd 100644 --- a/tests/test_thread_messages.c +++ b/tests/test_thread_messages.c @@ -116,6 +116,7 @@ server_thread(void *arg) pthread_barrier_wait(&barrier); nc_server_notif_free(notif); + nc_assert(!nc_server_destroy()); return arg; } @@ -227,6 +228,7 @@ main(void) /* cleanup */ nc_session_free(sess, NULL); + nc_client_destroy(); ly_ctx_destroy(server_ctx); ly_ctx_destroy(client_ctx); for (uint8_t i = 0; i < 4; i++) {