diff --git a/SOURCES/opencryptoki-3.25.0-CVE-2026-23893.patch b/SOURCES/opencryptoki-3.25.0-CVE-2026-23893.patch new file mode 100644 index 0000000..56f403d --- /dev/null +++ b/SOURCES/opencryptoki-3.25.0-CVE-2026-23893.patch @@ -0,0 +1,517 @@ +commit 66e5bbdc0019fdf547409152df6b7668639f65e7 +Author: Pavel Kohout +Date: Tue Jan 13 00:00:00 2026 +0000 + + Fix symlink-following vulnerabilities (CWE-59) + + Multiple symlink-following vulnerabilities exist in OpenCryptoki that run + in privileged contexts. These allow a token-group user to redirect file + operations to arbitrary filesystem targets by planting symlinks in + group-writable token directories, resulting in privilege escalation or + data exposure. + + Affected components: + 1. pkcstok_admin: set_file_permissions() uses stat() which follows symlinks, + then applies chmod/chown to the symlink target. + 2. pkcstok_migrate: fopen() follows symlinks, then set_perm() modifies the + target permissions. + 3. loadsave.c: Multiple wrapper functions use fopen() followed by set_perm(). + 4. hsm_mk_change.c: hsm_mk_change_op_open() uses fopen() followed by + hsm_mk_change_op_set_perm(). + 5. pbkdf.c: fopen() followed by set_perms() in two locations. + + This fix: + - Introduces fopen_nofollow() helper in platform.h + - Checks for O_NOFOLLOW at compile time (not hardcoded per-platform) + - On platforms with O_NOFOLLOW: uses open(O_NOFOLLOW) + fdopen() for atomic + symlink rejection (race-condition free) + - On platforms without O_NOFOLLOW: falls back to lstat() + fopen() and emits + a compiler warning so the unsafe fallback doesn't go unnoticed + - Updates all affected wrapper functions to use fopen_nofollow() + - pkcstok_admin: Uses lstat() instead of stat() and skips symlinks + + Reported-by: Pavel Kohout, Aisle Research, www.aisle.com + Signed-off-by: Pavel Kohout + Signed-off-by: Ingo Franzki + +diff --git a/usr/lib/common/loadsave.c b/usr/lib/common/loadsave.c +index 3b919709..e416c2b2 100644 +--- a/usr/lib/common/loadsave.c ++++ b/usr/lib/common/loadsave.c +@@ -66,9 +66,17 @@ static FILE *open_token_object_path(char *buf, size_t buflen, + STDLL_TokData_t *tokdata, char *path, + char *mode) + { ++ FILE *fp; ++ + if (get_token_object_path(buf, buflen, tokdata, path) < 0) + return NULL; +- return fopen(buf, mode); ++ ++ /* CWE-59 fix: Use fopen_nofollow to prevent symlink attacks */ ++ fp = fopen_nofollow(buf, mode); ++ if (fp == NULL && errno == ELOOP) ++ TRACE_ERROR("Refusing to follow symlink: %s\n", buf); ++ ++ return fp; + } + + static int get_token_data_store_path(char *buf, size_t buflen, +@@ -85,9 +93,17 @@ static FILE *open_token_data_store_path(char *buf, size_t buflen, + STDLL_TokData_t *tokdata, char *path, + char *mode) + { ++ FILE *fp; ++ + if (get_token_data_store_path(buf, buflen, tokdata, path) < 0) + return NULL; +- return fopen(buf, mode); ++ ++ /* CWE-59 fix: Use fopen_nofollow to prevent symlink attacks */ ++ fp = fopen_nofollow(buf, mode); ++ if (fp == NULL && errno == ELOOP) ++ TRACE_ERROR("Refusing to follow symlink: %s\n", buf); ++ ++ return fp; + } + + static FILE *open_token_object_index(char *buf, size_t buflen, +@@ -99,11 +115,19 @@ static FILE *open_token_object_index(char *buf, size_t buflen, + static FILE *open_token_nvdat(char *buf, size_t buflen, + STDLL_TokData_t *tokdata, char *mode) + { ++ FILE *fp; ++ + if (ock_snprintf(buf, buflen, "%s/" PK_LITE_NV, tokdata->data_store)) { + TRACE_ERROR("NVDAT.TOK file name buffer overflow\n"); + return NULL; + } +- return fopen(buf, mode); ++ ++ /* CWE-59 fix: Use fopen_nofollow to prevent symlink attacks */ ++ fp = fopen_nofollow(buf, mode); ++ if (fp == NULL && errno == ELOOP) ++ TRACE_ERROR("Refusing to follow symlink: %s\n", buf); ++ ++ return fp; + } + + char *get_pk_dir(STDLL_TokData_t *tokdata, char *fname, size_t len) +@@ -223,9 +247,12 @@ CK_RV save_token_object(STDLL_TokData_t *tokdata, OBJECT *obj) + // we didn't find it...either the index file doesn't exist or this + // is a new object... + // +- fp = fopen(fname, "a"); ++ fp = fopen_nofollow(fname, "a"); + if (!fp) { +- TRACE_ERROR("fopen(%s): %s\n", fname, strerror(errno)); ++ if (errno == ELOOP) ++ TRACE_ERROR("Refusing to follow symlink: %s\n", fname); ++ else ++ TRACE_ERROR("fopen(%s): %s\n", fname, strerror(errno)); + return CKR_FUNCTION_FAILED; + } + +@@ -286,8 +313,12 @@ CK_RV delete_token_object(STDLL_TokData_t *tokdata, OBJECT *obj) + + fclose(fp1); + fclose(fp2); +- fp2 = fopen(objidx, "w"); +- fp1 = fopen(idxtmp, "r"); ++ fp2 = fopen_nofollow(objidx, "w"); ++ if (fp1 == NULL && errno == ELOOP) ++ TRACE_ERROR("Refusing to follow symlink: %s\n", objidx); ++ fp1 = fopen_nofollow(idxtmp, "r"); ++ if (fp2 == NULL && errno == ELOOP) ++ TRACE_ERROR("Refusing to follow symlink: %s\n", idxtmp); + if (!fp1 || !fp2) { + if (fp1) + fclose(fp1); +@@ -616,11 +647,14 @@ CK_RV load_token_data_old(STDLL_TokData_t *tokdata, CK_SLOT_ID slot_id) + if (errno == ENOENT) { + init_token_data(tokdata, slot_id); + +- fp = fopen(fname, "r"); ++ fp = fopen_nofollow(fname, "r"); + if (!fp) { + // were really hosed here since the created + // did not occur +- TRACE_ERROR("fopen(%s): %s\n", fname, strerror(errno)); ++ if (errno == ELOOP) ++ TRACE_ERROR("Refusing to follow symlink: %s\n", fname); ++ else ++ TRACE_ERROR("fopen(%s): %s\n", fname, strerror(errno)); + rc = CKR_FUNCTION_FAILED; + goto out_unlock; + } +@@ -824,9 +858,12 @@ static CK_RV save_private_token_object_old(STDLL_TokData_t *tokdata, OBJECT *obj + rc = CKR_FUNCTION_FAILED; + goto error; + } +- fp = fopen(fname, "w"); ++ fp = fopen_nofollow(fname, "w"); + if (!fp) { +- TRACE_ERROR("fopen(%s): %s\n", fname, strerror(errno)); ++ if (errno == ELOOP) ++ TRACE_ERROR("Refusing to follow symlink: %s\n", fname); ++ else ++ TRACE_ERROR("fopen(%s): %s\n", fname, strerror(errno)); + rc = CKR_FUNCTION_FAILED; + goto error; + } +@@ -1590,9 +1627,12 @@ CK_RV reload_token_object_old(STDLL_TokData_t *tokdata, OBJECT *obj) + rc = CKR_FUNCTION_FAILED; + goto done; + } +- fp = fopen(fname, "r"); ++ fp = fopen_nofollow(fname, "r"); + if (!fp) { +- TRACE_ERROR("fopen(%s): %s\n", fname, strerror(errno)); ++ if (errno == ELOOP) ++ TRACE_ERROR("Refusing to follow symlink: %s\n", fname); ++ else ++ TRACE_ERROR("fopen(%s): %s\n", fname, strerror(errno)); + rc = CKR_FUNCTION_FAILED; + goto done; + } +@@ -1681,9 +1721,12 @@ CK_RV save_public_token_object_old(STDLL_TokData_t *tokdata, OBJECT * obj) + rc = CKR_FUNCTION_FAILED; + goto error; + } +- fp = fopen(fname, "w"); ++ fp = fopen_nofollow(fname, "w"); + if (!fp) { +- TRACE_ERROR("fopen(%s): %s\n", fname, strerror(errno)); ++ if (errno == ELOOP) ++ TRACE_ERROR("Refusing to follow symlink: %s\n", fname); ++ else ++ TRACE_ERROR("fopen(%s): %s\n", fname, strerror(errno)); + rc = CKR_FUNCTION_FAILED; + goto error; + } +@@ -2303,11 +2346,14 @@ CK_RV load_token_data(STDLL_TokData_t *tokdata, CK_SLOT_ID slot_id) + if (errno == ENOENT) { + init_token_data(tokdata, slot_id); + +- fp = fopen(fname, "r"); ++ fp = fopen_nofollow(fname, "r"); + if (!fp) { + // were really hosed here since the created + // did not occur +- TRACE_ERROR("fopen(%s): %s\n", fname, strerror(errno)); ++ if (errno == ELOOP) ++ TRACE_ERROR("Refusing to follow symlink: %s\n", fname); ++ else ++ TRACE_ERROR("fopen(%s): %s\n", fname, strerror(errno)); + rc = CKR_FUNCTION_FAILED; + goto out_unlock; + } +@@ -2481,7 +2527,7 @@ CK_RV save_private_token_object(STDLL_TokData_t *tokdata, OBJECT *obj) + goto done; + } + +- fp = fopen(fname, "r"); ++ fp = fopen_nofollow(fname, "r"); + if (fp == NULL) { + /* create new token object */ + new = 1; +@@ -2579,9 +2625,12 @@ do_work: + if (rc != CKR_OK) + goto done; + +- fp = fopen(fname, "w"); ++ fp = fopen_nofollow(fname, "w"); + if (!fp) { +- TRACE_ERROR("fopen(%s): %s\n", fname, strerror(errno)); ++ if (errno == ELOOP) ++ TRACE_ERROR("Refusing to follow symlink: %s\n", fname); ++ else ++ TRACE_ERROR("fopen(%s): %s\n", fname, strerror(errno)); + rc = CKR_FUNCTION_FAILED; + goto done; + } +@@ -2778,9 +2827,12 @@ CK_RV reload_token_object(STDLL_TokData_t *tokdata, OBJECT *obj) + sprintf(fname, "%s/%s/", tokdata->data_store, PK_LITE_OBJ_DIR); + strncat(fname, (char *) obj->name, 8); + +- fp = fopen(fname, "r"); ++ fp = fopen_nofollow(fname, "r"); + if (!fp) { +- TRACE_ERROR("fopen(%s): %s\n", fname, strerror(errno)); ++ if (errno == ELOOP) ++ TRACE_ERROR("Refusing to follow symlink: %s\n", fname); ++ else ++ TRACE_ERROR("fopen(%s): %s\n", fname, strerror(errno)); + rc = CKR_FUNCTION_FAILED; + goto done; + } +@@ -2893,9 +2945,12 @@ CK_RV save_public_token_object(STDLL_TokData_t *tokdata, OBJECT *obj) + sprintf(fname, "%s/%s/", tokdata->data_store, PK_LITE_OBJ_DIR); + strncat(fname, (char *) obj->name, 8); + +- fp = fopen(fname, "w"); ++ fp = fopen_nofollow(fname, "w"); + if (!fp) { +- TRACE_ERROR("fopen(%s): %s\n", fname, strerror(errno)); ++ if (errno == ELOOP) ++ TRACE_ERROR("Refusing to follow symlink: %s\n", fname); ++ else ++ TRACE_ERROR("fopen(%s): %s\n", fname, strerror(errno)); + rc = CKR_FUNCTION_FAILED; + goto done; + } +@@ -2956,9 +3011,12 @@ CK_RV load_public_token_objects(STDLL_TokData_t *tokdata) + sprintf(fname, "%s/%s/", tokdata->data_store, PK_LITE_OBJ_DIR); + strcat(fname, tmp); + +- fp2 = fopen(fname, "r"); +- if (!fp2) ++ fp2 = fopen_nofollow(fname, "r"); ++ if (!fp2) { ++ if (errno == ELOOP) ++ TRACE_ERROR("Refusing to follow symlink: %s\n", fname); + continue; ++ } + + if (fread(header, PUB_HEADER_LEN, 1, fp2) != 1) { + fclose(fp2); +diff --git a/usr/lib/common/platform.h b/usr/lib/common/platform.h +index 799821b5..51cc1c73 100644 +--- a/usr/lib/common/platform.h ++++ b/usr/lib/common/platform.h +@@ -7,7 +7,16 @@ + * found in the file LICENSE file or at + * https://opensource.org/licenses/cpl1.0.php + */ ++#ifndef PLATFORM_H ++#define PLATFORM_H ++ + #include ++#include ++#include ++#include ++#include ++#include ++#include + + #if defined(_AIX) + #include "aix/getopt.h" +@@ -30,10 +39,81 @@ + /* for htobexx, htolexx, bexxtoh and lexxtoh macros */ + #include + /* macros from bsdlog and friends */ +-#include + #include + + #define OCK_API_LIBNAME "libopencryptoki.so" + #define DYNLIB_LDFLAGS (RTLD_NOW) + + #endif /* _AIX */ ++ ++/* ++ * Check for O_NOFOLLOW support at compile time. ++ * If not available, fall back to lstat() + fopen() (has TOCTOU race). ++ */ ++#ifndef O_NOFOLLOW ++#define OCK_NO_O_NOFOLLOW 1 ++#warning "O_NOFOLLOW not supported, symlink protection uses racy lstat() fallback!" ++#endif ++ ++/* ++ * CWE-59 fix: Open file without following symlinks. ++ * ++ * On platforms with O_NOFOLLOW support: ++ * Uses open(O_NOFOLLOW) + fdopen() for atomic symlink rejection. ++ * ++ * On platforms without O_NOFOLLOW (e.g., older AIX): ++ * Falls back to lstat() + fopen(). This has a TOCTOU race condition, ++ * but still catches pre-planted symlinks which is the common attack ++ * scenario. Better than no protection at all. ++ * ++ * Returns NULL with errno=ELOOP if path is a symlink. ++ */ ++static inline FILE *fopen_nofollow(const char *path, const char *mode) ++{ ++#ifdef OCK_NO_O_NOFOLLOW ++ /* ++ * Fallback for platforms without O_NOFOLLOW: use lstat() check. ++ * This has a TOCTOU race but catches pre-planted symlinks. ++ */ ++ struct stat sb; ++ ++ if (lstat(path, &sb) == 0) { ++ if (S_ISLNK(sb.st_mode)) { ++ errno = ELOOP; ++ return NULL; ++ } ++ } ++ /* Note: if lstat fails (e.g., file doesn't exist for "w" mode), ++ * we proceed with fopen() which will handle the error appropriately */ ++ return fopen(path, mode); ++#else ++ /* Preferred: atomic symlink rejection via O_NOFOLLOW */ ++ int flags = O_NOFOLLOW; ++ int fd; ++ FILE *fp; ++ ++ /* Determine flags based on mode */ ++ if (mode[0] == 'r') { ++ flags |= (mode[1] == '+') ? O_RDWR : O_RDONLY; ++ } else if (mode[0] == 'w') { ++ flags |= O_CREAT | O_TRUNC | ((mode[1] == '+') ? O_RDWR : O_WRONLY); ++ } else if (mode[0] == 'a') { ++ flags |= O_CREAT | O_APPEND | ((mode[1] == '+') ? O_RDWR : O_WRONLY); ++ } else { ++ return NULL; ++ } ++ ++ fd = open(path, flags, 0600); ++ if (fd < 0) ++ return NULL; ++ ++ fp = fdopen(fd, mode); ++ if (fp == NULL) { ++ close(fd); ++ return NULL; ++ } ++ return fp; ++#endif ++} ++ ++#endif /* PLATFORM_H */ +diff --git a/usr/lib/hsm_mk_change/hsm_mk_change.c b/usr/lib/hsm_mk_change/hsm_mk_change.c +index 161ed880..492bbd83 100644 +--- a/usr/lib/hsm_mk_change/hsm_mk_change.c ++++ b/usr/lib/hsm_mk_change/hsm_mk_change.c +@@ -623,9 +623,13 @@ static FILE* hsm_mk_change_op_open(const char *id, CK_SLOT_ID slot_id, + + TRACE_DEVEL("file to open: %s mode: %s\n", hsm_mk_change_file, mode); + +- fp = fopen(hsm_mk_change_file, mode); ++ /* CWE-59 fix: Use fopen_nofollow to prevent symlink attacks */ ++ fp = fopen_nofollow(hsm_mk_change_file, mode); + if (fp == NULL) { +- TRACE_ERROR("%s fopen(%s, %s): %s\n", __func__, ++ if (errno == ELOOP) ++ TRACE_ERROR("Refusing to follow symlink: %s\n", hsm_mk_change_file); ++ else ++ TRACE_ERROR("%s fopen(%s, %s): %s\n", __func__, + hsm_mk_change_file, mode, strerror(errno)); + } + +diff --git a/usr/lib/icsf_stdll/pbkdf.c b/usr/lib/icsf_stdll/pbkdf.c +index 47d1b97c..91230804 100644 +--- a/usr/lib/icsf_stdll/pbkdf.c ++++ b/usr/lib/icsf_stdll/pbkdf.c +@@ -26,6 +26,7 @@ + #include "h_extern.h" + #include "pbkdf.h" + #include "trace.h" ++#include "platform.h" + + + CK_RV get_randombytes(unsigned char *output, int bytes) +@@ -546,9 +547,13 @@ CK_RV secure_racf(STDLL_TokData_t *tokdata, + totallen = outputlen + AES_INIT_VECTOR_SIZE; + + snprintf(fname, sizeof(fname), "%s/%s/%s", CONFIG_PATH, tokname, RACFFILE); +- fp = fopen(fname, "w"); ++ /* CWE-59 fix: Use fopen_nofollow to prevent symlink attacks */ ++ fp = fopen_nofollow(fname, "w"); + if (!fp) { +- TRACE_ERROR("fopen failed: %s\n", strerror(errno)); ++ if (errno == ELOOP) ++ TRACE_ERROR("Refusing to follow symlink: %s\n", fname); ++ else ++ TRACE_ERROR("fopen failed: %s\n", strerror(errno)); + return CKR_FUNCTION_FAILED; + } + +@@ -619,9 +624,13 @@ CK_RV secure_masterkey(STDLL_TokData_t *tokdata, + /* get the total length */ + totallen = outputlen + SALTSIZE; + +- fp = fopen(fname, "w"); ++ /* CWE-59 fix: Use fopen_nofollow to prevent symlink attacks */ ++ fp = fopen_nofollow(fname, "w"); + if (!fp) { +- TRACE_ERROR("fopen failed: %s\n", strerror(errno)); ++ if (errno == ELOOP) ++ TRACE_ERROR("Refusing to follow symlink: %s\n", fname); ++ else ++ TRACE_ERROR("fopen failed: %s\n", strerror(errno)); + return CKR_FUNCTION_FAILED; + } + +diff --git a/usr/sbin/pkcstok_admin/pkcstok_admin.c b/usr/sbin/pkcstok_admin/pkcstok_admin.c +index 9912804e..d144cc04 100644 +--- a/usr/sbin/pkcstok_admin/pkcstok_admin.c ++++ b/usr/sbin/pkcstok_admin/pkcstok_admin.c +@@ -336,11 +336,18 @@ static int set_file_permissions(const char *fname, const struct group *group, + pr_verbose("Setting permissions for '%s' with group '%s'", fname, + group->gr_name); + +- if (stat(fname, &sb) != 0) { ++ /* CWE-59 fix: Use lstat to detect symlinks */ ++ if (lstat(fname, &sb) != 0) { + warnx("'%s' does not exist.", fname); + return -1; + } + ++ /* Only process regular files and directories (CWE-59 fix) */ ++ if (!S_ISREG(sb.st_mode) && !S_ISDIR(sb.st_mode)) { ++ warnx("Skipping '%s': not a regular file or directory.", fname); ++ return 0; ++ } ++ + if (sb.st_uid != 0) { + /* owner is not root */ + pwd = getpwuid(sb.st_uid); +diff --git a/usr/sbin/pkcstok_migrate/pkcstok_migrate.c b/usr/sbin/pkcstok_migrate/pkcstok_migrate.c +index 12b605b5..9579e236 100644 +--- a/usr/sbin/pkcstok_migrate/pkcstok_migrate.c ++++ b/usr/sbin/pkcstok_migrate/pkcstok_migrate.c +@@ -48,6 +48,7 @@ + #include "local_types.h" + #include "h_extern.h" + #include "slotmgr.h" // for ock_snprintf ++#include "platform.h" + + #define OCK_TOOL + #include "pkcs_utils.h" +@@ -77,9 +78,14 @@ static FILE *open_datastore_file(char *buf, size_t buflen, + TRACE_ERROR("Path overflow for datastore file %s\n", file); + return NULL; + } +- res = fopen(buf, mode); +- if (!res) +- TRACE_ERROR("fopen(%s) failed, errno=%s\n", buf, strerror(errno)); ++ /* CWE-59 fix: Use fopen_nofollow to prevent symlink attacks */ ++ res = fopen_nofollow(buf, mode); ++ if (!res) { ++ if (errno == ELOOP) ++ TRACE_ERROR("Refusing to follow symlink: %s\n", buf); ++ else ++ TRACE_ERROR("fopen(%s) failed, errno=%s\n", buf, strerror(errno)); ++ } + return res; + } + +@@ -94,9 +100,14 @@ static FILE *open_tokenobject(char *buf, size_t buflen, + file, tokenobj); + return NULL; + } +- res = fopen(buf, mode); +- if (!res) +- TRACE_ERROR("fopen(%s) failed, errno=%s\n", buf, strerror(errno)); ++ /* CWE-59 fix: Use fopen_nofollow to prevent symlink attacks */ ++ res = fopen_nofollow(buf, mode); ++ if (!res) { ++ if (errno == ELOOP) ++ TRACE_ERROR("Refusing to follow symlink: %s\n", buf); ++ else ++ TRACE_ERROR("fopen(%s) failed, errno=%s\n", buf, strerror(errno)); ++ } + return res; + } + diff --git a/SPECS/opencryptoki.spec b/SPECS/opencryptoki.spec index c78c8f4..8319a7a 100644 --- a/SPECS/opencryptoki.spec +++ b/SPECS/opencryptoki.spec @@ -1,7 +1,7 @@ Name: opencryptoki Summary: Implementation of the PKCS#11 (Cryptoki) specification v3.0 and partially v3.1 Version: 3.25.0 -Release: 4%{?dist}.1 +Release: 4%{?dist}.2 License: CPL-1.0 URL: https://github.com/opencryptoki/opencryptoki Source0: https://github.com/opencryptoki/%{name}/archive/v%{version}/%{name}-%{version}.tar.gz @@ -36,6 +36,9 @@ Patch13: opencryptoki-3.25.0-fix-unwrapping-attribute-bound-EC-keys.patch # https://github.com/ifranzki/opencryptoki/commit/ab740fd Patch14: opencryptoki-3.25.0-fix-private-secure-key-blob-import.patch +# CVE-2026-23893, symlink-following vulnerabilities +Patch15: opencryptoki-3.25.0-CVE-2026-23893.patch + Requires(pre): coreutils Requires: (selinux-policy >= 38.1.14-1 if selinux-policy-targeted) BuildRequires: gcc gcc-c++ @@ -421,6 +424,9 @@ fi %changelog +* Tue Mar 03 2026 Than Ngo - 3.25.0-4.2 +- Resolves: RHEL-144820, Privilege Escalation or Data Exposure via Symlink Following + * Tue Feb 03 2026 Than Ngo - 3.25.0-4.1 - Fix unwrapping of attribute bound EC keys - Fix private secure key blob import