Cloudflare нашли редчайший баг — прямо в компиляторе Go для ARM64

Да, это не опечатка: не рантайм, не race condition в их коде, а чистый косяк в сгенерированном машинном коде Go. И баг был настолько редким, что проявиться он мог только в инфраструктуре масштаба Cloudflare — при 84 миллионах HTTP-запросов в секунду.

На ARM64-машинах Cloudflare стали вылезать странные паники вроде traceback did not unwind completely — ошибка, указывающая на повреждённый стек при попытке раскрутки. Поначалу инженеры списали это на баг в старом коде с panic/recover, потом — на библиотеку Go Netlink. Но когда даже без неё паники продолжились, стало ясно: проблема глубже.

После недель отладки выяснилось: краш происходит при асинхронном вытестении (введённом в Go 1.14), когда рантайм прерывает горутину между двумя машинными инструкциями, корректирующими указатель стека. В этот момент стек оказывается в «разрезанном» состоянии — раскрутчик стека получает некорректный указатель и падает.

Инженеры написали минимальный Go-пример, где функция с большим стеком (>64 КБ) порождает тот самый двойной ADD. После пары минут работы программа стабильно умирала с SIGSEGV. Без сторонних библиотек. Только чистый Go.

package main

import (
	"runtime"
)

//go:noinline
func big_stack(val int) int {
	var big_buffer = make([]byte, 1 << 16)

	sum := 0
	// предотвращаем оптимизацию стека компилятором
	for i := 0; i < (1<<16); i++ {
		big_buffer[i] = byte(val)
	}
	for i := 0; i < (1<<16); i++ {
		sum ^= int(big_buffer[i])
	}
	return sum
}

func main() {
	go func() {
		for {
			runtime.GC()
		}
	}()
	for {
		_ = big_stack(1000)
	}
}

Разобравшись, они подтвердили: это ошибка в компиляторе Go, который на ARM64 разбивает корректировку стека на две инструкции, не учитывая возможность асинхронного вытеснения между ними.

Go-команда признала баг и исправила его в версиях go1.23.12, go1.24.6 и go1.25.0.

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

Источник

Русскоязычное Go сообщество

Друзья! Эту статью подготовила команда «Go for Devs» — сообщества, где мы делимся практическими кейсами, инструментами для разработчиков и свежими новостями из мира Go. Подписывайтесь, чтобы быть в курсе и ничего не упустить!

@python_leader
14.10.2025 14:55 UTC
Первоисточник

Комментарии

@Yak52
14.10.2025 09:57 UTC
+1

С подключением !

@unreal_undead2
14.10.2025 10:15 UTC
-1

По идее отдельные изменения указателя стека должны явно прописываться в .eh_frame и учитываться в процессе раскрутки. Но не знаю, как конкретно работает раскрутчик в рантайме Go.

@fenrir1121
14.10.2025 10:27 UTC
+5

Дубль https://habr.com/ru/companies/ruvds/articles/955294/
Причем урезанный в деталях

@oookkdjjjdjdj
14.10.2025 10:40 UTC
0

Редкий случай, когда виноват не рантайм и не GC, а именно кодоген. Асинхронное вытеснение попало между двумя ADD - привет, битый стек

@unreal_undead2
14.10.2025 10:50 UTC
0

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

@wl2776
14.10.2025 10:50 UTC
+3

Получается, даже компиляторы ошибаются) 

Да нуу! Не может такого быть!