Перетяжка, Премия ТПрогер, 13.11
Перетяжка, Премия ТПрогер, 13.11
Перетяжка, Премия ТПрогер, 13.11

Cloudflare поймала редкий баг в компиляторе Go для arm64: падения при разворачивании стека

Новости

Cloudflare нашла редкий баг в Go/arm64: падения из-за гонки при разворачивании стека — фикс в Go 1.24.6/1.23.12/1.25

88 открытий1К показов
Cloudflare поймала редкий баг в компиляторе Go для arm64: падения при разворачивании стека

Cloudflare опубликовала технический разбор редкой, но критичной проблемы в экосистеме Go: из-за ошибки в генерации кода для arm64 в некоторых случаях возникала гонка на уровне одной инструкции, приводившая к крашам рантайма при разворачивании стека (stack unwinding) в runtime.(*unwinder).next.

Cloudflare связала учащение «фатальных паник» на arm64 с кодом, где выполнялась работа с Netlink, но корневая причина оказалась глубже — в связанке компилятор ↔ ассемблер Go для arm64 и в том, как разбивалось большое смещение стека на две инструкции ADD со «сдвигом на 12 бит». Если прерывание попадало между этими двумя ADD, стековый указатель оказывался «наполовину скорректирован», и трассировщик стека считывал нерелевантные данные как адрес возврата, что заканчивалось segfault/фатальной паникой. Схожие аварии ранее отмечали и другие команды, в частности Datadog: именно в runtime.(*unwinder).next на Linux/arm64 (Go 1.23.x/1.24.x).

Что именно ломалось

  • Сценарий сбоя: асинхронная предвыборка (preemption) попадает между двумя инструкциями корректировки SP (split ADD), после чего GC/трассировка начинают разворачивать некорректный фрейм, обращаясь по невалидному SP. Итог — падение в (*unwinder).next.
  • Подтверждение на практике: сходные трассы стеков и «рандомные» крэши на arm64 фиксировались у сторонних команд; проблема оформлена в ишью #73259 в репозитории Go и была признана кандидатом на бэкпорт.

Как починили (и какие версии содержат фикс)

Исправление прошло через бэкпорт и доступно в поддерживаемых ветках Go:

  • Go 1.24.6 — явное упоминание бэкпорта по ишью #73259.
  • Go 1.23.12 — минор с правками рантайма (включая бэкпорт по связанным arm64-сбоям). 
  • Go 1.25.0 и новее — фиксация на мажорной ветке.

Суть изменения: вместо двух поочерёдных ADD для большого смещения теперь собирают оффсет в временный регистр и выполняют одну атомарную корректировку SP.

Это исключает «окно» между ADD-инструкциями и делает разворачивание стека предсказуемым даже при async preemption. (Подробные обсуждения и сопутствующие arm64-проблемы рантайма см. также в #63830, #65449, #73413.)

Кому это важно

  • Все сервисы Go на arm64, где возможны большие стековые фреймы и активна асинхронная предвыборка (по умолчанию с Go 1.14+). В условиях продакшн-нагрузок редкая гонка становится статистически вероятной. Читайте подробнее: https://unskilled.blog/posts/preemption-in-go-an-introduction/ 

Что делать инженерам прямо сейчас

  1. Обновиться до: Go 1.25.x или минимум до 1.24.6 / 1.23.12 (если «зажаты» веткой).
  2. Пересобрать сервисы, критичные по доступности, и раскатить обновления в первую очередь на arm64-инфраструктуру.
  3. Если апгрейд временно невозможен — минимизируйте большие стек-фреймы (вынос больших буферов в heap), снизьте вероятность попадания preemption в эпилог и проверьте зависимости на предмет собственного asm/unsafe-кода.
  4. Отслеживайте релизы Go и связанные тикеты об unwinding/arm64 (см. #73259 и бекпорт #74694).

Источники и материалы:

  • Обсуждение с примерами падений: runtime: segfaults in runtime.(*unwinder).next (GitHub, #73259).  https://github.com/golang/go/issues/73259 
  • Бэкпорт фикса в ветку 1.24: (GitHub, #74694) — релиз Go 1.24.6. https://github.com/golang/go/issues/74694 
  • История релизов, минор Go 1.23.12 с правками рантайма. https://go.dev/doc/devel/release 
  • Release notes Go 1.25 (фикс присутствует в мажорной ветке).  https://tip.golang.org/doc/go1.25 
  • Ранние разборы проблем разворачивания стека/прерываний на arm64 https://github.com/golang/go/issues/63830 
Следите за новыми постами
Следите за новыми постами по любимым темам
88 открытий1К показов