Skip to content

Commit 6da0c9a

Browse files
[AUTO-CHERRYPICK] [AUTO-PR] Cherry-picked CVE-2025-22247 fix in openvm-tools - branch main (#13775)
Co-authored-by: Andrew Phelps <[email protected]>
1 parent 55fc9b8 commit 6da0c9a

File tree

2 files changed

+384
-1
lines changed

2 files changed

+384
-1
lines changed
Lines changed: 379 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,379 @@
1+
From 2a2607c6bd94ae22a937fd2adde7472d9a6d506c Mon Sep 17 00:00:00 2001
2+
From: John Wolfe <[email protected]>
3+
Date: Mon, 5 May 2025 16:10:07 -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+
vgauth/common/VGAuthUtil.c | 33 +++++++++
23+
vgauth/common/VGAuthUtil.h | 2 +
24+
vgauth/common/prefs.h | 3 +
25+
vgauth/common/usercheck.c | 28 +++++--
26+
vgauth/serviceImpl/alias.c | 74 ++++++++++++++++++-
27+
vgauth/serviceImpl/service.c | 27 +++++++
28+
vgauth/serviceImpl/serviceInt.h | 1 +
29+
7 files changed, 160 insertions(+), 8 deletions(-)
30+
31+
diff --git a/vgauth/common/VGAuthUtil.c b/vgauth/common/VGAuthUtil.c
32+
index 76383c462..9c2adb8d0 100644
33+
--- a/vgauth/common/VGAuthUtil.c
34+
+++ b/vgauth/common/VGAuthUtil.c
35+
@@ -309,3 +309,36 @@ Util_Assert(const char *cond,
36+
#endif
37+
g_assert(0);
38+
}
39+
+
40+
+
41+
+/*
42+
+ ******************************************************************************
43+
+ * Util_Utf8CaseCmp -- */ /**
44+
+ *
45+
+ * Case insensitive comparison for utf8 strings which can have non-ascii
46+
+ * characters.
47+
+ *
48+
+ * @param[in] str1 Null terminated utf8 string.
49+
+ * @param[in] str2 Null terminated utf8 string.
50+
+ *
51+
+ ******************************************************************************
52+
+ */
53+
+
54+
+int
55+
+Util_Utf8CaseCmp(const gchar *str1,
56+
+ const gchar *str2)
57+
+{
58+
+ int ret;
59+
+ gchar *str1Case;
60+
+ gchar *str2Case;
61+
+
62+
+ str1Case = g_utf8_casefold(str1, -1);
63+
+ str2Case = g_utf8_casefold(str2, -1);
64+
+
65+
+ ret = g_strcmp0(str1Case, str2Case);
66+
+
67+
+ g_free(str1Case);
68+
+ g_free(str2Case);
69+
+
70+
+ return ret;
71+
+}
72+
diff --git a/vgauth/common/VGAuthUtil.h b/vgauth/common/VGAuthUtil.h
73+
index f7f3aa216..ef32a91da 100644
74+
--- a/vgauth/common/VGAuthUtil.h
75+
+++ b/vgauth/common/VGAuthUtil.h
76+
@@ -105,4 +105,6 @@ gboolean Util_CheckExpiration(const GTimeVal *start, unsigned int duration);
77+
78+
void Util_Assert(const char *cond, const char *file, int lineNum);
79+
80+
+int Util_Utf8CaseCmp(const gchar *str1, const gchar *str2);
81+
+
82+
#endif
83+
diff --git a/vgauth/common/prefs.h b/vgauth/common/prefs.h
84+
index ff116928c..7cddb3e17 100644
85+
--- a/vgauth/common/prefs.h
86+
+++ b/vgauth/common/prefs.h
87+
@@ -165,6 +165,9 @@ msgCatalog = /etc/vmware-tools/vgauth/messages
88+
/** Where the localized version of the messages were installed. */
89+
#define VGAUTH_PREF_LOCALIZATION_DIR "msgCatalog"
90+
91+
+/** If symlinks or junctions are allowed in alias store file path */
92+
+#define VGAUTH_PREF_ALLOW_SYMLINKS "allowSymlinks"
93+
+
94+
/*
95+
* Pref values
96+
*/
97+
diff --git a/vgauth/common/usercheck.c b/vgauth/common/usercheck.c
98+
index 31eeb5a77..145f1f056 100644
99+
--- a/vgauth/common/usercheck.c
100+
+++ b/vgauth/common/usercheck.c
101+
@@ -78,6 +78,8 @@
102+
* Solaris as well, but that path is untested.
103+
*/
104+
105+
+#define MAX_USER_NAME_LEN 256
106+
+
107+
/*
108+
* A single retry works for the LDAP case, but try more often in case NIS
109+
* or something else has a related issue. Note that a bad username/uid won't
110+
@@ -354,17 +356,29 @@ Usercheck_UsernameIsLegal(const gchar *userName)
111+
*
112+
*/
113+
size_t len;
114+
-#ifdef _WIN32
115+
- // allow '\' in for Windows domain usernames
116+
- char *illegalChars = "<>/";
117+
-#else
118+
- char *illegalChars = "\\<>/";
119+
-#endif
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+
+
144+
return TRUE;
145+
}
146+
147+
diff --git a/vgauth/serviceImpl/alias.c b/vgauth/serviceImpl/alias.c
148+
index b28351eea..687d1b373 100644
149+
--- a/vgauth/serviceImpl/alias.c
150+
+++ b/vgauth/serviceImpl/alias.c
151+
@@ -41,6 +41,7 @@
152+
#include "certverify.h"
153+
#include "VGAuthProto.h"
154+
#include "vmxlog.h"
155+
+#include "VGAuthUtil.h"
156+
157+
// puts the identity store in an easy to find place
158+
#undef WIN_TEST_MODE
159+
@@ -66,6 +67,7 @@
160+
#define ALIASSTORE_FILE_PREFIX "user-"
161+
#define ALIASSTORE_FILE_SUFFIX ".xml"
162+
163+
+static gboolean allowSymlinks = FALSE;
164+
static gchar *aliasStoreRootDir = DEFAULT_ALIASSTORE_ROOT_DIR;
165+
166+
#ifdef _WIN32
167+
@@ -252,6 +254,12 @@ mapping file layout:
168+
169+
*/
170+
171+
+#ifdef _WIN32
172+
+#define ISPATHSEP(c) ((c) == '\\' || (c) == '/')
173+
+#else
174+
+#define ISPATHSEP(c) ((c) == '/')
175+
+#endif
176+
+
177+
178+
/*
179+
******************************************************************************
180+
@@ -466,6 +474,7 @@ ServiceLoadFileContentsWin(const gchar *fileName,
181+
gunichar2 *fileNameW = NULL;
182+
BOOL ok;
183+
DWORD bytesRead;
184+
+ gchar *realPath = NULL;
185+
186+
*fileSize = 0;
187+
*contents = NULL;
188+
@@ -622,6 +631,22 @@ ServiceLoadFileContentsWin(const gchar *fileName,
189+
goto done;
190+
}
191+
192+
+ if (!allowSymlinks) {
193+
+ /*
194+
+ * Check if fileName is real path.
195+
+ */
196+
+ if ((realPath = ServiceFileGetPathByHandle(hFile)) == NULL) {
197+
+ err = VGAUTH_E_FAIL;
198+
+ goto done;
199+
+ }
200+
+ if (Util_Utf8CaseCmp(realPath, fileName) != 0) {
201+
+ Warning("%s: Real path (%s) is not same as file path (%s)\n",
202+
+ __FUNCTION__, realPath, fileName);
203+
+ err = VGAUTH_E_FAIL;
204+
+ goto done;
205+
+ }
206+
+ }
207+
+
208+
/*
209+
* Now finally read the contents.
210+
*/
211+
@@ -650,6 +675,7 @@ done:
212+
CloseHandle(hFile);
213+
}
214+
g_free(fileNameW);
215+
+ g_free(realPath);
216+
217+
return err;
218+
}
219+
@@ -672,6 +698,7 @@ ServiceLoadFileContentsPosix(const gchar *fileName,
220+
gchar *buf;
221+
gchar *bp;
222+
int fd = -1;
223+
+ gchar realPath[PATH_MAX] = { 0 };
224+
225+
*fileSize = 0;
226+
*contents = NULL;
227+
@@ -817,6 +844,23 @@ ServiceLoadFileContentsPosix(const gchar *fileName,
228+
goto done;
229+
}
230+
231+
+ if (!allowSymlinks) {
232+
+ /*
233+
+ * Check if fileName is real path.
234+
+ */
235+
+ if (realpath(fileName, realPath) == NULL) {
236+
+ Warning("%s: realpath() failed. errno (%d)\n", __FUNCTION__, errno);
237+
+ err = VGAUTH_E_FAIL;
238+
+ goto done;
239+
+ }
240+
+ if (g_strcmp0(realPath, fileName) != 0) {
241+
+ Warning("%s: Real path (%s) is not same as file path (%s)\n",
242+
+ __FUNCTION__, realPath, fileName);
243+
+ err = VGAUTH_E_FAIL;
244+
+ goto done;
245+
+ }
246+
+ }
247+
+
248+
/*
249+
* All sanity checks passed; read the bits.
250+
*/
251+
@@ -2803,8 +2847,13 @@ ServiceAliasRemoveAlias(const gchar *reqUserName,
252+
253+
/*
254+
* We don't verify the user exists in a Remove operation, to allow
255+
- * cleanup of deleted user's stores.
256+
+ * cleanup of deleted user's stores, but we do check whether the
257+
+ * user name is legal or not.
258+
*/
259+
+ if (!Usercheck_UsernameIsLegal(userName)) {
260+
+ Warning("%s: Illegal user name '%s'\n", __FUNCTION__, userName);
261+
+ return VGAUTH_E_FAIL;
262+
+ }
263+
264+
if (!CertVerify_IsWellFormedPEMCert(pemCert)) {
265+
return VGAUTH_E_INVALID_CERTIFICATE;
266+
@@ -3036,6 +3085,16 @@ ServiceAliasQueryAliases(const gchar *userName,
267+
}
268+
#endif
269+
270+
+ /*
271+
+ * We don't verify the user exists in a Query operation to allow
272+
+ * cleaning up after a deleted user, but we do check whether the
273+
+ * user name is legal or not.
274+
+ */
275+
+ if (!Usercheck_UsernameIsLegal(userName)) {
276+
+ Warning("%s: Illegal user name '%s'\n", __FUNCTION__, userName);
277+
+ return VGAUTH_E_FAIL;
278+
+ }
279+
+
280+
err = AliasLoadAliases(userName, num, aList);
281+
if (VGAUTH_E_OK != err) {
282+
Warning("%s: failed to load Aliases for '%s'\n", __FUNCTION__, userName);
283+
@@ -3294,6 +3353,7 @@ ServiceAliasInitAliasStore(void)
284+
VGAuthError err = VGAUTH_E_OK;
285+
gboolean saveBadDir = FALSE;
286+
char *defaultDir = NULL;
287+
+ size_t len;
288+
289+
#ifdef _WIN32
290+
{
291+
@@ -3324,6 +3384,10 @@ ServiceAliasInitAliasStore(void)
292+
defaultDir = g_strdup(DEFAULT_ALIASSTORE_ROOT_DIR);
293+
#endif
294+
295+
+ allowSymlinks = Pref_GetBool(gPrefs,
296+
+ VGAUTH_PREF_ALLOW_SYMLINKS,
297+
+ VGAUTH_PREF_GROUP_NAME_SERVICE,
298+
+ FALSE);
299+
/*
300+
* Find the alias store directory. This allows an installer to put
301+
* it somewhere else if necessary.
302+
@@ -3337,6 +3401,14 @@ ServiceAliasInitAliasStore(void)
303+
VGAUTH_PREF_GROUP_NAME_SERVICE,
304+
defaultDir);
305+
306+
+ /*
307+
+ * Remove the trailing separator if any from aliasStoreRootDir path.
308+
+ */
309+
+ len = strlen(aliasStoreRootDir);
310+
+ if (ISPATHSEP(aliasStoreRootDir[len - 1])) {
311+
+ aliasStoreRootDir[len - 1] = '\0';
312+
+ }
313+
+
314+
Log("Using '%s' for alias store root directory\n", aliasStoreRootDir);
315+
316+
g_free(defaultDir);
317+
diff --git a/vgauth/serviceImpl/service.c b/vgauth/serviceImpl/service.c
318+
index d4716526c..e053ed0fa 100644
319+
--- a/vgauth/serviceImpl/service.c
320+
+++ b/vgauth/serviceImpl/service.c
321+
@@ -28,6 +28,7 @@
322+
#include "VGAuthUtil.h"
323+
#ifdef _WIN32
324+
#include "winUtil.h"
325+
+#include <glib.h>
326+
#endif
327+
328+
static ServiceStartListeningForIOFunc startListeningIOFunc = NULL;
329+
@@ -283,9 +284,35 @@ static gchar *
330+
ServiceUserNameToPipeName(const char *userName)
331+
{
332+
gchar *escapedName = ServiceEncodeUserName(userName);
333+
+#ifdef _WIN32
334+
+ /*
335+
+ * Adding below pragma only in windows to suppress the compile time warning
336+
+ * about unavailability of g_uuid_string_random() since compiler flag
337+
+ * GLIB_VERSION_MAX_ALLOWED is defined to GLIB_VERSION_2_34.
338+
+ * TODO: Remove below pragma when GLIB_VERSION_MAX_ALLOWED is bumped up to
339+
+ * or greater than GLIB_VERSION_2_52.
340+
+ */
341+
+#pragma warning(suppress : 4996)
342+
+ gchar *uuidStr = g_uuid_string_random();
343+
+ /*
344+
+ * Add a unique suffix to avoid a name collision with an existing named pipe
345+
+ * created by someone else (intentionally or by accident).
346+
+ * This is not needed for Linux; name collisions on sockets are already
347+
+ * avoided there since (1) file system paths to VGAuthService sockets are in
348+
+ * a directory that is writable only by root and (2) VGAuthService unlinks a
349+
+ * socket path before binding it to a newly created socket.
350+
+ */
351+
+ gchar *pipeName = g_strdup_printf("%s-%s-%s",
352+
+ SERVICE_PUBLIC_PIPE_NAME,
353+
+ escapedName,
354+
+ uuidStr);
355+
+
356+
+ g_free(uuidStr);
357+
+#else
358+
gchar *pipeName = g_strdup_printf("%s-%s",
359+
SERVICE_PUBLIC_PIPE_NAME,
360+
escapedName);
361+
+#endif
362+
363+
g_free(escapedName);
364+
return pipeName;
365+
diff --git a/vgauth/serviceImpl/serviceInt.h b/vgauth/serviceImpl/serviceInt.h
366+
index ef49f42c2..c37f42fa6 100644
367+
--- a/vgauth/serviceImpl/serviceInt.h
368+
+++ b/vgauth/serviceImpl/serviceInt.h
369+
@@ -441,6 +441,7 @@ VGAuthError ServiceFileVerifyAdminGroupOwnedByHandle(const HANDLE hFile);
370+
VGAuthError ServiceFileVerifyEveryoneReadableByHandle(const HANDLE hFile);
371+
VGAuthError ServiceFileVerifyUserAccessByHandle(const HANDLE hFile,
372+
const char *userName);
373+
+gchar *ServiceFileGetPathByHandle(HANDLE hFile);
374+
#else
375+
VGAuthError ServiceFileVerifyFileOwnerAndPerms(const char *fileName,
376+
const char *userName,
377+
--
378+
2.43.5
379+

0 commit comments

Comments
 (0)