From 753ffb699934331b31028d4e271f2f6d6db85074 Mon Sep 17 00:00:00 2001
From: Cristy <urban-warrior@imagemagick.org>
Date: Sun, 22 Feb 2026 19:00:06 -0500
Subject: [PATCH] 
 https://github.com/ImageMagick/ImageMagick/security/advisories/GHSA-493f-jh8w-qhx3

---
 MagickCore/blob.c        | 128 +++++++++++++++++++++++++++++++++++----
 MagickCore/utility.c     |  10 ++-
 config/policy-secure.xml |   2 +
 3 files changed, 127 insertions(+), 13 deletions(-)

Index: ImageMagick-7.1.1-21/MagickCore/blob.c
===================================================================
--- ImageMagick-7.1.1-21.orig/MagickCore/blob.c
+++ ImageMagick-7.1.1-21/MagickCore/blob.c
@@ -1437,19 +1437,36 @@ MagickExport void *FileToBlob(const char
   file=fileno(stdin);
   if (LocaleCompare(filename,"-") != 0)
     {
+      int
+        flags = O_RDONLY | O_BINARY;
+
       status=GetPathAttributes(filename,&attributes);
       if ((status == MagickFalse) || (S_ISDIR(attributes.st_mode) != 0))
         {
           ThrowFileException(exception,BlobError,"UnableToReadBlob",filename);
           return(NULL);
         }
-      file=open_utf8(filename,O_RDONLY | O_BINARY,0);
+#if defined(O_NOFOLLOW)
+      status=IsRightsAuthorized(SystemPolicyDomain,ReadPolicyRights,"follow");
+      if (status == MagickFalse)
+        flags|=O_NOFOLLOW;
+#endif
+      file=open_utf8(filename,flags,0);
     }
   if (file == -1)
     {
       ThrowFileException(exception,BlobError,"UnableToOpenFile",filename);
       return(NULL);
     }
+  status=IsRightsAuthorized(PathPolicyDomain,ReadPolicyRights,filename);
+  if (status == MagickFalse)
+    {
+      file=close(file)-1;
+      errno=EPERM;
+      (void) ThrowMagickException(exception,GetMagickModule(),PolicyError,
+        "NotAuthorized","`%s'",filename);
+      return(NULL);
+    }
   offset=(MagickOffsetType) lseek(file,0,SEEK_END);
   count=0;
   if ((file == fileno(stdin)) || (offset < 0) ||
@@ -1643,7 +1660,7 @@ MagickExport MagickBooleanType FileToIma
   assert(filename != (const char *) NULL);
   if (IsEventLogging() != MagickFalse)
     (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",filename);
-  status=IsRightsAuthorized(PathPolicyDomain,WritePolicyRights,filename);
+  status=IsRightsAuthorized(PathPolicyDomain,ReadPolicyRights,filename);
   if (status == MagickFalse)
     {
       errno=EPERM;
@@ -1653,12 +1670,31 @@ MagickExport MagickBooleanType FileToIma
     }
   file=fileno(stdin);
   if (LocaleCompare(filename,"-") != 0)
-    file=open_utf8(filename,O_RDONLY | O_BINARY,0);
+    {
+      int
+        flags = O_RDONLY | O_BINARY;
+
+#if defined(O_NOFOLLOW)
+      status=IsRightsAuthorized(SystemPolicyDomain,ReadPolicyRights,"follow");
+      if (status == MagickFalse)
+        flags|=O_NOFOLLOW;
+#endif
+      file=open_utf8(filename,flags,0);
+    }
   if (file == -1)
     {
       ThrowFileException(exception,BlobError,"UnableToOpenBlob",filename);
       return(MagickFalse);
     }
+  status=IsRightsAuthorized(PathPolicyDomain,ReadPolicyRights,filename);
+  if (status == MagickFalse)
+    {
+      file=close(file);
+      errno=EPERM;
+      (void) ThrowMagickException(exception,GetMagickModule(),PolicyError,
+        "NotAuthorized","`%s'",filename);
+      return(MagickFalse);
+    }
   quantum=(size_t) MagickMaxBufferExtent;
   if ((fstat(file,&file_stats) == 0) && (file_stats.st_size > 0))
     quantum=(size_t) MagickMin(file_stats.st_size,MagickMaxBufferExtent);
@@ -3254,6 +3290,9 @@ MagickExport MagickBooleanType OpenBlob(
   const char
     *type;
 
+  int
+    flags = O_RDONLY;
+
   MagickBooleanType
     status;
 
@@ -3286,13 +3325,48 @@ MagickExport MagickBooleanType OpenBlob(
   blob_info->mode=mode;
   switch (mode)
   {
-    default: type="r"; break;
-    case ReadBlobMode: type="r"; break;
-    case ReadBinaryBlobMode: type="rb"; break;
-    case WriteBlobMode: type="w"; break;
-    case WriteBinaryBlobMode: type="w+b"; break;
-    case AppendBlobMode: type="a"; break;
-    case AppendBinaryBlobMode: type="a+b"; break;
+    case ReadBlobMode:
+    {
+      flags=O_RDONLY;
+      type="r";
+      break;
+    }
+    case ReadBinaryBlobMode:
+    {
+      flags=O_RDONLY | O_BINARY;
+      type="rb";
+      break;
+    }
+    case WriteBlobMode:
+    {
+      flags=O_WRONLY | O_CREAT | O_TRUNC;
+      type="w";
+      break;
+    }
+    case WriteBinaryBlobMode:
+    {
+      flags=O_RDWR | O_CREAT | O_TRUNC | O_BINARY;
+      type="w+b";
+      break;
+    }
+    case AppendBlobMode:
+    {
+      flags=O_WRONLY | O_CREAT | O_APPEND;
+      type="a";
+      break;
+    }
+    case AppendBinaryBlobMode:
+    {
+      flags=O_RDWR | O_CREAT | O_APPEND | O_BINARY;
+      type="a+b";
+      break;
+    }
+    default:
+    {
+      flags=O_RDONLY;
+      type="r";
+      break;
+    }
   }
   if (*type != 'r')
     blob_info->synchronize=image_info->synchronize;
@@ -3437,7 +3511,18 @@ MagickExport MagickBooleanType OpenBlob(
   else
     if (*type == 'r')
       {
-        blob_info->file_info.file=(FILE *) fopen_utf8(filename,type);
+        int
+          file;
+
+        blob_info->file_info.file=(FILE *) NULL;
+#if defined(O_NOFOLLOW)
+        status=IsRightsAuthorized(SystemPolicyDomain,ReadPolicyRights,"follow");
+        if (status == MagickFalse)
+          flags|=O_NOFOLLOW;
+#endif
+        file=open_utf8(filename,flags,0);
+        if (file >= 0)
+          blob_info->file_info.file=fdopen(file,type);
         if (blob_info->file_info.file != (FILE *) NULL)
           {
             size_t
@@ -3557,13 +3642,32 @@ MagickExport MagickBooleanType OpenBlob(
         else
 #endif
           {
-            blob_info->file_info.file=(FILE *) fopen_utf8(filename,type);
+            int
+              file;
+
+            blob_info->file_info.file=(FILE *) NULL;
+#if defined(O_NOFOLLOW)
+            status=IsRightsAuthorized(SystemPolicyDomain,WritePolicyRights,
+              "follow");
+            if (status == MagickFalse)
+              flags|=O_NOFOLLOW;
+#endif
+            file=open_utf8(filename,flags,0666);
+            if (file >= 0)
+              blob_info->file_info.file=fdopen(file,type);
             if (blob_info->file_info.file != (FILE *) NULL)
               {
                 blob_info->type=FileStream;
                 (void) SetStreamBuffering(image_info,blob_info);
               }
           }
+  if (IsRightsAuthorized(PathPolicyDomain,rights,filename) == MagickFalse)
+    {
+      errno=EPERM;
+      (void) ThrowMagickException(exception,GetMagickModule(),PolicyError,
+        "NotAuthorized","`%s'",filename);
+      return(MagickFalse);
+    }
   blob_info->status=MagickFalse;
   blob_info->error_number=0;
   if (blob_info->type != UndefinedStream)
Index: ImageMagick-7.1.1-21/MagickCore/utility.c
===================================================================
--- ImageMagick-7.1.1-21.orig/MagickCore/utility.c
+++ ImageMagick-7.1.1-21/MagickCore/utility.c
@@ -180,10 +180,15 @@ MagickExport MagickBooleanType AcquireUn
     char
       *passes;
 
+    /*
+      Does policy permit symbolic links?
+    */
+    status=IsRightsAuthorized(SystemPolicyDomain,ReadPolicyRights |
+      WritePolicyRights,"follow");
     (void) AcquireUniqueFilename(destination);
     (void) RelinquishUniqueFileResource(destination);
     passes=GetPolicyValue("system:shred");
-    if (passes != (char *) NULL)
+    if ((passes != (char *) NULL) || (status == MagickFalse))
       passes=DestroyString(passes);
     else
       {
@@ -209,6 +214,9 @@ MagickExport MagickBooleanType AcquireUn
       }
   }
 #endif
+  /*
+    Copy file from source to destination.
+  */
   destination_file=AcquireUniqueFileResource(destination);
   if (destination_file == -1)
     return(MagickFalse);
Index: ImageMagick-7.1.1-21/config/policy-secure.xml
===================================================================
--- ImageMagick-7.1.1-21.orig/config/policy-secure.xml
+++ ImageMagick-7.1.1-21/config/policy-secure.xml
@@ -103,4 +103,6 @@
   <!-- Set the maximum amount of memory in bytes that are permitted for
        allocation requests. -->
   <policy domain="system" name="max-memory-request" value="256MiB"/>
+  <!-- If the basename of path is a symbolic link, the open fails -->
+  <policy domain="system" name="symlink" rights="none" pattern="follow"/>
 </policymap>
