Savestate: Bounds-check sizes from the file, and plug leaks on load

Reject sizes past the end of the state before allocating (FPL, PGF,
achievements, SAS grain, savedata list, the memory fast path), fail
instead of desyncing on a SAS voice count mismatch, and free what old
states' paths and shrinking pointer containers dropped.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
This commit is contained in:
Henrik RydgårdandClaude Opus 5.5 committed 2026-09-28 09:34:06 -06:00
1 parent 7fa6be6a25
commit 0a687b9435
12 files changed
+68 -3

No files matched your search

+8 -1
View File
@@ -27,7 +27,7 @@ void DoDeque(PointerWrap &p, std::deque<T> &x, T &default_val) {
Do(p, deq_size);
// Guard against an attacker-controlled size driving a huge resize, same as DoVector.
if (p.mode == PointerWrap::MODE_READ || p.mode == PointerWrap::MODE_VERIFY) {
if (deq_size > p.Remaining() / sizeof(T)) {
if (deq_size > p.Remaining() / SerializeMinElemSize<T>()) {
p.SetError(PointerWrap::ERROR_FAILURE);
return;
}
@@ -40,6 +40,13 @@ void DoDeque(PointerWrap &p, std::deque<T> &x, T &default_val) {
template<class T>
void Do(PointerWrap &p, std::deque<T *> &x) {
if (p.mode == PointerWrap::MODE_READ) {
// The elements are owned (DoClass replaces them), and a shorter deque would drop the rest.
for (T *elem : x) {
delete elem;
}
x.clear();
}
T *dv = nullptr;
DoDeque(p, x, dv);
}
+7
View File
@@ -126,6 +126,13 @@ void DoVector(PointerWrap &p, std::vector<T> &x, T &default_val) {
template<class T>
void Do(PointerWrap &p, std::vector<T *> &x) {
if (p.mode == PointerWrap::MODE_READ) {
// The elements are owned (DoClass replaces them), and a shorter vector would drop the rest.
for (T *elem : x) {
delete elem;
}
x.clear();
}
T *dv = nullptr;
DoVector(p, x, dv);
}
+7
View File
@@ -40,6 +40,13 @@ void DoList(PointerWrap &p, std::list<T> &x, T &default_val) {
template<class T>
void Do(PointerWrap &p, std::list<T *> &x) {
if (p.mode == PointerWrap::MODE_READ) {
// The elements are owned (DoClass replaces them), and a shorter list would drop the rest.
for (T *elem : x) {
delete elem;
}
x.clear();
}
T *dv = nullptr;
Do(p, x, dv);
}