-
Notifications
You must be signed in to change notification settings - Fork 42
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
makes SDL_perl regress when it (incorrectly) frees a former video surface #305
Comments
valgrind report on i686-linux:
|
The larger test-case that I condensed to get this should work on any recent-ish Debian/Ubuntu system, if you happen to have one available:
plus an appropriate I believe fixing this libsdl-perl test suite regression is the last blocker for getting sdl12-compat used as the replacement for classic SDL 1.2 in Debian 13, and hopefully Ubuntu 23.10 as well. If my reproducer (and therefore libsdl-perl) is doing something that was never meant to work, then fixing it from the API-user side is likely also an option. |
So sdl12-compat generally tries to be bug-for-bug compatible with classic 1.2, but to be clear: calling SDL_FreeSurface on the return value of SDL_SetVideoMode is illegal. Doubly-so for freeing a surface that was possibly already freed by a second call to SDL_SetVideoMode. That this test works in classic 1.2 is pure luck. |
Yeah, it's the second FreeSurface that's upset; both SDL 1.2 and sdl12-compat see the first one is the current screen surface and treat this as a no-op. The second call isn't a valid pointer anymore, hence the problem. This works in SDL 1.2 because SDL_SetVideoMode happens to return the same surface pointer with the dummy video backend. But to be fair, the X11 backend preserves the pointer, too, in this case. Ugh. Okay, let me think on this. |
Having it be invalid to free the result of SDL_SetVideoMode fits my limited understanding of the memory model in use here. In the terms I'm used to using in the GLib world, I think SDL_SetVideoMode returns a pointer "borrowed" from SDL-internal infrastructure (the caller must not free it, and it may become invalid at a subsequent video mode change), while things like SDL_CreateRGBSurface return a pointer that becomes "owned" by the caller (which may free it whenever convenient). Is that right?
In the Perl bindings, the If calling SDL_FreeSurface on the return value of SDL_SetVideoMode is considered to be a programming error (invalid/illegal/undefined behaviour), then I think the Perl binding would have to distinguish between functions where the caller owns the result, like SDL_CreateRGBSurface (which would return a Perl SDL::Surface flagged as "OK to free the surface when the wrapper is destroyed"), and functions where the caller "borrows" the result from SDL, like SDL_SetVideoMode (which would return a Perl SDL::Surface flagged as "don't free the surface when the wrapper is destroyed"). Does that sound about right? If sdl12-compat aims to be bug-for-bug compatible with classic SDL 1.2 but libsdl-perl is doing something wrong, then it's probably best to fix both sdl12-compat and libsdl-perl. |
So here's why this is happening in sdl12-compat and not SDL-1.2:
The internal 2.0 surface format is different than what the app is requesting, so we nuke the surface and start from scratch, even in the dummy driver. appfmt=0x15151002 and VideoSurface12->surface20->format->format=0x16161804. I need to dig further still, but if those match when we hit this point (or can fix the conditional), this test will work. |
Is it also considered to be undefined behaviour (invalid, illegal, programming error, whatever) to free the result of |
Or we can just hang onto all the old video surfaces (maybe just free the pixels) until SDL_Quit(). It would take a small amount of memory more, but it wouldn't crash. :) |
If the Perl bindings are using automatic memory management, but have an explicit SDL_Quit() call, might they end up trying to free a video surface after SDL_Quit()? (Assuming that real-world programs only call SDL_Quit() during exit, there isn't really much difference between holding onto a resource until SDL_Quit() and intentionally leaking it, so perhaps intentionally leaking it would be pragmatic?) |
Hmm, true... |
I reported PerlGameDev/SDL#305.
Was that guess correct? |
In many SDL APIs that return a SDL_Surface *, the surface is considered to be owned by the caller, and must be freed by the caller. However, SDL_SetVideoMode and presumably SDL_GetVideoSurface return a pointer to SDL's internal video surface, which will be freed by SDL if necessary, and must not be freed by library users. Incorrectly freeing this surface can lead to a use-after-free crash, manifesting as a test failure in t/core_video.t. Resolves: libsdl-org/sdl12-compat#305 Signed-off-by: Simon McVittie <[email protected]>
In many SDL APIs that return a SDL_Surface *, the surface is considered to be owned by the caller, and must be freed by the caller. However, SDL_SetVideoMode and presumably SDL_GetVideoSurface return a pointer to SDL's internal video surface, which will be freed by SDL if necessary, and must not be freed by library users. Incorrectly freeing this surface can lead to a use-after-free crash, manifesting as a test failure in t/core_video.t. See also libsdl-org/sdl12-compat#305 Resolves: PerlGameDev#305 Signed-off-by: Simon McVittie <[email protected]>
Yep! |
Yep, it's the same pointer. |
The codepath is set up for this, there's just a block of conditions where we flush everything and start over (going from a software video mode to SDL_OPENGL, etc)...this is probably just overaggressive in this case. |
Also, I'm not a careful reader: obviously this is going from 32 bit to 16 bit between calls. :) |
Since libsdl-perl is doing something undefined, I'm now aiming to get this fixed in Debian via libsdl-perl as a higher priority than in sdl12-compat, with sdl12-compat bug-for-bug compatibility as more of a nice-to-have (and if that means we temporarily lose libsdl-perl from Debian, I'll be sad to lose frozen-bubble but it's still a net benefit). |
The SDL Perl bindings incorrectly call SDL_FreeSurface() on the result of functions that return a "borrowed" pointer to the video surface, namely SDL_SetVideoMode() and SDL_GetVideoSurface(). (See PerlGameDev/SDL#305) When we would previously have allocated or freed the video surface wrapper object, instead allocate or free its contents in-place. When checking whether the video surface exists, because we never destroy it, we must now also check whether its underlying SDL2 video surface exists. Resolves: libsdl-org#305 Signed-off-by: Simon McVittie <[email protected]>
So here's a stupid idea: what if we keep the same global struct representing the SDL 1.2 video surface at all times, and just reallocate the SDL 2 object and pixel buffer that it points to? (see #306) We can even avoid it being a one-per-process leak (harmless, but an annoying false-positive for valgrind/asan) by having it never be malloc'd in the first place: it can just be global data. |
That is a great idea! 😊 |
The SDL Perl bindings incorrectly call SDL_FreeSurface() on the result of functions that return a "borrowed" pointer to the video surface, namely SDL_SetVideoMode() and SDL_GetVideoSurface(). (See PerlGameDev/SDL#305) When we would previously have allocated or freed the video surface wrapper object, instead allocate or free its contents in-place. When checking whether the video surface exists, because we never destroy it, we must now also check whether its underlying SDL2 video surface exists. Resolves: libsdl-org#305 Signed-off-by: Simon McVittie <[email protected]>
The SDL Perl bindings incorrectly call SDL_FreeSurface() on the result of functions that return a "borrowed" pointer to the video surface, namely SDL_SetVideoMode() and SDL_GetVideoSurface(). (See PerlGameDev/SDL#305) When we would previously have allocated or freed the video surface wrapper object, instead allocate or free its contents in-place. When checking whether the video surface exists, because we never destroy it, we must now also check whether its underlying SDL2 video surface exists. Resolves: #305 Signed-off-by: Simon McVittie <[email protected]>
Since trying to switch from classic SDL 1.2 to sdl12-compat in Debian, we've been seeing crashes in the test
perl t/core_video.t
in the test suite of the SDL Perl bindings (packaged as libsdl-perl). On 32-bit platforms it seems to segfault repeatably. On 64-bit platforms it usually succeeds, but using valgrind or AddressSanitizer reveals that there is still a use-after-free, it's just not fatal for whatever reason.I was able to cut down the failing test to this C code, which I think is equivalent to what the relevant part of
t/core_video.t
is doing:With classic SDL 1.2 (1.2.15, from Debian 12) this succeeds, and
valgrind --leak-check=no
reports no memory errors.With sdl12-compat (1.2.64 plus the fixes I contributed since then, from Debian unstable) this fails:
I tried to understand what the ownership model is for these surfaces, but found it quite confusing.
The text was updated successfully, but these errors were encountered: