[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