Commit c73f7913f for imagemagick.org
commit c73f7913f72b8d97fe89f4094eefdc8d34edf5a9
Author: Cristy <urban-warrior@imagemagick.org>
Date: Sat Oct 3 16:55:04 2026 -0400
https://github.com/ImageMagick/ImageMagick/security/advisories/GHSA-79p5-w6jv-r997
diff --git a/MagickCore/policy.c b/MagickCore/policy.c
index 294cd7665..f1786e9fa 100644
--- a/MagickCore/policy.c
+++ b/MagickCore/policy.c
@@ -802,14 +802,14 @@ MagickExport MagickBooleanType IsRightsAuthorizedByName(
*exception;
MagickBooleanType
- canonical_matched_any = MagickFalse,
matched_any = MagickFalse,
paths_provisioned = MagickFalse,
+ resolved_matched_any = MagickFalse,
status;
PolicyRights
- canonical_allowed_accumulator = AllPolicyRights,
- effective_rights = AllPolicyRights;
+ effective_rights = AllPolicyRights,
+ resolved_rights = AllPolicyRights;
/*
Load policies.
@@ -832,8 +832,11 @@ MagickExport MagickBooleanType IsRightsAuthorizedByName(
return(MagickTrue);
}
/*
- Evaluate policies in order; the last matching lexical or canonical policy
- wins.
+ Evaluate policies in order; the last matching policy wins. A path is
+ evaluated twice: once by the name it was given (lexical, canonical
+ directory, or canonical directory plus basename) and once by its fully
+ resolved path. Both evaluations must authorize the request, so a symbolic
+ link inside an allowed directory cannot reach a target the policy denies.
*/
LockSemaphoreInfo(policy_semaphore);
ResetLinkedListIterator(policy_cache);
@@ -844,8 +847,7 @@ MagickExport MagickBooleanType IsRightsAuthorizedByName(
*policy = (PolicyInfo *) p->value;
MagickBooleanType
- match = MagickFalse,
- matched_canonical = MagickFalse;
+ match = MagickFalse;
if (policy->domain != domain)
continue;
@@ -878,50 +880,28 @@ MagickExport MagickBooleanType IsRightsAuthorizedByName(
canonical_path=realpath_utf8(pattern);
}
/*
- Match against directory, basename, and canonical path.
+ Match the given name against the directory and basename forms.
*/
if ((canonical_directory != (char *) NULL) && (match == MagickFalse))
match=GlobExpression(canonical_directory,policy->pattern,MagickFalse);
if ((canonical_candidate != (char *) NULL) && (match == MagickFalse))
match=GlobExpression(canonical_candidate,policy->pattern,MagickFalse);
- if ((canonical_path != (char *) NULL) && (match == MagickFalse))
- match=GlobExpression(canonical_path,policy->pattern,MagickFalse);
+ /*
+ Match the fully resolved path on its own.
+ */
if ((canonical_path != (char *) NULL) &&
(GlobExpression(canonical_path,policy->pattern,MagickFalse) != MagickFalse))
- matched_canonical=MagickTrue;
- else
- if ((canonical_candidate != (char *) NULL) &&
- (GlobExpression(canonical_candidate,policy->pattern,MagickFalse) != MagickFalse))
- matched_canonical=MagickTrue;
- else
- if ((canonical_directory != (char *) NULL) &&
- (GlobExpression(canonical_directory,policy->pattern,MagickFalse) != MagickFalse))
- matched_canonical=MagickTrue;
+ {
+ resolved_matched_any=MagickTrue;
+ resolved_rights=policy->rights;
+ }
}
if (match == MagickFalse)
continue;
matched_any=MagickTrue;
effective_rights=policy->rights;
- if (matched_canonical != MagickFalse)
- {
- /*
- If this matched a canonical form, retain the last matching rights.
- */
- canonical_matched_any=MagickTrue;
- if (policy->domain != PathPolicyDomain)
- canonical_allowed_accumulator=policy->rights;
- else
- canonical_allowed_accumulator=(PolicyRights) ((int)
- canonical_allowed_accumulator & (int) policy->rights);
- }
}
UnlockSemaphoreInfo(policy_semaphore);
- if (canonical_directory != (char *) NULL)
- canonical_directory=DestroyString(canonical_directory);
- if (canonical_candidate != (char *) NULL)
- canonical_candidate=DestroyString(canonical_candidate);
- if (canonical_path != (char *) NULL)
- canonical_path=DestroyString(canonical_path);
/*
Is rights authorized?
*/
@@ -938,16 +918,31 @@ MagickExport MagickBooleanType IsRightsAuthorizedByName(
((effective_rights & ExecutePolicyRights) == 0))
status=MagickFalse;
}
- /*
- Enforce sticky canonical denies.
- */
- if (canonical_matched_any != MagickFalse)
+ if (resolved_matched_any != MagickFalse)
{
- PolicyRights canonical_denied_mask = (PolicyRights) ((int)
- AllPolicyRights & (int) ~canonical_allowed_accumulator);
- if ((canonical_denied_mask & rights) != 0)
+ if (((rights & ReadPolicyRights) != 0) &&
+ ((resolved_rights & ReadPolicyRights) == 0))
+ status=MagickFalse;
+ if (((rights & WritePolicyRights) != 0) &&
+ ((resolved_rights & WritePolicyRights) == 0))
+ status=MagickFalse;
+ if (((rights & ExecutePolicyRights) != 0) &&
+ ((resolved_rights & ExecutePolicyRights) == 0))
status=MagickFalse;
}
+ /*
+ A symbolic link that does not resolve (dangling or looping) has no target
+ to evaluate; following it on write could create a file anywhere.
+ */
+ if ((paths_provisioned != MagickFalse) && (canonical_path == (char *) NULL) &&
+ (is_symlink_utf8(pattern) != MagickFalse))
+ status=MagickFalse;
+ if (canonical_directory != (char *) NULL)
+ canonical_directory=DestroyString(canonical_directory);
+ if (canonical_candidate != (char *) NULL)
+ canonical_candidate=DestroyString(canonical_candidate);
+ if (canonical_path != (char *) NULL)
+ canonical_path=DestroyString(canonical_path);
if ((GetLogEventMask() & PolicyEvent) != 0)
(void) LogMagickEvent(PolicyEvent,GetMagickModule(),
" authorized: %s",status == MagickFalse ? "false" : "true");
@@ -1149,7 +1144,7 @@ static MagickBooleanType LoadPolicyCache(LinkedListInfo *cache,
{
int
bracket_depth = 0,
- quote=0;
+ quote = 0;
/*
Parse DOCTYPE element.
@@ -1174,7 +1169,7 @@ static MagickBooleanType LoadPolicyCache(LinkedListInfo *cache,
if (quote != 0)
{
if (*q == quote)
- quote=0;
+ quote = 0;
}
else
{