[Scummvm-tracker] [ScummVM :: Bugs] #17287: COMMON: GZipReadStream::seek() behavior on invalid resources created by ScummVM
ScummVM :: Bugs
trac at scummvm.org
Mon Oct 5 08:39:16 UTC 2026
#17287: COMMON: GZipReadStream::seek() behavior on invalid resources created by
ScummVM
---------------------+-----------------------
Reporter: dwatteau | Owner: (none)
Type: defect | Status: new
Priority: normal | Component: Common
Version: | Keywords: gzip,zlib
Game: |
---------------------+-----------------------
This was triggered on current Git HEAD, after the rewrite of the MADS
engine, and with an old autosave of Rex Nebular (attached).
This `nebular.000` save is from 2022-07-27, and was a Gzip stream made
corrupt because of a ScummVM bug; before MADS bug #13642 was fixed, it'd
try to write an autosave during a cutscene (and this shouldn't happen for
this game) but then ScummVM would abort after having written this invalid
save (and always left this file in place -- this was before any attempt to
have any atomic stream, I think).
If the stream is corrupted, calling `assert()` may not seem like a bad
idea, but:
1. what about non-debug builds?
2. it's not really a "it shouldn't happen" case; ScummVM would produce
such files itself, in some circumstances. And then it'd try to read them
back.
3. maybe a better error interface than a plain `assert()` is possible, in
this case?
Anyway, if you start Rex Nebular with this autosave nearby, debug builds
will hit this:
{{{
User picked target 'nebular' (engine ID 'mads', game ID 'nebular')...
Running Rex Nebular and the Cosmic Gender Bender (DOS/English)
section1.hag: 6f725eb38660de8af31ec7cdd628d615, 927222 bytes.
Assertion failed: (newPos >= 0), function seek, file zlib.cpp, line 237.
Process 85645 stopped
- thread #1, queue = 'com.apple.main-thread', stop reason = hit program
assert
frame #4: 0x00000001006ce7ac
scummvm`Common::GZipReadStream::seek(this=0x00000007e873c000, offset=-4,
whence=2) at zlib.cpp:237:3
234 break;
235 }
236
-> 237 assert(newPos >= 0);
238
239 if ((uint32)newPos < _pos) {
240 // To search backward, we have to restart the
whole decompression
Target 0: (scummvm) stopped.
}}}
In this case, `newPos` is a negative value because a value bigger than
INT32_MAX was read, but `newPos` still is an `int32`.
By making it an `int64`, we avoid aborting ScummVM every time such a save
is encountered (maybe a debug()/warning() line there would be helpful?).
Also, returning an error instead of calling `assert()` may be a cleaner
interface (but I can't say if all callers properly handle its errors).
{{{
bool seek(int64 offset, int whence = SEEK_SET) override {
- int32 newPos = 0;
+ int64 newPos = 0;
switch (whence) {
default:
// fallthrough intended
@@ -234,9 +234,14 @@ public:
break;
}
- assert(newPos >= 0);
+ // Offsets and sizes may come from bogus data (e.g. the
gzip trailer of
+ // a truncated file for SEEK_END), so fail instead of
asserting
+ if (newPos < 0)
+ return false;
- if ((uint32)newPos < _pos) {
+ if (newPos < _pos) {
// To search backward, we have to restart the
whole decompression
// from the start of the file. A rather wasteful
operation, best
// to avoid it. :/
}}}
I'm not submitting a PR, and I just report my findings here, because I'm
not sure about the impacts of such a change.
--
Ticket URL: <https://bugs.scummvm.org/ticket/17287>
ScummVM :: Bugs <https://bugs.scummvm.org>
ScummVM
More information about the Scummvm-tracker
mailing list