{
  "description": "Browse aymeric's, an independent, searchable archive of readable snapshots from followed feeds, preserved in Git with aggr.",
  "feed_url": "https://aggr.aymericbeaumet.com/sources/seriot.ch/feed.json",
  "home_page_url": "https://aggr.aymericbeaumet.com/sources/seriot.ch/",
  "items": [
    {
      "authors": [
        {
          "name": "seriot.ch via kytta"
        }
      ],
      "content_html": "<h3>Nicolas Seriot</h3>\n<h4><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://seriot.ch/computation/index.html\">Computation</a> &gt; How curl was accidentally Turing-complete for nine months</h4>\n<p><em>29th October 2026</em></p>\n<p>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.</p>\n<p>Curl 8.17.0 added a five-level include cap that breaks this reduction.</p>\n<p><strong>Update 2026-10-30</strong>: Jonathan Tremesaygues wrote me: <em>I couldn't help but implement “99 Bottles of Beer on the Wall”</em> <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://seriot.ch/resources/99_bottles.tar.gz\">99_bottles.tar.gz</a></p>\n<h3>Minsky 2-Counter Reduction</h3>\n<p>A counter value <code>n</code> is represented by <code>n</code> copies of <code>x</code>.</p>\n<p>Each instruction state uses one or more config files, with file names acting as labels, and <code>expand-config</code> selecting the next file.</p>\n<p>Syntax:</p>\n<ul>\n<li><code>{{b}}</code> expands the current value</li>\n<li><code>[0-0]</code> keeps only the first byte (<code>x</code> or empty)</li>\n<li><code>[1-]</code> keeps everything except the first byte</li>\n<li>empty slices yield empty strings (<a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://github.com/curl/curl/blob/curl-8_12_0/docs/cmdline-opts/variable.md\">doc</a>)</li>\n</ul>\n<table>\n<thead>\n<tr>\n<th>Minsky</th>\n<th>Curl config</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>Register <code>R</code></td>\n<td>Variable <code>r</code> (<code>R</code> is the number of <code>x</code> bytes)</td>\n</tr>\n<tr>\n<td><code>INC(R)</code></td>\n<td><code>expand-variable = &quot;r={{r}}x&quot;</code></td>\n</tr>\n<tr>\n<td><code>DEC(R)</code></td>\n<td><code>expand-variable = &quot;r[1-]={{r}}&quot;</code></td>\n</tr>\n<tr>\n<td><code>JZ(R, _, _x)</code></td>\n<td><code>expand-variable = &quot;t[0-0]={{r}}&quot;</code> <code>expand-config = &quot;state_{{t}}.conf&quot;</code></td>\n</tr>\n<tr>\n<td><code>JMP(S)</code></td>\n<td><code>expand-config = &quot;S.conf&quot;</code></td>\n</tr>\n<tr>\n<td>Halt</td>\n<td>No further <code>expand-config</code></td>\n</tr>\n</tbody>\n</table>\n<p>With that, we can compile any two-counter program into curl config files.</p>\n<h3>PoC: Adding 2 + 3</h3>\n<p>Four files in <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://seriot.ch/resources/curl_addition.zip\">curl_addition.zip</a> compute 2 + 3.</p>\n<p><picture class=\"article-picture article-picture-fallback\" style=\"--image-ratio:16 / 9\"><img src=\"https://seriot.ch/resources/curl_config.png\" alt=\"\" loading=\"eager\" decoding=\"async\" fetchpriority=\"high\" referrerpolicy=\"no-referrer\" class=\"progressive-image\"></picture></p>\n<p>Build a local curl 8.12.0 binary if needed:</p>\n<pre><code><span class=\"code-snippet\" data-language=\"Shell\"><span class=\"syntax-source syntax-shell syntax-bash\"><span class=\"syntax-meta syntax-function-call syntax-shell\"><span class=\"syntax-variable syntax-function syntax-shell\">mkdir</span></span><span class=\"syntax-meta syntax-function-call syntax-arguments syntax-shell\"><span class=\"syntax-variable syntax-parameter syntax-option syntax-shell\"><span class=\"syntax-punctuation syntax-definition syntax-parameter syntax-shell\"> -</span>p</span> /tmp/curl-tc-build</span>\n<span class=\"syntax-meta syntax-function-call syntax-shell\"><span class=\"syntax-support syntax-function syntax-cd syntax-shell\">cd</span></span><span class=\"syntax-meta syntax-function-call syntax-arguments syntax-shell\"> /tmp/curl-tc-build</span>\n<span class=\"syntax-meta syntax-function-call syntax-shell\"><span class=\"syntax-variable syntax-function syntax-shell\">curl</span></span><span class=\"syntax-meta syntax-function-call syntax-arguments syntax-shell\"><span class=\"syntax-variable syntax-parameter syntax-option syntax-shell\"><span class=\"syntax-punctuation syntax-definition syntax-parameter syntax-shell\"> -</span>fsSLO</span> https://curl.se/download/curl-8.12.0.tar.gz</span>\n<span class=\"syntax-meta syntax-function-call syntax-shell\"><span class=\"syntax-variable syntax-function syntax-shell\">tar</span></span><span class=\"syntax-meta syntax-function-call syntax-arguments syntax-shell\"><span class=\"syntax-variable syntax-parameter syntax-option syntax-shell\"><span class=\"syntax-punctuation syntax-definition syntax-parameter syntax-shell\"> -</span>xzf</span> curl-8.12.0.tar.gz</span>\n<span class=\"syntax-meta syntax-function-call syntax-shell\"><span class=\"syntax-support syntax-function syntax-cd syntax-shell\">cd</span></span><span class=\"syntax-meta syntax-function-call syntax-arguments syntax-shell\"> curl-8.12.0</span>\n<span class=\"syntax-meta syntax-function-call syntax-shell\"><span class=\"syntax-variable syntax-function syntax-shell\">./configure</span></span><span class=\"syntax-meta syntax-function-call syntax-arguments syntax-shell\"><span class=\"syntax-variable syntax-parameter syntax-option syntax-shell\"><span class=\"syntax-punctuation syntax-definition syntax-parameter syntax-shell\"> --</span>without-ssl</span><span class=\"syntax-variable syntax-parameter syntax-option syntax-shell\"><span class=\"syntax-punctuation syntax-definition syntax-parameter syntax-shell\"> --</span>disable-shared</span><span class=\"syntax-variable syntax-parameter syntax-option syntax-shell\"><span class=\"syntax-punctuation syntax-definition syntax-parameter syntax-shell\"> --</span>disable-manual</span><span class=\"syntax-variable syntax-parameter syntax-option syntax-shell\"><span class=\"syntax-punctuation syntax-definition syntax-parameter syntax-shell\"> --</span>disable-docs</span></span>\n<span class=\"syntax-meta syntax-function-call syntax-shell\"><span class=\"syntax-variable syntax-function syntax-shell\">make</span></span><span class=\"syntax-meta syntax-function-call syntax-arguments syntax-shell\"><span class=\"syntax-variable syntax-parameter syntax-option syntax-shell\"><span class=\"syntax-punctuation syntax-definition syntax-parameter syntax-shell\"> -</span>j4</span></span>\n</span></span></code></pre>\n<p>Then, from the <code>addition/</code> directory, run:</p>\n<pre><code><span class=\"code-snippet\">/tmp/curl-tc-build/curl-8.12.0/src/curl -q -s -K main.conf\n</span></code></pre>\n<p>Expected output:</p>\n<pre><code><span class=\"code-snippet\">A=xxxxx B=\n</span></code></pre>\n<h3>Runtime Bounds</h3>\n<p>The reduction assumes unbounded counters and unbounded steps. The real binary has neither:</p>\n<ul>\n<li><strong>Expanded content (curl's own limit).</strong> Each expansion is capped at <code>MAX_EXPAND_CONTENT</code> = 10,000,000 bytes (<a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://github.com/curl/curl/blob/curl-8_12_0/src/var.c#L42\"><code>src/var.c</code></a>).</li>\n<li><strong>Step count.</strong> There is no loop: each step is a nested <code>--config</code> 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.</li>\n<li><strong>Retained variable versions.</strong> Reassignment <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://github.com/curl/curl/blob/curl-8_12_0/src/var.c#L347-L370\">adds a new entry</a> while retaining the old one until <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://github.com/curl/curl/blob/curl-8_12_0/src/var.c#L58-L78\">cleanup</a>. Memory use and some variable lookups grow with the number of steps.</li>\n</ul>\n<h3>History and Version Support</h3>\n<ul>\n<li>curl 8.3 (2023-09): Variables expansion</li>\n<li>curl 8.12 (2025-02): Byte-range assignment</li>\n<li>curl 8.17 (2025-11): <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://github.com/curl/curl/blob/curl-8_17_0/src/tool_parsecfg.h#L28-L30\">five-level cap</a> defeats the reduction</li>\n</ul>\n<p>The <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://github.com/curl/curl/pull/19168\">pull request</a> says the cap prevents a config file from including itself indefinitely. Its <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://github.com/curl/curl/blob/curl-8_17_0/tests/data/test774\">regression test</a> does exactly that and expects an error. The public explanation does not discuss Turing completeness or why the cap is five.</p>\n<p>A Bash <code>while</code> 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.</p>\n<h3>Security impact</h3>\n<p>None.</p>\n",
      "date_modified": "2026-10-30T07:24:01+00:00",
      "date_published": "2026-10-30T07:24:01+00:00",
      "external_url": "https://seriot.ch/computation/curl.html",
      "id": "urn:aggr:item:9731107b0848ba5ad88591a5167bdc86a8f11a55",
      "image": "https://seriot.ch/resources/curl_config.png",
      "summary": "Comments",
      "tags": [
        "news",
        "compsci"
      ],
      "title": "How curl was accidentally Turing-complete for nine months",
      "url": "https://aggr.aymericbeaumet.com/items/lobste-rs/2026-10-30-how-curl-was-accidentally-turing-complete-for-nine-months/"
    }
  ],
  "language": "en",
  "title": "seriot.ch",
  "version": "https://jsonfeed.org/version/1.1"
}