Skip to content

Conversation

cxcorp
Copy link
Contributor

@cxcorp cxcorp commented Mar 4, 2025

From #1025

This PR fixes two issues:

  1. When parsing color strings (e.g. text-color: "red"), parse_css_color.ts attempted to look up named colors from an object literal by doing namedColors[input]. If the color string was "__proto__" or "toString" or some other key in the Object prototype, the value was truthy, and caused an unhandled error to be thrown because the value was destructured.
    const namedColorsMatch = namedColors[input];
    if (namedColorsMatch) {
    const [r, g, b] = namedColorsMatch;
    return [r / 255, g / 255, b / 255, 1];
    }
  2. When a Coercion expression was used to coerce an expression's string value to a color (e.g. to_color), it used EvaluationContext.parseColor() which checked if the string had already been parsed and cached. The cache lookup was done via this._parseColorCache[input], where input comes from the user controlled style. As a result, the input was never parsed as the value was truthy, and the value from the prototype was returned and became the expression's value.
    _parseColorCache: {[_: string]: Color};

    parseColor(input: string): Color {
    let cached = this._parseColorCache[input];
    if (!cached) {
    cached = this._parseColorCache[input] = Color.parse(input);
    }
    return cached;

Fixing issue 1 revealed issue 2, as EvaluationContext.parseColor() used Color.parse() which used parseCssColor(). Color.parse() is also used in function expressions.

The fixes for the issues were:

  1. For parseCssColor(), since we probably want the convenience of using an object literal to initialize it, I opted to use the previously introduced (Fix validate_object crashing when object prototype keys used in style's objects #1028) getOwn() utility to ensure that only the literal's keys are tested.
  2. For EvaluationContext.parseColor(), the _parseColorCache was previously initialized to an empty object literal {}. Since there was no need for the ergonomics of using a literal, I replaced it with Object.create(null) which creates an object without a prototype (note: {} is syntactic sugar for Object.create(Object.prototype)).

The ideal solution for this would be to systematically use a Map if the key is user-controlled, but I did not find any uses of it from the codebase and assumed that the library targets ES5 compatibility.

Launch Checklist

  • Confirm your changes do not include backports from Mapbox projects (unless with compliant license) - if you are not sure about this, please ask!
  • Briefly describe the changes in this PR.
  • Link to related issues.
  • Include before/after visuals or gifs if this PR includes visual changes.
  • Write tests for all new functionality.
  • Document any changes to public APIs.
  • Post benchmark scores.
  • Add an entry to CHANGELOG.md under the ## main section.

cxcorp added 2 commits March 4, 2025 23:52
parseCssColor checks `namedColors[input]` to see if the given color
string is a predetermined named color. If "__proto__" or any other key
found in Object.prototype was used, the evaluated value was truthy, and
the function attempted to destructure the returned value, which throws
an error.

This commit fixes that issue by using getOwn() to access only own
properties of the namedColors object.
Color coercion (Coercion class) uses EvaluationContext.parseColor to
parse color strings. EvaluationContext has an internal cache for color
strings, which it checks before parsing to skip parsing the same value
multiple times.

If a color coercion expression was passed "__proto__" or some other key
present in Object.prototype, the parseColor function would skip parsing
altogether as this._parseColorCache["__proto__"] evaluated to a truthy
value, and thus parseColor thought the value was cached and returned the
evaluated value directly.

This caused the entire coercion expression to evaluate to the value of
Object.prototype, which in the end resulted in the color being
[null, null, null, null] somewhere in maplibre-gl-js.

Fixed by creating the cache with a null prototype, instead of an empty
object literal.
@codecov-commenter
Copy link

codecov-commenter commented Mar 4, 2025

Codecov Report

All modified and coverable lines are covered by tests ✅

Project coverage is 92.74%. Comparing base (91175b3) to head (89d3c87).

Additional details and impacted files
@@           Coverage Diff           @@
##             main    #1036   +/-   ##
=======================================
  Coverage   92.74%   92.74%           
=======================================
  Files         108      108           
  Lines        4728     4730    +2     
  Branches     1347     1347           
=======================================
+ Hits         4385     4387    +2     
  Misses        343      343           

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@@ -10,5 +10,17 @@
{
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I noticed that there were both *.output-api-supported.json and *.output.json files in the tests, but was unable to find references to output-api-supported or api-supported in the codebase, other than a changelog entry saying --mapbox-api-supported had been removed from gl-style-validation in 14.0.0. Are the .output-api-supported.json files still used, or subject for removal in a later PR?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can't find anything related to this in the history of the repo, I'm not sure they were ever used, and also from a very shallow test I made these looks duplicates of the output files, so I would recommend removing them.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure. Remove in this PR or shall I make a separate PR?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A separate PR seems like the right approach.

@cxcorp cxcorp marked this pull request as ready for review March 5, 2025 20:31
@cxcorp cxcorp changed the title Fix prototype keys causing error in color parsing and unexpected behavior in color coercion expressions Fix prototype keys causing error in color parsing and invalid colors in color coercion expressions Mar 5, 2025
@cxcorp cxcorp changed the title Fix prototype keys causing error in color parsing and invalid colors in color coercion expressions Fix prototype keys causing crash in color parsing and invalid colors in color coercion expressions Mar 5, 2025
@cxcorp cxcorp force-pushed the fix-color-validation-prototype-issues branch from c526145 to 89d3c87 Compare March 8, 2025 02:49
@HarelM HarelM merged commit d4bd583 into maplibre:main Mar 8, 2025
6 checks passed
@cxcorp cxcorp deleted the fix-color-validation-prototype-issues branch March 8, 2025 22:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants