<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
  <title>seriot.ch</title>
  <subtitle>Browse aymeric&#39;s, an independent, searchable archive of readable snapshots from followed feeds, preserved in Git with aggr.</subtitle>
  <id>https://aggr.aymericbeaumet.com/sources/seriot.ch/</id>
  <link rel="alternate" type="text/html" href="https://aggr.aymericbeaumet.com/sources/seriot.ch/"/>
  <link rel="self" type="application/atom+xml" href="https://aggr.aymericbeaumet.com/sources/seriot.ch/atom.xml"/>
  <updated>2026-10-30T07:24:01+00:00</updated>
  <author>
    <name>aymeric&#39;s</name>
  </author>
  <generator version="2.0.0" uri="https://github.com/aymericbeaumet/aggr">aggr</generator>
  <entry>
    <id>urn:aggr:item:9731107b0848ba5ad88591a5167bdc86a8f11a55</id>
    <title type="text">How curl was accidentally Turing-complete for nine months</title>
    <link rel="alternate" type="text/html" href="https://aggr.aymericbeaumet.com/items/lobste-rs/2026-10-30-how-curl-was-accidentally-turing-complete-for-nine-months/"/>
    <link rel="via" type="text/html" href="https://seriot.ch/computation/curl.html"/>
    <published>2026-10-30T07:24:01+00:00</published>
    <updated>2026-10-30T07:24:01+00:00</updated>
    <author>
      <name>seriot.ch via kytta</name>
    </author>
    <category term="news"/>
    <category term="compsci"/>
    <source>
      <title>Lobsters: Top Stories of the Past Week</title>
    </source>
    <summary type="text">Comments</summary>
    <content type="html">&lt;h3&gt;Nicolas Seriot&lt;/h3&gt;
&lt;h4&gt;&lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://seriot.ch/computation/index.html&quot;&gt;Computation&lt;/a&gt; &amp;gt; How curl was accidentally Turing-complete for nine months&lt;/h4&gt;
&lt;p&gt;&lt;em&gt;29th October 2026&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;From curl 8.12 through 8.16, config files could slice variable values and include other config files without an explicit depth cap. Together, these features let curl simulate any two-counter Minsky program. Under the usual idealization of unbounded strings and resources, this construction was Turing-complete.&lt;/p&gt;
&lt;p&gt;Curl 8.17.0 added a five-level include cap that breaks this reduction.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Update 2026-10-30&lt;/strong&gt;: Jonathan Tremesaygues wrote me: &lt;em&gt;I couldn&#39;t help but implement “99 Bottles of Beer on the Wall”&lt;/em&gt; &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://seriot.ch/resources/99_bottles.tar.gz&quot;&gt;99_bottles.tar.gz&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;Minsky 2-Counter Reduction&lt;/h3&gt;
&lt;p&gt;A counter value &lt;code&gt;n&lt;/code&gt; is represented by &lt;code&gt;n&lt;/code&gt; copies of &lt;code&gt;x&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Each instruction state uses one or more config files, with file names acting as labels, and &lt;code&gt;expand-config&lt;/code&gt; selecting the next file.&lt;/p&gt;
&lt;p&gt;Syntax:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;{{b}}&lt;/code&gt; expands the current value&lt;/li&gt;
&lt;li&gt;&lt;code&gt;[0-0]&lt;/code&gt; keeps only the first byte (&lt;code&gt;x&lt;/code&gt; or empty)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;[1-]&lt;/code&gt; keeps everything except the first byte&lt;/li&gt;
&lt;li&gt;empty slices yield empty strings (&lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://github.com/curl/curl/blob/curl-8_12_0/docs/cmdline-opts/variable.md&quot;&gt;doc&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Minsky&lt;/th&gt;
&lt;th&gt;Curl config&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Register &lt;code&gt;R&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Variable &lt;code&gt;r&lt;/code&gt; (&lt;code&gt;R&lt;/code&gt; is the number of &lt;code&gt;x&lt;/code&gt; bytes)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;INC(R)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;expand-variable = &amp;quot;r={{r}}x&amp;quot;&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;DEC(R)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;expand-variable = &amp;quot;r[1-]={{r}}&amp;quot;&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;JZ(R, _, _x)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;expand-variable = &amp;quot;t[0-0]={{r}}&amp;quot;&lt;/code&gt; &lt;code&gt;expand-config = &amp;quot;state_{{t}}.conf&amp;quot;&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;JMP(S)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;expand-config = &amp;quot;S.conf&amp;quot;&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Halt&lt;/td&gt;
&lt;td&gt;No further &lt;code&gt;expand-config&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;With that, we can compile any two-counter program into curl config files.&lt;/p&gt;
&lt;h3&gt;PoC: Adding 2 + 3&lt;/h3&gt;
&lt;p&gt;Four files in &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://seriot.ch/resources/curl_addition.zip&quot;&gt;curl_addition.zip&lt;/a&gt; compute 2 + 3.&lt;/p&gt;
&lt;p&gt;&lt;picture class=&quot;article-picture article-picture-fallback&quot; style=&quot;--image-ratio:16 / 9&quot;&gt;&lt;img src=&quot;https://seriot.ch/resources/curl_config.png&quot; alt=&quot;&quot; loading=&quot;eager&quot; decoding=&quot;async&quot; fetchpriority=&quot;high&quot; referrerpolicy=&quot;no-referrer&quot; class=&quot;progressive-image&quot;&gt;&lt;/picture&gt;&lt;/p&gt;
&lt;p&gt;Build a local curl 8.12.0 binary if needed:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span class=&quot;code-snippet&quot; data-language=&quot;Shell&quot;&gt;&lt;span class=&quot;syntax-source syntax-shell syntax-bash&quot;&gt;&lt;span class=&quot;syntax-meta syntax-function-call syntax-shell&quot;&gt;&lt;span class=&quot;syntax-variable syntax-function syntax-shell&quot;&gt;mkdir&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;syntax-meta syntax-function-call syntax-arguments syntax-shell&quot;&gt;&lt;span class=&quot;syntax-variable syntax-parameter syntax-option syntax-shell&quot;&gt;&lt;span class=&quot;syntax-punctuation syntax-definition syntax-parameter syntax-shell&quot;&gt; -&lt;/span&gt;p&lt;/span&gt; /tmp/curl-tc-build&lt;/span&gt;
&lt;span class=&quot;syntax-meta syntax-function-call syntax-shell&quot;&gt;&lt;span class=&quot;syntax-support syntax-function syntax-cd syntax-shell&quot;&gt;cd&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;syntax-meta syntax-function-call syntax-arguments syntax-shell&quot;&gt; /tmp/curl-tc-build&lt;/span&gt;
&lt;span class=&quot;syntax-meta syntax-function-call syntax-shell&quot;&gt;&lt;span class=&quot;syntax-variable syntax-function syntax-shell&quot;&gt;curl&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;syntax-meta syntax-function-call syntax-arguments syntax-shell&quot;&gt;&lt;span class=&quot;syntax-variable syntax-parameter syntax-option syntax-shell&quot;&gt;&lt;span class=&quot;syntax-punctuation syntax-definition syntax-parameter syntax-shell&quot;&gt; -&lt;/span&gt;fsSLO&lt;/span&gt; https://curl.se/download/curl-8.12.0.tar.gz&lt;/span&gt;
&lt;span class=&quot;syntax-meta syntax-function-call syntax-shell&quot;&gt;&lt;span class=&quot;syntax-variable syntax-function syntax-shell&quot;&gt;tar&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;syntax-meta syntax-function-call syntax-arguments syntax-shell&quot;&gt;&lt;span class=&quot;syntax-variable syntax-parameter syntax-option syntax-shell&quot;&gt;&lt;span class=&quot;syntax-punctuation syntax-definition syntax-parameter syntax-shell&quot;&gt; -&lt;/span&gt;xzf&lt;/span&gt; curl-8.12.0.tar.gz&lt;/span&gt;
&lt;span class=&quot;syntax-meta syntax-function-call syntax-shell&quot;&gt;&lt;span class=&quot;syntax-support syntax-function syntax-cd syntax-shell&quot;&gt;cd&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;syntax-meta syntax-function-call syntax-arguments syntax-shell&quot;&gt; curl-8.12.0&lt;/span&gt;
&lt;span class=&quot;syntax-meta syntax-function-call syntax-shell&quot;&gt;&lt;span class=&quot;syntax-variable syntax-function syntax-shell&quot;&gt;./configure&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;syntax-meta syntax-function-call syntax-arguments syntax-shell&quot;&gt;&lt;span class=&quot;syntax-variable syntax-parameter syntax-option syntax-shell&quot;&gt;&lt;span class=&quot;syntax-punctuation syntax-definition syntax-parameter syntax-shell&quot;&gt; --&lt;/span&gt;without-ssl&lt;/span&gt;&lt;span class=&quot;syntax-variable syntax-parameter syntax-option syntax-shell&quot;&gt;&lt;span class=&quot;syntax-punctuation syntax-definition syntax-parameter syntax-shell&quot;&gt; --&lt;/span&gt;disable-shared&lt;/span&gt;&lt;span class=&quot;syntax-variable syntax-parameter syntax-option syntax-shell&quot;&gt;&lt;span class=&quot;syntax-punctuation syntax-definition syntax-parameter syntax-shell&quot;&gt; --&lt;/span&gt;disable-manual&lt;/span&gt;&lt;span class=&quot;syntax-variable syntax-parameter syntax-option syntax-shell&quot;&gt;&lt;span class=&quot;syntax-punctuation syntax-definition syntax-parameter syntax-shell&quot;&gt; --&lt;/span&gt;disable-docs&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;syntax-meta syntax-function-call syntax-shell&quot;&gt;&lt;span class=&quot;syntax-variable syntax-function syntax-shell&quot;&gt;make&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;syntax-meta syntax-function-call syntax-arguments syntax-shell&quot;&gt;&lt;span class=&quot;syntax-variable syntax-parameter syntax-option syntax-shell&quot;&gt;&lt;span class=&quot;syntax-punctuation syntax-definition syntax-parameter syntax-shell&quot;&gt; -&lt;/span&gt;j4&lt;/span&gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then, from the &lt;code&gt;addition/&lt;/code&gt; directory, run:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span class=&quot;code-snippet&quot;&gt;/tmp/curl-tc-build/curl-8.12.0/src/curl -q -s -K main.conf
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Expected output:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span class=&quot;code-snippet&quot;&gt;A=xxxxx B=
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Runtime Bounds&lt;/h3&gt;
&lt;p&gt;The reduction assumes unbounded counters and unbounded steps. The real binary has neither:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Expanded content (curl&#39;s own limit).&lt;/strong&gt; Each expansion is capped at &lt;code&gt;MAX_EXPAND_CONTENT&lt;/code&gt; = 10,000,000 bytes (&lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://github.com/curl/curl/blob/curl-8_12_0/src/var.c#L42&quot;&gt;&lt;code&gt;src/var.c&lt;/code&gt;&lt;/a&gt;).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Step count.&lt;/strong&gt; There is no loop: each step is a nested &lt;code&gt;--config&lt;/code&gt; that returns only at halt. Every level uses a C stack frame and keeps its config file open until it finishes. In a minimal countdown loop on 8.12, an 8 MB stack allowed 922 steps before a segfault.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Retained variable versions.&lt;/strong&gt; Reassignment &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://github.com/curl/curl/blob/curl-8_12_0/src/var.c#L347-L370&quot;&gt;adds a new entry&lt;/a&gt; while retaining the old one until &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://github.com/curl/curl/blob/curl-8_12_0/src/var.c#L58-L78&quot;&gt;cleanup&lt;/a&gt;. Memory use and some variable lookups grow with the number of steps.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;History and Version Support&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;curl 8.3 (2023-09): Variables expansion&lt;/li&gt;
&lt;li&gt;curl 8.12 (2025-02): Byte-range assignment&lt;/li&gt;
&lt;li&gt;curl 8.17 (2025-11): &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://github.com/curl/curl/blob/curl-8_17_0/src/tool_parsecfg.h#L28-L30&quot;&gt;five-level cap&lt;/a&gt; defeats the reduction&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://github.com/curl/curl/pull/19168&quot;&gt;pull request&lt;/a&gt; says the cap prevents a config file from including itself indefinitely. Its &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://github.com/curl/curl/blob/curl-8_17_0/tests/data/test774&quot;&gt;regression test&lt;/a&gt; does exactly that and expects an error. The public explanation does not discuss Turing completeness or why the cap is five.&lt;/p&gt;
&lt;p&gt;A Bash &lt;code&gt;while&lt;/code&gt; loop can complement bounded curl by invoking a config once per step and carrying the counters and instruction label between invocations. The repetition then comes from Bash, so this is a composition rather than curl-alone Turing completeness.&lt;/p&gt;
&lt;h3&gt;Security impact&lt;/h3&gt;
&lt;p&gt;None.&lt;/p&gt;
</content>
  </entry>
</feed>
