<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Ben Kettle</title>
    <link>https://benkettle.xyz/</link>
    <description>Recent content on Ben Kettle</description>
    <generator>Hugo</generator>
    <language>en-us</language>
    <lastBuildDate>Tue, 25 Aug 2026 04:33:10 +0000</lastBuildDate>
    <atom:link href="https://benkettle.xyz/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>Making Semgrep rip: how ripgrep inspired us to shave hours off scans</title>
      <link>https://benkettle.xyz/posts/making-semgrep-rip/</link>
      <pubDate>Wed, 10 Jun 2026 00:00:00 +0000</pubDate>
      <guid>https://benkettle.xyz/posts/making-semgrep-rip/</guid>
      <description></description>
    </item>
    <item>
      <title>Photos from Darktable to my blog in one click</title>
      <link>https://benkettle.xyz/posts/darktable-to-website/</link>
      <pubDate>Tue, 17 Mar 2026 00:00:00 +0000</pubDate>
      <guid>https://benkettle.xyz/posts/darktable-to-website/</guid>
      <description>&lt;p&gt;I fixed the graphics drivers on my laptop and Darktable&amp;mdash;for the unaware, an extremely solid OSS photo editing software&amp;mdash;went from essentially unusable to decently fast. After years of anticipation, I finally plugged my big hard drive into my little server and set up a network share (and backups!). I consolidated all my various photo libraries that were previously strewn across four portable HDDs and SSDs in various degrees of duplication into a single library on said big hard drive. And I even bought a new camera that online hype claimed would make it so I didn&amp;rsquo;t have to edit pictures anymore (then I felt bad about how much it cost, so returned it and bought a different one that was half the price and fit my needs much better).&lt;/p&gt;&#xA;&lt;p&gt;These all helped me get out of my several-year drought where I occasionally decided to drag my camera around and took some photos, but rarely actually looked at the results. I&amp;rsquo;ve come to quite like Darktable now that it&amp;rsquo;s usable on my laptop, especially after spending some time learning about tone mapping and color theory and following a few tutorials. I&amp;rsquo;ve been carrying my new small camera around way more often and taking photos more frequently, but also more intentionally, as a result. I&amp;rsquo;ve produced some photos that I&amp;rsquo;m pretty happy with in the last couple months. I&amp;rsquo;ve wanted to post these photos on my website, but that so far has meant:&lt;/p&gt;&#xA;&lt;ol&gt;&#xA;&lt;li&gt;Exporting the photo from Darktable into a folder somewhere&lt;/li&gt;&#xA;&lt;li&gt;Making a new folder in my website&amp;rsquo;s &lt;code&gt;content/photos&lt;/code&gt; directory&lt;/li&gt;&#xA;&lt;li&gt;Copying the exported image file into that folder&lt;/li&gt;&#xA;&lt;li&gt;Copying an &lt;code&gt;index.md&lt;/code&gt; from a previous photo post into the folder&lt;/li&gt;&#xA;&lt;li&gt;Updating the date and caption for the new photo and copying the filename into the &lt;code&gt;featured_image&lt;/code&gt; field&lt;/li&gt;&#xA;&lt;li&gt;Commit and push with git to trigger the auto-build-and-deploy that happens on each push.&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;p&gt;This is tedious enough that I so far have rarely posted photos to my website.&lt;/p&gt;&#xA;&lt;p&gt;Luckily, though, Darktable provides a nice solution. Darktable exposes an API to Lua and allows integrating Lua scripts into the UI and, especially important for my purposes, it allows &lt;a href=&#34;https://docs.darktable.org/usermanual/4.0/en/lua/exporting-images/&#34;&gt;defining custom export destinations&lt;/a&gt; with these Lua scripts. So I set up a simple automation that combines a bash script in my website&amp;rsquo;s directory with a simple Lua exporter.&lt;/p&gt;&#xA;&lt;p&gt;The bash script takes a single JPEG filename and produces a Hugo post from it (I use &lt;a href=&#34;https://github.com/casey/just&#34;&gt;Just&lt;/a&gt; for this repo, but this could also just be a bash script). It reads the date and caption from the image EXIF data, but sets &lt;code&gt;pubDate&lt;/code&gt; to the current date since I often edit old photos and still want these to show up when I publish them.&lt;/p&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;# Create a photo post from an image file, reading EXIF data for the date and description.&#xA;# Usage: just photo-from-file /path/to/photo.jpg&#xA;photo-from-file filepath:&#xA;    #!/usr/bin/env bash&#xA;    set -euo pipefail&#xA;&#xA;    filepath=$(cd &amp;#34;{{invocation_directory()}}&amp;#34; &amp;amp;&amp;amp; realpath &amp;#34;{{filepath}}&amp;#34;)&#xA;    filename=$(basename &amp;#34;$filepath&amp;#34;)&#xA;&#xA;    # Get datetime from EXIF&#xA;    exif_datetime=$(exiftool -s3 -d &amp;#34;%Y-%m-%dT%H:%M:%S&amp;#34; -DateTimeOriginal &amp;#34;$filepath&amp;#34; 2&amp;gt;/dev/null || true)&#xA;    if [ -z &amp;#34;$exif_datetime&amp;#34; ]; then&#xA;        exif_datetime=$(exiftool -s3 -d &amp;#34;%Y-%m-%dT%H:%M:%S&amp;#34; -CreateDate &amp;#34;$filepath&amp;#34; 2&amp;gt;/dev/null || true)&#xA;    fi&#xA;    if [ -z &amp;#34;$exif_datetime&amp;#34; ]; then&#xA;        echo &amp;#34;Error: could not read date from EXIF data&amp;#34; &amp;gt;&amp;amp;2&#xA;        exit 1&#xA;    fi&#xA;&#xA;    offset=$(exiftool -s3 -OffsetTimeOriginal &amp;#34;$filepath&amp;#34; 2&amp;gt;/dev/null | tr -d &amp;#39;[:space:]&amp;#39; || true)&#xA;    if [ -z &amp;#34;$offset&amp;#34; ]; then&#xA;        offset=&amp;#34;+00:00&amp;#34;&#xA;    fi&#xA;    date_str=&amp;#34;${exif_datetime}${offset}&amp;#34;&#xA;&#xA;    publish_date_str=$(date --iso-8601=seconds)&#xA;&#xA;    # Use EXIF ImageDescription as title/caption&#xA;    description=$(exiftool -s3 -ImageDescription &amp;#34;$filepath&amp;#34; 2&amp;gt;/dev/null | sed &amp;#39;s/^[[:space:]]*//;s/[[:space:]]*$//&amp;#39; || true)&#xA;    if [ -z &amp;#34;$description&amp;#34; ]; then&#xA;        echo &amp;#34;Error: no ImageDescription found in EXIF data&amp;#34; &amp;gt;&amp;amp;2&#xA;        exit 1&#xA;    fi&#xA;&#xA;    # Derive slug from description: lowercase, replace spaces/special chars with hyphens&#xA;    slug=$(echo &amp;#34;$description&amp;#34; | tr &amp;#39;[:upper:]&amp;#39; &amp;#39;[:lower:]&amp;#39; | sed -E &amp;#39;s/[^a-z0-9]+/-/g; s/^-+|-+$//g&amp;#39;)&#xA;&#xA;    site_dir=&amp;#34;{{justfile_directory()}}&amp;#34;&#xA;    post_dir=&amp;#34;$site_dir/content/photos/$slug&amp;#34;&#xA;    mkdir -p &amp;#34;$post_dir/images&amp;#34;&#xA;&#xA;    # Write index.md&#xA;    {&#xA;        echo &amp;#39;+++&amp;#39;&#xA;        echo &amp;#34;title=\&amp;#34;$description\&amp;#34;&amp;#34;&#xA;        echo &amp;#34;publishDate=\&amp;#34;$publish_date_str\&amp;#34;&amp;#34;&#xA;        echo &amp;#34;date=\&amp;#34;$date_str\&amp;#34;&amp;#34;&#xA;        echo &amp;#34;draft=false&amp;#34;&#xA;        echo &amp;#34;caption=\&amp;#34;$description\&amp;#34;&amp;#34;&#xA;        echo &amp;#34;featured_image=&amp;#39;images/$filename&amp;#39;&amp;#34;&#xA;        echo &amp;#39;+++&amp;#39;&#xA;    } &amp;gt; &amp;#34;$post_dir/index.md&amp;#34;&#xA;&#xA;    cp &amp;#34;$filepath&amp;#34; &amp;#34;$post_dir/images/$filename&amp;#34;&#xA;&#xA;    echo &amp;#34;Created $post_dir/index.md&amp;#34;&#xA;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;And then, in Darktable, all we do is call this script to create the post and run the git commands to commit and push.&lt;/p&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;darktable = require &amp;#34;darktable&amp;#34;&#xA;&#xA;local SITE_DIR = &amp;#34;/home/ben/dev/personal-website&amp;#34;&#xA;local JUSTFILE = SITE_DIR .. &amp;#34;/justfile&amp;#34;&#xA;&#xA;local function export_image(storage, image, format, filename, number, total, high_quality, extra_data)&#xA;    -- filename is the absolute temp export path darktable wrote the image to.&#xA;    local cmd = string.format(&#xA;        &amp;#34;just -f %q photo-from-file %q 2&amp;gt;&amp;amp;1&amp;#34;,&#xA;        JUSTFILE, filename&#xA;    )&#xA;&#xA;    local base = filename:match(&amp;#34;([^/]+)$&amp;#34;)&#xA;    darktable.print(&amp;#34;Creating photo post for &amp;#34; .. base .. &amp;#34;...&amp;#34;)&#xA;&#xA;    local result = darktable.control.execute(cmd)&#xA;&#xA;    if result ~= 0 then&#xA;        darktable.print_error(&amp;#34;photo-from-file failed for &amp;#34; .. base .. &amp;#34; (exit code &amp;#34; .. result .. &amp;#34;)&amp;#34;)&#xA;        darktable.print(&amp;#34;ERROR: photo-from-file failed for &amp;#34; .. base .. &amp;#34; — check the darktable log&amp;#34;)&#xA;    else&#xA;        darktable.print(&amp;#34;Photo post created for &amp;#34; .. base)&#xA;    end&#xA;end&#xA;&#xA;local function finalize(storage, image_table, extra_data)&#xA;    local first_image, first_filename = next(image_table)&#xA;    local first_image_description = first_image.description&#xA;    local cmd = string.format(&#xA;        &amp;#34;cd %q &amp;amp;&amp;amp; git add . &amp;amp;&amp;amp; git commit -m &amp;#39;export darktable image %q&amp;#39; &amp;amp;&amp;amp; git push&amp;#34;,&#xA;        SITE_DIR, first_image_description&#xA;    )&#xA;    local result = darktable.control.execute(cmd)&#xA;    darktable.print(&amp;#34;Committed. Open &amp;#34; .. SITE_DIR .. &amp;#34;/content/photos/ to review posts.&amp;#34;)&#xA;end&#xA;&#xA;darktable.register_storage(&amp;#34;website_export&amp;#34;,&amp;#34;Export to benkettle.xyz&amp;#34;, export_image, finalize)&#xA;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The full workflow looks like this now:&lt;/p&gt;&#xA;&lt;ol&gt;&#xA;&lt;li&gt;Do my regular photo editing process and end up with a photo that I like (hopefully)&lt;/li&gt;&#xA;&lt;li&gt;Add a &amp;ldquo;description&amp;rdquo; to the photo in Darktable&amp;rsquo;s UI &lt;img src=&#34;images/darktable-metadata-description.png&#34; alt=&#34;&#34;&gt;&lt;/li&gt;&#xA;&lt;li&gt;Export the photo using my custom exporter &lt;img src=&#34;images/custom-export-dialog.png&#34; alt=&#34;&#34;&gt;&#xA;And with that, my photo is published! No filesystem manipulation or even command-line usage necessary.&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;</description>
    </item>
    <item>
      <title>Finding Fibonacci in the NYT Pips</title>
      <link>https://benkettle.xyz/posts/fibonacci-pips/</link>
      <pubDate>Sun, 11 Jan 2026 00:00:00 +0000</pubDate>
      <guid>https://benkettle.xyz/posts/fibonacci-pips/</guid>
      <description>&lt;p&gt;When I was home over the holidays, one of my good high school friends got me hooked on the &lt;a href=&#34;https://www.nytimes.com/games/pips&#34;&gt;NYT Pips&lt;/a&gt; game. If you&amp;rsquo;re not familiar, it&amp;rsquo;s a puzzle game where you arrange a provided set of dominoes onto a board so that their values satisfy some constraints like &amp;ldquo;these 3 spots add to 5&amp;rdquo; or &amp;ldquo;these spots are all equal&amp;rdquo;.&lt;/p&gt;&#xA;&lt;p&gt;&lt;img src=&#34;images/easy-pips.png&#34; alt=&#34;The &amp;ldquo;easy&amp;rdquo; pips puzzle from Jan. 12, 2026, showing a grid of squares with constraints and a set of five dominoes with varying values&#34;&gt;&lt;/p&gt;&#xA;&lt;p&gt;While we were downhill skiing over the break, said friend and I got to discussing how you might write a solver for Pips. I thought that SMT solvers like Z3 might be a good fit, and indeed we found a couple of blog posts (&lt;a href=&#34;https://www.righto.com/2025/10/solve-nyt-pips-with-constraints.html&#34;&gt;one&lt;/a&gt;, &lt;a href=&#34;https://kerrigan.dev/blog/nyt-pips&#34;&gt;two&lt;/a&gt;) solving the game that way. But we were curious about how we might write one ourselves. In particular, we wanted to know how big the solution space really was: could we just brute-force it, or were there too many possible solutions for a computer to handle?&lt;/p&gt;&#xA;&lt;h2 id=&#34;thinking-about-the-number-of-solutions&#34;&gt;Thinking about the number of solutions&lt;/h2&gt;&#xA;&lt;p&gt;The number of possible solutions breaks down&amp;mdash;the way we thought about it&amp;mdash;into two main components:&lt;/p&gt;&#xA;&lt;ol&gt;&#xA;&lt;li&gt;The number of geometric arrangements of dominoes onto the board. To be valid, an assignment must fill the entire board.&lt;/li&gt;&#xA;&lt;li&gt;For each of those arrangements, the number of ways that the dominoes can be assigned to each space.&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;p&gt;The second component seemed easy enough to reason about:  a geometric arrangement of dominoes gives $d$ spaces to each be filled by one of the $d$ given dominoes in one of two ways (either not-flipped or flipped, ignoring doubles). There are $d!$ permutations of dominoes into the $d$ spaces, and each domino in each permutation can be either flipped or not flipped, for a total of:&#xA;&lt;/p&gt;&#xA;$$ \text{solutions per arrangement} = d! \cdot 2^{d} $$&lt;p&gt;&#xA;But figuring out the number of possible geometric arrangements was too hard for us to figure out on the chairlift (even after several runs): the simple cases were easy enough but as the grid got bigger it was hard to see any pattern. So I decided I&amp;rsquo;d just write some code to figure it out for the most common cases first.&lt;/p&gt;&#xA;&lt;h1 id=&#34;calculating-geometric-arrangements&#34;&gt;Calculating geometric arrangements&lt;/h1&gt;&#xA;&lt;p&gt;Since we were just interested in approximating the number of solutions, I thought it&amp;rsquo;d be good to first think about just rectangular boards. Though the boards for Pips are rarely rectangles, the rectangular case should be a good place to start. And on the day that I decided to do this, the hard puzzle &lt;a href=&#34;https://pipspuzzle.com/nyt-pips-archive-2026-01-11&#34;&gt;&lt;em&gt;was&lt;/em&gt;&lt;/a&gt; just a $6 \times 5$ rectangle.&lt;/p&gt;&#xA;&lt;p&gt;To get going, I wrote some quick code to model a board, pieces on a board, and conflicts between pieces. Then I wrote a basic recursive backtracking algorithm to enumerate all possible assignments, and got the following results for $m \times n$ boards:&lt;/p&gt;&#xA;&lt;table&gt;&#xA;  &lt;thead&gt;&#xA;    &lt;tr&gt;&#xA;      &lt;th&gt;&lt;/th&gt;&#xA;      &lt;th&gt;1&lt;/th&gt;&#xA;      &lt;th&gt;2&lt;/th&gt;&#xA;      &lt;th&gt;3&lt;/th&gt;&#xA;      &lt;th&gt;4&lt;/th&gt;&#xA;      &lt;th&gt;5&lt;/th&gt;&#xA;      &lt;th&gt;6&lt;/th&gt;&#xA;      &lt;th&gt;7&lt;/th&gt;&#xA;      &lt;th&gt;8&lt;/th&gt;&#xA;    &lt;/tr&gt;&#xA;  &lt;/thead&gt;&#xA;  &lt;tbody&gt;&#xA;    &lt;tr&gt;&#xA;      &lt;th&gt;1&lt;/th&gt;&#xA;      &lt;td&gt;0&lt;/td&gt;&#xA;      &lt;td&gt;1&lt;/td&gt;&#xA;      &lt;td&gt;0&lt;/td&gt;&#xA;      &lt;td&gt;1&lt;/td&gt;&#xA;      &lt;td&gt;0&lt;/td&gt;&#xA;      &lt;td&gt;1&lt;/td&gt;&#xA;      &lt;td&gt;0&lt;/td&gt;&#xA;      &lt;td&gt;1&lt;/td&gt;&#xA;    &lt;/tr&gt;&#xA;    &lt;tr&gt;&#xA;      &lt;th&gt;2&lt;/th&gt;&#xA;      &lt;td&gt;1&lt;/td&gt;&#xA;      &lt;td&gt;2&lt;/td&gt;&#xA;      &lt;td&gt;3&lt;/td&gt;&#xA;      &lt;td&gt;5&lt;/td&gt;&#xA;      &lt;td&gt;8&lt;/td&gt;&#xA;      &lt;td&gt;13&lt;/td&gt;&#xA;      &lt;td&gt;21&lt;/td&gt;&#xA;      &lt;td&gt;34&lt;/td&gt;&#xA;    &lt;/tr&gt;&#xA;    &lt;tr&gt;&#xA;      &lt;th&gt;3&lt;/th&gt;&#xA;      &lt;td&gt;0&lt;/td&gt;&#xA;      &lt;td&gt;3&lt;/td&gt;&#xA;      &lt;td&gt;0&lt;/td&gt;&#xA;      &lt;td&gt;11&lt;/td&gt;&#xA;      &lt;td&gt;0&lt;/td&gt;&#xA;      &lt;td&gt;41&lt;/td&gt;&#xA;      &lt;td&gt;0&lt;/td&gt;&#xA;      &lt;td&gt;153&lt;/td&gt;&#xA;    &lt;/tr&gt;&#xA;    &lt;tr&gt;&#xA;      &lt;th&gt;4&lt;/th&gt;&#xA;      &lt;td&gt;1&lt;/td&gt;&#xA;      &lt;td&gt;5&lt;/td&gt;&#xA;      &lt;td&gt;11&lt;/td&gt;&#xA;      &lt;td&gt;36&lt;/td&gt;&#xA;      &lt;td&gt;95&lt;/td&gt;&#xA;      &lt;td&gt;281&lt;/td&gt;&#xA;      &lt;td&gt;781&lt;/td&gt;&#xA;      &lt;td&gt;2245&lt;/td&gt;&#xA;    &lt;/tr&gt;&#xA;    &lt;tr&gt;&#xA;      &lt;th&gt;5&lt;/th&gt;&#xA;      &lt;td&gt;0&lt;/td&gt;&#xA;      &lt;td&gt;8&lt;/td&gt;&#xA;      &lt;td&gt;0&lt;/td&gt;&#xA;      &lt;td&gt;95&lt;/td&gt;&#xA;      &lt;td&gt;0&lt;/td&gt;&#xA;      &lt;td&gt;1183&lt;/td&gt;&#xA;      &lt;td&gt;0&lt;/td&gt;&#xA;      &lt;td&gt;14824&lt;/td&gt;&#xA;    &lt;/tr&gt;&#xA;    &lt;tr&gt;&#xA;      &lt;th&gt;6&lt;/th&gt;&#xA;      &lt;td&gt;1&lt;/td&gt;&#xA;      &lt;td&gt;13&lt;/td&gt;&#xA;      &lt;td&gt;41&lt;/td&gt;&#xA;      &lt;td&gt;281&lt;/td&gt;&#xA;      &lt;td&gt;1183&lt;/td&gt;&#xA;      &lt;td&gt;6728&lt;/td&gt;&#xA;      &lt;td&gt;31529&lt;/td&gt;&#xA;      &lt;td&gt;167089&lt;/td&gt;&#xA;    &lt;/tr&gt;&#xA;    &lt;tr&gt;&#xA;      &lt;th&gt;7&lt;/th&gt;&#xA;      &lt;td&gt;0&lt;/td&gt;&#xA;      &lt;td&gt;21&lt;/td&gt;&#xA;      &lt;td&gt;0&lt;/td&gt;&#xA;      &lt;td&gt;781&lt;/td&gt;&#xA;      &lt;td&gt;0&lt;/td&gt;&#xA;      &lt;td&gt;31529&lt;/td&gt;&#xA;      &lt;td&gt;0&lt;/td&gt;&#xA;      &lt;td&gt;1292697&lt;/td&gt;&#xA;    &lt;/tr&gt;&#xA;    &lt;tr&gt;&#xA;      &lt;th&gt;8&lt;/th&gt;&#xA;      &lt;td&gt;1&lt;/td&gt;&#xA;      &lt;td&gt;34&lt;/td&gt;&#xA;      &lt;td&gt;153&lt;/td&gt;&#xA;      &lt;td&gt;2245&lt;/td&gt;&#xA;      &lt;td&gt;14824&lt;/td&gt;&#xA;      &lt;td&gt;167089&lt;/td&gt;&#xA;      &lt;td&gt;1292697&lt;/td&gt;&#xA;      &lt;td&gt;12988816&lt;/td&gt;&#xA;    &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;p&gt;I sent this to my friend, and he pointed out that the $2 \times n$ case looks an awful lot like the Fibonacci sequence! Of course, I learned from Grant Sanderson that &lt;a href=&#34;https://www.youtube.com/watch?v=NOCsdhzo6Jg&#34;&gt;patterns fool ya&lt;/a&gt;, so I had my program compute some more $2 \times n$ arrangements:&lt;/p&gt;&#xA;$$\begin{array}{r|rrrrrrrrrrrrrrrrr}&#xA;n &amp; 1 &amp; 2 &amp; 3 &amp; 4 &amp; 5 &amp; 6 &amp; 7 &amp; 8 &amp; 9 &amp; 10 &amp; 11 &amp; 12 &amp; 13 &amp; 14 &amp; 15 &amp; 16 &amp; 17 &amp; \cdots \\&#xA;\hline&#xA;&amp; 1 &amp; 2 &amp; 3 &amp; 5 &amp; 8 &amp; 13 &amp; 21 &amp; 34 &amp; 55 &amp; 89 &amp; 144 &amp; 233 &amp; 377 &amp; 610 &amp; 987 &amp; 1597 &amp; 2584 &amp; \cdots \\ &#xA;\end{array}$$&lt;p&gt;&#xA;I went up to $2 \times 36$ ($24157817$) before my computer started to really slow down, and it kept following Fibonacci.&lt;/p&gt;&#xA;&lt;h1 id=&#34;why-fibonacci&#34;&gt;Why Fibonacci?&lt;/h1&gt;&#xA;&lt;p&gt;At first, it seemed pretty crazy that Fibonacci would show up inside the NYT Pips. But the $2 \times n$ case is pretty simple, and the fact that the number of total arrangements for each $n$ looks like Fibonacci gives us a hint about a good way to think about the relation. Fibonacci is defined as:&#xA;&lt;/p&gt;&#xA;$$F(n) = F(n-1) + F(n-2)$$&lt;p&gt;so let&amp;rsquo;s think about the $n - 1$ and $n - 2$ cases of a $2 \times n$ Pips board and how they each contribute to the total number of arrangements that fill a $2 \times n$ board.&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;Claim 1: There is exactly one way to turn a valid $2 \times (n -1)$ arrangement into a valid $2 \times n$ arrangement.&lt;/strong&gt;&lt;/p&gt;&#xA;&lt;p&gt;Drawing it out, it looks like for any arrangement that fills a $2 \times (n - 1)$ Pips board, we can simply add one vertical domino at the end to fill a $2 \times n$ Pips board. And indeed this is the &lt;em&gt;only&lt;/em&gt; way to fill a $2 \times n$ Pips board given an arrangement that fills a $2 \times (n - 1)$ Pips board.&lt;/p&gt;&#xA;&lt;p&gt;&lt;img src=&#34;images/n-1.excalidraw.svg&#34; alt=&#34;&#34;&gt;&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;Claim 2: There is exactly one way to turn a valid $2 \times (n -2)$ arrangement into a valid $2 \times n$ arrangement that is distinct from any valid $2 \times (n - 1)$-derived arrangement.&lt;/strong&gt;&lt;/p&gt;&#xA;&lt;p&gt;If we think about a $2 \times (n - 2)$ Pips board, there are two ways to fill it out: we need two dominoes to fill the four empty squares, but those two dominoes can be either vertical or horizontal.&lt;/p&gt;&#xA;&lt;p&gt;&lt;img src=&#34;images/n-2.excalidraw.svg&#34; alt=&#34;&#34;&gt;&lt;/p&gt;&#xA;&lt;p&gt;However, we are interested in counting the number of unique geometric arrangements of dominoes to fill a $2 \times n$ board. With the final dominoes in the $n-2$ case &lt;em&gt;vertical&lt;/em&gt;, we&amp;rsquo;ve created a duplicate with the arrangements covered by the $n-1$ case: since removing the right of the two new dominoes would give a valid arrangement for a $2 \times (n -1)$ board and we already consider adding a vertical domino to every valid $n-1$ arrangement, all of those arrangements must already be covered by the $n - 1$ cases. So the only contributors to unique geometric arrangements for the $2 \times (n - 2)$ case are those with two &lt;em&gt;horizontal&lt;/em&gt; dominoes filling the extra space.&lt;/p&gt;&#xA;&lt;p&gt;&lt;img src=&#34;images/no-vertical-recurrence.excalidraw.svg&#34; alt=&#34;&#34;&gt;&lt;/p&gt;&#xA;&lt;p&gt;This is sounding like Fibonacci, but one might finally wonder if we need to also consider any smaller instances of the recurrence. For example, is it possible to create any new arrangements that are derived from an arrangement that fills a $2 \times (n - 3)$ board?  I claim that it is not and indeed that it is not possible for any small instance:&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;Claim 3: All valid $2 \times n$ arrangements must be an extension of either a valid $2 \times (n - 1)$ or a valid $2 \times (n - 2)$ arrangement.&lt;/strong&gt;&lt;/p&gt;&#xA;&lt;p&gt;Taking the example of $2 \times (n - 3)$ from above, filling the empty space to the right of the $2 \times (n-3)$ arrangement requires that each space is filled by a portion of either a horizontal domino or a vertical domino. Since all dominoes have dimension $1 \times 2$, it is necessary to complete either a valid $2 \times (n -1)$ or $2 \times (n -2)$ arrangement in order to complete a valid $2 \times n$ arrangement, and therefore any valid arrangement derived from a $2 \times (n -3)$ arrangement would also be included in the arrangements considered above. This applies in the same way to any smaller instance of the recurrence, so it is sufficient to count all possible arrangements to consider only the $n-1$ and $n-2$ cases.&lt;/p&gt;&#xA;&lt;p&gt;&lt;img src=&#34;images/no-n-3.excalidraw.svg&#34; alt=&#34;&#34;&gt;&lt;/p&gt;&#xA;&lt;p&gt;Combining these three claims, since there is exactly one way to turn a valid $2 \times (n-1)$ arrangement into a valid $2 \times n$ arrangement and exactly one way to turn a valid $2 \times (n-2)$ arrangement into a valid $2 \times n$ arrangement, and because all valid $2 \times n$ arrangements must also be a rightward extension of either a  $2 \times (n-1)$ or a  $2 \times (n-2)$ arrangement, we can say that the number of possible arrangements of dominoes to fill a $2 \times n$ Pips board is equal to the sum of the number of possible arrangements to fill a  $2 \times (n-1)$ board and to fill a  $2 \times (n-2)$ board. If we call $F(n)$ the number of possible arrangements to fill a $2 \times n$ board, then, and set base cases of $F(1) = 1$ and $F(2) = 2$ for the $2\times 1$ and $2 \times 2$ cases, this gives:&#xA;&lt;/p&gt;&#xA;$$ F(n) = F(n - 1) + F(n - 2)$$&lt;p&gt;&lt;img src=&#34;images/fib-recurrence.excalidraw.svg&#34; alt=&#34;&#34;&gt;&lt;/p&gt;&#xA;&lt;p&gt;Just like Fibonacci.&lt;/p&gt;&#xA;&lt;h1 id=&#34;nothing-new&#34;&gt;Nothing new&lt;/h1&gt;&#xA;&lt;p&gt;Of course, my friend and I did not discover a new field of math by playing Pips. Some Googling suggested that this problem is called &lt;a href=&#34;https://en.wikipedia.org/wiki/Domino_tiling&#34;&gt;Domino Tiling&lt;/a&gt;, and the general $m \times n$ case was solved by &lt;a href=&#34;https://www.tandfonline.com/doi/abs/10.1080/14786436108243366&#34;&gt;Temperley &amp;amp; Fisher&lt;/a&gt; and by &lt;a href=&#34;https://doi.org/10.1016/0031-8914(61)90063-5&#34;&gt;Kasteleyn&lt;/a&gt; in 1961 in the context of statistical mechanics and physics, respectively, as:&#xA;&lt;/p&gt;&#xA;$$ \prod^{\lceil\frac{m}{2}\rceil}_{j=1}\prod^{\lceil\frac{n}{2}\rceil}_{k=1} \left( 4\cos^2 \frac{\pi j}{m + 1} + 4 \cos^2 \frac{\pi k}{n + 1}\right)$$&lt;p&gt;But it was a lot more fun to play around with it myself and be surprised by the Fibonacci than to read it on Wikipedia :)&lt;/p&gt;&#xA;&lt;h1 id=&#34;so-can-we-brute-force-it&#34;&gt;So, can we brute-force it?&lt;/h1&gt;&#xA;&lt;p&gt;Fibonacci aside, the original question we set out to answer was whether the solution space of a regular Pips game was small enough for a computer to brute-force. We discovered above that the number of geometric arrangements for a standard-sized &amp;ldquo;hard&amp;rdquo; board, such as the $6 \times 5$ one from Jan. 11, contributes only a factor of a few thousand geometric arrangements ($1183 \approx 2^{10}$ in the $6 \times 5$ case). The number of dominoes $d$ and the way that they can be assigned within a geometric arrangement contributes a much larger factor: $d! \cdot 2^d$ or, for $d = 15$, $4.28 \times 10^{19} \approx 2^{55}$.&lt;/p&gt;&#xA;&lt;p&gt;$2^{55}$ is already outside the range of brute-forcing effectively&amp;mdash;at even a wildly unrealistic checking rate of 1 solution per CPU cycle, a 5GHz CPU ($\approx 2^{32}$ cycles/sec) it would take about $2^{23}$ seconds, or 97 days, to check every possible solution &lt;em&gt;for a single geometric arrangement&lt;/em&gt;. Adding in the factor of $2^{10}$ for the number of geometric arrangements bumps us up to $2^{32}$ seconds, or 272 years. I think we need something smarter.&lt;/p&gt;&#xA;</description>
    </item>
    <item>
      <title>Autogenerating a Book Series From Three Years of iMessages</title>
      <link>https://benkettle.xyz/posts/message-book/</link>
      <pubDate>Thu, 08 Feb 2024 00:00:00 +0000</pubDate>
      <guid>https://benkettle.xyz/posts/message-book/</guid>
      <description>&lt;p&gt;I am frequently annoyed at the things that I can&amp;rsquo;t remember. And when I&amp;rsquo;m trying to remember the details of something, I often turn to my text messages&amp;mdash;thanks to big improvements recently, it is now quite fast to search my whole iMessage history on my phone, provided that I can remember some verbatim part of the message I&amp;rsquo;m looking for. And often, once I&amp;rsquo;m in the past, I want to look around: text messages from ages ago provide surprisingly interesting insights into the past.&lt;/p&gt;&#xA;&lt;p&gt;But iMessage isn&amp;rsquo;t set up well for this casual browsing: when you try to scroll away from a search result, the loading is very slow. And the interface provides no way to jump to a specific date. I&amp;rsquo;d really like to be able to &amp;ldquo;flip through&amp;rdquo; my messages and stop at a random place for a view into that moment in time. Apple doesn&amp;rsquo;t provide a way to do that, so, I thought, why not enable it myself? I though it&amp;rsquo;d be great to enable this &amp;ldquo;flipping through messages&amp;rdquo; in the most literal way possible: by creating a physical book of my biggest conversation.&lt;/p&gt;&#xA;&lt;p&gt;&lt;img src=&#34;images/toc.jpeg&#34; alt=&#34;&#34;&gt;&lt;/p&gt;&#xA;&lt;h1 id=&#34;is-it-possible&#34;&gt;Is it possible?&lt;/h1&gt;&#xA;&lt;p&gt;In order to do anything at all with the messages, I needed to get them out of my phone and onto my computer. I&amp;rsquo;d looked &lt;a href=&#34;https://community.signalusers.org/t/ios-backup-keeping-message-history-when-switching-phones/1736/1014&#34;&gt;many times&lt;/a&gt; for a way to do this with Signal, so wasn&amp;rsquo;t sure what I&amp;rsquo;d find, but was pleased that it seemed relatively straightforward to pull messages off an iPhone (even easier if your messages are already on a Mac). According to the very helpful &lt;a href=&#34;https://www.theiphonewiki.com/wiki/Messages&#34;&gt;iPhone wiki&lt;/a&gt;, all I had to do was grab &lt;code&gt;sms.db&lt;/code&gt; from a backup of my phone, and I&amp;rsquo;d have a SQLite database that I could do whatever I liked with.&lt;/p&gt;&#xA;&lt;h1 id=&#34;querying-my-texts-with-sql&#34;&gt;Querying my texts with SQL&lt;/h1&gt;&#xA;&lt;p&gt;This simplicity seemed a bit too good to be true&amp;mdash;for some reason I expected some proprietary format that would be a pain to reverse-engineer. So I had to see it for myself. I took a standard backup on my Mac in finder (that was a trip&amp;mdash;the &amp;ldquo;plugged-in iPhone&amp;rdquo; UI has barely changed since I used iTunes to sync music to my iPod touch in seventh grade). While the backup format is really not complicated, it was intimidating browsing the backup folder at first because an &lt;code&gt;ls&lt;/code&gt; in the root directory yields a bunch of directories named after a single hex byte:&lt;/p&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;/.../00008120-001854410CEB401E &amp;gt;&amp;gt;&amp;gt; ls&#xA;00  0e&#x9;1c  2a&#x9;38  46&#x9;54  62&#x9;70  7e&#x9;8c  9a&#x9;a8  b6&#x9;c4  d2&#x9;e0  ee&#x9;fc&#xA;01  0f&#x9;1d  2b&#x9;39  47&#x9;55  63&#x9;71  7f&#x9;8d  9b&#x9;a9  b7&#x9;c5  d3&#x9;e1  ef&#x9;fd&#xA;02  10&#x9;1e  2c&#x9;3a  48&#x9;56  64&#x9;72  80&#x9;8e  9c&#x9;aa  b8&#x9;c6  d4&#x9;e2  f0&#x9;fe&#xA;03  11&#x9;1f  2d&#x9;3b  49&#x9;57  65&#x9;73  81&#x9;8f  9d&#x9;ab  b9&#x9;c7  d5&#x9;e3  f1&#x9;ff&#xA;04  12&#x9;20  2e&#x9;3c  4a&#x9;58  66&#x9;74  82&#x9;90  9e&#x9;ac  ba&#x9;c8  d6&#x9;e4  f2&#x9;Info.plist&#xA;05  13&#x9;21  2f&#x9;3d  4b&#x9;59  67&#x9;75  83&#x9;91  9f&#x9;ad  bb&#x9;c9  d7&#x9;e5  f3&#x9;Manifest.db&#xA;06  14&#x9;22  30&#x9;3e  4c&#x9;5a  68&#x9;76  84&#x9;92  a0&#x9;ae  bc&#x9;ca  d8&#x9;e6  f4&#x9;Manifest.db-shm&#xA;07  15&#x9;23  31&#x9;3f  4d&#x9;5b  69&#x9;77  85&#x9;93  a1&#x9;af  bd&#x9;cb  d9&#x9;e7  f5&#x9;Manifest.db-wal&#xA;08  16&#x9;24  32&#x9;40  4e&#x9;5c  6a&#x9;78  86&#x9;94  a2&#x9;b0  be&#x9;cc  da&#x9;e8  f6&#x9;Manifest.plist&#xA;09  17&#x9;25  33&#x9;41  4f&#x9;5d  6b&#x9;79  87&#x9;95  a3&#x9;b1  bf&#x9;cd  db&#x9;e9  f7&#x9;Status.plist&#xA;0a  18&#x9;26  34&#x9;42  50&#x9;5e  6c&#x9;7a  88&#x9;96  a4&#x9;b2  c0&#x9;ce  dc&#x9;ea  f8&#xA;0b  19&#x9;27  35&#x9;43  51&#x9;5f  6d&#x9;7b  89&#x9;97  a5&#x9;b3  c1&#x9;cf  dd&#x9;eb  f9&#xA;0c  1a&#x9;28  36&#x9;44  52&#x9;60  6e&#x9;7c  8a&#x9;98  a6&#x9;b4  c2&#x9;d0  de&#x9;ec  fa&#xA;0d  1b&#x9;29  37&#x9;45  53&#x9;61  6f&#x9;7d  8b&#x9;99  a7&#x9;b5  c3&#x9;d1  df&#x9;ed  fb&#xA;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Entering one of these directories yields a bunch of files starting with the hex byte after which the directory was named:&lt;/p&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;/.../00008120-001854410CEB401E &amp;gt;&amp;gt;&amp;gt; cd 3d&#xA;/.../00008120-001854410CEB401E/3d &amp;gt;&amp;gt;&amp;gt; ls&#xA;3d0292d3fe90e1e22c247403c0e9105ea0f9ff44      3d8830b71e98aae80b6eaf8bdd5500d79ce74946&#xA;3d02fe309afa7de839822d6f1b8433aa90090d17      3d88cdc16ff2b5231e5ea4b52271ee195a6f4b96&#xA;3d072c4fca5db4a5678fa10b137435f757e98492      3d8a425d70f4049417e855d273c44d8199de30c9&#xA;3d0739c90579fa907246d5c21bd8d8ebaa2d9d6b      3d8a43a1921f504bb4393250f75b24bfc2c5cedb&#xA;3d0798b3cc4d2f5ad347ffb8bc5a0f9d8c82cfb9      3d8a7c0460aadabf1b7fc9adea9e6a2a6e7bc73b&#xA;3d07a0adc5c5c22dc525ccd3a93fb05a50ef1ac5      3d8b6ad12c7617b3d783790a457b0aa19b193b68&#xA;3d0880f091c51ddc145e17c78d8e6f9a3e7e20c8      3d8b82abe05a9d697102d8b665c9d499e07492ea&#xA;3d093e92cf03abf3650411e09a647630a1e0c478      3d8ba897240ad32580bf8dfd00db8f181658cdfd&#xA;3d095e908ff898be3b3ffd64a75db959a58ac70a      3d8bc227d67ec4944df8e75291102367034d7214&#xA;3d09d5dcd5a9bdad67a80cd83201a9e1fb75aada      3d8c722f1d92f7cd6f90c936c14f60f51aad128b&#xA;3d0abb83123be82abf43ce20118e72fea06023c5      3d8ca6eeabeb1c01fae05bb20f08dedf734cfd04&#xA;3d0b246304c42d2ab1eb1892d629fcdfde689cb7      3d8d0c6b1bf7946c6bef91d60cccb32207b7bc01&#xA;3d0bb5f49e6f0e31348ef8feb9a38d4ce71f5ec7      3d8fd2fbcaf3079a683a8e486ecde8875f0a591d&#xA;3d0c1283936c45fec533a507b78558b5aa3159fa      3d8ff93bd94b3ea14edc77d1e677cf4ee4306e4e&#xA;3d0cb8e28462780bb9af1440e297ecd8224c70ff      3d90ea8bfbf62feda080cd0ccbd12fa5c8673993&#xA;3d0ce10de5f69606c52882215b99ebab259dc194      3d932638fe8ed669725b7a143c6a8b02b8959923&#xA;3d0d7e5fb2ce288813306e4d4636395e047a3d28      3d93c92679aa9d398331e27fdeed64b5094e68d1&#xA;...&#xA;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Looking at these with a nice file explorer that looks at magic bytes to determine filetypes (I use &lt;a href=&#34;https://docs.xfce.org/xfce/thunar/start&#34;&gt;Thunar&lt;/a&gt;) helps make some sense of it, since it can show that these cryptic names really are just regular old images and other files. But really even that is unnecessary since the iPhone Wiki told us that the filename for the &lt;code&gt;sms.db&lt;/code&gt; file that we&amp;rsquo;re looking for is &lt;code&gt;3d0d7e5fb2ce288813306e4d4636395e047a3d28&lt;/code&gt;.  Copying this to my home directory:&lt;/p&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;$ cp 3d0d7e5fb2ce288813306e4d4636395e047a3d28 ~/imessage.db&#xA;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;And opening it up with the &lt;code&gt;sqlite3&lt;/code&gt; CLI we can actually see some tables!&lt;/p&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;~ &amp;gt;&amp;gt;&amp;gt; sqlite3 imessages.db&#xA;SQLite version 3.44.2 2023-11-24 11:41:44&#xA;Enter &amp;#34;.help&amp;#34; for usage hints.&#xA;sqlite&amp;gt; .tables&#xA;_SqliteDatabaseProperties              message&#xA;attachment                             message_attachment_join&#xA;chat                                   message_processing_task&#xA;chat_handle_join                       recoverable_message_part&#xA;chat_message_join                      sync_deleted_attachments&#xA;chat_recoverable_message_join          sync_deleted_chats&#xA;deleted_messages                       sync_deleted_messages&#xA;handle                                 unsynced_removed_recoverable_messages&#xA;kvtable&#xA;sqlite&amp;gt;&#xA;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The schema requires a couple of joins to extract an actual conversation, but without too much trouble we can start to pull out messages (in this case from CVS spamming me):&lt;/p&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;sqlite&amp;gt; select&#xA;        message.ROWID, message.date, message.text, message.is_from_me from message&#xA;        inner join chat_message_join on message_id=message.ROWID&#xA;        inner join chat on chat.ROWID=chat_message_join.chat_id&#xA;        where chat.chat_identifier=&amp;#39;28732&amp;#39;&#xA;        order by date asc;&#xA;278125|694030292385607040||0&#xA;278327|694647875648848000||0&#xA;&#xA;...&#xA;&#xA;314056|726702453329793024||0&#xA;314412|727316171079934976|CVS ExtraCare: 20% off one full-price item, just because. Tap the link to send to card: c.cvs.com/B0kjBMbNM|0&#xA;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;We got one, but a lot of blank ones too&amp;mdash;many of the messages are missing! It turns out that for some messages, message data is stored in an encoded &lt;code&gt;NSMutableAttributedString&lt;/code&gt; binary blob in the &lt;code&gt;message.attributedData&lt;/code&gt; column instead of in &lt;code&gt;message.text&lt;/code&gt;. With a bit of wrangling to get the binary data out of the SQLite CLI, we can look at one of these missing messages and see that the data is indeed there:&lt;/p&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;~ &amp;gt;&amp;gt;&amp;gt; sqlite3 imessages.db &amp;#34;select hex(attributedBody) from message where ROWID=278125;&amp;#34;   \&#xA;| cut -d\&amp;#39; -f2   \&#xA;| xxd -r -p      \&#xA;| xxd -g1&#xA;00000000: 04 0b 73 74 72 65 61 6d 74 79 70 65 64 81 e8 03  ..streamtyped...&#xA;00000010: 84 01 40 84 84 84 19 4e 53 4d 75 74 61 62 6c 65  ..@....NSMutable&#xA;00000020: 41 74 74 72 69 62 75 74 65 64 53 74 72 69 6e 67  AttributedString&#xA;00000030: 00 84 84 12 4e 53 41 74 74 72 69 62 75 74 65 64  ....NSAttributed&#xA;00000040: 53 74 72 69 6e 67 00 84 84 08 4e 53 4f 62 6a 65  String....NSObje&#xA;00000050: 63 74 00 85 92 84 84 84 0f 4e 53 4d 75 74 61 62  ct.......NSMutab&#xA;00000060: 6c 65 53 74 72 69 6e 67 01 84 84 08 4e 53 53 74  leString....NSSt&#xA;00000070: 72 69 6e 67 01 95 84 01 2b 81 f3 00 43 56 53 20  ring....+...CVS&#xA;00000080: 45 78 74 72 61 43 61 72 65 3a 20 24 32 20 6f 66  ExtraCare: $2 of&#xA;00000090: 66 20 79 6f 75 72 20 70 75 72 63 68 61 73 65 2c  f your purchase,&#xA;000000a0: 20 6a 75 73 74 20 66 6f 72 20 79 6f 75 21 20 49   just for you! I&#xA;000000b0: 6e 20 73 74 6f 72 65 20 6f 72 20 6f 6e 6c 69 6e  n store or onlin&#xA;000000c0: 65 2e 20 54 61 70 20 74 68 65 20 6c 69 6e 6b 20  e. Tap the link&#xA;000000d0: 74 6f 20 73 65 6e 64 20 64 65 61 6c 20 74 6f 20  to send deal to&#xA;000000e0: 63 61 72 64 3a 20 63 2e 63 76 73 2e 63 6f 6d 2f  card: c.cvs.com/&#xA;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Luckily, we don&amp;rsquo;t need to implement the parsing for this binary format ourselves. There&amp;rsquo;s a great &lt;a href=&#34;https://github.com/ReagentX/imessage-exporter/tree/develop/imessage-database&#34;&gt;&lt;code&gt;imessage-database&lt;/code&gt;&lt;/a&gt; crate that does exactly this: ingests an iMessage database and outputs the data in nice Rust data structures. Out of the box, it comes with a binary (&lt;code&gt;imessage-exporter&lt;/code&gt;) to generate text or HTML versions of your conversations&amp;mdash;so really quite similar to my goal.&lt;/p&gt;&#xA;&lt;p&gt;With just a couple of tweaks to the SQL statement the library uses to fetch messages, I&amp;rsquo;m able to narrow down the query to just a single conversation. But for this project I want to make a nicely formatted physical book that I can hold in my hand and flip through&amp;mdash;the HTML and text formats that the project ships with won&amp;rsquo;t quite work for this.&lt;/p&gt;&#xA;&lt;h1 id=&#34;generating-latex&#34;&gt;Generating LaTeX&lt;/h1&gt;&#xA;&lt;p&gt;I am a huge fan of LaTeX due to the beautiful documents it can be convinced to produce, and since leaving school have been itching to generate some more pretty PDFs. And since LaTeX&amp;rsquo;s text-based source code makes it perfect for templating and autogeneration, it seems like a great choice. I&amp;rsquo;ll my book by spitting out LaTeX code for every text message in the conversation.&lt;/p&gt;&#xA;&lt;p&gt;Thanks to the &lt;code&gt;imessage-database&lt;/code&gt; library it&amp;rsquo;s pretty easy to iterate through all the messages in the conversation, so I start by generating LaTeX code for each message. My first approach at this LaTeX generation is quite simple: align left if the message is from me and right otherwise, insert some text indicating an attachment where images are sent, and skip things like reactions and replies that I don&amp;rsquo;t want to bother rendering. This initial approach works well, and after splitting the text up into chapters based on date and bit of visual tweaking, I&amp;rsquo;m satisfied.&lt;/p&gt;&#xA;&lt;p&gt;&lt;img src=&#34;images/chapter.jpeg&#34; alt=&#34;&#34;&gt;&lt;/p&gt;&#xA;&lt;p&gt;But there&amp;rsquo;s one major problem: &lt;strong&gt;LaTeX doesn&amp;rsquo;t support unicode&lt;/strong&gt;. Of course, this means that as soon as I extend the rendering window enough to include an emoji, the LaTeX compiler explodes. Simply stripping out emojis from the source text works, but is hardly a tolerable solution&amp;mdash;after all, emojis are integral to modern communication.&lt;/p&gt;&#xA;&lt;p&gt;After a bit of research, it looks like XeLaTeX is the key: it adds support for unicode fonts to LaTeX. Switching to XeLaTeX proves quite straightforward, and by defining a &lt;code&gt;\emojifont&lt;/code&gt; to an emoji font and wrapping every emoji in &lt;code&gt;{\emojifont X}&lt;/code&gt; in my generated LaTeX source, the output renders successfully with emojis inline. But I don&amp;rsquo;t want to pay for every page of my book to be printed in color when I print it. Luckily, Google&amp;rsquo;s &lt;a href=&#34;https://fonts.google.com/noto/specimen/Noto+Emoji/glyphs&#34;&gt;Noto Emoji&lt;/a&gt; font has a great set of simple black-and-white emojis that are perfect for this purpose. I&amp;rsquo;m quite happy with the way these emojis look in print:&lt;/p&gt;&#xA;&lt;p&gt;&lt;img src=&#34;images/emojis.jpeg&#34; alt=&#34;Three messages including an array of black-and-white emojis printed on a white page.&#34;&gt;&lt;/p&gt;&#xA;&lt;p&gt;After a couple extra niceties like a header that tracks the current date (with a LaTeX command that sets &lt;code&gt;\markright&lt;/code&gt; with every message), I&amp;rsquo;m ready to put it all together.&lt;/p&gt;&#xA;&lt;p&gt;&lt;img src=&#34;images/headers.jpeg&#34; alt=&#34;&#34;&gt;&lt;/p&gt;&#xA;&lt;p&gt;When I finally compile all three years of messages that I want to be able to flip through, I&amp;rsquo;m surprised to find that the compiler dumps out well over a thousand pages of messages when I put them into a standard 6&amp;quot; x 9&amp;quot; page size. Since it&amp;rsquo;s exactly three years of messages anyway, though, there&amp;rsquo;s an easy solution: I split the opus into three volumes to get the size of each one down to something printable.&lt;/p&gt;&#xA;&lt;h1 id=&#34;ordering&#34;&gt;Ordering&lt;/h1&gt;&#xA;&lt;p&gt;When I decided to try to do this, I really wanted to end up with a physical book in my hand. So I had to figure out how to get these books printed. And to my surprise, printing a paperback book is quite cheap. After reviewing a bunch of options, &lt;a href=&#34;https://press.barnesandnoble.com/&#34;&gt;Barnes and Noble Press&lt;/a&gt; seems like the best option. It&amp;rsquo;s decently more expensive than some of the other options like Lulu and Amazon KDP, but most options are targeted at people that are trying to sell their books. B&amp;amp;N Press is too, but their story for personal books seems better than the others as you don&amp;rsquo;t need to &amp;ldquo;publish&amp;rdquo; your book to get it printed. And the price is still quite reasonable: I was able to print all three volumes, around 1300 pages total, for $30 including shipping.&lt;/p&gt;&#xA;&lt;p&gt;Before I can order books from my LaTeX-generated PDFs, the website tells me that the last step is to create covers. Upon uploading the body pages to B&amp;amp;N Press, the sites generates the dimensions required for the cover. Given these, I threw together a cover for each of the three volumes in Inkscape, which the website accepted without complaint.&lt;/p&gt;&#xA;&lt;p&gt;The B&amp;amp;N press website is not perfect: it generally is very slow, and while trying to place my order the checkout page was broken and wouldn&amp;rsquo;t show up for over 24 hours. But after that was fixed, ordering worked.&lt;/p&gt;&#xA;&lt;p&gt;And sure enough, after a couple weeks&amp;rsquo; wait, I had three actual books in hand. I flip through them regularly, and it is so much easier to revisit old conversations this way than trying to do so on my phone.&lt;/p&gt;&#xA;&lt;h1 id=&#34;create-your-own&#34;&gt;Create your own&lt;/h1&gt;&#xA;&lt;p&gt;The source code is in rough shape, and I haven&amp;rsquo;t packaged it as a cargo binary, but there&amp;rsquo;s not much of it. If you want to take a look or try for yourself, it&amp;rsquo;s available at &lt;a href=&#34;https://github.com/bkettle/message-book&#34;&gt;https://github.com/bkettle/message-book&lt;/a&gt;.&lt;/p&gt;&#xA;&lt;p&gt;&lt;img src=&#34;images/stack.jpeg&#34; alt=&#34;A stack of three paperback books, with pictures of a couple on the cover.&#34;&gt;&lt;/p&gt;&#xA;</description>
    </item>
    <item>
      <title>Building a Safer FIDO2 Key with Privilege Separation and WebAssembly</title>
      <link>https://benkettle.xyz/posts/plat/</link>
      <pubDate>Tue, 11 Jul 2023 15:33:43 -0500</pubDate>
      <guid>https://benkettle.xyz/posts/plat/</guid>
      <description>&lt;p&gt;FIDO2 is growing as a solution to the password hell we all know too well by&#xA;replacing passwords stored in brains with secret keys stored on dedicated&#xA;hardware security keys. This new protocol brings a huge increase in security and&#xA;in usability, but the security key poses an attractive new target for attackers.&#xA;For my Master&amp;rsquo;s thesis, I implemented a safer FIDO2 security key that I call&#xA;Plat. Plat is a new implementation of a FIDO2 security key that retrofits&#xA;privilege separation onto an existing codebase in order to prevent possible bugs&#xA;from compromising the security of the key. Plat uses a new WebAssembly-based&#xA;toolchain to create isolation domains on our embedded ARM platform while&#xA;minimizing the need to write new code. In addition&#xA;to isolating software-only libraries, Plat&amp;rsquo;s toolchain enables privilege&#xA;separation of drivers by controlling access to individual hardware peripherals.&lt;/p&gt;&#xA;&lt;p&gt;&lt;em&gt;This post talks about the work completed in my Master&amp;rsquo;s thesis. For more&#xA;details, you may want to look at &lt;a href=&#34;https://pdos.csail.mit.edu/papers/bkettle-meng.pdf&#34;&gt;the thesis&#xA;itself&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;&#xA;&lt;hr&gt;&#xA;&lt;h1 id=&#34;context&#34;&gt;Context&lt;/h1&gt;&#xA;&lt;p&gt;While passwords are in widespread usage due to their convenience and implementation&#xA;simplicity, they have a &lt;a href=&#34;https://dl.acm.org/doi/abs/10.1145/359168.359172&#34;&gt;long&#xA;history&lt;/a&gt; of security issues,&#xA;from weak passwords and common password reuse to phishing vulnerability and&#xA;server database leaks. FIDO2&amp;rsquo;s WebAuthn standard, which was formed by a group of tech&#xA;companies like Google and Apple and is quickly gaining momentum as an&#xA;authentication standard, aims to solve many of these issues by replacing&#xA;passwords with an automated public-key signature exchange. In doing so, FIDO2&#xA;removes the need for users to manually remember and enter passwords, and thus&#xA;eliminates many of passwords&amp;rsquo; inherent security risks, by reducing the security&#xA;of web-based authentication to the security of a user&amp;rsquo;s private key. There have&#xA;been previous attempts to replace passwords, but FIDO2 seems to be the one&#xA;that&amp;rsquo;s gaining steam: Apple has &lt;a href=&#34;https://support.apple.com/en-us/HT213305&#34;&gt;added authenticators to their&#xA;devices&lt;/a&gt; and Google &lt;a href=&#34;https://security.googleblog.com/2023/05/so-long-passwords-thanks-for-all-phish.html&#34;&gt;recently enabled&#xA;WebAuthn&lt;/a&gt;&#xA;as a password replacement for Google accounts.&lt;/p&gt;&#xA;&lt;p&gt;Though companies like Apple are embedding authenticators into their devices,&#xA;authentication via WebAuthn and its U2F predecessor today is often done via&#xA;physical security keys like the&#xA;&lt;a href=&#34;https://www.yubico.com/products/yubikey-5-overview/&#34;&gt;YubiKey&lt;/a&gt;. In addition to&#xA;a familiar lock-and-key mental model, these physical security keys can provide&#xA;strong security: since these devices focus on a single task, their codebase can&#xA;be much smaller than the millions of lines that comprise a modern PC&amp;rsquo;s operating&#xA;system, drivers, and applications, and&#xA;therefore can have far fewer bugs and vulnerabilities. However, security keys&#xA;and similar devices like hardware cryptocurrency wallets&#xA;&lt;a href=&#34;https://blog.inhq.net/posts/u2fhid_init_resp-information-leak/&#34;&gt;have&lt;/a&gt;&#xA;&lt;a href=&#34;https://blog.inhq.net/posts/trezor-one-dry-run-recovery-stack-overflow/&#34;&gt;still&lt;/a&gt;&#xA;&lt;a href=&#34;https://blog.inhq.net/posts/keepkey-CVE-2019-18671/&#34;&gt;had&lt;/a&gt;&#xA;&lt;a href=&#34;https://blog.inhq.net/posts/keepkey-CVE-2019-18671/&#34;&gt;many&lt;/a&gt;&#xA;&lt;a href=&#34;https://thecharlatan.ch/List-Of-Hardware-Wallet-Hacks/&#34;&gt;bugs&lt;/a&gt;. Careful review&#xA;can remove many of these bugs, as can replacing the C used in their source code&#xA;with a memory-safe language like Rust. But ultimately it is very difficult to&#xA;find and fix every bug in a system, leaving the door open to vulnerabilities.&#xA;Since authenticators have such high-value contents, they are a likely attack&#xA;target, and an exploit of a bug in an authenticator can compromise the account&#xA;security that users rely on these authenticators to provide.&lt;/p&gt;&#xA;&lt;p&gt;For my recently-completed Master&amp;rsquo;s thesis, I designed and implemented a security&#xA;key that I call Plat. Plat implements the same functionality as existing&#xA;security key, but uses &lt;em&gt;privilege separation&lt;/em&gt; to limit the damage that many&#xA;types of bugs can cause. Plat adds&#xA;this privilege separation to the open-source&#xA;&lt;a href=&#34;https://github.com/solokeys/solo1&#34;&gt;SoloKey&lt;/a&gt; codebase, using the same basic&#xA;hardware components (seen below on the breadboard I used to prototype) and&#xA;minimizing new code to be written.&lt;/p&gt;&#xA;&lt;figure&gt;&lt;img src=&#34;https://benkettle.xyz/posts/plat/images/breadboard.jpg&#34;&#xA;&#x9;&#x9;&#x9;alt=&#34;Plat&amp;#39;s hardware consists of an&#xA;STM32L432KC development board, a peripheral USB port, and a button.&#34;&gt;&#xA;&lt;/figure&gt;&#xA;&#xA;&lt;h2 id=&#34;threat-model--security-goals&#34;&gt;Threat Model &amp;amp; Security Goals&lt;/h2&gt;&#xA;&lt;p&gt;The security of the FIDO2 protocol relies fundamentally on the security of each&#xA;user&amp;rsquo;s &lt;em&gt;secret keys&lt;/em&gt;. If an attacker learns a user&amp;rsquo;s secret&#xA;key, they can forge the cryptographic signatures at the protocol&amp;rsquo;s core and&#xA;pretend to be the user. In order for the FIDO2 protocol to be usable, the risk&#xA;of secret key compromise must be low.&lt;/p&gt;&#xA;&lt;p&gt;Unfortunately, given the huge scale of modern PC software, computer compromises&#xA;are relatively common. If secret keys were simply stored in a regular program on&#xA;the user&amp;rsquo;s machine, a simple piece of malware would allow an attacker&#xA;permananent access to all of a user&amp;rsquo;s accounts.&lt;/p&gt;&#xA;&lt;p&gt;Implementing the authenticator as a standalone device like a&#xA;&lt;a href=&#34;https://www.yubico.com/products/how-the-yubikey-works/&#34;&gt;YubiKey&lt;/a&gt;&#xA;offers some protection against a compromised host PC: while an attacker who&#xA;compromises a user&amp;rsquo;s PC would be able to trivially read secret keys from an&#xA;authenticator implemented as an application, a &lt;em&gt;hardware&lt;/em&gt; authenticator that is&#xA;attached via a USB port can still enforce its own restrictions even when the&#xA;host PC is compromised. These security keys are typically designed to allow the&#xA;connected host PC to send signature requests to the key and, perhaps after a&#xA;required button press by a human, receive a signature in response. If the&#xA;security key works as intended, the security key should never reveal the secret&#xA;key to the host computer.&lt;/p&gt;&#xA;&lt;p&gt;But an attacker who compromises a host PC still gets significant power to interact&#xA;with a connected authenticator by sending arbitrary messages over USB&#xA;to the authenticator. If the authenticator includes bugs that are exploitable&#xA;via USB, an attacker could circumvent the protections that hardware security&#xA;keys aim to provide and get unrestricted access to a user&amp;rsquo;s accounts.&lt;/p&gt;&#xA;&lt;p&gt;Even if a user&amp;rsquo;s PC is compromised &lt;em&gt;and&lt;/em&gt; the authenticator code&#xA;includes bugs, Plat aims to ensure that these bugs cannot cause total&#xA;long-lasting account compromise. Specifically, we aim to ensure that the secret&#xA;key is not revealed and that each signature performed has the explicit approval&#xA;of the user.&lt;/p&gt;&#xA;&lt;h1 id=&#34;plats-approach-privilege-separation&#34;&gt;Plat&amp;rsquo;s Approach: Privilege Separation&lt;/h1&gt;&#xA;&lt;p&gt;Plat uses &lt;em&gt;privilege separation&lt;/em&gt; to limit the damage caused by many classes of&#xA;bugs and achieve these goals. Used in security-critical software like browsers,&#xA;privilege separation is a powerful technique that involves splitting a system&#xA;into several components and placing restrictions on each component that give it&#xA;permission to perform only actions that are strictly necessary for its function.&#xA;For effective privilege separation, each component must be placed in a &lt;em&gt;sandbox&lt;/em&gt;&#xA;that limits its interaction with other components and with the host environment&#xA;that coordinates all of the components. Except as explicitly allowed by the host&#xA;environment&amp;rsquo;s configuration, these sandboxed components should not be able to&#xA;affect the execution of anything outside of their sandbox: a component should&#xA;not be able to access memory not owned by that component, and a component should&#xA;not be able to jump to code that is not part of that same component. We call&#xA;these sandboxed components &lt;em&gt;modules&lt;/em&gt;.&lt;/p&gt;&#xA;&lt;figure&gt;&lt;img src=&#34;https://benkettle.xyz/posts/plat/images/overview.png&#34;&#xA;&#x9;&#x9;&#x9;alt=&#34;Plat places the USB Driver code and the FIDO2 logic/parsing code in their own modules and keeps the master secret, and cryptography functions that require access to it, in the trusted host.&#34;&gt;&#xA;&lt;/figure&gt;&#xA;&#xA;&lt;p&gt;The choice of the boundaries of these modules defines the security of a&#xA;privilege-separated system. With all code in a single module, privilege&#xA;separation gives no benefits&amp;mdash;code within a module is not protected from other&#xA;code within that same module. But with too-small modules, it may be necessary&#xA;for a module to expose a wide API, increasing its vulnerability exposure and&#xA;increasing overall complexity. To be effective, modules must be chosen to&#xA;encapsulate a given functionality with a narrow API, to meaningfully separate&#xA;components based on the privileges they require, and to separate bug-prone&#xA;code from the code we aim to protect.&lt;/p&gt;&#xA;&lt;p&gt;We expected most bugs to&#xA;reside in the most complex pieces of the codebase. First, the USB stack seemed&#xA;like a likely source of bugs: drivers historically have been responsible for&#xA;many bugs, including a &lt;a href=&#34;https://pdos.csail.mit.edu/papers/chen-kbugs.pdf&#34;&gt;majority of bugs in the Linux&#xA;kernel&lt;/a&gt;, and USB has been a&#xA;source of bugs &lt;a href=&#34;https://blog.trezor.io/details-of-security-updates-for-trezor-one-firmware-1-8-0-and-trezor-model-t-firmware-2-1-0-408e59dc012&#34;&gt;on embedded&#xA;devices&lt;/a&gt;&#xA;in the past. Parsers are also notoriously bug-prone, and we anticipated that the&#xA;FIDO2 processing logic, which includes a CBOR parsing library, may be another&#xA;source of bugs. As seen above, Plat places each of these bug-prone components&#xA;into their own module, restricting their access to hardware resources and to&#xA;sensitive cryptography state. This way, even if the USB stack or the CBOR parser&#xA;has a bug that allows an attacker to gain control of that module, the attacker&#xA;still will not be able to read out the secret key since the privilege separation&#xA;system does not provide this modules with any way to do so.&lt;/p&gt;&#xA;&lt;h2 id=&#34;isolating-modules-with-webassembly&#34;&gt;Isolating Modules with WebAssembly&lt;/h2&gt;&#xA;&lt;p&gt;We decided to implement the isolation that we needed using WebAssembly. While&#xA;WebAssembly, as the name implies, was designed to run in the browser, its&#xA;isolation guaratees are useful far beyond the web. In order to support the&#xA;execution of code shipped from an untrusted web server, Wasm is built around the&#xA;concept of sandboxed modules. The language ensures that WebAssembly code cannot&#xA;affect the host environment except through a clearly defined API. And unlike&#xA;other sandboxing and isolation solutions, WebAssembly does not rely on an&#xA;operating system to provide a process abstraction or on fancy processors to&#xA;provide page table hardware&amp;mdash;neither of which were an option for Plat.&lt;/p&gt;&#xA;&lt;p&gt;WebAssembly provides the isolation and OS-independence that we need for&#xA;privilege separation out of the box. Somewhat less straightforward was building&#xA;a system to build and run multiple WebAssembly modules to run together on the&#xA;ARM microprocessor that Plat is based on.&lt;/p&gt;&#xA;&lt;p&gt;Runtimes and compilers like &lt;a href=&#34;https://github.com/wasm3/wasm3&#34;&gt;wasm3&lt;/a&gt; and&#xA;&lt;a href=&#34;https://github.com/gwsystems/aWsm&#34;&gt;aWsm&lt;/a&gt; support ARM processors like the one we&#xA;use for Plat, but require including an interpreter in the case of aWsm or&#xA;working with generated binaries in the case of aWsm. Instead, we use a toolchain&#xA;similar to the one &lt;a href=&#34;https://hacks.mozilla.org/2021/12/webassembly-and-back-again-fine-grained-sandboxing-in-firefox-95/&#34;&gt;used in&#xA;Firefox&lt;/a&gt;&#xA;to sandbox untrusted libraries using &lt;code&gt;wasm2c&lt;/code&gt; and to add the features we need.&lt;/p&gt;&#xA;&lt;figure&gt;&lt;img src=&#34;https://benkettle.xyz/posts/plat/images/toolchain.png&#34;&#xA;&#x9;&#x9;&#x9;alt=&#34;Plat&amp;#39;s toolchain compiles&#xA;to-be-sandboxed C code into a WebAssembly module before using wasm2c to&#xA;convert it to C code that enforces the necessary sandboxing guarantees. We then&#xA;use the standard ARM GCC to generate a single final binary.&#34;&gt;&#xA;&lt;/figure&gt;&#xA;&#xA;&lt;p&gt;As seen above, Plat&amp;rsquo;s toolchain compiles C code into several WebAssembly modules&#xA;before translating them back into more C code with added safety checks to&#xA;preserve Wasm&amp;rsquo;s isolation guarantees. Unlike WebAssembly, C itself does not&#xA;include any safety checks or make any isolation guarantees. To preserve&#xA;isolation between modules, we trust &lt;code&gt;wasm2c&lt;/code&gt; to insert the necessary checks into&#xA;the generated C code. A nice benefit of this C-based toolchain is&#xA;that it makes linking to and between our modules very simple. For each function&#xA;that a module imports or exports, &lt;code&gt;wasm2c&lt;/code&gt; generates function declarations that&#xA;the host environment can then import as normal C functions&amp;mdash;no manual ABI&#xA;translation required. It also makes our compilation and linking process simple:&#xA;once we have the C code that &lt;code&gt;wasm2c&lt;/code&gt; generates, the final binary generation is&#xA;exactly the same as if we were compiling any monolithic C application.&lt;/p&gt;&#xA;&lt;p&gt;This toolchain allows us to create sandboxed modules from existing C code that&#xA;each have their own memory space and a statically defined set of jump targets,&#xA;ensuring that each module&amp;rsquo;s code escape its sandbox. Further, modules are able to import&#xA;and export functions to interact with each other and with functions defined in&#xA;the host environment. But the toolchain does not itself provide the safe access&#xA;to peripheral MMIO that we require for the USB driver or the safe access to&#xA;cryptography that the FIDO2 module needs.&lt;/p&gt;&#xA;&lt;h1 id=&#34;making-it-all-work&#34;&gt;Making It All Work&lt;/h1&gt;&#xA;&lt;p&gt;The set of constructs provided by WebAssembly means that it does not rely on&#xA;hardware support and thus makes it a compelling mechanism to implement privilege&#xA;separation in Plat. But, like any isolation strategy, it comes with some&#xA;challenges. Implementing Plat involved designing several new constructs to&#xA;enable passing of large arguments across module boundaries, to allow&#xA;sandboxing the USB driver that requires access to peripheral memory, and to&#xA;enable the FIDO2 module to perform signatures while ensuring that it cannot&#xA;learn, modify, or abuse the authenticator&amp;rsquo;s secret key.&lt;/p&gt;&#xA;&lt;h2 id=&#34;protecting-secret-state&#34;&gt;Protecting Secret State&lt;/h2&gt;&#xA;&lt;p&gt;Plat&amp;rsquo;s design aims to sandbox as much of the FIDO2 parsing and logic code&#xA;as possible in order to guarantee that bugs in that code cannot read, modify, or&#xA;use the secret key except as allowed. This is complicated by the fact that the&#xA;FIDO2 code necessarily needs to perform signatures and other cryptography using&#xA;this secret key. To address this, Plat&amp;rsquo;s design separates the core FIDO2 parsing&#xA;and logic from the code that accesses the secret key and uses Plat&amp;rsquo;s &lt;em&gt;State&#xA;Manager&lt;/em&gt; to ensure that the isolated FIDO2 code cannot learn the secret key.&lt;/p&gt;&#xA;&lt;p&gt;Plat separates the cryptography code into two pieces. Cryptography code that&#xA;does not require access to the secret data, such as simple SHA256 hashes, is&#xA;part of the sandboxed code. Cryptography functions that do use the secret key,&#xA;however, such as signature and HMAC code, run in the trusted host environment&#xA;and are available to the FIDO2 module only as an import. We call these functions&#xA;with access to the secret key &amp;ldquo;trusted crypto&amp;rdquo; functions, and their API is&#xA;designed to allow the module to learn signatures and MACs that &lt;em&gt;use&lt;/em&gt; the secret&#xA;key, but to prevent the module from learning the secret key itself. To get a signature over&#xA;the data required to perform a FIDO2 authentication, for example, the FIDO2&#xA;makes a call to &lt;code&gt;t_crypto_ecc256_sign()&lt;/code&gt; and recieves the signature in response.&#xA;The secret key is automatically supplied by the host environment without the&#xA;module ever seeing it.&lt;/p&gt;&#xA;&lt;p&gt;Placing the cryptography operations behind an API in this way also allows us to&#xA;place limits on when signatures can be calculated. Before computing a signature,&#xA;the trusted cryptography code will ensure that the user has pressed the&#xA;authenticator&amp;rsquo;s button to approve this signature and will increment the&#xA;signature counter.&lt;/p&gt;&#xA;&lt;p&gt;By storing the secret key in a separate memory region, we can ensure that&#xA;sandboxed code cannot access it while running. However, security keys are&#xA;designed to be unplugged and moved between computers frequently. To enable this,&#xA;the secret key is stored along with all other authenticator state in&#xA;persistent flash storage. Instead of allowing the FIDO2 module to access this&#xA;flash storage directly and potentially read or modify the secret key, Plat&amp;rsquo;s&#xA;state manager interposes on requests to read the state from flash and masks out&#xA;the secret key. As illustrated below, the state manager allows only the trusted&#xA;cryptography code to read the secret key from flash. The state manager also&#xA;provides the FIDO2 module with only read-only access to the signature counter,&#xA;ensuring that it cannot be corrupted by a bug in the module.&lt;/p&gt;&#xA;&lt;figure&gt;&lt;img src=&#34;https://benkettle.xyz/posts/plat/images/state_manager.svg&#34;&#xA;&#x9;&#x9;&#x9;alt=&#34;Plat&amp;#39;s state manager controls&#xA;access to the secret key&#34;&gt;&#xA;&lt;/figure&gt;&#xA;&#xA;&lt;p&gt;Sandboxing the majority of the FIDO2 code while leaving the trusted cryptography&#xA;functions in the trusted host, along with using the state manager to provide&#xA;safe access to persistent memory, enables Plat to perform all the cryptography&#xA;functionality required to perform its job while still ensuring that a bug in the&#xA;FIDO2 parsing and logic code cannot allow an attacker to learn the secret key.&lt;/p&gt;&#xA;&lt;h2 id=&#34;providing-safe-imports&#34;&gt;Providing Safe Imports&lt;/h2&gt;&#xA;&lt;p&gt;WebAssembly&amp;rsquo;s function call interface allows only integer arguments and return&#xA;types to imported and exported functions that cross the module boundary.&#xA;However, Plat needs to send more complex binary datatypes across the boundary.&#xA;For example, the USB stack sends and receives 64-byte packets, and the FIDO2 module requests&#xA;and receives signatures over arbitrary-length data blobs. To allow for&#xA;non-integer arguments and return types, Plat uses pointers.&lt;/p&gt;&#xA;&lt;p&gt;In order to preserve isolation, WebAssembly modules do not have direct access to&#xA;system memory. Instead, each WebAssembly module has a dedicated &amp;ldquo;linear memory&amp;rdquo;&#xA;region. It is not possible for a module to access memory outside of this&#xA;dedicated region. Because of this, pointers in the global&#xA;memory space&amp;mdash;that is, memory &lt;em&gt;addresses&lt;/em&gt;&amp;mdash;are not the same as pointers within&#xA;module memory&amp;mdash;we call these &lt;em&gt;offsets&lt;/em&gt;. Plat&amp;rsquo;s design includes machinery to&#xA;translate between host memory addresses and module memory offsets and vice&#xA;versa.&lt;/p&gt;&#xA;&lt;p&gt;But we must be careful not to break isolation when we do this translation. In&#xA;particular, it is not safe to blindly translate module-provided offsets into&#xA;host memory addresses and pass the addresses to host functions. For instance,&#xA;the FIDO2 module imports the function &lt;code&gt;usbhid_send&lt;/code&gt; to send packets to the host&#xA;PC. Thanks to our pointer-based approach, the function available inside the&#xA;module C code looks exactly like it would without any privilege separation at&#xA;all:&lt;/p&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;void usbhid_send(uint8_t * msg);&#xA;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;code&gt;wasm2c&lt;/code&gt;&amp;rsquo;s machinery generates a function declaration &lt;code&gt;Z_envZ_usbhid_send&lt;/code&gt; that&#xA;must be implemented by the host environment to provide the FIDO2 module with&#xA;this import. To provide this import, the host definition calls a translation&#xA;function &lt;code&gt;translateGuestAddr&lt;/code&gt;, which also runs in the host environment, to&#xA;convert the guest offset provided by the module into a physical memory address.&#xA;With the physical memory address of the data the module intends to pass as an&#xA;argument, the host definition then calls the underlying function call with the&#xA;true physical memory address of the argument. (In this case, &lt;code&gt;usbhid_send&lt;/code&gt; is&#xA;routed eventually to the USB module, but that does not matter for this example.)&lt;/p&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;void Z_envZ_usbhid_send(struct Z_env_instance_t* env, u32 msgOffset) {&#xA;    uint8_t * msgAddr = translateGuestOffset(env, msgOffset, PKT_BUF_SIZE)&#xA;    return usbhid_send(msgAddr);&#xA;}&#xA;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;If this translation is naive and does not consider the length of module memory,&#xA;an attacker who gains control of the FIDO2 sandbox could use this to read out&#xA;the secret key! By manipulating the &lt;code&gt;msgOffset&lt;/code&gt; argument to extend beyond the&#xA;end of module memory so that the resulting &lt;code&gt;msgAddr&lt;/code&gt; points to the beginning of&#xA;the secret key, the attacker can cause the secret key to be sent over USB to the&#xA;compromised host PC.&lt;/p&gt;&#xA;&lt;p&gt;To avoid this type of sandbox escape, the &lt;code&gt;translateGuestOffset&lt;/code&gt; function is&#xA;carefully written to ensure that the offset provided by the module actually&#xA;falls within the bounds of the module&amp;rsquo;s memory. For operations that will read&#xA;multiple bytes, the translation function also takes the maximum number of bytes&#xA;to be accessed (&lt;code&gt;PKT_BUF_SIZE&lt;/code&gt; above) and verifies that the entire region that could&#xA;be accessed is within module memory. By using this&#xA;&lt;code&gt;translateGuestOffset&lt;/code&gt; every time a module offset needs to be converted to a&#xA;physical memory address, we ensure that all translations are checked and that a&#xA;module cannot trick an imported function into violating the sandbox on its&#xA;behalf. This technique is used for every import with blob-type arguments in Plat.&lt;/p&gt;&#xA;&lt;h2 id=&#34;sandboxing-device-drivers&#34;&gt;Sandboxing Device Drivers&lt;/h2&gt;&#xA;&lt;p&gt;Since drivers are common sources of bugs, we sought to sandbox the USB driver.&#xA;But drivers typically require bare-metal access to the hardware: their job is to&#xA;provide an abstraction over the memory-mapped IO provided by the peripheral.&lt;/p&gt;&#xA;&lt;p&gt;However, the only memory accessible to a module directly is the module&amp;rsquo;s&#xA;dedicated linear memory region. When module code dereferences a&#xA;pointer, the pointer&amp;rsquo;s address is interpreted as an offset into this memory&#xA;region and the dereference fails if the offset falls outside of the allocated&#xA;memory region. This memory isolation is an integral part of WebAssembly&amp;rsquo;s&#xA;sandboxing plan, but precludes MMIO access by drivers via WebAssembly load and&#xA;store instructions: an attempted access to a specific MMIO address would be&#xA;converted into nonsense &lt;em&gt;and&lt;/em&gt; prohibited by WebAssembly&amp;rsquo;s guarantees.&lt;/p&gt;&#xA;&lt;p&gt;Instead, Plat provides each module with access to what we call the &lt;em&gt;peripheral&#xA;proxy&lt;/em&gt;. This peripheral proxy comprises a set of imported functions&#xA;that allow a module to access an explicitly defined set of physical memory&#xA;addresses. For example, if the driver needs to read or write a 32-bit word to an MMIO&#xA;register, it can use the following functions:&lt;/p&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;uint32_t Z_envZ_memReadWord(Z_env_instance_t* env, uint32_t addr) {&#xA;  if (checkAddr(env, addr))&#xA;    return (uint32_t) *(uint32_t *) addr;&#xA;  trap();&#xA;  return 0;&#xA;}&#xA;&#xA;uint32_t Z_envZ_memWriteWord(Z_env_instance_t* env, uint32_t addr, uint32_t value) {&#xA;  if (checkAddr(env, addr)) {&#xA;    *((uint32_t *) addr = value;&#xA;    return 0;&#xA;  }&#xA;  trap();&#xA;  return 0;&#xA;}&#xA;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Parallel functions exist for 8-bit and 16-bit widths. Importantly, these functions first&#xA;check that the module is allowed to access the given &lt;code&gt;addr&lt;/code&gt; and then, if&#xA;permitted, perform the requested read or write. This permission check is of&#xA;crucial importance: if we omitted the permission check and simply allowed the&#xA;module to read and write an arbitrary physical memory address, the sandboxed&#xA;driver could escape its sandbox by reading secret state or overwriting memory to hijack control&#xA;flow of the host environment. Plat&amp;rsquo;s peripheral proxy inspects the&#xA;module-provided address before accessing it to ensure that the address&#xA;corresponds to the peripherals that the driver is responsible for. To minimize&#xA;the opportunity for errors and to ease development, Plat includes infrastructure&#xA;to generate the &lt;code&gt;checkAddr&lt;/code&gt; function that performs this check from a YAML file&#xA;that specifies the individual peripheral memory regions and the modules that are&#xA;allowed to read each:&lt;/p&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;device-classes:&#xA;  - name: RCC&#xA;    allowed-regions:&#xA;    - start: 0x4002_1000&#xA;      end: 0x4002_13FF&#xA;  - name: PWR&#xA;    allowed-regions:&#xA;    - start: 0x4000_7000&#xA;      end: 0x4000_73FF&#xA;  - name: USB&#xA;    allowed-regions:&#xA;    - start: 0x4000_6800&#xA;      end: 0x4000_6BFF&#xA;    - start: 0x4000_6C00&#xA;      end: 0x4000_6FFF&#xA;&#xA;modules:&#xA;  - name: USBHID&#xA;    allowed-classes:&#xA;    - USB&#xA;    - RCC&#xA;    - PWR&#xA;  - name: FIDO2&#xA;    allowed-classes: []&#xA;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;From this specification, Plat&amp;rsquo;s infrastructure generates the &lt;code&gt;checkAddr&lt;/code&gt;&#xA;function definition, which simply translates the regions that each module is&#xA;allowed to access into a series of conditionals that return &lt;code&gt;true&lt;/code&gt; if an access&#xA;is permitted.&lt;/p&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;bool checkAddr(struct Z_env_instance_t * env, uint32_t addr) {&#xA;  switch (env-&amp;gt;module_class) {&#xA;    case MODULE_CLASS_USBHID: &#xA;    return (&#xA;      // USB&#xA;      (addr &amp;gt;= 0x40006800 &amp;amp;&amp;amp; addr &amp;lt; 0x40006bff) ||&#xA;      (addr &amp;gt;= 0x40006c00 &amp;amp;&amp;amp; addr &amp;lt; 0x40006fff) ||&#xA;      // RCC&#xA;      (addr &amp;gt;= 0x40021000 &amp;amp;&amp;amp; addr &amp;lt; 0x400213ff) ||&#xA;      // PWR&#xA;      (addr &amp;gt;= 0x40007000 &amp;amp;&amp;amp; addr &amp;lt; 0x400073ff) &#xA;    );&#xA;    break;&#xA;    case MODULE_CLASS_FIDO2: &#xA;    return (&#xA;      false&#xA;    );&#xA;    break;&#xA;    default:&#xA;    return false;&#xA;    break;&#xA;  }&#xA;}&#xA;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Plat does not permit any peripheral access for the FIDO2 module, which performs&#xA;only parsing and logic, but does permit access to the USB, PWR, and RCC&#xA;peripheral memory for the USB module, which includes the entire USB stack. PWR&#xA;and RCC, which control power and clock gating to all of the chip&amp;rsquo;s peripherals,&#xA;is necessary to allow the USB driver to configure the USB peripheral on startup.&#xA;A future iteration could allow the specification of bit-level access control&#xA;over these shared configuration regions to ensure that the USB module can&#xA;access only the fields of the RCC and PWR registers that correspond to the USB&#xA;peripheral.&lt;/p&gt;&#xA;&lt;p&gt;Plat&amp;rsquo;s peripheral proxy allows us to adapt existing device drivers to work in&#xA;a sandboxed environment. To adapt the standard USB driver used in the original&#xA;Solokey code to work in our sandbox, the only change necessary is to replace&#xA;peripheral memory accesses with calls to the imported peripheral proxy functions.&lt;/p&gt;&#xA;&lt;p&gt;This setup allows Plat to sandbox its complex USB device driver, ensuring that&#xA;bugs in the driver and USB stack cannot compromise the security of the entire&#xA;security key.&lt;/p&gt;&#xA;&lt;h1 id=&#34;conclusion&#34;&gt;Conclusion&lt;/h1&gt;&#xA;&lt;p&gt;Plat&amp;rsquo;s privilege separation approach allows it to preserve the confidentiality&#xA;and integrity of the secret key and prevent signature abuse even in the presence&#xA;of exploitable bugs in the bug-prone code contained in the FIDO2 and USB&#xA;modules. While Plat does not protect the system from all bugs, it does provide&#xA;strong security guarantees for many likely classes of bugs, and it does so with&#xA;reasonable performance overhead: the slowest step of an authentication&#xA;increases from 277ms to 600ms. For an authentication operation that a user&#xA;performs a few times a day at most, this increase is insignificant (not to&#xA;mention that many users today spend several minutes trying to guess the password&#xA;they used for each service).&lt;/p&gt;&#xA;&lt;p&gt;Privilege separation has the capability to convert devastating bugs into&#xA;trivial ones by means of &amp;ldquo;damage control&amp;rdquo;, but is difficult to implement&#xA;effectively. Plat explores privilege separation in the new context of embedded&#xA;devices, addressing the unique challenges that come with bare-metal execution&#xA;and demonstrating that for many applications the overhead that comes with&#xA;sandboxing is reasonable. We hope to see more systems adopt privilege separation&#xA;as an additional layer of defense and hope for improved tools to make it easy to&#xA;do so in the future.&lt;/p&gt;&#xA;&lt;figure&gt;&lt;img src=&#34;https://benkettle.xyz/posts/plat/images/transitville.jpg&#34;&#xA;&#x9;&#x9;&#x9;alt=&#34;An example of a Plat map.&#34;&gt;&#xA;&lt;/figure&gt;&#xA;&#xA;</description>
    </item>
  </channel>
</rss>