From: Jukka Kurkela Date: Sun, 18 Oct 2020 17:31:01 +0000 (+0300) Subject: Use Object.create(null) as `merge` target, to prevent prototype pollution (#7917) X-Git-Tag: v3.0.0-beta.5~30 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=73b4e82fd5680caddc70d4183f4223bad2764e56;p=thirdparty%2FChart.js.git Use Object.create(null) as `merge` target, to prevent prototype pollution (#7917) Use Object.create(null) as merge target to prevent polluting `Object.prototype` --- diff --git a/src/core/core.controller.js b/src/core/core.controller.js index d2df919bc..0621bc884 100644 --- a/src/core/core.controller.js +++ b/src/core/core.controller.js @@ -44,8 +44,8 @@ function mergeScaleConfig(config, options) { const chartDefaults = defaults[config.type] || {scales: {}}; const configScales = options.scales || {}; const chartIndexAxis = getIndexAxis(config.type, options); - const firstIDs = {}; - const scales = {}; + const firstIDs = Object.create(null); + const scales = Object.create(null); // First figure out first scale id's per axis. Object.keys(configScales).forEach(id => { @@ -53,12 +53,12 @@ function mergeScaleConfig(config, options) { const axis = determineAxis(id, scaleConf); const defaultId = getDefaultScaleIDFromAxis(axis, chartIndexAxis); firstIDs[axis] = firstIDs[axis] || id; - scales[id] = mergeIf({axis}, [scaleConf, chartDefaults.scales[axis], chartDefaults.scales[defaultId]]); + scales[id] = mergeIf(Object.create(null), [{axis}, scaleConf, chartDefaults.scales[axis], chartDefaults.scales[defaultId]]); }); // Backward compatibility if (options.scale) { - scales[options.scale.id || 'r'] = mergeIf({axis: 'r'}, [options.scale, chartDefaults.scales.r]); + scales[options.scale.id || 'r'] = mergeIf(Object.create(null), [{axis: 'r'}, options.scale, chartDefaults.scales.r]); firstIDs.r = firstIDs.r || options.scale.id || 'r'; } @@ -71,7 +71,7 @@ function mergeScaleConfig(config, options) { Object.keys(defaultScaleOptions).forEach(defaultID => { const axis = getAxisFromDefaultScaleID(defaultID, indexAxis); const id = dataset[axis + 'AxisID'] || firstIDs[axis] || axis; - scales[id] = scales[id] || {}; + scales[id] = scales[id] || Object.create(null); mergeIf(scales[id], [{axis}, configScales[id], defaultScaleOptions[defaultID]]); }); }); @@ -91,7 +91,7 @@ function mergeScaleConfig(config, options) { * a deep copy of the result, thus doesn't alter inputs. */ function mergeConfig(...args/* config objects ... */) { - return merge({}, args, { + return merge(Object.create(null), args, { merger(key, target, source, options) { if (key !== 'scales' && key !== 'scale') { _merger(key, target, source, options); @@ -118,8 +118,8 @@ function initConfig(config) { options.scales = scaleConfig; - options.title = (options.title !== false) && merge({}, [defaults.plugins.title, options.title]); - options.tooltips = (options.tooltips !== false) && merge({}, [defaults.plugins.tooltip, options.tooltips]); + options.title = (options.title !== false) && merge(Object.create(null), [defaults.plugins.title, options.title]); + options.tooltips = (options.tooltips !== false) && merge(Object.create(null), [defaults.plugins.tooltip, options.tooltips]); return config; } diff --git a/src/core/core.datasetController.js b/src/core/core.datasetController.js index ca26dffc9..c57c3ffe1 100644 --- a/src/core/core.datasetController.js +++ b/src/core/core.datasetController.js @@ -334,7 +334,7 @@ export default class DatasetController { */ configure() { const me = this; - me._config = merge({}, [ + me._config = merge(Object.create(null), [ me.chart.options[me._type].datasets, me.getDataset(), ], { diff --git a/src/core/core.defaults.js b/src/core/core.defaults.js index 61663fd18..6ee9e681e 100644 --- a/src/core/core.defaults.js +++ b/src/core/core.defaults.js @@ -12,7 +12,7 @@ function getScope(node, key) { const keys = key.split('.'); for (let i = 0, n = keys.length; i < n; ++i) { const k = keys[i]; - node = node[k] || (node[k] = {}); + node = node[k] || (node[k] = Object.create(null)); } return node; } diff --git a/src/core/core.layouts.js b/src/core/core.layouts.js index 18e2aff7f..789731a30 100644 --- a/src/core/core.layouts.js +++ b/src/core/core.layouts.js @@ -1,5 +1,5 @@ import defaults from './core.defaults'; -import {each} from '../helpers/helpers.core'; +import {each, isObject} from '../helpers/helpers.core'; import {toPadding} from '../helpers/helpers.options'; /** @@ -84,6 +84,10 @@ function updateDims(chartArea, params, layout) { const box = layout.box; const maxPadding = chartArea.maxPadding; + if (isObject(layout.pos)) { + // dynamically placed boxes are not considered + return; + } if (layout.size) { // this layout was already counted for, lets first reduce old size chartArea[layout.pos] -= layout.size; diff --git a/src/helpers/helpers.core.js b/src/helpers/helpers.core.js index 6aae6c158..ec936deb5 100644 --- a/src/helpers/helpers.core.js +++ b/src/helpers/helpers.core.js @@ -157,7 +157,7 @@ export function clone(source) { } if (isObject(source)) { - const target = {}; + const target = Object.create(null); const keys = Object.keys(source); const klen = keys.length; let k = 0; diff --git a/src/plugins/plugin.filler.js b/src/plugins/plugin.filler.js index 49d2d9ec9..b70ae53c3 100644 --- a/src/plugins/plugin.filler.js +++ b/src/plugins/plugin.filler.js @@ -55,11 +55,14 @@ function parseFillOption(line) { */ function decodeFill(line, index, count) { const fill = parseFillOption(line); - let target = parseFloat(fill); if (isObject(fill)) { return isNaN(fill.value) ? false : fill; - } else if (isFinite(target) && Math.floor(target) === target) { + } + + let target = parseFloat(fill); + + if (isFinite(target) && Math.floor(target) === target) { if (fill[0] === '-' || fill[0] === '+') { target = index + target; } diff --git a/src/plugins/plugin.legend.js b/src/plugins/plugin.legend.js index 9f6f894fe..c5de3d9c2 100644 --- a/src/plugins/plugin.legend.js +++ b/src/plugins/plugin.legend.js @@ -629,7 +629,7 @@ export class Legend extends Element { } function resolveOptions(options) { - return options !== false && merge({}, [defaults.plugins.legend, options]); + return options !== false && merge(Object.create(null), [defaults.plugins.legend, options]); } function createNewLegendAndAttach(chart, legendOpts) { diff --git a/src/plugins/plugin.tooltip.js b/src/plugins/plugin.tooltip.js index 7b9553c24..58e5fd932 100644 --- a/src/plugins/plugin.tooltip.js +++ b/src/plugins/plugin.tooltip.js @@ -139,7 +139,7 @@ function createTooltipItem(chart, item) { */ function resolveOptions(options, fallbackFont) { - options = merge({}, [defaults.plugins.tooltip, options]); + options = merge(Object.create(null), [defaults.plugins.tooltip, options]); options.bodyFont = toFont(options.bodyFont, fallbackFont); options.titleFont = toFont(options.titleFont, fallbackFont); diff --git a/test/specs/core.helpers.tests.js b/test/specs/core.helpers.tests.js index 178498125..d48a3a061 100644 --- a/test/specs/core.helpers.tests.js +++ b/test/specs/core.helpers.tests.js @@ -13,4 +13,12 @@ describe('Core helper tests', function() { expect(helpers.uid()).toBe(uid + 2); expect(helpers.uid()).toBe(uid + 3); }); + + describe('clone', function() { + it('should not allow prototype pollution', function() { + const test = helpers.clone(JSON.parse('{"__proto__":{"polluted": true}}')); + expect(test.prototype).toBeUndefined(); + expect(Object.prototype.polluted).toBeUndefined(); + }); + }); });