/** \file * \brief Wrappers for Workers * * \author Copyright (C) 2019 Libor Polcak * \author Copyright (C) 2020 Peter Hornak * \author Copyright (C) 2021 Matus Svancar * * \license SPDX-License-Identifier: GPL-3.0-or-later */ // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program. If not, see . // /** \file * \ingroup wrappers * * This wrapper aims on prevention of microarchitectural attacks. This * code was originally a part of [ChromeZero](https://github.com/IAIK/ChromeZero). * * The wrappers support the following behaviour: * * * Polyfill: Completely eliminates the paralelism. * * Randomly slow messages: Add noise to the `postMessage` method execution. * * \see Lipp, M., Gruss, D., Schwarz, M., Bidner, D., Maurice, C. et al. Practical Keystroke Timing Attacks in Sandboxed JavaScript. In:. August 2017, s. 191–209. ISBN 978-3-319-66398-2. * * \see Schwarz, M., Lipp, M. a Gruss, D. JavaScript Zero: Real JavaScript and Zero * Side-Channel Attacks. NDSS'18. * * \see */ /* * Create private namespace */ (function() { var polyfillBody = ` /// This polyfill was adopted from https://github.com/nolanlawson/pseudo-worker under Apache License 2.0 and modified. function doEval(self, __pseudoworker_script) { /* jshint unused:false */ (function () { /* jshint evil:true */ eval(__pseudoworker_script); }).call(window); } var messageListeners = []; var errorListeners = []; var workerMessageListeners = []; var workerErrorListeners = []; var postMessageListeners = []; var terminated = false; var script; var workerSelf; var api = this; // custom each loop is for IE8 support function executeEach(arr, fun) { var i = -1; while (++i < arr.length) { if (arr[i]) { fun(arr[i]); } } } function callErrorListener(err) { return function (listener) { listener({ type: 'error', error: err, message: err.message }); }; } function addEventListener(type, fun) { /* istanbul ignore else */ if (type === 'message') { messageListeners.push(fun); } else if (type === 'error') { errorListeners.push(fun); } } function removeEventListener(type, fun) { var listeners; /* istanbul ignore else */ if (type === 'message') { listeners = messageListeners; } else if (type === 'error') { listeners = errorListeners; } else { return; } var i = -1; while (++i < listeners.length) { var listener = listeners[i]; if (listener === fun) { delete listeners[i]; break; } } } function postError(err) { var callFun = callErrorListener(err); if (typeof api.onerror === 'function') { callFun(api.onerror); } if (workerSelf && typeof workerSelf.onerror === 'function') { callFun(workerSelf.onerror); } executeEach(errorListeners, callFun); executeEach(workerErrorListeners, callFun); } function runPostMessage(msg, transfer) { function callFun(listener) { try { listener({data: msg, ports: transfer}); } catch (err) { postError(err); } } if (workerSelf && typeof workerSelf.onmessage === 'function') { callFun(workerSelf.onmessage); } executeEach(workerMessageListeners, callFun); } function postMessage(msg, transfer) { if (typeof msg === 'undefined') { throw new Error('postMessage() requires an argument'); } if (terminated) { return; } if (!script) { postMessageListeners.push({msg: msg, transfer: (transfer ? transfer : undefined)}); return; } runPostMessage(msg, transfer); } function terminate() { terminated = true; } function workerPostMessage(msg) { if (terminated) { return; } function callFun(listener) { listener({ data: msg }); } if (typeof api.onmessage === 'function') { callFun(api.onmessage); } executeEach(messageListeners, callFun); } function workerAddEventListener(type, fun) { /* istanbul ignore else */ if (type === 'message') { workerMessageListeners.push(fun); } else if (type === 'error') { workerErrorListeners.push(fun); } } var xhr = new XMLHttpRequest(); xhr.open('GET', path); xhr.onreadystatechange = function () { if (xhr.readyState === 4) { if (xhr.status >= 200 && xhr.status < 400) { script = xhr.responseText; workerSelf = { postMessage: workerPostMessage, addEventListener: workerAddEventListener, close: terminate }; doEval(workerSelf, script); var currentListeners = postMessageListeners; postMessageListeners = []; for (var i = 0; i < currentListeners.length; i++) { runPostMessage(currentListeners[i].msg, currentListeners[i].transfer); } } else { postError(new Error('cannot find script ' + path)); } } }; xhr.send(); api.postMessage = postMessage; api.addEventListener = addEventListener; api.removeEventListener = removeEventListener; api.terminate = terminate; return api; `; var slowBody = ` let _data = new originalF(path); let _old = _data.postMessage; _data.postMessage = function(message) { let delay = Math.floor(Math.random() * 10**9) let j; for (let i = 0; i < delay;) { j = i; i = j + 1; } return _old.call(_data, message); } return _data; `; var wrappers = [ { parent_object: "Navigator.prototype", parent_object_property: "hardwareConcurrency", wrapped_objects: [], helping_code: ` var hw_prng = alea(domainHash, "Navigator.prototype.hardwareConcurrency"); var ret = 2; if(args[0]==0){ var realValue = navigator.hardwareConcurrency; ret = Math.floor(2+hw_prng()*(realValue-2)); } else if(args[0]==1){ ret = Math.floor(2+(hw_prng()*6)); } `, post_wrapping_code: [ { code_type: "object_properties", wrapped_name: "origConcurrency", wrapped_objects: [], parent_object: "Navigator.prototype", parent_object_property: "hardwareConcurrency", /** \brief replaces navigator.hardwareConcurrency getter * * Depending on level chosen this property returns: * * (0) - random valid value from range [2 - real value] * * (1) - random valid value from range [2 - 8] * * (2) - 2 */ wrapped_properties: [ { property_name: "get", property_value: ` function() { return ret; }`, }, ], } ], }, { parent_object: "window", parent_object_property: "Worker", original_function: "window.Worker", wrapped_objects: [], helping_code: ` let doPolyfill = args[0]; `, wrapping_function_args: `path`, wrapping_function_body: ` if (doPolyfill) { ${polyfillBody} } else { ${slowBody} } `, } ] add_wrappers(wrappers); })();