Prevent function module overwriting built-in sandbox properties

This commit is contained in:
Nick O'Leary 2021-02-16 13:58:59 +00:00
parent 9d34abf603
commit 785c349adc
No known key found for this signature in database
GPG Key ID: 4F2157149161A6C9
6 changed files with 115 additions and 41 deletions

View File

@ -344,6 +344,16 @@
that.element.val(that.value()); that.element.val(that.value());
that.element.trigger('change',that.propertyType,that.value()); that.element.trigger('change',that.propertyType,that.value());
}); });
this.input.on('keyup', function(evt) {
that.validate();
that.element.val(that.value());
that.element.trigger('keyup',evt);
});
this.input.on('paste', function(evt) {
that.validate();
that.element.val(that.value());
that.element.trigger('paste',evt);
});
this.input.on('keydown', function(evt) { this.input.on('keydown', function(evt) {
if (evt.keyCode >= 37 && evt.keyCode <= 40) { if (evt.keyCode >= 37 && evt.keyCode <= 40) {
evt.stopPropagation(); evt.stopPropagation();

View File

@ -23,6 +23,11 @@
border-left: none; border-left: none;
border-top: none; border-top: none;
border-right: none; border-right: none;
padding-top: 2px;
padding-bottom: 2px;
margin-top: 4px;
margin-bottom: 2px;
height: 26px;
} }
.node-libs-entry > span > i { .node-libs-entry > span > i {
@ -89,6 +94,25 @@
(function() { (function() {
var invalidModuleVNames = [
'console',
'util',
'Buffer',
'Date',
'RED',
'node',
'__node__',
'context',
'flow',
'global',
'env',
'setTimeout',
'clearTimeout',
'setInterval',
'clearInterval',
'promisify'
]
var knownFunctionNodes = {}; var knownFunctionNodes = {};
RED.events.on("nodes:add", function(n) { RED.events.on("nodes:add", function(n) {
if (n.type === "function") { if (n.type === "function") {
@ -193,6 +217,16 @@
width: "120px", width: "120px",
"margin-left": "5px" "margin-left": "5px"
}).appendTo(row0).val(opt.var); }).appendTo(row0).val(opt.var);
var vnameWarning = $('<span style="display:inline-block; width: 16px;"><i class="fa fa-warning"></i></span>').appendTo(row0);
RED.popover.tooltip(vnameWarning.find("i"),function() {
var val = fvar.val();
if (invalidModuleVNames.indexOf(val) !== -1) {
return RED._("node-red:function.error.moduleNameReserved",{name:val})
} else {
return RED._("node-red:function.error.moduleNameError",{name:val})
}
})
$('<code> = require(</code>').appendTo(row0); $('<code> = require(</code>').appendTo(row0);
var fmodule = $("<input/>", { var fmodule = $("<input/>", {
class: "node-input-libs-val", class: "node-input-libs-val",
@ -210,29 +244,33 @@
$('<code>)</code>').appendTo(row0); $('<code>)</code>').appendTo(row0);
var warning = $('<span style="display:inline-block; width: 16px;"><i class="fa fa-warning"></i></span>').appendTo(row0); var moduleWarning = $('<span style="display:inline-block; width: 16px;"><i class="fa fa-warning"></i></span>').appendTo(row0);
RED.popover.tooltip(warning.find("i"),function() { RED.popover.tooltip(moduleWarning.find("i"),function() {
var val = fmodule.typedInput("type"); var val = fmodule.typedInput("type");
if (val === "_custom_") { if (val === "_custom_") {
val = fmodule.val(); val = fmodule.val();
} }
var errors = [];
if (!RED.utils.checkModuleAllowed(val,null,installAllowList,installDenyList)) { if (!RED.utils.checkModuleAllowed(val,null,installAllowList,installDenyList)) {
return "Module not allowed" return RED._("node-red:function.error.moduleNotAllowed",{module:val});
} else { } else {
return "Module not installed: "+missingModuleReasons[val] return RED._("node-red:function.error.moduleLoadError",{module:val,error:missingModuleReasons[val]});
} }
}) })
fvar.on("change", function (e) { fvar.on("change keyup paste", function (e) {
var v = $(this).val().trim(); var v = $(this).val().trim();
if (v === "" || / /.test(v)) { if (v === "" || / /.test(v) || invalidModuleVNames.indexOf(v) !== -1) {
fvar.addClass("input-error"); fvar.addClass("input-error");
vnameWarning.addClass("input-error");
} else { } else {
fvar.removeClass("input-error"); fvar.removeClass("input-error");
vnameWarning.removeClass("input-error");
} }
}); });
fmodule.on("change", function (e) { fmodule.on("change keyup paste", function (e) {
var val = $(this).typedInput("type"); var val = $(this).typedInput("type");
if (val === "_custom_") { if (val === "_custom_") {
val = $(this).val(); val = $(this).val();
@ -243,18 +281,18 @@
if (RED.utils.checkModuleAllowed(val,null,installAllowList,installDenyList) && (missingModules.indexOf(val) === -1)) { if (RED.utils.checkModuleAllowed(val,null,installAllowList,installDenyList) && (missingModules.indexOf(val) === -1)) {
fmodule.removeClass("input-error"); fmodule.removeClass("input-error");
warning.removeClass("input-error"); moduleWarning.removeClass("input-error");
} else { } else {
fmodule.addClass("input-error"); fmodule.addClass("input-error");
warning.addClass("input-error"); moduleWarning.addClass("input-error");
} }
}); });
if (RED.utils.checkModuleAllowed(opt.module,null,installAllowList,installDenyList) && (missingModules.indexOf(opt.module) === -1)) { if (RED.utils.checkModuleAllowed(opt.module,null,installAllowList,installDenyList) && (missingModules.indexOf(opt.module) === -1)) {
fmodule.removeClass("input-error"); fmodule.removeClass("input-error");
warning.removeClass("input-error"); moduleWarning.removeClass("input-error");
} else { } else {
fmodule.addClass("input-error"); fmodule.addClass("input-error");
warning.addClass("input-error"); moduleWarning.addClass("input-error");
} }
if (opt.var) { if (opt.var) {
fvar.trigger("change"); fvar.trigger("change");
@ -293,6 +331,9 @@
if (missingModules.indexOf(m.module) > -1) { if (missingModules.indexOf(m.module) > -1) {
return false; return false;
} }
if (invalidModuleVNames.indexOf(m.var) !== -1){
return false;
}
} }
return true; return true;
}} }}
@ -325,15 +366,15 @@
tabs.addTab({ tabs.addTab({
id: "func-tab-init", id: "func-tab-init",
label: "On Start", //that._("function.label.initialize") label: that._("function.label.initialize")
}); });
tabs.addTab({ tabs.addTab({
id: "func-tab-body", id: "func-tab-body",
label: "On Message"//that._("function.label.function") label: that._("function.label.function")
}); });
tabs.addTab({ tabs.addTab({
id: "func-tab-finalize", id: "func-tab-finalize",
label: "On Stop"//that._("function.label.finalize") label: that._("function.label.finalize")
}); });
tabs.activateTab("func-tab-body"); tabs.activateTab("func-tab-body");

View File

@ -273,6 +273,37 @@ module.exports = function(RED) {
sandbox.promisify = util.promisify; sandbox.promisify = util.promisify;
} }
if (node.hasOwnProperty("libs")) {
let moduleErrors = false;
var modules = node.libs;
modules.forEach(module => {
var vname = module.hasOwnProperty("var") ? module.var : null;
if (vname && (vname !== "")) {
if (sandbox.hasOwnProperty(vname) || vname === 'node') {
node.error(RED._("function.error.moduleNameError",{name:vname}))
moduleErrors = true;
return;
}
sandbox[vname] = null;
try {
var spec = module.module;
if (spec && (spec !== "")) {
var lib = RED.require(module.module);
sandbox[vname] = lib;
}
} catch (e) {
//TODO: NLS error message
node.error(RED._("function.error.moduleLoadError",{module:module.spec, error:e.toString()}))
moduleErrors = true;
}
}
});
if (moduleErrors) {
throw new Error("Function node failed to load external modules");
}
}
const RESOLVING = 0; const RESOLVING = 0;
const RESOLVED = 1; const RESOLVED = 1;
const ERROR = 2; const ERROR = 2;
@ -289,26 +320,6 @@ module.exports = function(RED) {
} }
}); });
if (node.hasOwnProperty("libs")) {
var modules = node.libs;
modules.forEach(module => {
var vname = module.hasOwnProperty("var") ? module.var : null;
if (vname && (vname !== "")) {
sandbox[vname] = null;
try {
var spec = module.module;
if (spec && (spec !== "")) {
var lib = RED.require(module.module);
sandbox[vname] = lib;
}
}
catch (e) {
console.log(e);
node.warn("failed to load library: "+ module.spec);
}
}
});
}
var context = vm.createContext(sandbox); var context = vm.createContext(sandbox);
try { try {
var iniScript = null; var iniScript = null;

View File

@ -209,9 +209,9 @@
"function": { "function": {
"function": "", "function": "",
"label": { "label": {
"function": "Function", "function": "On Message",
"initialize": "Setup", "initialize": "On Start",
"finalize": "Close", "finalize": "On Stop",
"outputs": "Outputs", "outputs": "Outputs",
"require": "Require" "require": "Require"
}, },
@ -224,6 +224,10 @@
"module": "module" "module": "module"
}, },
"error": { "error": {
"moduleNotAllowed": "Module __module__ not allowed",
"moduleLoadError": "Failed to load module __module__: __error__",
"moduleNameError": "Invalid module variable name: __name__",
"moduleNameReserved": "Reserved variable name: __name__",
"inputListener":"Cannot add listener to 'input' event within Function", "inputListener":"Cannot add listener to 'input' event within Function",
"non-message-returned":"Function tried to send a message of type __type__" "non-message-returned":"Function tried to send a message of type __type__"
} }

View File

@ -1438,7 +1438,6 @@ describe('function node', function() {
} }
},20); },20);
}).catch(err => done(err)); }).catch(err => done(err));
}) })
it('should require the OS module', function(done) { it('should require the OS module', function(done) {
var flow = [ var flow = [
@ -1459,9 +1458,18 @@ describe('function node', function() {
}); });
n1.receive({payload:"foo",topic: "bar"}); n1.receive({payload:"foo",topic: "bar"});
}).catch(err => done(err)); }).catch(err => done(err));
}) })
it('should fail if module variable name clashes with sandbox builtin', function(done) {
var flow = [
{id:"n1",type:"function",wires:[["n2"]],func:"msg.payload = os.type(); return msg;", "libs": [{var:"flow", module:"os"}]},
{id:"n2", type:"helper"}
];
helper.load(functionNode, flow, function() {
var n1 = helper.getNode("n1");
should.not.exist(n1);
done();
}).catch(err => done(err));
})
}) })

View File

@ -94,7 +94,7 @@ describe("externalModules api", function() {
it("installs missing modules", async function() { it("installs missing modules", async function() {
externalModules.init({userDir: homeDir}); externalModules.init({userDir: homeDir});
externalModules.register("function", "libs"); externalModules.register("function", "libs");
fs.existsSync(path.join(homeDir,"externalModuels")).should.be.false(); fs.existsSync(path.join(homeDir,"externalModules")).should.be.false();
await externalModules.checkFlowDependencies([ await externalModules.checkFlowDependencies([
{type: "function", libs:[{module: "foo"}]} {type: "function", libs:[{module: "foo"}]}
]) ])