Version in base suite: 12.2.0-1+deb12u2 Base version: open-vm-tools_12.2.0-1+deb12u2 Target version: open-vm-tools_12.2.0-1+deb12u3 Base file: /srv/ftp-master.debian.org/ftp/pool/main/o/open-vm-tools/open-vm-tools_12.2.0-1+deb12u2.dsc Target file: /srv/ftp-master.debian.org/policy/pool/main/o/open-vm-tools/open-vm-tools_12.2.0-1+deb12u3.dsc changelog | 11 patches/CVE-2025-22247-1100-1225-VGAuth-updates.patch | 379 ++++++++++++++++++ patches/series | 1 3 files changed, 391 insertions(+) diff -Nru open-vm-tools-12.2.0/debian/changelog open-vm-tools-12.2.0/debian/changelog --- open-vm-tools-12.2.0/debian/changelog 2023-10-30 16:59:25.000000000 +0000 +++ open-vm-tools-12.2.0/debian/changelog 2025-05-12 13:22:02.000000000 +0000 @@ -1,3 +1,14 @@ +open-vm-tools (2:12.2.0-1+deb12u3) bookworm-security; urgency=medium + + * [df2a118] Fixing an insecure file handling vulnerability. + It allowed a malicious actor with non-administrative privileges + on a guest VM to tamper the local files to trigger insecure file + operations within that VM. + VMSA-2025-0007 + CVE-2025-22247 (Closes: #1105159) + + -- Bernd Zeimetz Mon, 12 May 2025 15:22:02 +0200 + open-vm-tools (2:12.2.0-1+deb12u2) bookworm-security; urgency=medium * Closes: #1054666 diff -Nru open-vm-tools-12.2.0/debian/patches/CVE-2025-22247-1100-1225-VGAuth-updates.patch open-vm-tools-12.2.0/debian/patches/CVE-2025-22247-1100-1225-VGAuth-updates.patch --- open-vm-tools-12.2.0/debian/patches/CVE-2025-22247-1100-1225-VGAuth-updates.patch 1970-01-01 00:00:00.000000000 +0000 +++ open-vm-tools-12.2.0/debian/patches/CVE-2025-22247-1100-1225-VGAuth-updates.patch 2025-05-12 13:22:02.000000000 +0000 @@ -0,0 +1,379 @@ +From 2a2607c6bd94ae22a937fd2adde7472d9a6d506c Mon Sep 17 00:00:00 2001 +From: John Wolfe +Date: Mon, 5 May 2025 16:10:07 -0700 +Subject: [PATCH] Validate user names and file paths + +Prevent usage of illegal characters in user names and file paths. +Also, disallow unexpected symlinks in file paths. + +This patch contains changes to common source files not applicable +to open-vm-tools. + +All files being updated should be consider to have the copyright to +be updated to: + + * Copyright (c) XXXX-2025 Broadcom. All Rights Reserved. + * The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. + +The 2025 Broadcom copyright information update is not part of this +patch set to allow the patch to be easily applied to previous +open-vm-tools source releases. +--- + open-vm-tools/vgauth/common/VGAuthUtil.c | 33 +++++++++ + open-vm-tools/vgauth/common/VGAuthUtil.h | 2 + + open-vm-tools/vgauth/common/prefs.h | 3 + + open-vm-tools/vgauth/common/usercheck.c | 28 +++++-- + open-vm-tools/vgauth/serviceImpl/alias.c | 74 ++++++++++++++++++- + open-vm-tools/vgauth/serviceImpl/service.c | 27 +++++++ + open-vm-tools/vgauth/serviceImpl/serviceInt.h | 1 + + 7 files changed, 160 insertions(+), 8 deletions(-) + +diff --git a/open-vm-tools/vgauth/common/VGAuthUtil.c b/open-vm-tools/vgauth/common/VGAuthUtil.c +index 76383c462..9c2adb8d0 100644 +--- a/open-vm-tools/vgauth/common/VGAuthUtil.c ++++ b/open-vm-tools/vgauth/common/VGAuthUtil.c +@@ -309,3 +309,36 @@ Util_Assert(const char *cond, + #endif + g_assert(0); + } ++ ++ ++/* ++ ****************************************************************************** ++ * Util_Utf8CaseCmp -- */ /** ++ * ++ * Case insensitive comparison for utf8 strings which can have non-ascii ++ * characters. ++ * ++ * @param[in] str1 Null terminated utf8 string. ++ * @param[in] str2 Null terminated utf8 string. ++ * ++ ****************************************************************************** ++ */ ++ ++int ++Util_Utf8CaseCmp(const gchar *str1, ++ const gchar *str2) ++{ ++ int ret; ++ gchar *str1Case; ++ gchar *str2Case; ++ ++ str1Case = g_utf8_casefold(str1, -1); ++ str2Case = g_utf8_casefold(str2, -1); ++ ++ ret = g_strcmp0(str1Case, str2Case); ++ ++ g_free(str1Case); ++ g_free(str2Case); ++ ++ return ret; ++} +diff --git a/open-vm-tools/vgauth/common/VGAuthUtil.h b/open-vm-tools/vgauth/common/VGAuthUtil.h +index f7f3aa216..ef32a91da 100644 +--- a/open-vm-tools/vgauth/common/VGAuthUtil.h ++++ b/open-vm-tools/vgauth/common/VGAuthUtil.h +@@ -105,4 +105,6 @@ gboolean Util_CheckExpiration(const GTimeVal *start, unsigned int duration); + + void Util_Assert(const char *cond, const char *file, int lineNum); + ++int Util_Utf8CaseCmp(const gchar *str1, const gchar *str2); ++ + #endif +diff --git a/open-vm-tools/vgauth/common/prefs.h b/open-vm-tools/vgauth/common/prefs.h +index ff116928c..7cddb3e17 100644 +--- a/open-vm-tools/vgauth/common/prefs.h ++++ b/open-vm-tools/vgauth/common/prefs.h +@@ -165,6 +165,9 @@ msgCatalog = /etc/vmware-tools/vgauth/messages + /** Where the localized version of the messages were installed. */ + #define VGAUTH_PREF_LOCALIZATION_DIR "msgCatalog" + ++/** If symlinks or junctions are allowed in alias store file path */ ++#define VGAUTH_PREF_ALLOW_SYMLINKS "allowSymlinks" ++ + /* + * Pref values + */ +diff --git a/open-vm-tools/vgauth/common/usercheck.c b/open-vm-tools/vgauth/common/usercheck.c +index 31eeb5a77..145f1f056 100644 +--- a/open-vm-tools/vgauth/common/usercheck.c ++++ b/open-vm-tools/vgauth/common/usercheck.c +@@ -78,6 +78,8 @@ + * Solaris as well, but that path is untested. + */ + ++#define MAX_USER_NAME_LEN 256 ++ + /* + * A single retry works for the LDAP case, but try more often in case NIS + * or something else has a related issue. Note that a bad username/uid won't +@@ -354,17 +356,29 @@ Usercheck_UsernameIsLegal(const gchar *userName) + * + */ + size_t len; +-#ifdef _WIN32 +- // allow '\' in for Windows domain usernames +- char *illegalChars = "<>/"; +-#else +- char *illegalChars = "\\<>/"; +-#endif ++ size_t i = 0; ++ int backSlashCnt = 0; ++ /* ++ * As user names are used to generate its alias store file name/path, it ++ * should not contain path traversal characters ('/' and '\'). ++ */ ++ char *illegalChars = "<>/\\"; + + len = strlen(userName); +- if (strcspn(userName, illegalChars) != len) { ++ if (len > MAX_USER_NAME_LEN) { + return FALSE; + } ++ ++ while ((i += strcspn(userName + i, illegalChars)) < len) { ++ /* ++ * One backward slash is allowed for domain\username separator. ++ */ ++ if (userName[i] != '\\' || ++backSlashCnt > 1) { ++ return FALSE; ++ } ++ ++i; ++ } ++ + return TRUE; + } + +diff --git a/open-vm-tools/vgauth/serviceImpl/alias.c b/open-vm-tools/vgauth/serviceImpl/alias.c +index b28351eea..687d1b373 100644 +--- a/open-vm-tools/vgauth/serviceImpl/alias.c ++++ b/open-vm-tools/vgauth/serviceImpl/alias.c +@@ -41,6 +41,7 @@ + #include "certverify.h" + #include "VGAuthProto.h" + #include "vmxlog.h" ++#include "VGAuthUtil.h" + + // puts the identity store in an easy to find place + #undef WIN_TEST_MODE +@@ -66,6 +67,7 @@ + #define ALIASSTORE_FILE_PREFIX "user-" + #define ALIASSTORE_FILE_SUFFIX ".xml" + ++static gboolean allowSymlinks = FALSE; + static gchar *aliasStoreRootDir = DEFAULT_ALIASSTORE_ROOT_DIR; + + #ifdef _WIN32 +@@ -252,6 +254,12 @@ mapping file layout: + + */ + ++#ifdef _WIN32 ++#define ISPATHSEP(c) ((c) == '\\' || (c) == '/') ++#else ++#define ISPATHSEP(c) ((c) == '/') ++#endif ++ + + /* + ****************************************************************************** +@@ -466,6 +474,7 @@ ServiceLoadFileContentsWin(const gchar *fileName, + gunichar2 *fileNameW = NULL; + BOOL ok; + DWORD bytesRead; ++ gchar *realPath = NULL; + + *fileSize = 0; + *contents = NULL; +@@ -622,6 +631,22 @@ ServiceLoadFileContentsWin(const gchar *fileName, + goto done; + } + ++ if (!allowSymlinks) { ++ /* ++ * Check if fileName is real path. ++ */ ++ if ((realPath = ServiceFileGetPathByHandle(hFile)) == NULL) { ++ err = VGAUTH_E_FAIL; ++ goto done; ++ } ++ if (Util_Utf8CaseCmp(realPath, fileName) != 0) { ++ Warning("%s: Real path (%s) is not same as file path (%s)\n", ++ __FUNCTION__, realPath, fileName); ++ err = VGAUTH_E_FAIL; ++ goto done; ++ } ++ } ++ + /* + * Now finally read the contents. + */ +@@ -650,6 +675,7 @@ done: + CloseHandle(hFile); + } + g_free(fileNameW); ++ g_free(realPath); + + return err; + } +@@ -672,6 +698,7 @@ ServiceLoadFileContentsPosix(const gchar *fileName, + gchar *buf; + gchar *bp; + int fd = -1; ++ gchar realPath[PATH_MAX] = { 0 }; + + *fileSize = 0; + *contents = NULL; +@@ -817,6 +844,23 @@ ServiceLoadFileContentsPosix(const gchar *fileName, + goto done; + } + ++ if (!allowSymlinks) { ++ /* ++ * Check if fileName is real path. ++ */ ++ if (realpath(fileName, realPath) == NULL) { ++ Warning("%s: realpath() failed. errno (%d)\n", __FUNCTION__, errno); ++ err = VGAUTH_E_FAIL; ++ goto done; ++ } ++ if (g_strcmp0(realPath, fileName) != 0) { ++ Warning("%s: Real path (%s) is not same as file path (%s)\n", ++ __FUNCTION__, realPath, fileName); ++ err = VGAUTH_E_FAIL; ++ goto done; ++ } ++ } ++ + /* + * All sanity checks passed; read the bits. + */ +@@ -2803,8 +2847,13 @@ ServiceAliasRemoveAlias(const gchar *reqUserName, + + /* + * We don't verify the user exists in a Remove operation, to allow +- * cleanup of deleted user's stores. ++ * cleanup of deleted user's stores, but we do check whether the ++ * user name is legal or not. + */ ++ if (!Usercheck_UsernameIsLegal(userName)) { ++ Warning("%s: Illegal user name '%s'\n", __FUNCTION__, userName); ++ return VGAUTH_E_FAIL; ++ } + + if (!CertVerify_IsWellFormedPEMCert(pemCert)) { + return VGAUTH_E_INVALID_CERTIFICATE; +@@ -3036,6 +3085,16 @@ ServiceAliasQueryAliases(const gchar *userName, + } + #endif + ++ /* ++ * We don't verify the user exists in a Query operation to allow ++ * cleaning up after a deleted user, but we do check whether the ++ * user name is legal or not. ++ */ ++ if (!Usercheck_UsernameIsLegal(userName)) { ++ Warning("%s: Illegal user name '%s'\n", __FUNCTION__, userName); ++ return VGAUTH_E_FAIL; ++ } ++ + err = AliasLoadAliases(userName, num, aList); + if (VGAUTH_E_OK != err) { + Warning("%s: failed to load Aliases for '%s'\n", __FUNCTION__, userName); +@@ -3294,6 +3353,7 @@ ServiceAliasInitAliasStore(void) + VGAuthError err = VGAUTH_E_OK; + gboolean saveBadDir = FALSE; + char *defaultDir = NULL; ++ size_t len; + + #ifdef _WIN32 + { +@@ -3324,6 +3384,10 @@ ServiceAliasInitAliasStore(void) + defaultDir = g_strdup(DEFAULT_ALIASSTORE_ROOT_DIR); + #endif + ++ allowSymlinks = Pref_GetBool(gPrefs, ++ VGAUTH_PREF_ALLOW_SYMLINKS, ++ VGAUTH_PREF_GROUP_NAME_SERVICE, ++ FALSE); + /* + * Find the alias store directory. This allows an installer to put + * it somewhere else if necessary. +@@ -3337,6 +3401,14 @@ ServiceAliasInitAliasStore(void) + VGAUTH_PREF_GROUP_NAME_SERVICE, + defaultDir); + ++ /* ++ * Remove the trailing separator if any from aliasStoreRootDir path. ++ */ ++ len = strlen(aliasStoreRootDir); ++ if (ISPATHSEP(aliasStoreRootDir[len - 1])) { ++ aliasStoreRootDir[len - 1] = '\0'; ++ } ++ + Log("Using '%s' for alias store root directory\n", aliasStoreRootDir); + + g_free(defaultDir); +diff --git a/open-vm-tools/vgauth/serviceImpl/service.c b/open-vm-tools/vgauth/serviceImpl/service.c +index d4716526c..e053ed0fa 100644 +--- a/open-vm-tools/vgauth/serviceImpl/service.c ++++ b/open-vm-tools/vgauth/serviceImpl/service.c +@@ -28,6 +28,7 @@ + #include "VGAuthUtil.h" + #ifdef _WIN32 + #include "winUtil.h" ++#include + #endif + + static ServiceStartListeningForIOFunc startListeningIOFunc = NULL; +@@ -283,9 +284,35 @@ static gchar * + ServiceUserNameToPipeName(const char *userName) + { + gchar *escapedName = ServiceEncodeUserName(userName); ++#ifdef _WIN32 ++ /* ++ * Adding below pragma only in windows to suppress the compile time warning ++ * about unavailability of g_uuid_string_random() since compiler flag ++ * GLIB_VERSION_MAX_ALLOWED is defined to GLIB_VERSION_2_34. ++ * TODO: Remove below pragma when GLIB_VERSION_MAX_ALLOWED is bumped up to ++ * or greater than GLIB_VERSION_2_52. ++ */ ++#pragma warning(suppress : 4996) ++ gchar *uuidStr = g_uuid_string_random(); ++ /* ++ * Add a unique suffix to avoid a name collision with an existing named pipe ++ * created by someone else (intentionally or by accident). ++ * This is not needed for Linux; name collisions on sockets are already ++ * avoided there since (1) file system paths to VGAuthService sockets are in ++ * a directory that is writable only by root and (2) VGAuthService unlinks a ++ * socket path before binding it to a newly created socket. ++ */ ++ gchar *pipeName = g_strdup_printf("%s-%s-%s", ++ SERVICE_PUBLIC_PIPE_NAME, ++ escapedName, ++ uuidStr); ++ ++ g_free(uuidStr); ++#else + gchar *pipeName = g_strdup_printf("%s-%s", + SERVICE_PUBLIC_PIPE_NAME, + escapedName); ++#endif + + g_free(escapedName); + return pipeName; +diff --git a/open-vm-tools/vgauth/serviceImpl/serviceInt.h b/open-vm-tools/vgauth/serviceImpl/serviceInt.h +index ef49f42c2..c37f42fa6 100644 +--- a/open-vm-tools/vgauth/serviceImpl/serviceInt.h ++++ b/open-vm-tools/vgauth/serviceImpl/serviceInt.h +@@ -441,6 +441,7 @@ VGAuthError ServiceFileVerifyAdminGroupOwnedByHandle(const HANDLE hFile); + VGAuthError ServiceFileVerifyEveryoneReadableByHandle(const HANDLE hFile); + VGAuthError ServiceFileVerifyUserAccessByHandle(const HANDLE hFile, + const char *userName); ++gchar *ServiceFileGetPathByHandle(HANDLE hFile); + #else + VGAuthError ServiceFileVerifyFileOwnerAndPerms(const char *fileName, + const char *userName, +-- +2.43.5 + diff -Nru open-vm-tools-12.2.0/debian/patches/series open-vm-tools-12.2.0/debian/patches/series --- open-vm-tools-12.2.0/debian/patches/series 2023-10-30 16:59:25.000000000 +0000 +++ open-vm-tools-12.2.0/debian/patches/series 2025-05-12 13:22:02.000000000 +0000 @@ -5,3 +5,4 @@ CVE-2023-20900.patch CVE-2023-34059.patch CVE-2023-34058.patch +CVE-2025-22247-1100-1225-VGAuth-updates.patch