Commit 7499ad060 for imagemagick.org

commit 7499ad060e4e5730242a33afa917a1a26385e197
Author: Cristy <urban-warrior@imagemagick.org>
Date:   Fri Apr 17 21:57:53 2026 -0400

    allow namespace::pattern when checking policy rights

diff --git a/MagickCore/blob.c b/MagickCore/blob.c
index 262013fa8..f4c175734 100644
--- a/MagickCore/blob.c
+++ b/MagickCore/blob.c
@@ -1475,7 +1475,7 @@ MagickExport void *FileToBlob(const char *filename,const size_t extent,
           return(NULL);
         }
 #if defined(O_NOFOLLOW)
-      status=IsRightsAuthorized(SystemPolicyDomain,ReadPolicyRights,"follow");
+      status=IsRightsAuthorized(SystemPolicyDomain,ReadPolicyRights,"symlink::follow");
       if (status == MagickFalse)
         flags|=O_NOFOLLOW;
 #endif
@@ -1709,7 +1709,7 @@ MagickExport MagickBooleanType FileToImage(Image *image,const char *filename,
         flags = O_RDONLY | O_BINARY;

 #if defined(O_NOFOLLOW)
-      status=IsRightsAuthorized(SystemPolicyDomain,ReadPolicyRights,"follow");
+      status=IsRightsAuthorized(SystemPolicyDomain,ReadPolicyRights,"symlink::follow");
       if (status == MagickFalse)
         flags|=O_NOFOLLOW;
 #endif
@@ -3359,29 +3359,29 @@ MagickExport MagickBooleanType OpenBlob(const ImageInfo *image_info,
     {
       flags=O_RDONLY;
       type="r";
-      status=IsRightsAuthorized(SystemPolicyDomain,ReadPolicyRights,"follow");
+      status=IsRightsAuthorized(SystemPolicyDomain,ReadPolicyRights,"symlink::follow");
       break;
     }
     case ReadBinaryBlobMode:
     {
       flags=O_RDONLY | O_BINARY;
       type="rb";
-      status=IsRightsAuthorized(SystemPolicyDomain,ReadPolicyRights,"follow");
+      status=IsRightsAuthorized(SystemPolicyDomain,ReadPolicyRights,"symlink::follow");
       break;
     }
     case WriteBlobMode:
     {
       flags=O_WRONLY | O_CREAT | O_TRUNC;
       type="w";
-      status=IsRightsAuthorized(SystemPolicyDomain,WritePolicyRights,"follow");
+      status=IsRightsAuthorized(SystemPolicyDomain,WritePolicyRights,"symlink::follow");
       break;
     }
     case WriteBinaryBlobMode:
     {
       flags=O_RDWR | O_CREAT | O_TRUNC | O_BINARY;
       type="w+b";
-      status=IsRightsAuthorized(SystemPolicyDomain,ReadPolicyRights,"follow") &&
-        IsRightsAuthorized(SystemPolicyDomain,WritePolicyRights,"follow") ?
+      status=IsRightsAuthorized(SystemPolicyDomain,ReadPolicyRights,"symlink::follow") &&
+        IsRightsAuthorized(SystemPolicyDomain,WritePolicyRights,"symlink::follow") ?
         MagickTrue : MagickFalse;
       break;
     }
@@ -3389,15 +3389,15 @@ MagickExport MagickBooleanType OpenBlob(const ImageInfo *image_info,
     {
       flags=O_WRONLY | O_CREAT | O_APPEND;
       type="a";
-      status=IsRightsAuthorized(SystemPolicyDomain,WritePolicyRights,"follow");
+      status=IsRightsAuthorized(SystemPolicyDomain,WritePolicyRights,"symlink::follow");
       break;
     }
     case AppendBinaryBlobMode:
     {
       flags=O_RDWR | O_CREAT | O_APPEND | O_BINARY;
       type="a+b";
-      status=IsRightsAuthorized(SystemPolicyDomain,ReadPolicyRights,"follow") &&
-        IsRightsAuthorized(SystemPolicyDomain,WritePolicyRights,"follow") ?
+      status=IsRightsAuthorized(SystemPolicyDomain,ReadPolicyRights,"symlink::follow") &&
+        IsRightsAuthorized(SystemPolicyDomain,WritePolicyRights,"symlink::follow") ?
         MagickTrue : MagickFalse;
       break;
     }
@@ -3405,7 +3405,7 @@ MagickExport MagickBooleanType OpenBlob(const ImageInfo *image_info,
     {
       flags=O_RDONLY;
       type="r";
-      status=IsRightsAuthorized(SystemPolicyDomain,ReadPolicyRights,"follow");
+      status=IsRightsAuthorized(SystemPolicyDomain,ReadPolicyRights,"symlink::follow");
       break;
     }
   }
diff --git a/MagickCore/nt-base-private.h b/MagickCore/nt-base-private.h
index 550c339f0..0cec86eb7 100644
--- a/MagickCore/nt-base-private.h
+++ b/MagickCore/nt-base-private.h
@@ -190,7 +190,7 @@ extern MagickPrivate MagickBooleanType
   NTReportEvent(const char *,const MagickBooleanType);

 extern MagickExport MagickBooleanType
-  NTIsLinkWide(const char *),
+  NTIsSymlinkWide(const char *),
   NTLongPathsEnabled(void);

 extern MagickPrivate struct dirent
diff --git a/MagickCore/nt-base.c b/MagickCore/nt-base.c
index d2966cf94..67ca97992 100644
--- a/MagickCore/nt-base.c
+++ b/MagickCore/nt-base.c
@@ -1623,19 +1623,19 @@ MagickPrivate void NTGhostscriptUnLoadDLL(void)
 %                                                                             %
 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
 %
-%  NTIsLinkWide() returns a boolean value indicating whether the specified path
+%  NTIsSymlinkWide() returns a boolean value indicating whether the specified path
 %  is a a link.
 %
-%  The format of the NTIsLinkWide method is:
+%  The format of the NTIsSymlinkWide method is:
 %
-%      MagickBooleanType NTIsLinkWide(const char *path)
+%      MagickBooleanType NTIsSymlinkWide(const char *path)
 %
 %  A description of each parameter follows:
 %
 %    o path: the file path.
 %
 */
-MagickExport MagickBooleanType NTIsLinkWide(const char *path)
+MagickExport MagickBooleanType NTIsSymlinkWide(const char *path)
 {
   DWORD
     attributes;
diff --git a/MagickCore/policy.c b/MagickCore/policy.c
index 2d96f5dce..25e9467bf 100644
--- a/MagickCore/policy.c
+++ b/MagickCore/policy.c
@@ -626,7 +626,7 @@ static MagickBooleanType IsPolicyCacheInstantiated(ExceptionInfo *exception)
 %  The format of the IsRightsAuthorized method is:
 %
 %      MagickBooleanType IsRightsAuthorized(const PolicyDomain domain,
-%        const PolicyRights rights,const char *pattern)
+%        const PolicyRights rights,const char *qualified_pattern)
 %
 %  A description of each parameter follows:
 %
@@ -634,13 +634,53 @@ static MagickBooleanType IsPolicyCacheInstantiated(ExceptionInfo *exception)
 %
 %    o rights: the policy rights.
 %
-%    o pattern: the coder, delegate, filter, or path pattern.
+%    o qualified_pattern: the pattern.
 %
 */
+
+static inline MagickBooleanType ParseNamespace(
+  const char *restrict qualified_pattern,char **name,char **pattern)
+{
+  const char
+    *p,
+    *separator;
+
+  size_t
+    length;
+
+  if ((qualified_pattern == (const char *) NULL) || (name == (char **) NULL) ||
+      (pattern == (char **) NULL))
+    return(MagickFalse);
+  *name=(char *) NULL;
+  *pattern=(char *) NULL;
+  separator=strstr(qualified_pattern,"::");
+  if (separator == (const char *) NULL)
+    {
+      *pattern=AcquireString(qualified_pattern);
+      return(*pattern != (char *) NULL ? MagickTrue : MagickFalse);
+    }
+  length=(size_t) (separator-qualified_pattern);
+  *name=(char *) AcquireQuantumMemory(length+1,sizeof(char));
+  if (*name == (char *) NULL)
+    return(MagickFalse);
+  (void) CopyMagickString(*name,qualified_pattern,length+1);
+  p=separator+2;
+  *pattern=AcquireString(p);
+  if (*pattern == (char *) NULL)
+    {
+      *name=DestroyString(*name);
+      *name=(char *) NULL;
+      return(MagickFalse);
+    }
+  return(MagickTrue);
+}
+
 MagickExport MagickBooleanType IsRightsAuthorized(const PolicyDomain domain,
-  const PolicyRights rights,const char *pattern)
+  const PolicyRights rights,const char *qualified_pattern)
 {
   char
+    *name = (char *) NULL,
+    *pattern = (char *) NULL,
     *real_pattern = (char *) NULL;

   const PolicyInfo
@@ -650,7 +690,7 @@ MagickExport MagickBooleanType IsRightsAuthorized(const PolicyDomain domain,
     *exception;

   MagickBooleanType
-    authorized,
+    authorized = MagickTrue,
     match;

   ElementInfo
@@ -660,13 +700,15 @@ MagickExport MagickBooleanType IsRightsAuthorized(const PolicyDomain domain,
     (void) LogMagickEvent(PolicyEvent,GetMagickModule(),
       "Domain: %s; rights=%s; pattern=\"%s\" ...",
       CommandOptionToMnemonic(MagickPolicyDomainOptions,domain),
-      CommandOptionToMnemonic(MagickPolicyRightsOptions,rights),pattern);
+      CommandOptionToMnemonic(MagickPolicyRightsOptions,rights),
+      qualified_pattern);
   exception=AcquireExceptionInfo();
   policy_info=GetPolicyInfo("*",exception);
   exception=DestroyExceptionInfo(exception);
   if (policy_info == (PolicyInfo *) NULL)
     return(MagickTrue);
-  authorized=MagickTrue;
+  if (ParseNamespace(qualified_pattern,&name,&pattern) == MagickFalse)
+    return(MagickFalse);
   LockSemaphoreInfo(policy_semaphore);
   p=GetHeadElementInLinkedList(policy_cache);
   while (p != (ElementInfo *) NULL)
@@ -675,7 +717,8 @@ MagickExport MagickBooleanType IsRightsAuthorized(const PolicyDomain domain,
       *policy;

     policy=(const PolicyInfo *) p->value;
-    if (policy->domain == domain)
+    if ((policy->domain == domain) &&
+        ((name == (char *) NULL) || (LocaleCompare(name,policy->name) == 0)))
       {
         if ((policy->domain == PathPolicyDomain) &&
             (real_pattern == (const char *) NULL))
@@ -687,8 +730,8 @@ MagickExport MagickBooleanType IsRightsAuthorized(const PolicyDomain domain,
         if (match != MagickFalse)
           {
             if ((rights & ReadPolicyRights) != 0)
-              authorized=(policy->rights & ReadPolicyRights) != 0 ? MagickTrue :
-                MagickFalse;
+              authorized=(policy->rights & ReadPolicyRights) != 0 ?
+                MagickTrue : MagickFalse;
             if ((rights & WritePolicyRights) != 0)
               authorized=(policy->rights & WritePolicyRights) != 0 ?
                 MagickTrue : MagickFalse;
@@ -700,6 +743,10 @@ MagickExport MagickBooleanType IsRightsAuthorized(const PolicyDomain domain,
     p=p->next;
   }
   UnlockSemaphoreInfo(policy_semaphore);
+  if (pattern != (char *) NULL)
+    pattern=DestroyString(pattern);
+  if (name != (char *) NULL)
+    name=DestroyString(name);
   if (real_pattern != (char *) NULL)
     real_pattern=DestroyString(real_pattern);
   return(authorized);
diff --git a/MagickCore/utility-private.h b/MagickCore/utility-private.h
index ae2928dc5..df6f45054 100644
--- a/MagickCore/utility-private.h
+++ b/MagickCore/utility-private.h
@@ -82,7 +82,7 @@ static inline FILE *fopen_utf8(const char *path,const char *mode)
 #endif
 }

-static inline MagickBooleanType is_link_utf8(const char *path)
+static inline MagickBooleanType is_symlink_utf8(const char *path)
 {
 #if !defined(MAGICKCORE_WINDOWS_SUPPORT) || defined(__CYGWIN__)
 #if defined(MAGICKCORE_POSIX_SUPPORT)
@@ -96,7 +96,7 @@ static inline MagickBooleanType is_link_utf8(const char *path)
   return(MagickFalse);
 #endif
 #else
- return(NTIsLinkWide(path));
+ return(NTIsSymlinkWide(path));
 #endif
 }

diff --git a/MagickCore/utility.c b/MagickCore/utility.c
index 4258f7f19..edbc8e8be 100644
--- a/MagickCore/utility.c
+++ b/MagickCore/utility.c
@@ -184,7 +184,7 @@ MagickExport MagickBooleanType AcquireUniqueSymbolicLink(const char *source,
       Does policy permit symbolic links?
     */
     status=IsRightsAuthorized(SystemPolicyDomain,(PolicyRights)
-      (ReadPolicyRights | WritePolicyRights),"follow");
+      (ReadPolicyRights | WritePolicyRights),"symlink::follow");
     passes=GetPolicyValue("system:shred");
     if ((passes != (char *) NULL) || (status == MagickFalse))
       passes=DestroyString(passes);