133df533c4df3b1ebfcf3bbf14ab070f7d5c2ff2
braney
  Wed Sep 9 12:52:00 2026 -0700
sessionData: return kent-allocated memory from sessionDataSaveTrashFile

sessionDataSaveTrashFile() returned the result of realpath(path, NULL) when the
trash file it saves is already a relative symlink, which is what the trashCleaner
scripts leave behind. That pointer comes from the system malloc, but all three
callers release it with freeMem() or freez(), which dispatch through kent's own
handler stack. Under pushCarefulMemHandler() the free reads a block header that
was never written and subtracts a garbage size from the running total, so the
next allocation dies with "carefulAlloc: Allocated too much memory".

Nothing reaches it today. hgSession, hgPhyloPlace and snapshotSession are the
callers and none of them installs that handler, so the bug was latent. The fix
is a PATH_MAX stack buffer and a cloneString, the same shape already used by
pathIsUnderDirOrItsTarget() in trashDir.c. PATH_MAX needs an explicit include
of limits.h.

That same branch also dropped the readlink buffer on the floor, because it
replaced newPath with the resolved path instead of with the link target. It is
freed now.

Checked with a probe linking jkhgap under pushCarefulMemHandler: the patched
function resolves the symlink, frees, allocates again and passes
carefulCheckHeap, while the old pattern exits with a negative total.

refs #38318

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

diff --git src/hg/lib/sessionData.c src/hg/lib/sessionData.c
index dbf31ac2ea4..dbe01266907 100644
--- src/hg/lib/sessionData.c
+++ src/hg/lib/sessionData.c
@@ -1,20 +1,21 @@
 /* sessionData - functions for moving user data out of trash into permanent storage
  *
  * Copyright (C) 2019-2024 The Regents of the University of California
  * See kent/LICENSE or http://genome.ucsc.edu/license/ for licensing information. */
 
+#include <limits.h>
 #include "common.h"
 #include "cart.h"
 #include "cheapcgi.h"
 #include "customComposite.h"
 #include "customTrack.h"
 #include "hdb.h"
 #include "hgConfig.h"
 #include "md5.h"
 #include "trashDir.h"
 #include "sessionData.h"
 #include "quickLift.h"
 
 static char *sessionDataPathFromTrash(char *trashPath, char *sessionDir)
 /* Make a new path from a trash path -- replace "../trash" with safe location. */
 {
@@ -76,31 +77,36 @@
  * Return NULL if trashPath does not exist (can happen with expired custom track files). */
 {
 char *newPath = NULL;
 if (fileExists(trashPath))
     {
     char *existingLink = maybeReadlink(trashPath);
     if (existingLink)
         {
         // It may be a multi-directory-level relative symlink created by the trashCleaner scripts
         if (existingLink[0] != '/')
             {
             char trashPathDir[PATH_LEN];
             splitPath(trashPath, trashPathDir, NULL, NULL);
             char fullLinkPath[strlen(trashPathDir) + strlen(existingLink) + 1];
             safef(fullLinkPath, sizeof fullLinkPath, "%s%s", trashPathDir, existingLink);
-            newPath = realpath(fullLinkPath, NULL);
+            // realpath(path, NULL) would allocate with the system malloc, but callers release
+            // what we return with freeMem, which goes through kent's own handler stack.
+            char resolved[PATH_MAX];
+            if (realpath(fullLinkPath, resolved) != NULL)
+                newPath = cloneString(resolved);
+            freeMem(existingLink);
             }
         else
             newPath = existingLink;
         }
     else
         {
         newPath = sessionDataPathFromTrash(trashPath, sessionDir);
         if (fileExists(newPath))
             {
             if (unlink(newPath) != 0)
                 errnoAbort("sessionDataSaveTrashFile: newPath='%s' already existed but unlink failed",
                            newPath);
             fprintf(stderr, "sessionDataSaveTrashFile: new path '%s' already exists; overwriting", newPath);
             }
         makeDirsForFile(newPath);