<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Tmp · XB Field Notes</title><link>https://www.xbcatm.com/tags/tmp/</link><description>Illustrated field notes on HTTP, DNS, TLS, Caddy, SSH, releases, and recovery.</description><generator>Hugo</generator><language>en-us</language><atom:link href="https://www.xbcatm.com/tags/tmp/index.xml" rel="self" type="application/rss+xml"/><item><title>Temp directories are a shared space</title><link>https://www.xbcatm.com/posts/temp-directories-are-a-shared-space/</link><pubDate>Sun, 26 Sep 2027 00:00:00 +0000</pubDate><guid>https://www.xbcatm.com/posts/temp-directories-are-a-shared-space/</guid><description>The world can usually write to the temp directory, so filenames there can collide or be predicted. Treat anything you place under tmp as public, short-lived data.</description><content:encoded>&lt;p&gt;&lt;code&gt;/tmp&lt;/code&gt; is a shared namespace of names, not a private scratch area. Any local user can create the name your process is about to use, so a name is not a claim on a file, and hier(7) says the contents may be deleted with no notice.&lt;/p&gt;
&lt;p&gt;The sticky bit makes the directory safe to share, not safe to use predictably. The fix is therefore not a permission change but the shape of the syscall: one call that names and creates the file, with nothing in between for anyone else to take.&lt;/p&gt;
&lt;h2 id="1777-is-what-makes-sharing-possible-at-all"&gt;1777 is what makes sharing possible at all&lt;/h2&gt;
&lt;p&gt;Mode 1777 is world-writable plus the sticky bit, and on this host that is not hypothetical: &lt;code&gt;/tmp&lt;/code&gt; is a symlink to &lt;code&gt;private/tmp&lt;/code&gt;, which is &lt;code&gt;drwxrwxrwt&lt;/code&gt;. Without the sticky bit, any writer could unlink or rename anyone else&amp;rsquo;s file; with it, only the file&amp;rsquo;s owner, the directory&amp;rsquo;s owner or root can. That single bit is why &lt;code&gt;/tmp&lt;/code&gt; is usable by many mutually untrusting users at once.&lt;/p&gt;
&lt;p&gt;What the sticky bit does not do is protect the name before the file exists. &lt;code&gt;open(&amp;quot;/tmp/svc.log&amp;quot;, &amp;quot;w&amp;quot;)&lt;/code&gt; follows a symlink that is already there, so the write lands wherever the link points. The fixture verified that with a real &lt;code&gt;open(2)&lt;/code&gt;, then verified the two refusals: &lt;code&gt;O_CREAT|O_EXCL&lt;/code&gt; fails with EEXIST because the name is taken, and &lt;code&gt;O_CREAT|O_NOFOLLOW&lt;/code&gt; fails with ELOOP because the final component is a symlink.&lt;/p&gt;
&lt;h2 id="the-mode-of-a-file-you-did-not-create-is-not-yours"&gt;The mode of a file you did not create is not yours&lt;/h2&gt;
&lt;p&gt;The second half of the same bug is subtler: no symlink, no visible race. &lt;code&gt;O_CREAT&lt;/code&gt; applies its mode only when it creates the file, so a pre-created world-readable file keeps 0666 when you open it with mode 0600, and the secret written into it stays world-readable. The fixture reproduced that with a real &lt;code&gt;stat(2)&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The fix is one call that names and creates atomically. mkstemp(3) documents the contract: the last six characters of the template are &lt;code&gt;XXXXXX&lt;/code&gt;, the file is created with permissions 0600, and it is opened with &lt;code&gt;O_EXCL&lt;/code&gt;, guaranteeing the caller created it. mktemp(1) does the same from the shell, honouring &lt;code&gt;TMPDIR&lt;/code&gt; and defaulting the template to &lt;code&gt;tmp.XXXXXXXXXX&lt;/code&gt;. Its &lt;code&gt;-u&lt;/code&gt; flag prints a name without creating anything, and the manual labels it &amp;ldquo;(unsafe)&amp;rdquo;; the fixture confirmed the name was still free afterwards, which is exactly the window mkstemp closes. For a directory rather than a file, &lt;code&gt;mkdtemp(3)&lt;/code&gt; applies the same rule with mode 0700 in one call.&lt;/p&gt;
&lt;h2 id="isolation-and-what-it-costs"&gt;Isolation, and what it costs&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;TMPDIR&lt;/code&gt; is the cheapest isolation available: the fixture showed &lt;code&gt;tempfile.gettempdir()&lt;/code&gt; follows it, and this host already gives each user a private temp directory. systemd&amp;rsquo;s &lt;code&gt;PrivateTmp=&lt;/code&gt; does the same per unit, setting up a filesystem namespace whose &lt;code&gt;/tmp&lt;/code&gt; and &lt;code&gt;/var/tmp&lt;/code&gt; are not shared with processes outside it and whose files are removed when the service stops. The same page is honest about the price: it makes sharing via &lt;code&gt;/tmp&lt;/code&gt; or &lt;code&gt;/var/tmp&lt;/code&gt; impossible, and the implementation can be unavailable, so a unit should not rely on it alone for security.&lt;/p&gt;
&lt;p&gt;A tmpfs &lt;code&gt;/tmp&lt;/code&gt; adds an unrelated failure. tmpfs defaults to half of RAM, and those pages are RAM, so a runaway build cache that fills &lt;code&gt;/tmp&lt;/code&gt; makes the next service&amp;rsquo;s 4 KiB &lt;code&gt;mkstemp&lt;/code&gt; fail with ENOSPC even though its own data directory is empty. The service that fails is not the one that filled the directory, and every byte parked in &lt;code&gt;/tmp&lt;/code&gt; is memory the rest of the machine cannot have.&lt;/p&gt;
&lt;h2 id="when-it-breaks"&gt;When it breaks&lt;/h2&gt;
&lt;p&gt;The first failure is the predictable name, presenting as a file that turns out to be a symlink, or as a secret that ends up world-readable. The audit is mechanical: look for fixed paths under &lt;code&gt;/tmp&lt;/code&gt;, and for &lt;code&gt;tmpnam&lt;/code&gt;/&lt;code&gt;tempnam&lt;/code&gt;, which return a name before any file exists. Replace them with &lt;code&gt;mkstemp&lt;/code&gt;/&lt;code&gt;mkdtemp&lt;/code&gt;, or &lt;code&gt;tempfile.mkstemp(prefix=..., dir=...)&lt;/code&gt;, and remember that a crashed process leaves its file behind, so clean up in a &lt;code&gt;finally&lt;/code&gt; block rather than on the happy path.&lt;/p&gt;
&lt;p&gt;The second failure is the shared &lt;code&gt;/tmp&lt;/code&gt; that takes down a bystander. Because a tmpfs &lt;code&gt;/tmp&lt;/code&gt; has a size cap unrelated to the disk, one service&amp;rsquo;s abandoned temp file makes another service&amp;rsquo;s &lt;code&gt;mkstemp&lt;/code&gt; fail with ENOSPC while its own storage is empty. That blast radius makes it worth monitoring as a service-level dependency, and writers capable of producing large files belong under a real path or &lt;code&gt;$XDG_STATE_HOME&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id="check-it-quickly"&gt;Check it quickly&lt;/h2&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;stat -c &amp;#39;%A %a %n&amp;#39; /tmp
ls -ld &amp;#34;$TMPDIR&amp;#34;
python3 -c &amp;#39;import tempfile,os;fd,p=tempfile.mkstemp(prefix=&amp;#34;probe-&amp;#34;);print(p,oct(os.stat(p).st_mode &amp;amp; 0o777));os.close(fd)&amp;#39;
mktemp; mktemp -u
grep -rn &amp;#34;tmpnam\|tempnam\|/tmp/[a-z]&amp;#34; --include=&amp;#39;*.py&amp;#39; --include=&amp;#39;*.c&amp;#39; --include=&amp;#39;*.sh&amp;#39; . | head
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The &lt;code&gt;stat&lt;/code&gt; line should show &lt;code&gt;drwxrwxrwt&lt;/code&gt; with mode &lt;code&gt;1777&lt;/code&gt;; a mode without the sticky bit is a shared directory where any user can delete files. The &lt;code&gt;mkstemp&lt;/code&gt; line should print a random name and &lt;code&gt;0o600&lt;/code&gt;, so a &lt;code&gt;0o644&lt;/code&gt; or a fixed name means the code is not using the atomic API. &lt;code&gt;mktemp&lt;/code&gt; creates a file while &lt;code&gt;mktemp -u&lt;/code&gt; only prints a name, so finding &lt;code&gt;-u&lt;/code&gt; in a script is finding the race. The &lt;code&gt;grep&lt;/code&gt; audits fixed temp paths.&lt;/p&gt;
&lt;h2 id="sources"&gt;Sources&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://man7.org/linux/man-pages/man1/mktemp.1.html"&gt;mktemp(1)&lt;/a&gt;, the default template, TMPDIR handling, and the -u dry run that the manual labels unsafe, checked 2026-09-16.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://man7.org/linux/man-pages/man3/mkstemp.3.html"&gt;mkstemp(3)&lt;/a&gt;, the XXXXXX template, the 0600 creation mode, and the O_EXCL guarantee, checked 2026-09-16.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://man7.org/linux/man-pages/man7/hier.7.html"&gt;hier(7)&lt;/a&gt;, /tmp as temporary files that may be deleted with no notice, checked 2026-09-16.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://man.archlinux.org/man/systemd.exec.5.en"&gt;systemd.exec(5)&lt;/a&gt;, PrivateTmp= namespaces and the sharing it makes impossible, checked 2026-09-16.&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item></channel></rss>