Commit 6b71e2bfb for imagemagick.org

commit 6b71e2bfb5bb82e361030ce46f8bd1c80c658417
Author: Cristy <urban-warrior@imagemagick.org>
Date:   Thu Jan 29 20:42:14 2026 -0500

    jumbo security patch: addresses memory leak, stack overflow, out-of-bounds, integer overflow, OOB read

diff --git a/MagickCore/module.c b/MagickCore/module.c
index ca5ed0fe9..480dc53bb 100644
--- a/MagickCore/module.c
+++ b/MagickCore/module.c
@@ -595,15 +595,16 @@ static MagickBooleanType GetMagickModulePath(const char *filename,
           (void) ConcatenateMagickString(path,DirectorySeparator,
             MagickPathExtent);
         (void) ConcatenateMagickString(path,filename,MagickPathExtent);
-#if defined(MAGICKCORE_HAVE_REALPATH)
         {
           char
-            resolved_path[PATH_MAX+1];
+            *real_path = realpath_utf8(path);

-          if (realpath(path,resolved_path) != (char *) NULL)
-            (void) CopyMagickString(path,resolved_path,MagickPathExtent);
+          if (real_path != (char *) NULL)
+            {
+              (void) CopyMagickString(path,real_path,MagickPathExtent);
+              real_path=DestroyString(real_path);
+            }
         }
-#endif
         if (IsPathAccessible(path) != MagickFalse)
           {
             module_path=DestroyString(module_path);
diff --git a/MagickCore/policy.c b/MagickCore/policy.c
index 9ef1d6e67..a557f59bd 100644
--- a/MagickCore/policy.c
+++ b/MagickCore/policy.c
@@ -671,18 +671,31 @@ MagickExport MagickBooleanType IsRightsAuthorized(const PolicyDomain domain,
       *policy;

     policy=(const PolicyInfo *) p->value;
-    if ((policy->domain == domain) &&
-        (GlobExpression(pattern,policy->pattern,MagickFalse) != MagickFalse))
+    if (policy->domain == domain)
       {
-        if ((rights & ReadPolicyRights) != 0)
-          authorized=(policy->rights & ReadPolicyRights) != 0 ? MagickTrue :
-            MagickFalse;
-        if ((rights & WritePolicyRights) != 0)
-          authorized=(policy->rights & WritePolicyRights) != 0 ? MagickTrue :
-            MagickFalse;
-        if ((rights & ExecutePolicyRights) != 0)
-          authorized=(policy->rights & ExecutePolicyRights) != 0 ? MagickTrue :
-            MagickFalse;
+        char
+          *real_pattern = (char *) pattern;
+
+        if (policy->domain == PathPolicyDomain)
+          {
+            real_pattern=realpath_utf8(pattern);
+            if (real_pattern == (char *) NULL)
+              real_pattern=AcquireString(pattern);
+          }
+        if (GlobExpression(real_pattern,policy->pattern,MagickFalse) != MagickFalse)
+          {
+            if ((rights & ReadPolicyRights) != 0)
+              authorized=(policy->rights & ReadPolicyRights) != 0 ? MagickTrue :
+                MagickFalse;
+            if ((rights & WritePolicyRights) != 0)
+              authorized=(policy->rights & WritePolicyRights) != 0 ?
+                MagickTrue : MagickFalse;
+            if ((rights & ExecutePolicyRights) != 0)
+              authorized=(policy->rights & ExecutePolicyRights) != 0 ?
+                MagickTrue : MagickFalse;
+          }
+        if (policy->domain == PathPolicyDomain)
+          real_pattern=DestroyString(real_pattern);
       }
     p=p->next;
   }
diff --git a/MagickCore/utility-private.h b/MagickCore/utility-private.h
index 444c706ca..22393d028 100644
--- a/MagickCore/utility-private.h
+++ b/MagickCore/utility-private.h
@@ -267,6 +267,125 @@ static inline FILE *popen_utf8(const char *command,const char *type)
 #endif
 }

+static inline char *realpath_utf8(const char *path)
+{
+#if !defined(MAGICKCORE_WINDOWS_SUPPORT) || defined(__CYGWIN__)
+#if defined(MAGICKCORE_HAVE_REALPATH)
+  return(realpath(path,(char *) NULL));
+#else
+  return(AcquireString(path));
+#endif
+#else
+  char
+    *real_path;
+
+  DWORD
+    final_path_length,
+    full_path_length;
+
+  HANDLE
+    file_handle;
+
+  int
+    length,
+    utf8_length,
+
+  wchar_t
+    *clean_path,
+    *final_path,
+    *full_path,
+    *wide_path;
+
+  /*
+    Convert UTF-8 to UTF-16.
+  */
+  if (path == (const char *) NULL)
+    return((char *) NULL);
+  length=MultiByteToWideChar(CP_UTF8,0,path,-1,NULL,0);
+  if (length <= 0)
+    return((char *) NULL);
+  wide_path=(wchar_t *) AcquireQuantumMeory(length,sizeof(wchar_t));
+  if (wide_path == (wchar_t *) NULL)
+    return((char *) NULL);
+  MultiByteToWideChar(CP_UTF8,0,path,-1,wide_path,length);
+  /*
+    Normalize syntactically.
+  */
+  full_path_length=GetFullPathNameW(wide_path,0,NULL,NULL);
+  if (full_path_length == 0)
+    {
+      wide_path=(wchar_t *) RelinquishMagickMemory(wide_path);
+      return((char *) NULL);
+    }
+  full_path=(wchar_t *) AcquireQuantumMemory(full_path_length,sizeof(wchar_t));
+  if (full_path == (wchar_t *) NULL);
+    {
+      wide_path=(wchar_t *) RelinquishMagickMemory(wide_path);
+      return((char *) NULL);
+    }
+  GetFullPathNameW(wide_path,full_path_length,full_path,NULL);
+  wide_path=(wchar_t *) RelinquishMagickMemory(wide_path);
+  /*
+    Open the file/directory to resolve symlinks.
+  */
+  file_handle=CreateFileW(full_path,GENERIC_READ,FILE_SHARE_READ |
+    FILE_SHARE_WRITE | FILE_SHARE_DELETE,NULL,OPEN_EXISTING,
+    FILE_FLAG_BACKUP_SEMANTICS,NULL);
+  if (file_handle == INVALID_HANDLE_VALUE)
+    {
+      full_path=(wchar_t *) RelinquishMagickMemory(full_path);
+      return((char *) NULL);
+    }
+  /*
+    Resolve final canonical path.
+  */
+  final_path_length=GetFinalPathNameByHandleW(file_handle,NULL,0,
+    FILE_NAME_NORMALIZED);
+  if (final_path_length == 0)
+    {
+      CloseHandle(file_handle);
+      full_path=(wchar_t *) RelinquishMagickMemory(full_path);
+      return((char *) NULL);
+    }
+  final_path=(wchar_t *) AcquireQuantumMemory(final_path_length,
+    sizeof(wchar_t));
+  if (final_path == (wchar_t *) NULL)
+    {
+      CloseHandle(file_handle);
+      full_path=(wchar_t *) RelinquishMagickMemory(full_path);
+      return((char *) NULL);
+    }
+  GetFinalPathNameByHandleW(file_handle,final_path,final_path_length,
+    FILE_NAME_NORMALIZED);
+  CloseHandle(file_handle);
+  full_path=(wchar_t *) RelinquishMagickMemory(full_path);
+  /*
+    Remove \\?\ prefix for POSIX-like behavior.
+  */
+  clean_path=final_path;
+  if (wcsncmp(final_path,L"\\\\?\\",4) == 0)
+    clean_path=final_path+4;
+  /*
+    Convert UTF-16 to UTF-8.
+  */
+  utf8_length=WideCharToMultiByte(CP_UTF8,0,clean_path,-1,NULL,0,NULL,NULL);
+  if (utf8_length <= 0)
+    {
+      final_path=(wchar_t *) RelinquishMagickMemory(final_path);
+      return NULL;
+    }
+  real_path=(char *) AcquireQuantumMemory(utf8_length,sizeof(char));
+  if (real_path == (char *) NULL)
+    {
+      final_path=(wchar_t *) RelinquishMagickMemory(final_path);
+      return NULL;
+    }
+  WideCharToMultiByte(CP_UTF8,0,clean_path,-1,real_path,utf8_length,NULL,NULL);
+  final_path=(wchar_t *) RelinquishMagickMemory(final_path);
+  return(real_path);
+#endif
+}
+
 static inline int remove_utf8(const char *path)
 {
 #if !defined(MAGICKCORE_WINDOWS_SUPPORT) || defined(__CYGWIN__)
diff --git a/MagickCore/utility.c b/MagickCore/utility.c
index 5b8f117c3..4e78d2891 100644
--- a/MagickCore/utility.c
+++ b/MagickCore/utility.c
@@ -1042,16 +1042,23 @@ MagickPrivate MagickBooleanType GetExecutionPath(char *path,const size_t extent)
 #if defined(MAGICKCORE_HAVE__NSGETEXECUTABLEPATH)
   {
     char
-      executable_path[PATH_MAX << 1],
-      execution_path[PATH_MAX+1];
+      executable_path[PATH_MAX << 1];

     uint32_t
       length;

     length=sizeof(executable_path);
-    if ((_NSGetExecutablePath(executable_path,&length) == 0) &&
-        (realpath(executable_path,execution_path) != (char *) NULL))
-      (void) CopyMagickString(path,execution_path,extent);
+    if (_NSGetExecutablePath(executable_path,&length) == 0)
+      {
+        char
+          *real_path = realpath_utf8(executable_path);
+
+        if (real_path != (char *) NULL)
+          {
+            (void) CopyMagickString(path,real_path,extent);
+            real_path=DestroyString(real_path);
+          }
+      }
   }
 #endif
 #if defined(MAGICKCORE_HAVE_GETEXECNAME)
@@ -1097,10 +1104,13 @@ MagickPrivate MagickBooleanType GetExecutionPath(char *path,const size_t extent)
     if (count != -1)
       {
         char
-          execution_path[PATH_MAX+1];
+          *real_path = realpath_utf8(program_name);

-        if (realpath(program_name,execution_path) != (char *) NULL)
-          (void) CopyMagickString(path,execution_path,extent);
+        if (real_path != (char *) NULL)
+          {
+            (void) CopyMagickString(path,real_path,extent);
+            real_path=DestroyString(real_path);
+          }
       }
     if (program_name != program_invocation_name)
       program_name=(char *) RelinquishMagickMemory(program_name);
@@ -1882,7 +1892,7 @@ MagickPrivate MagickBooleanType ShredFile(const char *path)
     {
       char
         *property;
-
+
       passes=0;
       property=GetEnvironmentValue("MAGICK_SHRED_PASSES");
       if (property != (char *) NULL)
diff --git a/coders/dcm.c b/coders/dcm.c
index f9090667d..f4fa2cb6e 100644
--- a/coders/dcm.c
+++ b/coders/dcm.c
@@ -2705,6 +2705,7 @@ typedef struct _DCMInfo

   size_t
     bits_allocated,
+    bits_per_entry,
     bytes_per_pixel,
     depth,
     mask,
@@ -3159,6 +3160,7 @@ static Image *ReadDCMImage(const ImageInfo *image_info,ExceptionInfo *exception)
   */
   (void) CopyMagickString(photometric,"MONOCHROME1 ",MagickPathExtent);
   info.bits_allocated=8;
+  info.bits_per_entry=1;
   info.bytes_per_pixel=1;
   info.depth=8;
   info.mask=0xffff;
@@ -3700,7 +3702,7 @@ static Image *ReadDCMImage(const ImageInfo *image_info,ExceptionInfo *exception)
                 else
                   index=(unsigned short) (*p | (*(p+1) << 8));
                 map.red[i]=(int) index;
-                p+=(ptrdiff_t) 2;
+                p+=(ptrdiff_t) info.bits_per_entry;
               }
               break;
             }
@@ -3732,7 +3734,7 @@ static Image *ReadDCMImage(const ImageInfo *image_info,ExceptionInfo *exception)
                 else
                   index=(unsigned short) (*p | (*(p+1) << 8));
                 map.green[i]=(int) index;
-                p+=(ptrdiff_t) 2;
+                p+=(ptrdiff_t) info.bits_per_entry;
               }
               break;
             }
@@ -3764,10 +3766,20 @@ static Image *ReadDCMImage(const ImageInfo *image_info,ExceptionInfo *exception)
                 else
                   index=(unsigned short) (*p | (*(p+1) << 8));
                 map.blue[i]=(int) index;
-                p+=(ptrdiff_t) 2;
+                p+=(ptrdiff_t) info.bits_per_entry;
               }
               break;
             }
+            case 0x3002:
+            {
+              /*
+                Bytes per entry.
+              */
+              info.bits_per_entry=(size_t) datum;
+              if ((info.bits_per_entry == 0) || (info.bits_per_entry > 2))
+                ThrowDCMException(CorruptImageError,"ImproperImageHeader")
+              break;
+            }
             default:
               break;
           }
diff --git a/coders/msl.c b/coders/msl.c
index 76645b3e7..77ce55db9 100644
--- a/coders/msl.c
+++ b/coders/msl.c
@@ -4770,13 +4770,21 @@ static void MSLStartElement(void *context,const xmlChar *tag,
                 if (LocaleCompare(keyword,"filename") == 0)
                   {
                     Image
-                      *next;
+                      *next = (Image *) NULL;

                     if (value == (char *) NULL)
                       break;
+                    *msl_info->image_info[n]->magick='\0';
                     (void) CopyMagickString(msl_info->image_info[n]->filename,
                       value,MagickPathExtent);
-                    next=ReadImage(msl_info->image_info[n],exception);
+                    (void) SetImageInfo(msl_info->image_info[n],1,exception);
+                    if (LocaleCompare(msl_info->image_info[n]->magick,"msl") != 0)
+                      next=ReadImage(msl_info->image_info[n],exception);
+                    else
+                      (void) ThrowMagickException(msl_info->exception,
+                        GetMagickModule(),DrawError,
+                        "VectorGraphicsNestedTooDeeply","`%s'",
+                        msl_info->image_info[n]->filename);
                     CatchException(exception);
                     if (next == (Image *) NULL)
                       continue;
@@ -5825,10 +5833,11 @@ static void MSLStartElement(void *context,const xmlChar *tag,
                   Quantum  opac = OpaqueAlpha;
                   ssize_t len = (ssize_t) strlen( value );

-                  if (value[len-1] == '%') {
-                    char  tmp[100];
+                  if ((len > 0) && (value[len-1] == '%')) {
+                    char *tmp = AcquireString(value);
                     (void) CopyMagickString(tmp,value,(size_t) len);
                     opac = (Quantum) StringToLong( tmp );
+                    tmp=DestroyString(tmp);
                     opac = (Quantum)(QuantumRange * ((float)opac/100));
                   } else
                     opac = (Quantum) StringToLong( value );
@@ -7039,6 +7048,7 @@ static void MSLStartElement(void *context,const xmlChar *tag,

           /* process */
           {
+            *msl_info->image_info[n]->magick='\0';
             (void) CopyMagickString(msl_info->image_info[n]->filename,
               msl_info->image[n]->filename,MagickPathExtent);
             (void) SetImageInfo(msl_info->image_info[n],1,exception);
@@ -7889,6 +7899,7 @@ static MagickBooleanType WriteMSLImage(const ImageInfo *image_info,Image *image,
     (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
   msl_image=CloneImage(image,0,0,MagickTrue,exception);
   status=ProcessMSLScript(image_info,&msl_image,exception);
+  msl_image=DestroyImage(msl_image);
   return(status);
 }
 #endif
diff --git a/coders/ps.c b/coders/ps.c
index 66b08e8c7..e0477e355 100644
--- a/coders/ps.c
+++ b/coders/ps.c
@@ -1086,6 +1086,82 @@ static inline unsigned char *PopHexPixel(const char hex_digits[][3],
   return(pixels);
 }

+static void inline FilenameToTitle(const char *filename,char *title,
+  const size_t extent)
+{
+  int
+    depth = 0;
+
+  ssize_t
+    i,
+    offset = 0;
+
+  if (extent == 0)
+    return;
+  for (i=0; (filename[i] != '\0') && ((offset+1) < extent); i++)
+  {
+    unsigned char
+      c = filename[i];
+
+    /*
+      Only allow printable ASCII.
+    */
+    if ((c < 32) || (c > 126))
+      {
+        title[offset++]='_';
+        continue;
+      }
+    /*
+      Percent signs break DSC parsing.
+    */
+    if (c == '%')
+      {
+        title[offset++]='_';
+        continue;
+      }
+    /*
+      Parentheses must remain balanced.
+    */
+    if (c == '(')
+      {
+        depth++;
+        title[offset++] = '(';
+        continue;
+      }
+    if (c == ')')
+      {
+        if (depth <= 0)
+          title[offset++]='_';
+        else
+          {
+            depth--;
+            title[offset++]=')';
+          }
+         continue;
+     }
+    /*
+      Everything else is allowed.
+    */
+    title[offset++]=c;
+  }
+  /*
+    If parentheses remain unbalanced, close them.
+  */
+  while ((depth > 0) && ((offset+1) < extent)) {
+    title[offset++]=')';
+    depth--;
+  }
+  title[offset]='\0';
+  /*
+    Ensure non-empty result.
+  */
+  if (offset == 0)
+    {
+      (void) strncpy(title,"Untitled",extent-1);
+      title[extent-1]='\0';
+    }
+}
+
 static MagickBooleanType WritePSImage(const ImageInfo *image_info,Image *image,
   ExceptionInfo *exception)
 {
@@ -1554,6 +1630,9 @@ static MagickBooleanType WritePSImage(const ImageInfo *image_info,Image *image,
       text_size=(size_t) (MultilineCensus(value)*pointsize+12);
     if (page == 1)
       {
+        char
+          title[MagickPathExtent];
+
         /*
           Output Postscript header.
         */
@@ -1564,8 +1643,9 @@ static MagickBooleanType WritePSImage(const ImageInfo *image_info,Image *image,
             MagickPathExtent);
         (void) WriteBlobString(image,buffer);
         (void) WriteBlobString(image,"%%Creator: (ImageMagick)\n");
+        FilenameToTitle(image->filename,title,MagickPathExtent);
         (void) FormatLocaleString(buffer,MagickPathExtent,"%%%%Title: (%s)\n",
-          image->filename);
+          title);
         (void) WriteBlobString(image,buffer);
         timer=GetMagickTime();
         (void) FormatMagickTime(timer,sizeof(date),date);
diff --git a/coders/ps2.c b/coders/ps2.c
index 82935dc8e..af412358f 100644
--- a/coders/ps2.c
+++ b/coders/ps2.c
@@ -225,6 +225,82 @@ static MagickBooleanType Huffman2DEncodeImage(const ImageInfo *image_info,
   return(status);
 }

+static void inline FilenameToTitle(const char *filename,char *title,
+  const size_t extent)
+{
+  int
+    depth = 0;
+
+  ssize_t
+    i,
+    offset = 0;
+
+  if (extent == 0)
+    return;
+  for (i=0; (filename[i] != '\0') && ((offset+1) < extent); i++)
+  {
+    unsigned char
+      c = filename[i];
+
+    /*
+      Only allow printable ASCII.
+    */
+    if ((c < 32) || (c > 126))
+      {
+        title[offset++]='_';
+        continue;
+      }
+    /*
+      Percent signs break DSC parsing.
+    */
+    if (c == '%')
+      {
+        title[offset++]='_';
+        continue;
+      }
+    /*
+      Parentheses must remain balanced.
+    */
+    if (c == '(')
+      {
+        depth++;
+        title[offset++] = '(';
+        continue;
+      }
+    if (c == ')')
+      {
+        if (depth <= 0)
+          title[offset++]='_';
+        else
+          {
+            depth--;
+            title[offset++]=')';
+          }
+         continue;
+     }
+    /*
+      Everything else is allowed.
+    */
+    title[offset++]=c;
+  }
+  /*
+    If parentheses remain unbalanced, close them.
+  */
+  while ((depth > 0) && ((offset+1) < extent)) {
+    title[offset++]=')';
+    depth--;
+  }
+  title[offset]='\0';
+  /*
+    Ensure non-empty result.
+  */
+  if (offset == 0)
+    {
+      (void) strncpy(title,"Untitled",extent-1);
+      title[extent-1]='\0';
+    }
+}
+
 static MagickBooleanType WritePS2Image(const ImageInfo *image_info,Image *image,
   ExceptionInfo *exception)
 {
@@ -547,6 +623,9 @@ static MagickBooleanType WritePS2Image(const ImageInfo *image_info,Image *image,
       text_size=(size_t) (MultilineCensus(value)*pointsize+12);
     if (page == 1)
       {
+        char
+          title[MagickPathExtent];
+
         /*
           Output Postscript header.
         */
@@ -557,8 +636,9 @@ static MagickBooleanType WritePS2Image(const ImageInfo *image_info,Image *image,
             MagickPathExtent);
         (void) WriteBlobString(image,buffer);
         (void) WriteBlobString(image,"%%Creator: (ImageMagick)\n");
+        FilenameToTitle(image->filename,title,MagickPathExtent);
         (void) FormatLocaleString(buffer,MagickPathExtent,"%%%%Title: (%s)\n",
-          image->filename);
+          title);
         (void) WriteBlobString(image,buffer);
         timer=GetMagickTime();
         (void) FormatMagickTime(timer,sizeof(date),date);
diff --git a/coders/ps3.c b/coders/ps3.c
index 77ddf050b..bbb5fea33 100644
--- a/coders/ps3.c
+++ b/coders/ps3.c
@@ -203,6 +203,82 @@ ModuleExport void UnregisterPS3Image(void)
 %
 */

+static void inline FilenameToTitle(const char *filename,char *title,
+  const size_t extent)
+{
+  int
+    depth = 0;
+
+  ssize_t
+    i,
+    offset = 0;
+
+  if (extent == 0)
+    return;
+  for (i=0; (filename[i] != '\0') && ((offset+1) < extent); i++)
+  {
+    unsigned char
+      c = filename[i];
+
+    /*
+      Only allow printable ASCII.
+    */
+    if ((c < 32) || (c > 126))
+      {
+        title[offset++]='_';
+        continue;
+      }
+    /*
+      Percent signs break DSC parsing.
+    */
+    if (c == '%')
+      {
+        title[offset++]='_';
+        continue;
+      }
+    /*
+      Parentheses must remain balanced.
+    */
+    if (c == '(')
+      {
+        depth++;
+        title[offset++] = '(';
+        continue;
+      }
+    if (c == ')')
+      {
+        if (depth <= 0)
+          title[offset++]='_';
+        else
+          {
+            depth--;
+            title[offset++]=')';
+          }
+         continue;
+     }
+    /*
+      Everything else is allowed.
+    */
+    title[offset++]=c;
+  }
+  /*
+    If parentheses remain unbalanced, close them.
+  */
+  while ((depth > 0) && ((offset+1) < extent)) {
+    title[offset++]=')';
+    depth--;
+  }
+  title[offset]='\0';
+  /*
+    Ensure non-empty result.
+  */
+  if (offset == 0)
+    {
+      (void) strncpy(title,"Untitled",extent-1);
+      title[extent-1]='\0';
+    }
+}
+
 static MagickBooleanType Huffman2DEncodeImage(const ImageInfo *image_info,
   Image *image,Image *inject_image,ExceptionInfo *exception)
 {
@@ -1007,6 +1083,9 @@ static MagickBooleanType WritePS3Image(const ImageInfo *image_info,Image *image,
     is_gray=IdentifyImageCoderGray(image,exception);
     if (page == 1)
       {
+        char
+          title[MagickPathExtent];
+
         /*
           Postscript header on the first page.
         */
@@ -1019,8 +1098,9 @@ static MagickBooleanType WritePS3Image(const ImageInfo *image_info,Image *image,
         (void) FormatLocaleString(buffer,MagickPathExtent,
           "%%%%Creator: ImageMagick %s\n",MagickLibVersionText);
         (void) WriteBlobString(image,buffer);
+        FilenameToTitle(image->filename,title,MagickPathExtent);
         (void) FormatLocaleString(buffer,MagickPathExtent,"%%%%Title: %s\n",
-          image->filename);
+          title);
         (void) WriteBlobString(image,buffer);
         timer=GetMagickTime();
         (void) FormatMagickTime(timer,sizeof(date),date);