Автор разобрал, как из двух возможностей конфигурационного формата curl складывается машина Тьюринга. Первая — синтаксис вроде [0-0] и [1-], который оставляет от значения переменной только первый байт или всё, кроме первого. Вторая — директива expand-config, подгружающая внешний файл, причём глубина вложенности ничем не ограничена.
Этого достаточно, чтобы смоделировать двухсчётчиковую регистровую машину Минского, то есть систему, эквивалентную машине Тьюринга. Счётчик представляется строкой из символов x: инкремент — конкатенация (expand-variable = "r={{r}}x"), декремент — усечение (expand-variable = "r[1-]={{r}}"), переход — подключение файла с именем метки (expand-config = "S.conf"). В качестве демонстрации автор собрал сложение чисел и программу «99 бутылок пива», которая традиционно используется при обучении циклам.
Где пределы
На практике всё упирается в ресурсы. Каждый шаг требует загрузки нового вложенного конфига, поэтому при стеке в 8 МБ переполнение наступает примерно после 922 шагов. Есть и ограничение на размер раскрываемых переменных — 10 МБ, — и заметный расход памяти, потому что старые версии переменных сохраняются.
Важно, что это не уязвимость: удалённо выполнить код через конфиг никто не может, речь о том, что формат оказался выразительнее, чем задумывалось. Тем не менее в curl 8.17 глубину вложенности ограничили пятью уровнями — в том числе чтобы исключить бесконечную рекурсию.
Почему об этом стоит знать
История хорошо показывает, как из безобидных возможностей — подключить файл, вырезать подстроку — складывается вычислительная среда, о которой никто не проектировал. Для тех, кто пишет конфигурационные форматы, вывод простой: любые «динамические» элементы и рекурсивные включения нужно ограничивать явно. Для тех, кто читает чужие конфиги в автоматике, — помнить, что разбор конфига может стоить больше, чем кажется.
The author worked out how two features of curl’s configuration format add up to a Turing machine. The first is syntax like [0-0] and [1-], which keeps only the first byte of a variable’s value or everything after it. The second is the expand-config directive, which loads an external file, with no limit on nesting depth.
That is enough to simulate a two-counter Minsky register machine — a system equivalent to a Turing machine. A counter is a string of x characters: increment is concatenation (expand-variable = "r={{r}}x"), decrement is truncation (expand-variable = "r[1-]={{r}}"), and a jump is loading a file named after the label (expand-config = "S.conf"). As a demonstration, the author built addition and the “99 bottles of beer” program traditionally used to teach loops.
Where the limits are
In practice, resources get in the way. Every step requires loading another nested config, so with an 8 MB stack the overflow arrives after roughly 922 steps. There is also a 10 MB cap on expanded variable content, and noticeable memory use because older variable versions are kept.
Crucially, this is not a vulnerability: nobody can execute code remotely through a config. The point is that the format turned out to be more expressive than intended. Even so, curl 8.17 limited nesting depth to five levels, partly to rule out infinite recursion.
Why it matters
The story shows how harmless features — include a file, slice a substring — can add up to a computing environment nobody designed. For people who write configuration formats, the lesson is to bound dynamic elements and recursive includes explicitly. For people who parse other people’s configs in automation, it is worth remembering that parsing a config can cost more than it looks.