Еще в марте, когда я работал над новым 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 без коллизий!

Смотрите также