7 8 Ноября Parity объявила о критической ошибке в коде, отвечающим за работу их multi-sig кошелька в основе которого лежит общедоступный смарт-котракт. Это уже второй подобный случай, первый произошел 19/07/2017, когда из-за найденной уязвимости смарт-контракта были выведены 150000 ETH (Подробнее на Хабре).

Попробуем описать в чем заключалась проблема и почему стали недоступны около $300 млн.

Что такое multi-sig кошельки
Multi-sig(nature) кошельки созданы для решения проблемы повышения защиты приватного ключа, так как для совершения транзакции необходимы 1 и более приватных ключей (в общем виде M из N ключей). Пример для случая 2 из 3 ключей: один ключ хранится локально, один в облаке, и еще 1 резервный (в укромном месте). Для совершения транзакции нужны 2 ключа. Чуть подробнее можно почитать здесь

Пользователь GitHub devops199, экспериентируя с публичными методами общедоступных смарт-контрактов вызвал метод kill смарт-контракта (библиотека Parity Wallet). Issue #6995 на GitHub.
В результате этого 584 кошелька на общую сумму около 1 миллиона ETH ($300 миллионов по текущему курсу) стали «заморожены», то есть пропала возможность перевести с них деньги.

Картинка с комментариями devops199
image

Технически говоря, сперва он вызвал метод инициализации контракта initWallet

  function initWallet(address[] _owners, uint _required, uint _daylimit) only_uninitialized {
    initDaylimit(_daylimit);
    initMultiowned(_owners, _required);
  }

Модификатор only_uninitialized описан так

  // throw unless the contract is not yet initialized.
  modifier only_uninitialized { if (m_numOwners > 0) throw; _; }

Однако при размещении контракта список владельцев не был инициализован, и переменная m_numOwners была равна 0. Историю всех операций с контрактом можно посмотреть здесь

В результате вызов initWallet позволил devops199 стать владельцем контракта (при вызове initWallet по адресу смарт-контракта библиотеки, она превратилась в обычный кошелек с владельцем msg.sender, то есть devops199).

Далее можно просто выполнить kill для удаления, что и было сделано:

  // kills the contract sending everything to `_to`.
  function kill(address _to) onlymanyowners(sha3(msg.data)) external {
    suicide(_to);
  }

Операция suicide в EVM используется для уничтожения контракта и переводит весь оставшийся баланс на указанный адрес. При этом suicide эффективнее операции send и стоит отрицательное значение газа так как освобождает место от данных контракта в блокчейне.

Так как вся логика работы multi-sig кошелька была зависима от контракта, все владельцы, создавшие кошелек после 20/07/2017 (а это фактически все их пользователи, так как до 20 июля была другая уязвимость) потеряли доступ к возможности делать переводы своих средств кошельков Parity Wallet. Исправить ошибку можно лишь очередным форком Ethereum-a, но этого конечно не будет.

Полагаю, что проблему можно было бы легко избежать, если бы Parity убрала функцию kill при публикации смарт-контракта библиотеки. Она полезна лишь на момент тестирования, но никак не в целевой системе.

Таким вот образом закончилась история кошельков Parity Wallet и $300 млн, которые больше нельзя истратить.

Не забывайте удалять код для тестирования, перед передачей в production!

Комментарии (11)


  1. sshmakov
    14.11.2017 10:35

    Вот так просто. Если в обычном банке отказала автоматизация, то люди исправят положение вручную. Если же подобное случается в блокчейне, то исправить ничего нельзя. Поэтому я не верю, что криптовалюты в обозримом будущем заменят фиатные деньги.


    1. neptune
      14.11.2017 12:01

      Можно, есть практики обновления контрактов. Другое дело это нужно предусмотреть заранее


  1. vikarti
    14.11.2017 10:52

    Почему до сих пор нет средств статического анализа кода на Solidity? (неиницилизированная переменная — ну как можно?
    Почему нет фаззеров для тестирования?


    1. itsph Автор
      14.11.2017 12:03

      > Почему до сих пор нет средств статического анализа кода на Solidity? (неиницилизированная переменная — ну как можно?
      Будут появляться, нужно время. Сначала и code-completion и подсветки синтаксиса не было. Да и функция уничтожения контракта в целом обычная практика, поможет ли тут статический анализ?


    1. quantum
      14.11.2017 12:12

      Статический анализатор тут бы не помог. Технически с кодом все хорошо. А вот с головой плохо…


  1. quantum
    14.11.2017 12:13

    Скрин сейчас приложить не могу, но один из команды парити в телеграме довольно уверенно писал, что в одном из следующих запланированных хардфорков Виталик все поправит :)

    Так что увидим весной, так ли необратимы транзакции в эфире :)


    1. Pr0Ger
      14.11.2017 12:55

      Разговоры о форке связаны вот с этим предложением Виталика, но он в текущем виде не позволяет сделать такой вывод средств.
      Ну и кстати можно без отмены той транзакциии все починить, разрешив тому кто создал контракт делать на нем suicide всегда, но это еще более некрасивый вариант


  1. sshmakov
    14.11.2017 12:25

    Полагаю, что проблему можно было бы легко избежать, если бы Parity убрала функцию kill при публикации смарт-контракта библиотеки.

    Полагаю, что проблема случилась несколько раньше, чем вызов kill — юзер не должен был стать владельцем контракта, что бы он не вызывал.
    В результате вызов initWallet позволил devops199 стать владельцем контракта (при вызове initWallet по адресу смарт-контракта библиотеки, она превратилась в обычный кошелек с владельцем msg.sender, то есть devops199).


    1. Pr0Ger
      14.11.2017 12:57

      Проблема случилась еще раньше, когда разработчики parity решили разнести нормальный код multisig кошелька на два контракта тупой копипастой породив сначала баг что initWallet был публичен и доступен для вызова неограниченное число раз, а теперь и вот этот.


      1. quantum
        14.11.2017 14:16

        Так инит же был доступен для вызова только 1 раз. Они просто забыли его вызвать


        1. Pr0Ger
          14.11.2017 15:41

          Я говорил про их летний баг, когда можно было просто любой чужой кошелек повторно переинициализоровать добавив себя во владельцы