diff --git a/chrome/browser/media/cdm_document_service_impl.cc b/chrome/browser/media/cdm_document_service_impl.cc index e0ecf80323269..2dd31e7132995 100644 --- a/chrome/browser/media/cdm_document_service_impl.cc +++ b/chrome/browser/media/cdm_document_service_impl.cc @@ -75,6 +75,41 @@ base::FilePath GetCdmStorePathRootForProfile( return profile_path.AppendASCII(kCdmStore).AppendASCII( base::SysInfo::ProcessCPUArchitecture()); } + +// Recursively deletes `path`, but refuses to traverse into NTFS reparse +// points (mount-point junctions, symbolic links). +// +// This is a targeted mitigation for a junction-following arbitrary delete: +// the MediaFoundation LPAC process holds an inheritable +// FILE_GENERIC_WRITE|DELETE ACE on the CDM store root (granted at +// CreateCdmStorePathRootAndGrantAccessIfNeeded, line 101-106). FILE_WRITE_DATA +// is sufficient for FSCTL_SET_REPARSE_POINT (mount points do not require +// SeCreateSymbolicLinkPrivilege), so a compromised LPAC can plant a junction +// at / -> . +// +// base::DeletePathRecursively (file_util_win.cc DoDeleteFile) does not check +// FILE_ATTRIBUTE_REPARSE_POINT before recursing -- it tests only +// FILE_ATTRIBUTE_DIRECTORY at line 394, then calls DeleteFileRecursive which +// enumerates "\*" via FindFirstFileEx (which follows the mount +// point) and manually recurses. This causes the browser process to delete +// any directory tree the Chrome user has delete rights on. +// +// When `path` is a reparse point we simply remove the directory stub +// (RemoveDirectory does not traverse the junction). There is a +// theoretical TOCTOU window between GetFileAttributes and +// DeletePathRecursively, but exploiting it would require the LPAC to +// win a microsecond-scale race against an unpredictable user-triggered +// ThreadPool task. A robust fix should open the directory with +// FILE_FLAG_OPEN_REPARSE_POINT and recurse on a handle. +void SafeDeleteCdmStoreEntry(const base::FilePath& path) { + const DWORD attr = ::GetFileAttributesW(path.value().c_str()); + if (attr != INVALID_FILE_ATTRIBUTES && + (attr & FILE_ATTRIBUTE_REPARSE_POINT)) { + ::RemoveDirectoryW(path.value().c_str()); + return; + } + base::DeletePathRecursively(path); +} #endif // BUILDFLAG(IS_WIN) } // namespace @@ -398,7 +433,7 @@ void DeleteMediaFoundationCdmData( // If we couldn't find the origin, this mean the origin was not present in // the PrefService and we should also delete the folder. if (!origin) { - base::DeletePathRecursively(file_path); + SafeDeleteCdmStoreEntry(file_path); continue; } @@ -434,7 +469,7 @@ void DeleteMediaFoundationCdmData( } if (should_delete) - base::DeletePathRecursively(file_path); + SafeDeleteCdmStoreEntry(file_path); } }