[PATCH] OpenSSL: Validate PKCS#11/OpenSC engine and module paths before loading

Tu Naichao 1393179455 at qq.com
Sun Jul 12 19:49:43 PDT 2026


The PKCS#11 engine, PKCS#11 module, and OpenSC engine shared object paths
configured for EAP TLS are passed to OpenSSL, which dlopen()s them within the
wpa_supplicant/hostapd process. Since these paths can originate from sources
outside the administrator's direct control (for example the
SetPKCS11EngineAndModulePath D-Bus method), validate them before loading as a
defense-in-depth measure.

Add a tls_engine_path_trusted() helper that resolves the path with realpath()
and verifies that the target is a regular file owned by root, not writable by
group or others, and that every ancestor directory up to the file is itself a
directory, owned by root and not writable by group or others. lstat() is used
for the file check so that a symlink is never followed. Any failure is treated
as untrusted and the load is skipped.

This avoids loading a shared library from an unexpected location while keeping
legitimately installed, root-owned engine libraries working unchanged.

Signed-off-by: Tu Naichao <1393179455 at qq.com>
---
 src/crypto/tls_openssl.c | 96 +++++++++++++++++++++++++++++++++++++---
 1 file changed, 89 insertions(+), 7 deletions(-)

diff --git a/src/crypto/tls_openssl.c b/src/crypto/tls_openssl.c
index 292017cc4..87a118a9e 100644
--- a/src/crypto/tls_openssl.c
+++ b/src/crypto/tls_openssl.c
@@ -7,6 +7,8 @@
  */
 
 #include "includes.h"
+#include <sys/stat.h>
+#include <limits.h>
 #ifdef CONFIG_TESTING_OPTIONS
 #include <fcntl.h>
 #endif /* CONFIG_TESTING_OPTIONS */
@@ -1018,6 +1020,75 @@ static int tls_engine_load_dynamic_generic(const char *pre[],
 }
 
 
+#define TRUSTED_PATH "/usr/lib/"
+
+/**
+ * tls_engine_path_trusted - Verify engine .so path is trusted for loading
+ * @path: engine/module path supplied via configuration or D-Bus
+ * @real_path: buffer for the canonical path (PATH_MAX bytes)
+ * Returns: 0 if trusted (real_path filled), -1 otherwise
+ *
+ * The PKCS#11/OpenSC engine and module shared object paths are loaded with
+ * dlopen() within this process, which may be running with elevated
+ * privileges. Verify that the supplied path cannot be used to load an
+ * attacker-controlled shared library by requiring, in addition to being
+ * located under the trusted path, that the target is a regular file owned by
+ * root, not writable by group or others, and that every ancestor directory up
+ * to the file is itself a directory, owned by root and not writable by group
+ * or others (otherwise a root-owned file could be swapped by whoever controls
+ * such a directory). lstat() is used for the file check so that a symlink is
+ * never followed, even though realpath() has already canonicalised the path.
+ */
+static int tls_engine_path_trusted(const char *path, char *real_path)
+{
+	struct stat st;
+	char dir[PATH_MAX];
+	char *slash, *next;
+
+	if (!path)
+		return -1;
+
+	if (realpath(path, real_path) == NULL) {
+		wpa_printf(MSG_INFO,
+			   "ENGINE: refusing to load %s: realpath: %s",
+			   path, strerror(errno));
+		return -1;
+	}
+
+	if (os_strncmp(TRUSTED_PATH, real_path, os_strlen(TRUSTED_PATH)) != 0) {
+		wpa_printf(MSG_INFO,
+			   "ENGINE: refusing to load %s: not in trusted path %s",
+			   real_path, TRUSTED_PATH);
+		return -1;
+	}
+
+	if (lstat(real_path, &st) != 0 || !S_ISREG(st.st_mode) ||
+	    st.st_uid != 0 || (st.st_mode & (S_IWGRP | S_IWOTH))) {
+		wpa_printf(MSG_INFO,
+			   "ENGINE: refusing to load %s: not a root-owned, non-writable regular file",
+			   real_path);
+		return -1;
+	}
+
+	os_strlcpy(dir, real_path, sizeof(dir));
+	slash = dir;
+	while ((next = os_strchr(slash + 1, '/')) != NULL) {
+		*next = '\0';
+		if (stat(dir, &st) != 0 || !S_ISDIR(st.st_mode) ||
+		    st.st_uid != 0 || (st.st_mode & (S_IWGRP | S_IWOTH))) {
+			wpa_printf(MSG_INFO,
+				   "ENGINE: refusing to load %s: insecure ancestor directory %s",
+				   real_path, dir);
+			return -1;
+		}
+		*next = '/';
+		slash = next;
+	}
+
+	return 0;
+}
+
+
 /**
  * tls_engine_load_dynamic_pkcs11 - load the pkcs11 engine provided by opensc
  * @pkcs11_so_path: pksc11_so_path from the configuration
@@ -1027,6 +1098,8 @@ static int tls_engine_load_dynamic_pkcs11(const char *pkcs11_so_path,
 					  const char *pkcs11_module_path)
 {
 	char *engine_id = "pkcs11";
+	char real_pkcs11_so_path[PATH_MAX];
+	char real_pkcs11_module_path[PATH_MAX];
 	const char *pre_cmd[] = {
 		"SO_PATH", NULL /* pkcs11_so_path */,
 		"ID", NULL /* engine_id */,
@@ -1043,15 +1116,21 @@ static int tls_engine_load_dynamic_pkcs11(const char *pkcs11_so_path,
 	if (!pkcs11_so_path)
 		return 0;
 
-	pre_cmd[1] = pkcs11_so_path;
+	if (tls_engine_path_trusted(pkcs11_so_path, real_pkcs11_so_path) < 0)
+		return -1;
+	pre_cmd[1] = real_pkcs11_so_path;
 	pre_cmd[3] = engine_id;
-	if (pkcs11_module_path)
-		post_cmd[1] = pkcs11_module_path;
-	else
+	if (pkcs11_module_path) {
+		if (tls_engine_path_trusted(pkcs11_module_path,
+					    real_pkcs11_module_path) < 0)
+			return -1;
+		post_cmd[1] = real_pkcs11_module_path;
+	} else {
 		post_cmd[0] = NULL;
+	}
 
 	wpa_printf(MSG_DEBUG, "ENGINE: Loading pkcs11 Engine from %s",
-		   pkcs11_so_path);
+		   real_pkcs11_so_path);
 
 	return tls_engine_load_dynamic_generic(pre_cmd, post_cmd, engine_id);
 }
@@ -1064,6 +1143,7 @@ static int tls_engine_load_dynamic_pkcs11(const char *pkcs11_so_path,
 static int tls_engine_load_dynamic_opensc(const char *opensc_so_path)
 {
 	char *engine_id = "opensc";
+	char real_opensc_so_path[PATH_MAX];
 	const char *pre_cmd[] = {
 		"SO_PATH", NULL /* opensc_so_path */,
 		"ID", NULL /* engine_id */,
@@ -1075,11 +1155,13 @@ static int tls_engine_load_dynamic_opensc(const char *opensc_so_path)
 	if (!opensc_so_path)
 		return 0;
 
-	pre_cmd[1] = opensc_so_path;
+	if (tls_engine_path_trusted(opensc_so_path, real_opensc_so_path) < 0)
+		return -1;
+	pre_cmd[1] = real_opensc_so_path;
 	pre_cmd[3] = engine_id;
 
 	wpa_printf(MSG_DEBUG, "ENGINE: Loading OpenSC Engine from %s",
-		   opensc_so_path);
+		   real_opensc_so_path);
 
 	return tls_engine_load_dynamic_generic(pre_cmd, NULL, engine_id);
 }
-- 
2.50.1




More information about the Hostap mailing list