[2.x] fix: Guard diskcache against path traversal (#9605)

**Problem**
There are several places in diskcache where resolve is called
without guards.

**Solution**
This adds guards to prevent path traversal.
This commit is contained in:
eugene yokota 2026-08-17 14:09:03 -04:00 committed by GitHub
parent e8f40d68c3
commit 0ae3c152bd
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
1 changed files with 17 additions and 4 deletions

View File

@ -278,6 +278,15 @@ case class DiskActionCacheStore(base: Path, converter: FileConverter)
try Files.exists(casFile) && Digest.sameDigest(casFile, digest) try Files.exists(casFile) && Digest.sameDigest(casFile, digest)
catch case _: NoSuchFileException => false catch case _: NoSuchFileException => false
private def requireWithinBase(base: Path, target: Path, ref: HashedVirtualFileRef): Path =
val normalizedBase = base.toAbsolutePath.normalize()
val normalizedTarget = target.toAbsolutePath.normalize()
if normalizedTarget.startsWith(normalizedBase) then target
else
throw new IOException(
s"Refusing to sync ${ref.id}: resolved path $normalizedTarget is outside $normalizedBase"
)
private def getBlobs(refs: Seq[HashedVirtualFileRef]): Seq[VirtualFile] = private def getBlobs(refs: Seq[HashedVirtualFileRef]): Seq[VirtualFile] =
refs.flatMap: r => refs.flatMap: r =>
try try
@ -343,9 +352,13 @@ case class DiskActionCacheStore(base: Path, converter: FileConverter)
val result = linkOrCopy(outPath) val result = linkOrCopy(outPath)
afterFileWrite(ref, result, outputDirectory) afterFileWrite(ref, result, outputDirectory)
result result
val resolvedPath = converter.toPath(ref) match val resolvedPath = requireWithinBase(
case p if p.isAbsolute => p outputDirectory,
case p => outputDirectory.resolve(p) converter.toPath(ref) match
case p if p.isAbsolute => p
case p => outputDirectory.resolve(p),
ref
)
resolvedPath match resolvedPath match
case p if !Files.exists(p) => case p if !Files.exists(p) =>
// println(s"- syncFile: $p does not exist") // println(s"- syncFile: $p does not exist")
@ -407,7 +420,7 @@ case class DiskActionCacheStore(base: Path, converter: FileConverter)
// manifest contains the list of files in the dirzip, and their hashes // manifest contains the list of files in the dirzip, and their hashes
val m = ActionCache.manifestFromFile(mPath) val m = ActionCache.manifestFromFile(mPath)
m.outputFiles.foreach: ref => m.outputFiles.foreach: ref =>
val currentItem = converter.toPath(ref) val currentItem = requireWithinBase(outputDirectory, converter.toPath(ref), ref)
val shortPath = outputDirectory.relativize(currentItem).toString val shortPath = outputDirectory.relativize(currentItem).toString
allPaths.remove(currentItem) allPaths.remove(currentItem)
val d = Digest(ref) val d = Digest(ref)