Skip to content

Commit c6fb86e

Browse files
authored
ArchiveScanner: fix WriteCacheData crash when pool cache outlives .sdp archives (beyond-all-reason#2961)
Fixes an invalid end iterator dereference
1 parent acd074c commit c6fb86e

1 file changed

Lines changed: 59 additions & 34 deletions

File tree

rts/System/FileSystem/ArchiveScanner.cpp

Lines changed: 59 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1282,44 +1282,69 @@ void CArchiveScanner::WriteCacheData(const std::string& filename)
12821282
allPoolRootDirs.emplace(CPoolArchive::GetPoolRootDirectory(ai.path + ai.origName));
12831283
}
12841284

1285-
const uint32_t seed = std::chrono::system_clock::now().time_since_epoch().count();
1286-
std::mt19937 generator(seed);
1287-
std::uniform_int_distribution<size_t> distribution(0, poolFilesInfo.size() - 1);
1288-
auto startOffset = distribution(generator);
1289-
1290-
auto st = poolFilesInfo.begin();
1291-
std::advance(st, startOffset);
1292-
auto it = st;
1293-
1294-
// we only want to check if the file still exists, we don't check for size / modDate
1295-
// this is checked in the checksum code anyway.
1296-
const auto ExistenceTest = [&allPoolRootDirs = std::as_const(allPoolRootDirs)](const auto& it) {
1297-
for (const auto& poolRootDir : allPoolRootDirs) {
1298-
const auto fileName = CPoolArchive::GetPoolFilePath(poolRootDir, it->first);
1299-
if (FileSystem::FileExists(fileName)) {
1300-
return true;
1285+
// No pool archives means we have no roots to verify file existence
1286+
// against. Without this guard, ExistenceTest below returns false for
1287+
// every entry, the loop erases the entire cache, and the next
1288+
// iteration dereferences begin()==end() on the now-empty map.
1289+
if (!allPoolRootDirs.empty()) {
1290+
const uint32_t seed = std::chrono::system_clock::now().time_since_epoch().count();
1291+
std::mt19937 generator(seed);
1292+
std::uniform_int_distribution<size_t> distribution(0, poolFilesInfo.size() - 1);
1293+
auto startOffset = distribution(generator);
1294+
1295+
auto st = poolFilesInfo.begin();
1296+
std::advance(st, startOffset);
1297+
auto it = st;
1298+
1299+
// we only want to check if the file still exists, we don't check for size / modDate
1300+
// this is checked in the checksum code anyway.
1301+
const auto ExistenceTest = [&allPoolRootDirs = std::as_const(allPoolRootDirs)](const auto& it) {
1302+
for (const auto& poolRootDir : allPoolRootDirs) {
1303+
const auto fileName = CPoolArchive::GetPoolFilePath(poolRootDir, it->first);
1304+
if (FileSystem::FileExists(fileName)) {
1305+
return true;
1306+
}
1307+
}
1308+
return false;
1309+
};
1310+
1311+
// can't spend too much time in this code, thus set the deadline and rely on
1312+
// random luck and sheer amount of invocations to eventually remove most if not all
1313+
// stale items from the pool cache
1314+
static constexpr int64_t MAX_POOL_VERIFICATION_TIME = 1 * 1000;
1315+
for (auto t0 = spring_now(), t1 = t0; (t1 - t0).toMilliSecsi() < MAX_POOL_VERIFICATION_TIME; t1 = spring_now()) {
1316+
// Map can drain to empty mid-loop (e.g. all entries genuinely
1317+
// missing). begin() then equals end(); ExistenceTest(end())
1318+
// would dereference past the allocated buckets array.
1319+
if (it == poolFilesInfo.end())
1320+
break;
1321+
1322+
// cleanup files that got deleted in the meantime
1323+
if (ExistenceTest(it)) {
1324+
++it;
1325+
} else {
1326+
// erase invalidates the erased iterator. If we're erasing
1327+
// the loop's anchor, re-anchor to the successor so the
1328+
// `it == st` termination check below still works; otherwise
1329+
// st dangles and we'd burn the full deadline re-checking
1330+
// survivors.
1331+
const bool erasingStart = (it == st);
1332+
it = poolFilesInfo.erase(it);
1333+
if (erasingStart)
1334+
st = it;
13011335
}
1302-
}
1303-
return false;
1304-
};
1305-
1306-
// can't spend too much time in this code, thus set the deadline and rely on
1307-
// random luck and sheer amount of invocations to eventually remove most if not all
1308-
// stale items from the pool cache
1309-
static constexpr int64_t MAX_POOL_VERIFICATION_TIME = 1 * 1000;
1310-
for (auto t0 = spring_now(), t1 = t0; (t1 - t0).toMilliSecsi() < MAX_POOL_VERIFICATION_TIME; t1 = spring_now()) {
1311-
// cleanup files that got deleted in the meantime
13121336

1313-
if (ExistenceTest(it))
1314-
++it;
1315-
else
1316-
it = poolFilesInfo.erase(it);
1337+
if (it == poolFilesInfo.end())
1338+
it = poolFilesInfo.begin(); //rewind to the very start
13171339

1318-
if (it == poolFilesInfo.end())
1319-
it = poolFilesInfo.begin(); //rewind to the very start
1340+
// st may have been re-anchored to end() above; wrap it too so
1341+
// the comparison is meaningful.
1342+
if (st == poolFilesInfo.end())
1343+
st = poolFilesInfo.begin();
13201344

1321-
if (it == st)
1322-
break; // everything got checked and we're back to the starting iterator
1345+
if (it == st)
1346+
break; // everything got checked and we're back to the starting iterator
1347+
}
13231348
}
13241349
}
13251350

0 commit comments

Comments
 (0)