Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: CSP Security hole in tryConvertExpr (new Function(...)) #139

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,5 +48,8 @@
"rollup-plugin-commonjs": "^8.2.6",
"rollup-watch": "^4.3.1"
},
"dependencies": {}
"dependencies": {
"esprima": "^4.0.1",
"math-expression-evaluator": "^2.0.3"
}
}
68 changes: 54 additions & 14 deletions src/createCompositor.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ import Shader from './Shader';
import Texture from './Texture';
import Texture2D from './Texture2D';
import TextureCube from './TextureCube';
import * as esprimaLoad from 'esprima'
const esprima = esprimaLoad.default?esprimaLoad.default:esprimaLoad;
import * as mexpLoad from 'math-expression-evaluator';
const Mexp = mexpLoad.default?mexpLoad.default:mexpLoad;

import registerBuiltinCompositor from './shader/registerBuiltinCompositor';

Expand Down Expand Up @@ -144,7 +148,7 @@ function createNode(nodeInfo, lib, opts) {
else {
node.on(
'beforerender', createSizeSetHandler(
name, tryConvertExpr(val)
name, getSafeExpressionEvaluator(val)
)
);
}
Expand Down Expand Up @@ -197,7 +201,7 @@ function convertParameter(paramInfo) {
if (typeof val === 'string') {
val = val.trim();
param[name] = createSizeParser(
name, tryConvertExpr(val), sizeScale
name, getSafeExpressionEvaluator(val), sizeScale
);
}
else {
Expand Down Expand Up @@ -289,21 +293,57 @@ function createSizeParser(name, exprFunc, scale) {
};
}

function tryConvertExpr(string) {
// PENDING
var exprRes = /^expr\((.*)\)$/.exec(string);
if (exprRes) {
try {
var func = new Function('width', 'height', 'dpr', 'return ' + exprRes[1]);
// Try run t
func(1, 1);
function astToMathExpression(node, context) {
switch (node.type) {
case 'BinaryExpression':
let left = astToMathExpression(node.left, context);
let right = astToMathExpression(node.right, context);
return `(${left} ${node.operator} ${right})`;
case 'Literal':
return node.value;
case 'Identifier':
if (['width', 'height', 'dpr'].includes(node.name)) {
return context[node.name];
}
throw new Error(`Unknown identifier: ${node.name}`);
default:
throw new Error(`Unsupported node type: ${node.type}`);
}
}

return func;
}
catch (e) {
throw new Error('Invalid expression.');
function getSafeExpressionEvaluator(expression) {
if (!expression) return;
var exprRes = /^expr\((.*)\)$/.exec(expression);
if (!exprRes) return;

const ast = esprima.parseScript(exprRes[1]);

const func = function(width, height, dpr) {
const context = { width, height, dpr };
const mexp = new Mexp()
Copy link

Choose a reason for hiding this comment

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

If the expressions are as simple as the bug implies then rather than ast and all the overhead of generating it perhaps simple str substitution and split could do the job.
This would remove the need for esprima and the astToMathExpression() function

This could be better but something like:

expression = expression.replaceAll('width", width).replaceAll('height", height).replaceAll('dpr", dpr).replaceAll("[", "").replaceAll("]", "");

expressions = expression.split(",")

if (expressions.length === 1) {
    return mexp.eval(expressions[0]);
}

return [ mexp.eval(expressions[0]), mexp.eval(expressions[1]) ];

Probably needs some exception handling as well

Copy link

Choose a reason for hiding this comment

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

Can the const mexp value move out of the func scope so in instantiated for each call?

// Handle array of expressions
if (ast.body[0].type === 'ExpressionStatement' && ast.body[0].expression.type === 'ArrayExpression') {
return ast.body[0].expression.elements.map(element => {
const mathExpression = astToMathExpression(element, context);
return mexp.eval(mathExpression);
});
}

// Handle single expression
const mathExpression = astToMathExpression(ast.body[0].expression, context);
return mexp.eval(mathExpression);
};

// Try run t
try {
Copy link

Choose a reason for hiding this comment

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

Remove debug code

func(1, 1);
return func;
}
catch (e) {
throw new Error('Invalid expression.');
}

Copy link

Choose a reason for hiding this comment

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

return func; ?

}


export default createCompositor;