diff options
author | Hitendra Prajapati <hprajapati@mvista.com> | 2025-07-11 11:31:13 +0530 |
---|---|---|
committer | Armin Kuster <akuster808@gmail.com> | 2025-08-02 13:37:04 -0400 |
commit | 21e370fd3c60ef59216a7633c4bbd2356ea92a6b (patch) | |
tree | 7f38cf64dcd55461a1117eb9a9ff017580725679 | |
parent | c781171d34dc4e1b4074c48b5e392198c0458498 (diff) | |
download | meta-openembedded-21e370fd3c60ef59216a7633c4bbd2356ea92a6b.tar.gz |
open-vm-tools: fix CVE-2025-22247
VMware Tools contains an insecure file handling vulnerability.
\xa0A malicious actor with non-administrative privileges on a
guest VM may tamper the local files to trigger insecure file
operations within that VM.
Reference:
https://nvd.nist.gov/vuln/detail/CVE-2025-22247
Upstream patch: Backport from https://github.com/vmware/open-vm-tools/blob/CVE-2025-22247.patch/CVE-2025-22247-1230-1250-VGAuth-updates.patch
Signed-off-by: Hitendra Prajapati <hprajapati@mvista.com>
Signed-off-by: Armin Kuster <akuster808@gmail.com>
-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.3.5.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..4db79ef96d --- /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 | Upstream-Status: Backport [https://github.com/vmware/open-vm-tools/blob/CVE-2025-22247.patch/CVE-2025-22247-1230-1250-VGAuth-updates.patch] | ||
23 | CVE: CVE-2025-22247 | ||
24 | Signed-off-by: Hitendra Prajapati <hprajapati@mvista.com> | ||
25 | --- | ||
26 | open-vm-tools/vgauth/common/VGAuthUtil.c | 33 +++++++++ | ||
27 | open-vm-tools/vgauth/common/VGAuthUtil.h | 2 + | ||
28 | open-vm-tools/vgauth/common/prefs.h | 3 + | ||
29 | open-vm-tools/vgauth/common/usercheck.c | 23 +++++- | ||
30 | open-vm-tools/vgauth/serviceImpl/alias.c | 74 ++++++++++++++++++- | ||
31 | open-vm-tools/vgauth/serviceImpl/service.c | 27 +++++++ | ||
32 | open-vm-tools/vgauth/serviceImpl/serviceInt.h | 1 + | ||
33 | 7 files changed, 160 insertions(+), 3 deletions(-) | ||
34 | |||
35 | diff --git a/open-vm-tools/vgauth/common/VGAuthUtil.c b/open-vm-tools/vgauth/common/VGAuthUtil.c | ||
36 | index 76383c462..9c2adb8d0 100644 | ||
37 | --- a/open-vm-tools/vgauth/common/VGAuthUtil.c | ||
38 | +++ b/open-vm-tools/vgauth/common/VGAuthUtil.c | ||
39 | @@ -309,3 +309,36 @@ Util_Assert(const char *cond, | ||
40 | #endif | ||
41 | g_assert(0); | ||
42 | } | ||
43 | + | ||
44 | + | ||
45 | +/* | ||
46 | + ****************************************************************************** | ||
47 | + * Util_Utf8CaseCmp -- */ /** | ||
48 | + * | ||
49 | + * Case insensitive comparison for utf8 strings which can have non-ascii | ||
50 | + * characters. | ||
51 | + * | ||
52 | + * @param[in] str1 Null terminated utf8 string. | ||
53 | + * @param[in] str2 Null terminated utf8 string. | ||
54 | + * | ||
55 | + ****************************************************************************** | ||
56 | + */ | ||
57 | + | ||
58 | +int | ||
59 | +Util_Utf8CaseCmp(const gchar *str1, | ||
60 | + const gchar *str2) | ||
61 | +{ | ||
62 | + int ret; | ||
63 | + gchar *str1Case; | ||
64 | + gchar *str2Case; | ||
65 | + | ||
66 | + str1Case = g_utf8_casefold(str1, -1); | ||
67 | + str2Case = g_utf8_casefold(str2, -1); | ||
68 | + | ||
69 | + ret = g_strcmp0(str1Case, str2Case); | ||
70 | + | ||
71 | + g_free(str1Case); | ||
72 | + g_free(str2Case); | ||
73 | + | ||
74 | + return ret; | ||
75 | +} | ||
76 | diff --git a/open-vm-tools/vgauth/common/VGAuthUtil.h b/open-vm-tools/vgauth/common/VGAuthUtil.h | ||
77 | index f7f3aa216..ef32a91da 100644 | ||
78 | --- a/open-vm-tools/vgauth/common/VGAuthUtil.h | ||
79 | +++ b/open-vm-tools/vgauth/common/VGAuthUtil.h | ||
80 | @@ -105,4 +105,6 @@ gboolean Util_CheckExpiration(const GTimeVal *start, unsigned int duration); | ||
81 | |||
82 | void Util_Assert(const char *cond, const char *file, int lineNum); | ||
83 | |||
84 | +int Util_Utf8CaseCmp(const gchar *str1, const gchar *str2); | ||
85 | + | ||
86 | #endif | ||
87 | diff --git a/open-vm-tools/vgauth/common/prefs.h b/open-vm-tools/vgauth/common/prefs.h | ||
88 | index 6c58f3f4b..3299eb26c 100644 | ||
89 | --- a/open-vm-tools/vgauth/common/prefs.h | ||
90 | +++ b/open-vm-tools/vgauth/common/prefs.h | ||
91 | @@ -167,6 +167,9 @@ msgCatalog = /etc/vmware-tools/vgauth/messages | ||
92 | /** Where the localized version of the messages were installed. */ | ||
93 | #define VGAUTH_PREF_LOCALIZATION_DIR "msgCatalog" | ||
94 | |||
95 | +/** If symlinks or junctions are allowed in alias store file path */ | ||
96 | +#define VGAUTH_PREF_ALLOW_SYMLINKS "allowSymlinks" | ||
97 | + | ||
98 | /* | ||
99 | * Pref values | ||
100 | */ | ||
101 | diff --git a/open-vm-tools/vgauth/common/usercheck.c b/open-vm-tools/vgauth/common/usercheck.c | ||
102 | index 3beede2e8..340aa0411 100644 | ||
103 | --- a/open-vm-tools/vgauth/common/usercheck.c | ||
104 | +++ b/open-vm-tools/vgauth/common/usercheck.c | ||
105 | @@ -78,6 +78,8 @@ | ||
106 | * Solaris as well, but that path is untested. | ||
107 | */ | ||
108 | |||
109 | +#define MAX_USER_NAME_LEN 256 | ||
110 | + | ||
111 | /* | ||
112 | * A single retry works for the LDAP case, but try more often in case NIS | ||
113 | * or something else has a related issue. Note that a bad username/uid won't | ||
114 | @@ -354,12 +356,29 @@ Usercheck_UsernameIsLegal(const gchar *userName) | ||
115 | * restricted list for local usernames. | ||
116 | */ | ||
117 | size_t len; | ||
118 | - char *illegalChars = "<>/"; | ||
119 | + size_t i = 0; | ||
120 | + int backSlashCnt = 0; | ||
121 | + /* | ||
122 | + * As user names are used to generate its alias store file name/path, it | ||
123 | + * should not contain path traversal characters ('/' and '\'). | ||
124 | + */ | ||
125 | + char *illegalChars = "<>/\\"; | ||
126 | |||
127 | len = strlen(userName); | ||
128 | - if (strcspn(userName, illegalChars) != len) { | ||
129 | + if (len > MAX_USER_NAME_LEN) { | ||
130 | return FALSE; | ||
131 | } | ||
132 | + | ||
133 | + while ((i += strcspn(userName + i, illegalChars)) < len) { | ||
134 | + /* | ||
135 | + * One backward slash is allowed for domain\username separator. | ||
136 | + */ | ||
137 | + if (userName[i] != '\\' || ++backSlashCnt > 1) { | ||
138 | + return FALSE; | ||
139 | + } | ||
140 | + ++i; | ||
141 | + } | ||
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 4e170202c..c7040ebff 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 d4716526c..e053ed0fa 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 5f420192b..f4f88547d 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.49.0 | ||
378 | |||
diff --git a/meta-networking/recipes-support/open-vm-tools/open-vm-tools_12.3.5.bb b/meta-networking/recipes-support/open-vm-tools/open-vm-tools_12.3.5.bb index 82aab051f1..34ac34ba20 100644 --- a/meta-networking/recipes-support/open-vm-tools/open-vm-tools_12.3.5.bb +++ b/meta-networking/recipes-support/open-vm-tools/open-vm-tools_12.3.5.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+)+)" |