SDL_image: surface: verify surface palette before touching the filesystem (43cf3)

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);
     }