From 43cf316a2b7e204f54925ad6ee51a86d625c66ee Mon Sep 17 00:00:00 2001
From: Anonymous Maarten <[EMAIL REDACTED]>
Date: Sun, 1 Feb 2026 23:34:41 +0100
Subject: [PATCH] surface: verify surface palette before touching the
filesystem
(cherry picked from commit fe95c8d25e731df1dec959b29c71501bfda058ad)
---
VisualC/SDL_image.vcxproj | 1 +
VisualC/SDL_image.vcxproj.filters | 3 +++
src/IMG.c | 14 ++++++++++++-
src/IMG.h | 24 ++++++++++++++++++++++
src/IMG_avif.c | 8 ++++++--
src/IMG_bmp.c | 20 ++++++++++++++++++
src/IMG_gif.c | 7 +++++++
src/IMG_png.c | 7 +++++++
src/IMG_tga.c | 8 ++++++--
src/IMG_webp.c | 7 +++++--
test/testimage.c | 34 +++++++++++++++++++++++++++++++
11 files changed, 126 insertions(+), 7 deletions(-)
create mode 100644 src/IMG.h
diff --git a/VisualC/SDL_image.vcxproj b/VisualC/SDL_image.vcxproj
index 44a9b0377..c9fbcf066 100644
--- a/VisualC/SDL_image.vcxproj
+++ b/VisualC/SDL_image.vcxproj
@@ -228,6 +228,7 @@
</ItemGroup>
<ItemGroup>
<ClInclude Include="..\include\SDL3_image\SDL_image.h" />
+ <ClInclude Include="..\src\IMG.h" />
<ClInclude Include="..\src\IMG_ani.h" />
<ClInclude Include="..\src\IMG_anim_decoder.h" />
<ClInclude Include="..\src\IMG_anim_encoder.h" />
diff --git a/VisualC/SDL_image.vcxproj.filters b/VisualC/SDL_image.vcxproj.filters
index cade2c410..b750ddaa2 100644
--- a/VisualC/SDL_image.vcxproj.filters
+++ b/VisualC/SDL_image.vcxproj.filters
@@ -98,6 +98,9 @@
<ClInclude Include="..\include\SDL3_image\SDL_image.h">
<Filter>Public Headers</Filter>
</ClInclude>
+ <ClInclude Include="..\src\IMG.h">
+ <Filter>Sources</Filter>
+ </ClInclude>
<ClInclude Include="..\src\IMG_libpng.h">
<Filter>Sources</Filter>
</ClInclude>
diff --git a/src/IMG.c b/src/IMG.c
index cdd23eb93..bc1f5aed0 100644
--- a/src/IMG.c
+++ b/src/IMG.c
@@ -330,12 +330,24 @@ IMG_Animation *IMG_LoadAnimationTyped_IO(SDL_IOStream *src, bool closeio, const
return NULL;
}
-bool IMG_Save(SDL_Surface *surface, const char *file)
+bool IMG_VerifyCanSaveSurface(SDL_Surface *surface)
{
if (!surface) {
return SDL_InvalidParamError("surface");
}
+ if (SDL_ISPIXELFORMAT_INDEXED(surface->format) && !SDL_GetSurfacePalette(surface)) {
+ return SDL_SetError("Indexed surfaces must have a palette");
+ }
+ return true;
+}
+
+bool IMG_Save(SDL_Surface *surface, const char *file)
+{
+ if (!IMG_VerifyCanSaveSurface(surface)) {
+ return false;
+ }
+
if (!file || !*file) {
return SDL_InvalidParamError("file");
}
diff --git a/src/IMG.h b/src/IMG.h
new file mode 100644
index 000000000..727423c6e
--- /dev/null
+++ b/src/IMG.h
@@ -0,0 +1,24 @@
+/*
+ SDL_image: An example image loading library for use with SDL
+ Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
+
+ This software is provided 'as-is', without any express or implied
+ warranty. In no event will the authors be held liable for any damages
+ arising from the use of this software.
+
+ Permission is granted to anyone to use this software for any purpose,
+ including commercial applications, and to alter it and redistribute it
+ freely, subject to the following restrictions:
+
+ 1. The origin of this software must not be misrepresented; you must not
+ claim that you wrote the original software. If you use this software
+ in a product, an acknowledgment in the product documentation would be
+ appreciated but is not required.
+ 2. Altered source versions must be plainly marked as such, and must not be
+ misrepresented as being the original software.
+ 3. This notice may not be removed or altered from any source distribution.
+*/
+
+#include <SDL3_image/SDL_image.h>
+
+extern bool IMG_VerifyCanSaveSurface(SDL_Surface *surface);
diff --git a/src/IMG_avif.c b/src/IMG_avif.c
index fcd3227b8..73d20efd1 100644
--- a/src/IMG_avif.c
+++ b/src/IMG_avif.c
@@ -23,6 +23,7 @@
#include <SDL3_image/SDL_image.h>
+#include "IMG.h"
#include "IMG_avif.h"
#include "IMG_anim_encoder.h"
#include "IMG_anim_decoder.h"
@@ -745,8 +746,7 @@ bool IMG_SaveAVIF_IO(SDL_Surface *surface, SDL_IOStream *dst, bool closeio, int
{
bool result = false;
- if (!surface) {
- SDL_InvalidParamError("surface");
+ if (!IMG_VerifyCanSaveSurface(surface)) {
goto done;
}
if (!dst) {
@@ -765,6 +765,10 @@ bool IMG_SaveAVIF_IO(SDL_Surface *surface, SDL_IOStream *dst, bool closeio, int
bool IMG_SaveAVIF(SDL_Surface *surface, const char *file, int quality)
{
+ if (!IMG_VerifyCanSaveSurface(surface)) {
+ return false;
+ }
+
SDL_IOStream *dst = SDL_IOFromFile(file, "wb");
if (dst) {
return IMG_SaveAVIF_IO(surface, dst, true, quality);
diff --git a/src/IMG_bmp.c b/src/IMG_bmp.c
index 3eae26440..79cbf1631 100644
--- a/src/IMG_bmp.c
+++ b/src/IMG_bmp.c
@@ -42,6 +42,8 @@
#include <SDL3_image/SDL_image.h>
+#include "IMG.h"
+
#ifdef LOAD_BMP
#define RIFF_FOURCC(c0, c1, c2, c3) \
@@ -759,11 +761,17 @@ SDL_Surface *IMG_LoadICO_IO(SDL_IOStream *src)
bool IMG_SaveBMP_IO(SDL_Surface *surface, SDL_IOStream *dst, bool closeio)
{
+ if (!IMG_VerifyCanSaveSurface(surface)) {
+ return false;
+ }
return SDL_SaveBMP_IO(surface, dst, closeio);
}
bool IMG_SaveBMP(SDL_Surface *surface, const char *file)
{
+ if (!IMG_VerifyCanSaveSurface(surface)) {
+ return false;
+ }
SDL_IOStream *dst = SDL_IOFromFile(file, "wb");
if (dst) {
return IMG_SaveBMP_IO(surface, dst, true);
@@ -887,11 +895,17 @@ static bool SaveICOCUR(SDL_Surface *surface, SDL_IOStream *dst, bool closeio, in
bool IMG_SaveCUR_IO(SDL_Surface *surface, SDL_IOStream *dst, bool closeio)
{
+ if (!IMG_VerifyCanSaveSurface(surface)) {
+ return false;
+ }
return SaveICOCUR(surface, dst, closeio, ICON_TYPE_CUR);
}
bool IMG_SaveCUR(SDL_Surface *surface, const char *file)
{
+ if (!IMG_VerifyCanSaveSurface(surface)) {
+ return false;
+ }
SDL_IOStream *dst = SDL_IOFromFile(file, "wb");
if (dst) {
return SaveICOCUR(surface, dst, true, ICON_TYPE_CUR);
@@ -902,11 +916,17 @@ bool IMG_SaveCUR(SDL_Surface *surface, const char *file)
bool IMG_SaveICO_IO(SDL_Surface *surface, SDL_IOStream *dst, bool closeio)
{
+ if (!IMG_VerifyCanSaveSurface(surface)) {
+ return false;
+ }
return SaveICOCUR(surface, dst, closeio, ICON_TYPE_ICO);
}
bool IMG_SaveICO(SDL_Surface *surface, const char *file)
{
+ if (!IMG_VerifyCanSaveSurface(surface)) {
+ return false;
+ }
SDL_IOStream *dst = SDL_IOFromFile(file, "wb");
if (dst) {
return SaveICOCUR(surface, dst, true, ICON_TYPE_ICO);
diff --git a/src/IMG_gif.c b/src/IMG_gif.c
index 320413b52..2f0d58282 100644
--- a/src/IMG_gif.c
+++ b/src/IMG_gif.c
@@ -23,6 +23,7 @@
#include <SDL3_image/SDL_image.h>
+#include "IMG.h"
#include "IMG_gif.h"
#include "IMG_anim_encoder.h"
#include "IMG_anim_decoder.h"
@@ -2821,6 +2822,9 @@ bool IMG_CreateGIFAnimationEncoder(IMG_AnimationEncoder *encoder, SDL_Properties
bool IMG_SaveGIF_IO(SDL_Surface *surface, SDL_IOStream *dst, bool closeio)
{
+ if (!IMG_VerifyCanSaveSurface(surface)) {
+ return false;
+ }
IMG_AnimationEncoder *encoder = IMG_CreateAnimationEncoder_IO(dst, closeio, "gif");
if (!encoder) {
return false;
@@ -2840,6 +2844,9 @@ bool IMG_SaveGIF_IO(SDL_Surface *surface, SDL_IOStream *dst, bool closeio)
bool IMG_SaveGIF(SDL_Surface *surface, const char *file)
{
+ if (!IMG_VerifyCanSaveSurface(surface)) {
+ return false;
+ }
SDL_IOStream *dst = SDL_IOFromFile(file, "wb");
if (dst) {
return IMG_SaveGIF_IO(surface, dst, true);
diff --git a/src/IMG_png.c b/src/IMG_png.c
index f6fccd177..1f1b5de73 100644
--- a/src/IMG_png.c
+++ b/src/IMG_png.c
@@ -27,6 +27,7 @@
#include "IMG_ImageIO.h"
#endif
+#include "IMG.h"
#include "IMG_libpng.h"
#include "IMG_WIC.h"
@@ -105,6 +106,9 @@ SDL_Surface *IMG_LoadPNG_IO(SDL_IOStream *src)
bool IMG_SavePNG_IO(SDL_Surface *surface, SDL_IOStream *dst, bool closeio)
{
+ if (!IMG_VerifyCanSaveSurface(surface)) {
+ return false;
+ }
#ifdef SDL_IMAGE_LIBPNG
if (IMG_InitPNG()) {
return IMG_SavePNG_LIBPNG(surface, dst, closeio);
@@ -116,6 +120,9 @@ bool IMG_SavePNG_IO(SDL_Surface *surface, SDL_IOStream *dst, bool closeio)
bool IMG_SavePNG(SDL_Surface *surface, const char *file)
{
+ if (!IMG_VerifyCanSaveSurface(surface)) {
+ return false;
+ }
SDL_IOStream *dst = SDL_IOFromFile(file, "wb");
if (dst) {
return IMG_SavePNG_IO(surface, dst, true);
diff --git a/src/IMG_tga.c b/src/IMG_tga.c
index 7581524b1..b9ddad5e0 100644
--- a/src/IMG_tga.c
+++ b/src/IMG_tga.c
@@ -25,6 +25,8 @@
#include <SDL3_image/SDL_image.h>
+#include "IMG.h"
+
// We will have TGA saving feature by default.
#ifndef SAVE_TGA
#define SAVE_TGA 1
@@ -364,8 +366,7 @@ bool IMG_SaveTGA_IO(SDL_Surface *surface, SDL_IOStream *dst, bool closeio)
SDL_Surface *temp_surface = NULL;
bool result = false;
- if (!surface) {
- SDL_InvalidParamError("surface");
+ if (!IMG_VerifyCanSaveSurface(surface)) {
goto done;
}
if (!dst) {
@@ -526,6 +527,9 @@ bool IMG_SaveTGA_IO(SDL_Surface *surface, SDL_IOStream *dst, bool closeio)
bool IMG_SaveTGA(SDL_Surface *surface, const char *file)
{
+ if (!IMG_VerifyCanSaveSurface(surface)) {
+ return false;
+ }
SDL_IOStream *dst = SDL_IOFromFile(file, "wb");
if (dst) {
return IMG_SaveTGA_IO(surface, dst, true);
diff --git a/src/IMG_webp.c b/src/IMG_webp.c
index 4db0c61d5..5e3332408 100644
--- a/src/IMG_webp.c
+++ b/src/IMG_webp.c
@@ -23,6 +23,7 @@
#include <SDL3_image/SDL_image.h>
+#include "IMG.h"
#include "IMG_webp.h"
#include "IMG_anim_encoder.h"
#include "IMG_anim_decoder.h"
@@ -754,8 +755,7 @@ bool IMG_SaveWEBP_IO(SDL_Surface *surface, SDL_IOStream *dst, bool closeio, floa
bool converted_surface_locked = false;
Sint64 start = -1;
- if (!surface) {
- SDL_InvalidParamError("surface");
+ if (!IMG_VerifyCanSaveSurface(surface)) {
goto done;
}
if (!dst) {
@@ -873,6 +873,9 @@ bool IMG_SaveWEBP_IO(SDL_Surface *surface, SDL_IOStream *dst, bool closeio, floa
bool IMG_SaveWEBP(SDL_Surface *surface, const char *file, float quality)
{
+ if (!IMG_VerifyCanSaveSurface(surface)) {
+ return false;
+ }
SDL_IOStream *dst = SDL_IOFromFile(file, "wb");
if (dst) {
return IMG_SaveWEBP_IO(surface, dst, true, quality);
diff --git a/test/testimage.c b/test/testimage.c
index 53c9c328b..a23b08fff 100644
--- a/test/testimage.c
+++ b/test/testimage.c
@@ -797,6 +797,7 @@ FormatSaveTest(const Format *format,
char filename[64] = { 0 };
SDL_Surface *reference = NULL;
SDL_Surface *surface = NULL;
+ SDL_Surface *indexed_surface = NULL;
SDL_IOStream *dest = NULL;
int diff;
bool result;
@@ -863,10 +864,43 @@ FormatSaveTest(const Format *format,
}
}
+ SDLTest_Log("Saving an indexed surface without palette MUST fail");
+ indexed_surface = SDL_CreateSurface(32, 32, SDL_PIXELFORMAT_INDEX8);
+ SDLTest_AssertCheck(indexed_surface != NULL,
+ "SDL_CreateSurface(SDL_PIXELFORMAT_INDEX8) must succeed");
+ if (indexed_surface) {
+ if (rw) {
+ Sint64 position;
+
+ SDL_IOStream *dyn_io = SDL_IOFromDynamicMem();
+ SDLTest_AssertPass("About to call IMG_SaveTyped_IO(\"%s\")", format->name);
+ result = IMG_SaveTyped_IO(indexed_surface, dyn_io, false, format->name);
+ SDLTest_Assert(!result, "Calling IMG_SaveTyped_IO of an indexed surface without palette should return false (actual=%d)", result);
+ position = SDL_TellIO(dyn_io) ;
+ SDLTest_AssertCheck(position == 0, "Nothing has been written to IO stream (position=%" SDL_PRIs64 ")", position);
+ SDL_CloseIO(dyn_io);
+ } else {
+ char indexed_filename[64] = { 0 };
+ SDL_snprintf(indexed_filename, sizeof(indexed_filename),
+ "save%s_index.%s",
+ rw ? "Rwops" : "",
+ format->name);
+ SDL_RemovePath(indexed_filename);
+ SDLTest_AssertCheck(!SDL_GetPathInfo(indexed_filename, NULL), "\"%s\" should not exist BEFORE test", indexed_filename);
+ SDLTest_AssertPass("About to call IMG_Save(\"%s\")", indexed_filename);
+ result = IMG_Save(indexed_surface, indexed_filename);
+ SDLTest_Assert(!result, "Calling IMG_Save of an indexed surface without palette should return false (actual=%d)", result);
+ SDLTest_AssertCheck(!SDL_GetPathInfo(indexed_filename, NULL), "\"%s\" should not exist AFTER test", indexed_filename);
+ }
+ }
+
out:
if (surface != NULL) {
SDL_DestroySurface(surface);
}
+ if (indexed_surface != NULL) {
+ SDL_DestroySurface(indexed_surface);
+ }
if (reference != NULL) {
SDL_DestroySurface(reference);
}