diff options
| -rw-r--r-- | meta-networking/recipes-support/open-vm-tools/open-vm-tools/CVE-2025-22247.patch | 378 | ||||
| -rw-r--r-- | meta-networking/recipes-support/open-vm-tools/open-vm-tools_12.5.0.bb | 1 |
2 files changed, 379 insertions, 0 deletions
diff --git a/meta-networking/recipes-support/open-vm-tools/open-vm-tools/CVE-2025-22247.patch b/meta-networking/recipes-support/open-vm-tools/open-vm-tools/CVE-2025-22247.patch new file mode 100644 index 0000000000..2141443a1f --- /dev/null +++ b/meta-networking/recipes-support/open-vm-tools/open-vm-tools/CVE-2025-22247.patch | |||
| @@ -0,0 +1,378 @@ | |||
| 1 | From 7874e572b5aac5a418551dc5e3935c1e74bf6f1f Mon Sep 17 00:00:00 2001 | ||
| 2 | From: John Wolfe <john.wolfe@broadcom.com> | ||
| 3 | Date: Mon, 5 May 2025 15:58:03 -0700 | ||
| 4 | Subject: [PATCH] Validate user names and file paths | ||
| 5 | |||
| 6 | Prevent usage of illegal characters in user names and file paths. | ||
| 7 | Also, disallow unexpected symlinks in file paths. | ||
| 8 | |||
| 9 | This patch contains changes to common source files not applicable | ||
| 10 | to open-vm-tools. | ||
| 11 | |||
| 12 | All files being updated should be consider to have the copyright to | ||
| 13 | be updated to: | ||
| 14 | |||
| 15 | * Copyright (c) XXXX-2025 Broadcom. All Rights Reserved. | ||
| 16 | * The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. | ||
| 17 | |||
| 18 | The 2025 Broadcom copyright information update is not part of this | ||
| 19 | patch set to allow the patch to be easily applied to previous | ||
| 20 | open-vm-tools source releases. | ||
| 21 | |||
| 22 | CVE: CVE-2025-22247 | ||
| 23 | Upstream-Status: Backport [https://github.com/vmware/open-vm-tools/blob/CVE-2025-22247.patch/CVE-2025-22247-1230-1250-VGAuth-updates.patch] | ||
| 24 | |||
| 25 | Signed-off-by: Yogita Urade <yogita.urade@windriver.com> | ||
| 26 | --- | ||
| 27 | open-vm-tools/vgauth/common/VGAuthUtil.c | 33 +++++++++ | ||
| 28 | open-vm-tools/vgauth/common/VGAuthUtil.h | 2 + | ||
| 29 | open-vm-tools/vgauth/common/prefs.h | 3 + | ||
| 30 | open-vm-tools/vgauth/common/usercheck.c | 22 +++++- | ||
| 31 | open-vm-tools/vgauth/serviceImpl/alias.c | 74 ++++++++++++++++++- | ||
| 32 | open-vm-tools/vgauth/serviceImpl/service.c | 27 +++++++ | ||
| 33 | open-vm-tools/vgauth/serviceImpl/serviceInt.h | 1 + | ||
| 34 | 7 files changed, 159 insertions(+), 3 deletions(-) | ||
| 35 | |||
| 36 | diff --git a/open-vm-tools/vgauth/common/VGAuthUtil.c b/open-vm-tools/vgauth/common/VGAuthUtil.c | ||
| 37 | index 76383c46..06f1b51c 100644 | ||
| 38 | --- a/open-vm-tools/vgauth/common/VGAuthUtil.c | ||
| 39 | +++ b/open-vm-tools/vgauth/common/VGAuthUtil.c | ||
| 40 | @@ -309,3 +309,36 @@ Util_Assert(const char *cond, | ||
| 41 | #endif | ||
| 42 | g_assert(0); | ||
| 43 | } | ||
| 44 | + | ||
| 45 | + | ||
| 46 | +/* | ||
| 47 | ++ ****************************************************************************** | ||
| 48 | + * Util_Utf8CaseCmp -- */ /** | ||
| 49 | + * | ||
| 50 | + * Case insensitive comparison for utf8 strings which can have non-ascii | ||
| 51 | + * characters. | ||
| 52 | + * | ||
| 53 | + * @param[in] str1 Null terminated utf8 string. | ||
| 54 | + * @param[in] str2 Null terminated utf8 string. | ||
| 55 | + * | ||
| 56 | + ****************************************************************************** | ||
| 57 | + */ | ||
| 58 | + | ||
| 59 | +int | ||
| 60 | +Util_Utf8CaseCmp(const gchar *str1, | ||
| 61 | + const gchar *str2) | ||
| 62 | +{ | ||
| 63 | + int ret; | ||
| 64 | + gchar *str1Case; | ||
| 65 | + gchar *str2Case; | ||
| 66 | + | ||
| 67 | + str1Case = g_utf8_casefold(str1, -1); | ||
| 68 | + str2Case = g_utf8_casefold(str2, -1); | ||
| 69 | + | ||
| 70 | + ret = g_strcmp0(str1Case, str2Case); | ||
| 71 | + | ||
| 72 | + g_free(str1Case); | ||
| 73 | + g_free(str2Case); | ||
| 74 | + | ||
| 75 | + return ret; | ||
| 76 | +} | ||
| 77 | diff --git a/open-vm-tools/vgauth/common/VGAuthUtil.h b/open-vm-tools/vgauth/common/VGAuthUtil.h | ||
| 78 | index f7f3aa21..ef32a91d 100644 | ||
| 79 | --- a/open-vm-tools/vgauth/common/VGAuthUtil.h | ||
| 80 | +++ b/open-vm-tools/vgauth/common/VGAuthUtil.h | ||
| 81 | @@ -105,4 +105,6 @@ gboolean Util_CheckExpiration(const GTimeVal *start, unsigned int duration); | ||
| 82 | |||
| 83 | void Util_Assert(const char *cond, const char *file, int lineNum); | ||
| 84 | |||
| 85 | +int Util_Utf8CaseCmp(const gchar *str1, const gchar *str2); | ||
| 86 | + | ||
| 87 | #endif | ||
| 88 | diff --git a/open-vm-tools/vgauth/common/prefs.h b/open-vm-tools/vgauth/common/prefs.h | ||
| 89 | index 6c58f3f4..3299eb26 100644 | ||
| 90 | --- a/open-vm-tools/vgauth/common/prefs.h | ||
| 91 | +++ b/open-vm-tools/vgauth/common/prefs.h | ||
| 92 | @@ -167,6 +167,9 @@ msgCatalog = /etc/vmware-tools/vgauth/messages | ||
| 93 | /** Where the localized version of the messages were installed. */ | ||
| 94 | #define VGAUTH_PREF_LOCALIZATION_DIR "msgCatalog" | ||
| 95 | |||
| 96 | +/** If symlinks or junctions are allowed in alias store file path */ | ||
| 97 | +#define VGAUTH_PREF_ALLOW_SYMLINKS "allowSymlinks" | ||
| 98 | + | ||
| 99 | /* | ||
| 100 | * Pref values | ||
| 101 | */ | ||
| 102 | diff --git a/open-vm-tools/vgauth/common/usercheck.c b/open-vm-tools/vgauth/common/usercheck.c | ||
| 103 | index 3beede2e..8b4cbacb 100644 | ||
| 104 | --- a/open-vm-tools/vgauth/common/usercheck.c | ||
| 105 | +++ b/open-vm-tools/vgauth/common/usercheck.c | ||
| 106 | @@ -78,6 +78,8 @@ | ||
| 107 | * Solaris as well, but that path is untested. | ||
| 108 | */ | ||
| 109 | |||
| 110 | +#define MAX_USER_NAME_LEN 256 | ||
| 111 | + | ||
| 112 | /* | ||
| 113 | * A single retry works for the LDAP case, but try more often in case NIS | ||
| 114 | * or something else has a related issue. Note that a bad username/uid won't | ||
| 115 | @@ -354,12 +356,28 @@ Usercheck_UsernameIsLegal(const gchar *userName) | ||
| 116 | * restricted list for local usernames. | ||
| 117 | */ | ||
| 118 | size_t len; | ||
| 119 | - char *illegalChars = "<>/"; | ||
| 120 | + size_t i = 0; | ||
| 121 | + int backSlashCnt = 0; | ||
| 122 | + /* | ||
| 123 | + * As user names are used to generate its alias store file name/path, it | ||
| 124 | + * should not contain path traversal characters ('/' and '\'). | ||
| 125 | + */ | ||
| 126 | + char *illegalChars = "<>/\\"; | ||
| 127 | |||
| 128 | len = strlen(userName); | ||
| 129 | - if (strcspn(userName, illegalChars) != len) { | ||
| 130 | + if (len > MAX_USER_NAME_LEN) { | ||
| 131 | return FALSE; | ||
| 132 | } | ||
| 133 | + | ||
| 134 | + while ((i += strcspn(userName + i, illegalChars)) < len) { | ||
| 135 | + /* | ||
| 136 | + * One backward slash is allowed for domain\username separator. | ||
| 137 | + */ | ||
| 138 | + if (userName[i] != '\\' || ++backSlashCnt > 1) { | ||
| 139 | + return FALSE; | ||
| 140 | + } | ||
| 141 | + ++i; | ||
| 142 | + } | ||
| 143 | return TRUE; | ||
| 144 | } | ||
| 145 | |||
| 146 | diff --git a/open-vm-tools/vgauth/serviceImpl/alias.c b/open-vm-tools/vgauth/serviceImpl/alias.c | ||
| 147 | index 4e170202..c7040ebf 100644 | ||
| 148 | --- a/open-vm-tools/vgauth/serviceImpl/alias.c | ||
| 149 | +++ b/open-vm-tools/vgauth/serviceImpl/alias.c | ||
| 150 | @@ -41,6 +41,7 @@ | ||
| 151 | #include "certverify.h" | ||
| 152 | #include "VGAuthProto.h" | ||
| 153 | #include "vmxlog.h" | ||
| 154 | +#include "VGAuthUtil.h" | ||
| 155 | |||
| 156 | // puts the identity store in an easy to find place | ||
| 157 | #undef WIN_TEST_MODE | ||
| 158 | @@ -66,6 +67,7 @@ | ||
| 159 | #define ALIASSTORE_FILE_PREFIX "user-" | ||
| 160 | #define ALIASSTORE_FILE_SUFFIX ".xml" | ||
| 161 | |||
| 162 | +static gboolean allowSymlinks = FALSE; | ||
| 163 | static gchar *aliasStoreRootDir = DEFAULT_ALIASSTORE_ROOT_DIR; | ||
| 164 | |||
| 165 | #ifdef _WIN32 | ||
| 166 | @@ -252,6 +254,12 @@ mapping file layout: | ||
| 167 | |||
| 168 | */ | ||
| 169 | |||
| 170 | +#ifdef _WIN32 | ||
| 171 | +#define ISPATHSEP(c) ((c) == '\\' || (c) == '/') | ||
| 172 | +#else | ||
| 173 | +#define ISPATHSEP(c) ((c) == '/') | ||
| 174 | +#endif | ||
| 175 | + | ||
| 176 | |||
| 177 | /* | ||
| 178 | ****************************************************************************** | ||
| 179 | @@ -466,6 +474,7 @@ ServiceLoadFileContentsWin(const gchar *fileName, | ||
| 180 | gunichar2 *fileNameW = NULL; | ||
| 181 | BOOL ok; | ||
| 182 | DWORD bytesRead; | ||
| 183 | + gchar *realPath = NULL; | ||
| 184 | |||
| 185 | *fileSize = 0; | ||
| 186 | *contents = NULL; | ||
| 187 | @@ -622,6 +631,22 @@ ServiceLoadFileContentsWin(const gchar *fileName, | ||
| 188 | goto done; | ||
| 189 | } | ||
| 190 | |||
| 191 | + if (!allowSymlinks) { | ||
| 192 | + /* | ||
| 193 | + * Check if fileName is real path. | ||
| 194 | + */ | ||
| 195 | + if ((realPath = ServiceFileGetPathByHandle(hFile)) == NULL) { | ||
| 196 | + err = VGAUTH_E_FAIL; | ||
| 197 | + goto done; | ||
| 198 | + } | ||
| 199 | + if (Util_Utf8CaseCmp(realPath, fileName) != 0) { | ||
| 200 | + Warning("%s: Real path (%s) is not same as file path (%s)\n", | ||
| 201 | + __FUNCTION__, realPath, fileName); | ||
| 202 | + err = VGAUTH_E_FAIL; | ||
| 203 | + goto done; | ||
| 204 | + } | ||
| 205 | + } | ||
| 206 | + | ||
| 207 | /* | ||
| 208 | * Now finally read the contents. | ||
| 209 | */ | ||
| 210 | @@ -650,6 +675,7 @@ done: | ||
| 211 | CloseHandle(hFile); | ||
| 212 | } | ||
| 213 | g_free(fileNameW); | ||
| 214 | + g_free(realPath); | ||
| 215 | |||
| 216 | return err; | ||
| 217 | } | ||
| 218 | @@ -672,6 +698,7 @@ ServiceLoadFileContentsPosix(const gchar *fileName, | ||
| 219 | gchar *buf; | ||
| 220 | gchar *bp; | ||
| 221 | int fd = -1; | ||
| 222 | + gchar realPath[PATH_MAX] = { 0 }; | ||
| 223 | |||
| 224 | *fileSize = 0; | ||
| 225 | *contents = NULL; | ||
| 226 | @@ -817,6 +844,23 @@ ServiceLoadFileContentsPosix(const gchar *fileName, | ||
| 227 | goto done; | ||
| 228 | } | ||
| 229 | |||
| 230 | + if (!allowSymlinks) { | ||
| 231 | + /* | ||
| 232 | + * Check if fileName is real path. | ||
| 233 | + */ | ||
| 234 | + if (realpath(fileName, realPath) == NULL) { | ||
| 235 | + Warning("%s: realpath() failed. errno (%d)\n", __FUNCTION__, errno); | ||
| 236 | + err = VGAUTH_E_FAIL; | ||
| 237 | + goto done; | ||
| 238 | + } | ||
| 239 | + if (g_strcmp0(realPath, fileName) != 0) { | ||
| 240 | + Warning("%s: Real path (%s) is not same as file path (%s)\n", | ||
| 241 | + __FUNCTION__, realPath, fileName); | ||
| 242 | + err = VGAUTH_E_FAIL; | ||
| 243 | + goto done; | ||
| 244 | + } | ||
| 245 | + } | ||
| 246 | + | ||
| 247 | /* | ||
| 248 | * All confidence checks passed; read the bits. | ||
| 249 | */ | ||
| 250 | @@ -2803,8 +2847,13 @@ ServiceAliasRemoveAlias(const gchar *reqUserName, | ||
| 251 | |||
| 252 | /* | ||
| 253 | * We don't verify the user exists in a Remove operation, to allow | ||
| 254 | - * cleanup of deleted user's stores. | ||
| 255 | + * cleanup of deleted user's stores, but we do check whether the | ||
| 256 | + * user name is legal or not. | ||
| 257 | */ | ||
| 258 | + if (!Usercheck_UsernameIsLegal(userName)) { | ||
| 259 | + Warning("%s: Illegal user name '%s'\n", __FUNCTION__, userName); | ||
| 260 | + return VGAUTH_E_FAIL; | ||
| 261 | + } | ||
| 262 | |||
| 263 | if (!CertVerify_IsWellFormedPEMCert(pemCert)) { | ||
| 264 | return VGAUTH_E_INVALID_CERTIFICATE; | ||
| 265 | @@ -3036,6 +3085,16 @@ ServiceAliasQueryAliases(const gchar *userName, | ||
| 266 | } | ||
| 267 | #endif | ||
| 268 | |||
| 269 | + /* | ||
| 270 | + * We don't verify the user exists in a Query operation to allow | ||
| 271 | + * cleaning up after a deleted user, but we do check whether the | ||
| 272 | + * user name is legal or not. | ||
| 273 | + */ | ||
| 274 | + if (!Usercheck_UsernameIsLegal(userName)) { | ||
| 275 | + Warning("%s: Illegal user name '%s'\n", __FUNCTION__, userName); | ||
| 276 | + return VGAUTH_E_FAIL; | ||
| 277 | + } | ||
| 278 | + | ||
| 279 | err = AliasLoadAliases(userName, num, aList); | ||
| 280 | if (VGAUTH_E_OK != err) { | ||
| 281 | Warning("%s: failed to load Aliases for '%s'\n", __FUNCTION__, userName); | ||
| 282 | @@ -3294,6 +3353,7 @@ ServiceAliasInitAliasStore(void) | ||
| 283 | VGAuthError err = VGAUTH_E_OK; | ||
| 284 | gboolean saveBadDir = FALSE; | ||
| 285 | char *defaultDir = NULL; | ||
| 286 | + size_t len; | ||
| 287 | |||
| 288 | #ifdef _WIN32 | ||
| 289 | { | ||
| 290 | @@ -3324,6 +3384,10 @@ ServiceAliasInitAliasStore(void) | ||
| 291 | defaultDir = g_strdup(DEFAULT_ALIASSTORE_ROOT_DIR); | ||
| 292 | #endif | ||
| 293 | |||
| 294 | + allowSymlinks = Pref_GetBool(gPrefs, | ||
| 295 | + VGAUTH_PREF_ALLOW_SYMLINKS, | ||
| 296 | + VGAUTH_PREF_GROUP_NAME_SERVICE, | ||
| 297 | + FALSE); | ||
| 298 | /* | ||
| 299 | * Find the alias store directory. This allows an installer to put | ||
| 300 | * it somewhere else if necessary. | ||
| 301 | @@ -3337,6 +3401,14 @@ ServiceAliasInitAliasStore(void) | ||
| 302 | VGAUTH_PREF_GROUP_NAME_SERVICE, | ||
| 303 | defaultDir); | ||
| 304 | |||
| 305 | + /* | ||
| 306 | + * Remove the trailing separator if any from aliasStoreRootDir path. | ||
| 307 | + */ | ||
| 308 | + len = strlen(aliasStoreRootDir); | ||
| 309 | + if (ISPATHSEP(aliasStoreRootDir[len - 1])) { | ||
| 310 | + aliasStoreRootDir[len - 1] = '\0'; | ||
| 311 | + } | ||
| 312 | + | ||
| 313 | Log("Using '%s' for alias store root directory\n", aliasStoreRootDir); | ||
| 314 | |||
| 315 | g_free(defaultDir); | ||
| 316 | diff --git a/open-vm-tools/vgauth/serviceImpl/service.c b/open-vm-tools/vgauth/serviceImpl/service.c | ||
| 317 | index d4716526..e053ed0f 100644 | ||
| 318 | --- a/open-vm-tools/vgauth/serviceImpl/service.c | ||
| 319 | +++ b/open-vm-tools/vgauth/serviceImpl/service.c | ||
| 320 | @@ -28,6 +28,7 @@ | ||
| 321 | #include "VGAuthUtil.h" | ||
| 322 | #ifdef _WIN32 | ||
| 323 | #include "winUtil.h" | ||
| 324 | +#include <glib.h> | ||
| 325 | #endif | ||
| 326 | |||
| 327 | static ServiceStartListeningForIOFunc startListeningIOFunc = NULL; | ||
| 328 | @@ -283,9 +284,35 @@ static gchar * | ||
| 329 | ServiceUserNameToPipeName(const char *userName) | ||
| 330 | { | ||
| 331 | gchar *escapedName = ServiceEncodeUserName(userName); | ||
| 332 | +#ifdef _WIN32 | ||
| 333 | + /* | ||
| 334 | + * Adding below pragma only in windows to suppress the compile time warning | ||
| 335 | + * about unavailability of g_uuid_string_random() since compiler flag | ||
| 336 | + * GLIB_VERSION_MAX_ALLOWED is defined to GLIB_VERSION_2_34. | ||
| 337 | + * TODO: Remove below pragma when GLIB_VERSION_MAX_ALLOWED is bumped up to | ||
| 338 | + * or greater than GLIB_VERSION_2_52. | ||
| 339 | + */ | ||
| 340 | +#pragma warning(suppress : 4996) | ||
| 341 | + gchar *uuidStr = g_uuid_string_random(); | ||
| 342 | + /* | ||
| 343 | + * Add a unique suffix to avoid a name collision with an existing named pipe | ||
| 344 | + * created by someone else (intentionally or by accident). | ||
| 345 | + * This is not needed for Linux; name collisions on sockets are already | ||
| 346 | + * avoided there since (1) file system paths to VGAuthService sockets are in | ||
| 347 | + * a directory that is writable only by root and (2) VGAuthService unlinks a | ||
| 348 | + * socket path before binding it to a newly created socket. | ||
| 349 | + */ | ||
| 350 | + gchar *pipeName = g_strdup_printf("%s-%s-%s", | ||
| 351 | + SERVICE_PUBLIC_PIPE_NAME, | ||
| 352 | + escapedName, | ||
| 353 | + uuidStr); | ||
| 354 | + | ||
| 355 | + g_free(uuidStr); | ||
| 356 | +#else | ||
| 357 | gchar *pipeName = g_strdup_printf("%s-%s", | ||
| 358 | SERVICE_PUBLIC_PIPE_NAME, | ||
| 359 | escapedName); | ||
| 360 | +#endif | ||
| 361 | |||
| 362 | g_free(escapedName); | ||
| 363 | return pipeName; | ||
| 364 | diff --git a/open-vm-tools/vgauth/serviceImpl/serviceInt.h b/open-vm-tools/vgauth/serviceImpl/serviceInt.h | ||
| 365 | index 5f420192..f4f88547 100644 | ||
| 366 | --- a/open-vm-tools/vgauth/serviceImpl/serviceInt.h | ||
| 367 | +++ b/open-vm-tools/vgauth/serviceImpl/serviceInt.h | ||
| 368 | @@ -441,6 +441,7 @@ VGAuthError ServiceFileVerifyAdminGroupOwnedByHandle(const HANDLE hFile); | ||
| 369 | VGAuthError ServiceFileVerifyEveryoneReadableByHandle(const HANDLE hFile); | ||
| 370 | VGAuthError ServiceFileVerifyUserAccessByHandle(const HANDLE hFile, | ||
| 371 | const char *userName); | ||
| 372 | +gchar *ServiceFileGetPathByHandle(HANDLE hFile); | ||
| 373 | #else | ||
| 374 | VGAuthError ServiceFileVerifyFileOwnerAndPerms(const char *fileName, | ||
| 375 | const char *userName, | ||
| 376 | -- | ||
| 377 | 2.40.0 | ||
| 378 | |||
diff --git a/meta-networking/recipes-support/open-vm-tools/open-vm-tools_12.5.0.bb b/meta-networking/recipes-support/open-vm-tools/open-vm-tools_12.5.0.bb index d44709a64d..8f7c05b994 100644 --- a/meta-networking/recipes-support/open-vm-tools/open-vm-tools_12.5.0.bb +++ b/meta-networking/recipes-support/open-vm-tools/open-vm-tools_12.5.0.bb | |||
| @@ -43,6 +43,7 @@ SRC_URI = "git://github.com/vmware/open-vm-tools.git;protocol=https;branch=stabl | |||
| 43 | file://0012-hgfsServerLinux-Consider-64bit-time_t-possibility.patch;patchdir=.. \ | 43 | file://0012-hgfsServerLinux-Consider-64bit-time_t-possibility.patch;patchdir=.. \ |
| 44 | file://0013-open-vm-tools-Correct-include-path-for-poll.h.patch;patchdir=.. \ | 44 | file://0013-open-vm-tools-Correct-include-path-for-poll.h.patch;patchdir=.. \ |
| 45 | file://0014-timeSync-Portable-way-to-print-64bit-time_t.patch;patchdir=.. \ | 45 | file://0014-timeSync-Portable-way-to-print-64bit-time_t.patch;patchdir=.. \ |
| 46 | file://CVE-2025-22247.patch;patchdir=.. \ | ||
| 46 | " | 47 | " |
| 47 | 48 | ||
| 48 | UPSTREAM_CHECK_GITTAGREGEX = "stable-(?P<pver>\d+(\.\d+)+)" | 49 | UPSTREAM_CHECK_GITTAGREGEX = "stable-(?P<pver>\d+(\.\d+)+)" |
