背景javascript
前一陣子比特股的創始人Daniel Larimer質疑了lisk系統中的一系列問題,絕大多數都被lisk的創始人之一Max正面迴應過了,具體能夠看看這個http://ethereum.stackexchange...
可是有一個問題Max沒有迴應或者說尚未提出解決方案。
那就是lisk側鏈的運行環境,Larimer說
「Lisk所面臨的大多數問題,均可以經過一個高度定製的JavaScript 環境來解決。」
不然,lisk的系統面臨兩大考驗:
首先,也是最重要的,lisk目前的沙箱機制不足以限制側鏈代碼的權限,也就是說沒法運行不受信任的代碼,那些代碼可能會盜取服務器的關鍵信息,或者直接對服務器進行破壞
其次,是關於側鏈開發者的,lisk的側鏈代碼是運行在一個擁有所有能力的javascript環境,這裏面有些能夠致使不肯定因素的函數,好比Math.random,lisk官方的開發文檔特別指出但願開發者避開這些函數,這對開發者會形成心智負擔,若是是一個定製版的javascript環境呢,直接把那些致使不肯定因素的函數幹掉,開發者根本不須要花費額外的精力去避開那些陷阱。前端
咱們先說說爲何要用沙箱。沙箱(sandbox)是雲計算平臺普遍採用的安全防範措施,是一種訪問控制機制,其目的是爲了限制應用代碼的權限,防止應用代碼對系統進行隨意的訪問和破壞,由於在雲計算平臺中,應用代碼是由各類各樣的第三方開發者實現的,他們的代碼是不能被信任的,須要沙箱來作一層隔離,讓這些應用代碼只能作有限的事情。
lisk在區塊鏈領域是很相似於雲計算平臺的,他們提供一些服務,容許第三方開發者基於這些服務構建他們本身的應用程序(即dapps)。
因此lisk也須要沙箱機制來保證dapps的做者沒法做惡,lisk的作法是提供一個定製版的nodejs環境來達到這一目的。
具體實如今這段代碼裏,https://github.com/LiskHQ/lis...
我通過分析,發現這個sandbox名存實亡,只是使用管道手段實現了進程間通信,並且是使用一種繞彎的方式實現的。nodejs的進程間通信能夠經過javascript代碼直接實現,爲何要用c++來實現呢?
我帶着這個疑問,和一些指望,親手作了一個實驗。
我首先使用lisk-cli建立一個hello world側鏈java
lisk-cli dapp -a
接着我在側鏈程序的入口處 加了一段代碼node
console.log(require("fs").readFileSync("../../config.json"))
而後運行之,因而我獲得了這個服務器主節點的受託人的全部密碼linux
"forging": { "secret": [ "wKyoJM1vS4ucHmWvxDSdcpC23mJwqfg3G6MKZoXaFfcnWHTqo7", "2aTWYPpQidVunxTg3y8YESYps7za6f9d4wYn9Gy2GuGnE7JX7V", "65uZNjL36Bdg2tkJnueYkd2n6YPe76fpdeYtgu7fso1m385mwD", …………
果真,這個沙箱並無起隔離的做用,擁有管理員的權限,這等於給黑客敞開了大門。
有人說,lisk系統設置了二級密碼,你獲取了他的一級密碼,仍是無法盜取他們的錢。
也有人說,能夠經過Linux自身的權限機制,給側鏈代碼一個低級用戶,使之沒法訪問其餘用戶的文件。
這些都是治標不治本的辦法,根本解決途徑是按照lisk預先設想的那樣,要讓沙箱名副其實,作到真正的環境隔離,要讓側鏈代碼對外界一無所知。
解決方案webpack
那麼,如何實現真正的沙箱呢?有不少種方案,好比跳過nodejs,直接使用v8引擎,或者使用進程級別的權限控制,好比windows系統的SetWindowsHookEx,固然,lisk目前並不打算支持windows版本的完整版錢包,那麼在linux系統中可使用seccomp技術。
我這裏有種更簡單的方法,那就是利用nodejs自帶的vm模塊。
第一步 建立原生的javascript虛擬機c++
var vm = require('vm'); var context = vm.createContext(); vm.runInContext(sideChainCode, context);
這幾行代碼完成了對側鏈代碼的隔離,sideChainCode中只能進行純粹的運算邏輯,只能使用少許的v8引擎內置的javascript標準庫。甚至連setTimeout,console.log都沒有。
咱們須要作些額外的工做。
好比,給運行環境添加setTimeout和clearTimeoutgit
context.setTimeout= function(fn, delay) { if (typeof(fn) == 'string') { setTimeout(new Function(fn), delay) } else { setTimeout(fn, delay) } }; context.clearTimeout = clearTimeout;
添加日誌打印功能,讓側鏈的日誌,轉發到主系統的中github
global.print = send.bind(global, 'stdout'); global.console = { log: send.bind(global, 'stdout') }; global.process = { stdout: { write: send.bind(global, 'stdout') } }; global.postMessage = send.bind(global, 'message');
另外,還能夠禁用那些致使不肯定因素的函數web
global.Math.random = undefined;
這些都作完之後,側鏈代碼的訪問權限就被限制在一個狹小的範圍內了。它們沒法使用require,fs,http等nodejs內置的標準庫。
這樣安全的目的達到了,可是引起另外一個問題,那就是功能性的問題了,不能使用那些額外的庫,只有js的標準庫也太不方便了,不少複雜功能沒法實現,特別是沒有了require以後,連模塊化都作不到了。
因此咱們須要第二步。
第二步 webpack
webpack原本是一個前端經常使用的打包方案,用於模塊化的管理前端項目。不少人忽略了其實在後端webpack一樣適用,而且能夠把node_modules裏的庫,甚至nodejs的一部份內置庫一塊兒打包。
也就是說前端能用的js庫,除了UI相關的以外,在側鏈沙箱內,也均可以用,好比async,bytebuffer,crypto,js-nacl,bignum等等,這對於側鏈來講夠用了。
那些不能用的庫,好比文件系統、多進程、網絡模塊,正是咱們想要拋棄的。
vm + webpack的組合堪稱完美。
這是突飛猛進的前端技術帶給javascript這門語言的福利,也是以太坊的solidity等新語言可望不可即的。
不過咱們還須要一些收尾的工做
第三步 掃清障礙
目前側鏈代碼中有些地方使用了一些比較複雜的庫,好比ed2curve,涉及到很是多的依賴,咱們認爲是沒有必要的,這部分功能能夠在主鏈中提供,經過進程間通信以api的方式提供給側鏈使用。
這樣也能夠減輕側鏈代碼的累贅,還可讓側鏈開發者更加輕鬆。這些代碼對整個框架的影響很是小,能夠忽略不計,可是它們依賴的庫卻佔用了一半以上的代碼量,其中還包括了沙箱環境不容許的一些操做。
通過分析,我發現只須要禁用modules/api/crypto.js中的兩個函數便可
Crypto.prototype.encrypt Crypto.prototype.decrypt
另外,js-nacl這個庫裏依賴了fs模塊,可是相關函數並無被用到,暫時經過手工修改的方式,把fs相關代碼去掉就能夠正常打包並運行了。
最後,我把一個完整的側鏈項目和主鏈框架中的關鍵代碼打包放在這裏了。http://o7dyh3w0x.bkt.clouddn....
關於側鏈和區塊鏈開發的問題,歡迎加羣:485979564,一塊兒討論交流