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.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) {
if (evt.keyCode >= 37 && evt.keyCode <= 40) {
evt.stopPropagation();

View File

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

View File

@ -273,6 +273,37 @@ module.exports = function(RED) {
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 RESOLVED = 1;
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);
try {
var iniScript = null;

View File

@ -209,9 +209,9 @@
"function": {
"function": "",
"label": {
"function": "Function",
"initialize": "Setup",
"finalize": "Close",
"function": "On Message",
"initialize": "On Start",
"finalize": "On Stop",
"outputs": "Outputs",
"require": "Require"
},
@ -224,6 +224,10 @@
"module": "module"
},
"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",
"non-message-returned":"Function tried to send a message of type __type__"
}

View File

@ -1438,7 +1438,6 @@ describe('function node', function() {
}
},20);
}).catch(err => done(err));
})
it('should require the OS module', function(done) {
var flow = [
@ -1459,9 +1458,18 @@ describe('function node', function() {
});
n1.receive({payload:"foo",topic: "bar"});
}).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() {
externalModules.init({userDir: homeDir});
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([
{type: "function", libs:[{module: "foo"}]}
])