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);