Cloudflare поймала редкий баг в компиляторе Go для arm64: падения при разворачивании стека
Новости
Cloudflare нашла редкий баг в Go/arm64: падения из-за гонки при разворачивании стека — фикс в Go 1.24.6/1.23.12/1.25
88 открытий1К показов
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(splitADD), после чего 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/
Что делать инженерам прямо сейчас
- Обновиться до: Go 1.25.x или минимум до 1.24.6 / 1.23.12 (если «зажаты» веткой).
- Пересобрать сервисы, критичные по доступности, и раскатить обновления в первую очередь на arm64-инфраструктуру.
- Если апгрейд временно невозможен — минимизируйте большие стек-фреймы (вынос больших буферов в heap), снизьте вероятность попадания preemption в эпилог и проверьте зависимости на предмет собственного asm/
unsafe-кода. - Отслеживайте релизы 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К показов



