随着CelesOS开发进度的不断推进,CelesOS的主网开发进展可观。CelesOS作为一个拥抱监管,适合监管机构、金融机构、用户接入的区块链操作系统,「安全问题」一直是CelesOS研发团队技术攻坚的重点领域。
细数目前的主流公链,频繁出现安全问题:盗取账户私钥、dapp被黑客攻击......
据某安全公司透露,某公链平均每周都有1.5个项目被黑。
「安全」作为区块链技术的基础,为人们带来是信赖感,寄托人们对区块链技术发展的美好预景。
CelesOS从发布第一期周报开始,「攻城狮」们一直与CelesOS的「安全」bug做斗争,凭借身经百战的经验,也总结出一套“对付”公链bug的专业经验。
从本周起,CelesOS安全局的张局长将不定期为大家剖析那些公链开发中的坑,并为大家展示自己完美越过这些坑的姿势。
愿人间没有bug。
请各位系好安全带,局长开车了。
随着区块链越来越热,各种业务的合约也如雨后春笋般的出现在公众的视野里,但随之而来的安全问题一次又一次的给开发者敲响的警钟。作为一个新兴的技术,其初期是会存在很多的安全问题,这是不可避免的,另一方面安全问题的暴露也会促使区块链更好的发展,最终为区块链的稳定、成熟打下良好的基础。
作为一名从业多年的程序猿亦或攻城狮,写了多少代码已无从知晓,但是我可以负责任的告诉大家,这些年来80%到90%的时间是用来处理错误和异常的逻辑,让我心身俱疲,痛不欲生。但后来我也释然了,因为我知道这辈子写出没有bug的程序是不可能了,哈哈哈…不能写出没有bug的程序不代表我们对bug就放之任之的态度,我们要不断的总结吸取经验,让bug越来越少。下面我们就来分析一下,合约中数据溢出的问题,希望对大家有所帮助。
来先看一段简单的代码:
int main(int argc, const char * argv[]) {
int first = 2;
int second = 8;
int result = first * second;
std::cout << result << std::endl;
return 0;
}
这几句代码很简单,也没有什么节外生枝的事情发生。我们稍加修改一下看看:
int main(int argc, const char * argv[]) {
int first;
int second;
std::cin >> first;
std::cin >> second;
int result = first * second;
std::cout << result << std::endl;
return 0;
}
int result = first * second;这句就要考虑到溢出的问题了,INT_MAX = 2^31-1,INT_MIN= -2^31。所以这里必须增加判断,处理溢出的问题。
那么我们这样处理应该就可以了吧?
int main(int argc, const char * argv[]) {
int first;
int second;
std::cin >> first;
std::cin >> second;
int result = first * second;
eosio_assert(result >= INT_MIN,"multiplication underflow");
eosio_assert(result <= INT_MAX,"multiplication overflow");
std::cout << result << std::endl;
return 0;
}
很不幸,这样仍然无法达到我们的目的!因为如果有溢出也会在[INT_MAX,INT_MIN]这个范围内的。
好吧,那我们在来看看是否有其它方法处理溢出的问题呢?
int main(int argc, const char * argv[]) {
int first;
int second;
std::cin >> first;
std::cin >> second;
eosio_assert(second == 0 || (first * second)/second == first,"multiplication overflow or underflow");
int result = first * second;
std::cout << result << std::endl;
return 0;
}
这样写就如何呢,点击运行输入数值,嗯,这就是我们想要的!
但是如果是EOS的合约,那么这样写还是有问题的!为什么??别急,下面来解释这样写的问题。
因为eos合约先要翻译成wasm再去执行,而c++目前的代码是用clang编译的,默认采用O3的优化级别,那么eosio_assert(second == 0 || (first * second)/second ==first,"multiplication overflow or underflow”);翻译过来就是,(call $eosio_assert (i32.const 1) (i32.const 224)),也就是这句话一直是真,所以没有起到判断的结果。把优化级别修改成O0的话,这句话就没有任何问题了。
来看看EOS源码中早期的溢出问题和解决方案的代码。
asset& operator*=( int64_t a ) {
eosio_assert( a == 0 || (amount * a) / a == amount, "multiplication overflow or underflow" );
eosio_assert( -max_amount <= amount, "multiplication underflow" );
eosio_assert( amount <= max_amount, "multiplication overflow" );
amount *= a;
return *this;
}
很显然存在上述的错误,下来再看看修改后的代码。
asset& operator*=( int64_t a ) {
int128_t tmp = (int128_t)amount * (int128_t)a;
eosio_assert( tmp <= max_amount, "multiplication overflow" );
eosio_assert( tmp >= -max_amount, "multiplication underflow" );
amount = (int64_t)tmp;
return *this;
}
这里采用了int128_t作为辅助判断int64_t是否溢出。那么如果没有更大的数据类型,怎么才能更好的处理这种问题呢?看看下面的代码是否能解决我们的问题呢?
asset& operator*=( int64_t a ) {
if(a != 0){
int64_t temp = (amount * a)/a;
eosio_assert(temp == amount, "multiplication overflow or underflow" );
}
amount *= a;
return *this;
}
▲注:这段代码没有实际测试过
在合约中使用运算符操作的时候最好用一些系统提供的类型(虽然系统有时候也不是那么靠谱)比如这里的asset。
在编码中要格外细心,对一些模棱两可的问题要大胆猜想,小心求证,宁可错杀一千不可放过一个。
最后希望这篇文章对大家有所帮助。
联系我们
Website
https://www.celeschain.io/
Telegram Group
https://t.me/celeschain
https://twitter.com/CelesChain
https://www.reddit.com/user/CelesChain
Medium
https://medium.com/@CelesChain
https://www.facebook.com/celeschain
Youtube
https://www.youtube.com/channel/UC1Xsd8wU957D-R8RQVZPfGA
商业白皮书下载
https://dn-release.qbox.me/pdf/Celes-Chain-Business-Plan_en.pdf?v=1531279736435
技术白皮书下载
https://dn-release.qbox.me/pdf/Celes-Chain-WhitePaper_en.pdf?v=1531278564014
网友评论