|
| 1 | +/* |
| 2 | + * Licensed to Elasticsearch B.V. under one or more contributor |
| 3 | + * license agreements. See the NOTICE file distributed with |
| 4 | + * this work for additional information regarding copyright |
| 5 | + * ownership. Elasticsearch B.V. licenses this file to you under |
| 6 | + * the Apache License, Version 2.0 (the "License"); you may |
| 7 | + * not use this file except in compliance with the License. |
| 8 | + * You may obtain a copy of the License at |
| 9 | + * |
| 10 | + * http://www.apache.org/licenses/LICENSE-2.0 |
| 11 | + * |
| 12 | + * Unless required by applicable law or agreed to in writing, |
| 13 | + * software distributed under the License is distributed on an |
| 14 | + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| 15 | + * KIND, either express or implied. See the License for the |
| 16 | + * specific language governing permissions and limitations |
| 17 | + * under the License. |
| 18 | + */ |
| 19 | + |
| 20 | +// Ensure, when spawning a new child process, that the `options` and the |
| 21 | +// `options.env` object passed to the child process function doesn't inherit |
| 22 | +// from `Object.prototype`. This protects against similar RCE vulnerabilities |
| 23 | +// as described in CVE-2019-7609 |
| 24 | +module.exports = function(cp) { |
| 25 | + // The `exec` function is currently just a wrapper around `execFile`. So for |
| 26 | + // now there's no need to patch it. If this changes in the future, our tests |
| 27 | + // will fail and we can uncomment the line below. |
| 28 | + // |
| 29 | + // cp.exec = new Proxy(cp.exec, { apply: patchOptions() }); |
| 30 | + |
| 31 | + cp.execFile = new Proxy(cp.execFile, { apply: patchOptions(true) }); |
| 32 | + cp.fork = new Proxy(cp.fork, { apply: patchOptions(true) }); |
| 33 | + cp.spawn = new Proxy(cp.spawn, { apply: patchOptions(true) }); |
| 34 | + cp.execFileSync = new Proxy(cp.execFileSync, { apply: patchOptions(true) }); |
| 35 | + cp.execSync = new Proxy(cp.execSync, { apply: patchOptions() }); |
| 36 | + cp.spawnSync = new Proxy(cp.spawnSync, { apply: patchOptions(true) }); |
| 37 | + |
| 38 | + return cp; |
| 39 | +}; |
| 40 | + |
| 41 | +function patchOptions(hasArgs) { |
| 42 | + return function apply(target, thisArg, args) { |
| 43 | + var pos = 1; |
| 44 | + if (pos === args.length) { |
| 45 | + // fn(arg1) |
| 46 | + args[pos] = prototypelessSpawnOpts(); |
| 47 | + } else if (pos < args.length) { |
| 48 | + if (hasArgs && (Array.isArray(args[pos]) || args[pos] == null)) { |
| 49 | + // fn(arg1, args, ...) |
| 50 | + pos++; |
| 51 | + } |
| 52 | + |
| 53 | + if (typeof args[pos] === 'object' && args[pos] !== null) { |
| 54 | + // fn(arg1, {}, ...) |
| 55 | + // fn(arg1, args, {}, ...) |
| 56 | + args[pos] = prototypelessSpawnOpts(args[pos]); |
| 57 | + } else if (args[pos] == null) { |
| 58 | + // fn(arg1, null/undefined, ...) |
| 59 | + // fn(arg1, args, null/undefined, ...) |
| 60 | + args[pos] = prototypelessSpawnOpts(); |
| 61 | + } else if (typeof args[pos] === 'function') { |
| 62 | + // fn(arg1, callback) |
| 63 | + // fn(arg1, args, callback) |
| 64 | + args.splice(pos, 0, prototypelessSpawnOpts()); |
| 65 | + } |
| 66 | + } |
| 67 | + |
| 68 | + return target.apply(thisArg, args); |
| 69 | + }; |
| 70 | +} |
| 71 | + |
| 72 | +function prototypelessSpawnOpts(obj) { |
| 73 | + var prototypelessObj = Object.assign(Object.create(null), obj); |
| 74 | + prototypelessObj.env = Object.assign(Object.create(null), prototypelessObj.env || process.env); |
| 75 | + return prototypelessObj; |
| 76 | +} |
0 commit comments