948d0399a8d2e1f294fb04c82c8d2fbec735707a
max
  Tue Aug 11 07:33:13 2026 -0700
edw: fix heap overflow (edwWebXSendFile) and submit command injection (edwLib)

edwWebXSendFile tokenValid()/getFullFileName() used strcat onto exact-sized
cloneString buffers, overrunning the heap; tokenValid() does this before the
auth token is checked, so it is reachable without valid credentials. Build the
strings in right-sized buffers instead.

edwAddSubmitJob() interpolated a user-supplied submission URL into a shell
command later run by edwRunDaemon; a single quote broke out of the single-quote
context and injected commands. Reject quotes/newlines in url and userEmail and
single-quote both values in the command.

refs #38055, refs #38060

diff --git src/hg/encode3/encodeDataWarehouse/edwWebXSendFile/edwWebXSendFile.c src/hg/encode3/encodeDataWarehouse/edwWebXSendFile/edwWebXSendFile.c
index 25428ca5050..deb0b37ce99 100644
--- src/hg/encode3/encodeDataWarehouse/edwWebXSendFile/edwWebXSendFile.c
+++ src/hg/encode3/encodeDataWarehouse/edwWebXSendFile/edwWebXSendFile.c
@@ -57,60 +57,69 @@
 /* Return the value of user (email) in the query string */
 {
 return cloneString(cgiUsualString("user", ""));
 }
 
 char *getFullFileName(char * licensePlate)
 /* Return a complete path given dir and basename of file associated with this licenses plate */
 {
 struct sqlConnection *conn = edwConnect();
 char query[512];
 char *fileName;
 char *fullFileName;
 sqlSafef(query, sizeof(query),
     "select edwFileName from vf where licensePlate = '%s'", licensePlate);
 fileName = sqlQuickString(conn, query);
-fullFileName = strcat(edwDataRoot(), fileName);
+/* Do not strcat onto edwDataRoot()'s exact-sized cloneString buffer, which overruns it;
+ * build a right-sized string instead. refs #38055 */
+char *dataRoot = edwDataRoot();
+fullFileName = catTwoStrings(dataRoot, fileName);
+freeMem(dataRoot);
 return fullFileName;
 }
 
 char *getBaseName(char *fullFileName)
 /* Return basename part of the full file name */
 {
 return basename(fullFileName);
 }
 
 void xSendFile(char *baseName, char *fullFileName)
 /* send the file out using xsendfile */
 {
 printf("Content-Description: File Transfer\n");
 printf("Content-Disposition: attachment; filename=\"%s\"\n", baseName);
 printf("Content-Transfer-Encoding: binary\n");
 printf("Expires: 0\n");
 printf("Cache-Control: no-store, no-cache, must-revalidate\n");
 printf("Pragma: no-cache\n");
 printf("Content-Length: %lld\n", (long long)fileSize(fullFileName));
 printf("X-Sendfile: %s\n\n", fullFileName);
 }
 
 boolean tokenValid(char *accession, char *date, char *token, char *user)
 /* Check the token to ensure that the query string was created by
  * encodedcc without any modification */ 
 {
-char *hStr=strcat(strcat(date,accession),user);
-char *digest;
-digest=hmacSha1(edwKey(), hStr);
+/* Build the HMAC input in a right-sized buffer; do not strcat onto the callers'
+ * exact-sized cgi string buffers, which overruns them (and runs before this token
+ * check, so it is reachable without valid credentials). refs #38055 */
+int hLen = strlen(date) + strlen(accession) + strlen(user) + 1;
+char *hStr = needMem(hLen);
+safef(hStr, hLen, "%s%s%s", date, accession, user);
+char *digest = hmacSha1(edwKey(), hStr);
+freez(&hStr);
 if (sameString(digest, token))
     return TRUE;
 return FALSE;
 }
 
 int main(int argc, char *argv[])
 /* Process command line. */
 {
 boolean isFromWeb = cgiIsOnWeb();
 if (!isFromWeb && !cgiSpoof(&argc, argv))
     usage();
 char *lp = getLicensePlate();
 char *tk = getToken();
 char *dt = getDate();
 char *user = getUserMail();