How curl was accidentally Turing-complete for nine months

Translated by a model; the original is in Russian.

· Curiosities
From version 8.12 through 8.16, curl's config files could simulate a two-counter Minsky machine. The loophole was closed by an include-depth cap in 8.17.0, which was added for an entirely different reason.

cover.svg

curl is famous for doing nearly everything related to moving data over a network. As Nicolas Seriot found, for a while it could, at least in theory, do everything. From version 8.12 through 8.16, curl's config files formed a Turing-complete system.

Where the completeness came from

Two features combined. One let you slice a variable's value by byte range. The other let a config file include other config files with no explicit depth cap. Together they are enough to simulate any two-counter Minsky program. Under the usual idealization of unbounded strings and resources, such a machine is Turing-complete.

The construction works like this:

  • a counter value n is stored as n copies of x;
  • increment appends another x, and decrement drops the first byte with the [1-] slice;
  • a conditional jump takes the first byte with [0-0], which is either x or an empty string, and uses expand-config to load either state_.conf or state_x.conf;
  • an unconditional jump is simply including the right file, and halting means including nothing further.

File names act as labels, so any two-counter program can be "compiled" into a set of curl configs. As proof, the author provides four files that add 2 and 3. Build curl 8.12.0, run it with -K main.conf, and it prints A=xxxxx B=. No network is required.

Real-world bounds

In practice, of course, nothing was infinite. Each expansion is capped at 10,000,000 bytes, a limit built into curl itself. The construction also has no loop. Every step is a nested --config call that returns only at halt, so each step consumes a stack frame and keeps a file open. A minimal countdown loop managed 922 steps on an 8 MB stack before segfaulting. Reassigning a variable also keeps its old versions until cleanup, so memory use grows with every step.

Timeline

Variables arrived in curl 8.3 in September 2023, and byte-range assignment followed in 8.12 in February 2025. In 8.17, released in November 2025, a five-level include cap broke the reduction. According to the pull request, the cap stops a config file from including itself indefinitely, and the regression test checks exactly that. The public explanation says nothing about Turing completeness or why the limit is five.

Wrapping curl in a Bash while loop restores unbounded computation: the loop invokes a config once per step and carries the counters and the instruction label between invocations. But then Bash provides the repetition, so this is a composition of two programs rather than Turing completeness in curl alone.

The author sums up the security impact in a single word: none.


Source: Lobsters

Comments

Sign in, to comment.

  • No comments yet.