b7e0b5f7674dfa7600149aab93d79bed3c3b926a
chmalee
  Mon Jan 13 17:40:22 2025 -0800
When prefixing a file specified by the hubspace ui with the userDataDir, run realpath(3) on the file name to prevent looking up system files, refs #35018

diff --git src/hg/lib/userdata.c src/hg/lib/userdata.c
index 5b2af68..5a16df8 100644
--- src/hg/lib/userdata.c
+++ src/hg/lib/userdata.c
@@ -7,30 +7,31 @@
 #include "common.h"
 #include "hash.h"
 #include "portable.h"
 #include "trashDir.h"
 #include "md5.h"
 #include "hgConfig.h"
 #include "dystring.h"
 #include "cheapcgi.h"
 #include "customFactory.h"
 #include "wikiLink.h"
 #include "userdata.h"
 #include "jksql.h"
 #include "hdb.h"
 #include "hubSpace.h"
 #include "hubSpaceQuotas.h"
+#include <limits.h>
 
 char *getUserName()
 /* Query the right system for the users name */
 {
 return (loginSystemEnabled() || wikiLinkEnabled()) ? wikiLinkUserName() : NULL;
 }
 
 char *emailForUserName(char *userName)
 /* Fetch the email for this user from gbMembers hgcentral table */
 {
 struct sqlConnection *sc = hConnectCentral();
 struct dyString *query = sqlDyStringCreate("select email from gbMembers where userName = '%s'", userName);
 char *email = sqlQuickString(sc, dyStringCannibalize(&query));
 hDisconnectCentral(&sc);
 // this should be freeMem'd:
@@ -87,41 +88,47 @@
     char *encUserName = cgiEncode(userName);
     char *userPrefix = md5HexForString(encUserName);
     userPrefix[2] = '\0';
     struct dyString *userDirDy = dyStringNew(0);
     dyStringPrintf(userDirDy, "%s/%s/%s/", HUB_SPACE_URL, userPrefix, encUserName);
     retUrl = dyStringCannibalize(&userDirDy);
     }
 return retUrl;
 }
 
 char *prefixUserFile(char *userName, char *fname, char *parentDir)
 /* Allocate a new string that contains the full per-user path to fname, NULL otherwise.
  * parentDir is optional and will go in between the per-user dir and the fname */
 {
 char *pathPrefix = getDataDir(userName);
+char *path = NULL;
 if (pathPrefix)
     {
     if (parentDir)
         {
         struct dyString *ret = dyStringCreate("%s%s%s%s", pathPrefix, parentDir, lastChar(parentDir) == '/' ? "" : "/", fname);
-        return dyStringCannibalize(&ret);
+        path = dyStringCannibalize(&ret);
         }
     else
-        return catTwoStrings(pathPrefix, fname);
+        path = catTwoStrings(pathPrefix, fname);
+    char canonicalPath[PATH_MAX];
+    realpath(path, canonicalPath);
+    // after canonicalizing the path, make sure it starts with the userDataDir, to prevent
+    // deleting files like blah/../../../../systemFile.text
+    if (startsWith(pathPrefix, canonicalPath))
+        return cloneString(canonicalPath);
     }
-else
 return NULL;
 }
 
 static boolean checkHubSpaceRowExists(struct hubSpace *row)
 /* Return TRUE if row already exists */
 {
 struct sqlConnection *conn = hConnectCentral();
 struct dyString *queryCheck = sqlDyStringCreate("select count(*) from hubSpace where userName='%s' and fileName='%s' and parentDir='%s'", row->userName, row->fileName, row->parentDir);
 int ret = sqlQuickNum(conn, dyStringCannibalize(&queryCheck));
 hDisconnectCentral(&conn);
 return ret > 0;
 }
 
 char *hubNameFromPath(char *path)
 /* Return the last directory component of path. Assume that a '.' char in the last component