Еще в марте, когда я работал над новым API для личного пет-проекта, я наткнулся на ошибку, которая заставляла меня обрабатывать терабайты исходного кода и открывать проблемы почти в сотне проектов GitHub, пытаясь сдержать ее.
Мой API использует UUID версии 4 для именования некоторых файлов, которые генерируются моим процессором. Идея состоит в том, что, поскольку UUID уникальны для всех, я могу генерировать их локально на рабочих узлах моего кластера без какой-либо синхронизации между узлами и при этом быть уверенным, что не будет никаких коллизий. Согласно Википедии: количество случайных UUID версии 4, которые необходимо сгенерировать, чтобы иметь 50% вероятность хотя бы одного столкновения, составляет 2,71 квинтиллиона. Поскольку я генерирую несколько сотен UUID в день, я не ожидаю каких-либо коллизий в течение первых нескольких миллионов миллиардов лет работы моего API. Идеально.
Через несколько дней, глядя на сгенерированные имена файлов, я замечаю среди файлов странные неслучайные UUID:
02524e6f-65a7-4cf5-8000-0000000000002
6e3ef1c8-0000-4000-8000-0000000000001
fa07f1e9-a8a0-427f-8103-e34e000000000
2cfa392b-71d5-4000-8000-000000000000
5f16db16-56ce-4000-8000-000000000000
bac73b37-aab3-498b-8000-000000000000
da5bd82d-4f98-4b51-8000-000000000000
eb245e52-535f-4274-8350-3a7200000000
5936f955-286e-47ac-8000-000000000000
0a14da92-0000-4000-8000-000000000000
cd2b88a5-0000-4000-8000-000000000000
Некоторые UUID даже имеют всего два байта случайности!
80730000-0000-4000-8000-000000000000
f7510000-0000-4000-8000-000000000000
Из-за того, что выглядит как серьезная ошибка или даже проблема безопасности, если UUID используются для аутентификации, мой план по отбрасыванию коллизии UUID на несколько миллиардов лет останавливается. Пришло время отследить и исправить эту ошибку.
Имея опыт работы с C, мне нравится программировать на Go, так как мне нравится никакой магии, но все же выразительный (по сравнению с C) стиль этого языка. Для генерации UUID я использовал библиотеку satori/go.uuid. В то время это была справочная библиотека для генерации UUID с более чем 2 тысячами звезд на GitHub.
После серьезной отладки с использованием проверенных временем инструментов, таких как Println и Printf, я нахожу ошибку и отправляю исправление вверх по течению, ожидая быстрого ответа от сопровождающего. Он так и не пришел.
После четырех месяцев без ответа сатори/go.uuid явно больше не поддерживается. В надежде увидеть, что мое исправление исчезнет, я решил попытаться найти проекты, которым угрожает опасность, и предупредить их о ситуации.
На GitHub есть 2,9 миллиона проектов, это должно быть легко.
BigQuery приходит на помощь
К счастью для нас, GitHub каждую неделю экспортирует содержимое своих 2,9 млн общедоступных проектов с открытым исходным кодом в общедоступный набор данных BigQuery.
Давайте перейдем на https://bigquery.cloud.google.com и начнем. работаю над этим набором данных!
Во-первых, мы создаем список всех файлов go на GitHub. Мы храним список в частном наборе данных с именем go.files
:
SELECT * FROM [bigquery-public-data:github_repos.files] WHERE RIGHT(path, 3) = '.go' Query complete (45.5s elapsed, 318 GB processed)
Теперь мы создаем набор данных с именем go.contents
, который содержит содержимое (фактический код) всех файлов go из go.files
:
SELECT * FROM [bigquery-public-data:github_repos.contents] WHERE id IN (SELECT id FROM go.files) Query complete (192.6s elapsed, 2.14 TB processed)
Следующий шаг — удалить вендорные файлы, так как они нам не интересны. Результат хранится в go.content_not_vendor
:
SELECT * FROM [go.contents] WHERE id IN (SELECT id FROM go.files WHERE NOT path CONTAINS 'vendor')
Теперь давайте выберем файлы, которые действительно импортируют github.com/satori/go.uuid
, результат сохраняется в go.uuid
:
SELECT * FROM [go.content_not_vendor] WHERE content CONTAINS “github.com/satori/go.uuid” Query complete (5.1s elapsed, 11.3 GB processed)
Теперь нам просто нужно найти файлы, которые вызывают ошибочную версию uuid.NewV4()
: прототип функции NewV4
изменился с NewV4() uuid
на NewV4() (uuid, err)
в том же коммите, где была введена наша ошибка случайности, что дает нам простой способ обнаружения проектов, использующих плохую версию, путем поиск обработки ошибок вокруг NewV4:
SELECT id FROM [go.uuid] WHERE REGEXP_MATCH(content, r’Must\(\w+\.NewV4') SELECT id FROM [go.uuid] WHERE REGEXP_MATCH(content, r'err := uuid\.NewV4')
Мы сохраняем результат в наборе данных с именем go.baduuid
Из этих идентификаторов файлов мы наконец можем получить проекты GitHub, которым они принадлежат:
SELECT repo_name FROM [go.files] WHERE id IN (SELECT id from [go.baduuid]) GROUP BY repo_name
И теперь у нас есть список проектов, импортирующих уязвимую версию сатори/go.uuid! Вы можете увидеть список 90 затронутых проектов: здесь.
Оттуда легко создать скрипт, который использует ghi для открытия задачи в каждом проекте:
#!/bin/bash while read repo; do echo “Processing $repo” ghi open “Replace package satori/go.uuid” \ -m “This issue has been opened automatically because..." — $repo || true sleep 10 # Try to avoid potential rate limiting. done <repos
Вывод
BigQuery с общедоступным набором данных GitHub — отличный инструмент для гигиены всей экосистемы с открытым исходным кодом. Используя BigQuery, я смог за час, не вставая с дивана, просмотреть терабайты кода и найти проекты, на которые повлиял satori/go.uuid#73. И теперь каждый снова может иметь UUID без коллизий!