Skip to content

v0.3.2

Choose a tag to compare

@aaldersondev aaldersondev released this 07 Aug 13:00
· 23 commits to main since this release

A server owner could write files anywhere on the host, as root. This closes it.

The hole

The file jail probed for an existing path with access(2), which follows symlinks — so it answers ENOENT for a link whose target does not exist. A dangling link was therefore filed as a free name, the path came back unresolved with the link still on it, and the write that followed walked the link and created the target.

Planting one costs a server owner a single command from inside their own container, and nothing had to be timed:

ln -s /etc/cron.d/backdoor /home/container/notes.txt

The daemon writes as root.

What changed

Four mechanisms, none sufficient alone:

  • the path walk probes with lstat and follows a link chain by hand, so a name is seen for what it is whether or not its target exists;
  • every open passes O_NOFOLLOW, which moves the refusal into open(2) itself — a check describes the filesystem as it was, and the server's own process keeps running;
  • the descriptor is read back through /proc/self/fd and rejected if it landed outside the volume, which catches a parent directory swapped for a link;
  • lchown, lutimes and an fchmod through a vetted descriptor replace the name-based calls that followed links on their way.

Reads go through the same door. The dangling variant does not apply there, but the raced one does and is worth more to an attacker: win it and the daemon opens the link's target as root and streams it back with a 200.

The install and ownership-reclaim containers, which ran as uid 0 with Docker's full default capability set, now keep only the seven a package manager and a chown -R demonstrably need.

Upgrading

From the panel: Settings → Update now. Nothing changes for existing servers, and no template or configuration needs editing.

If you run a public instance

Every server owner on your node could reach this. Updating is the whole fix; there is nothing to clean up afterwards unless you have reason to think it was used, in which case the daemon's log records every refused path under "File access refused by the jail".