SDL: video: Update self-referential pointers when reallocating the display list

From 166afebcad4533e1db0e42a2401d24218208c52d Mon Sep 17 00:00:00 2001
From: Frank Praznik <[EMAIL REDACTED]>
Date: Wed, 1 Feb 2023 20:18:00 -0500
Subject: [PATCH] video: Update self-referential pointers when reallocating the
 display list

The display list can contain self-referential pointers if the current mode pointer points to the desktop mode or a fullscreen mode array element, and reallocating the display or fullscreen mode lists without updating the current mode pointer in these cases can leave them pointing to freed memory or garbage data. Manually copy the list items and update the self-referential pointers if necessary.
---
 src/video/SDL_video.c | 25 +++++++++++++++++++++++--
 1 file changed, 23 insertions(+), 2 deletions(-)

diff --git a/src/video/SDL_video.c b/src/video/SDL_video.c
index 25d08d314969..e5b47951958f 100644
--- a/src/video/SDL_video.c
+++ b/src/video/SDL_video.c
@@ -630,10 +630,21 @@ SDL_DisplayID SDL_AddVideoDisplay(const SDL_VideoDisplay *display, SDL_bool send
     SDL_VideoDisplay *displays, *new_display;
     SDL_DisplayID id = 0;
 
-    displays = (SDL_VideoDisplay *)SDL_realloc(_this->displays, (_this->num_displays + 1) * sizeof(*displays));
+    displays = (SDL_VideoDisplay *)SDL_malloc((_this->num_displays + 1) * sizeof(*displays));
     if (displays) {
         int i;
+
+        /* The display list may contain self-referential pointers to the desktop mode. */
+        SDL_memcpy(displays, _this->displays, _this->num_displays * sizeof(*displays));
+        for (i = 0; i < _this->num_displays; ++i) {
+            if (displays[i].current_mode == &_this->displays[i].desktop_mode) {
+                displays[i].current_mode = &displays[i].desktop_mode;
+            }
+        }
+
+        SDL_free(_this->displays);
         _this->displays = displays;
+
         id = _this->next_object_id++;
         new_display = &displays[_this->num_displays++];
 
@@ -938,10 +949,20 @@ SDL_bool SDL_AddFullscreenDisplayMode(SDL_VideoDisplay *display, const SDL_Displ
 
     /* Go ahead and add the new mode */
     if (nmodes == display->max_fullscreen_modes) {
-        modes = (SDL_DisplayMode *)SDL_realloc(modes, (display->max_fullscreen_modes + 32) * sizeof(*modes));
+        modes = (SDL_DisplayMode *)SDL_malloc((display->max_fullscreen_modes + 32) * sizeof(*modes));
         if (modes == NULL) {
             return SDL_FALSE;
         }
+
+        /* Copy the list and update the current mode pointer, if necessary. */
+        SDL_memcpy(modes, display->fullscreen_modes, nmodes * sizeof(*modes));
+        for (i = 0; i < nmodes; ++i) {
+            if (display->current_mode == &display->fullscreen_modes[i]) {
+                display->current_mode = &modes[i];
+            }
+        }
+
+        SDL_free(display->fullscreen_modes);
         display->fullscreen_modes = modes;
         display->max_fullscreen_modes += 32;
     }