44abc107a014bf17a4c5b0d5ede16f8d10ce180d
braney
  Tue Aug 11 09:58:31 2026 -0700
hgc: bound the copy in parseSs so an oversized ss or item cannot overflow, refs #38054

parseSs copied its argument into a fixed 1024-byte static buffer with strcpy and
no length check. The argument is the cart variable ss, or the item name, and a
visitor controls both. A legitimate value is a short triple of trash file paths,
so any oversized value is a bug or an attack.

Measured against an unpatched build, driving the item through QUERY_STRING:
1500 and 8000 bytes both wrote past the end of the buffer and the CGI carried on
and exited 0, so the corruption was silent. 100000 bytes gave SIGSEGV. Remote and
unauthenticated in every case.

safecpy does the same copy with a length check and aborts on overflow, which is
consistent with the three errAborts parseSs already has for the other malformed
cases. It also does not echo the value into the error message.

The boundary now behaves as expected. An item of 1023 bytes still parses, 1024
aborts, and a valid three-word value parses exactly as before. All six call sites
pass user data through this one copy, so they are all covered.

diff --git src/hg/hgc/hgc.c src/hg/hgc/hgc.c
index 48bfe4d4b8e..6ea351a2428 100644
--- src/hg/hgc/hgc.c
+++ src/hg/hgc/hgc.c
@@ -5522,31 +5522,36 @@
 
 for (ct=getCtList();  ct != NULL;  ct=ct->next)
     if (sameString(name, ct->tdb->track))
 	return(ct);
 
 return(NULL);
 }
 
 
 void parseSs(char *ss, char **retPslName, char **retFaName, char **retQName)
 /* Parse space separated 'ss' item. */
 {
 static char buf[512*2];
 int wordCount;
 char *words[4];
-strcpy(buf, ss);
+/* SECURITY (refs #38054): ss comes from the cart variable of the same name, or from
+ * the item name, and a visitor controls both.  A legitimate value is a short triple
+ * of trash file paths, so anything that does not fit is a bug or an attack.  safecpy
+ * aborts instead of writing past the end of the buffer, which matches the errAborts
+ * below on the other malformed cases. */
+safecpy(buf, sizeof buf, ss);
 wordCount = chopLine(buf, words);
 
 if (wordCount < 1)
     errAbort("Empty user cart variable ss.");
 *retPslName = words[0];
 if (retFaName != NULL)
     {
     if (wordCount < 2)
 	errAbort("Expecting psl filename and fa filename in cart variable ss, but only got one word: %s", ss);
     *retFaName = words[1];
     }
 if (retQName != NULL)
     {
     if (wordCount < 3)
 	errAbort("Expecting psl filename, fa filename and query name in cart variable ss, but got this: %s", ss);