Security: POSIX storage follows symlinks out of the storage root (read escape) #490
Labels
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference: Cordy/Cairn#490
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Status: verified at runtime against cairnd built from
bd006ef(posix driver, local auth, per-user homes). The affected code is unchanged onmain(e1c855d).What happens
A symbolic link inside the POSIX storage root is followed even when it points outside the root, and WebDAV serves the target's content.
echo outside-the-storage-root > /tmp/x/outside.txtln -s /tmp/x/outside.txt <storage.root>/home/<user>/link.txtcurl -u <user>:<pw> http://127.0.0.1:<port>/dav/home/link.txt→ HTTP 200, bodyoutside-the-storage-rootA
PUTto the same path (204) replaced the link with a regular file rather than writing through it, so this is a read escape, not a write escape.Where (line numbers at
bd006ef)internal/storage/posix/posix.go:45–56—resolveis purely lexical (noLstat/EvalSymlinks), despite the comment at:43–44;os.Stat/os.Openthen follow links.internal/storage/posix/posix.go:98— listings useDirEntry.Info()(Lstat semantics) whileStatfollows the link, so Depth 1 and Depth 0 disagree on type, size and ETag for the same entry.Impact
Anyone who can place a symlink inside the storage root — a shared or exported volume, another sync tool that preserves links, shell access, an archive extraction — can read any file the cairnd process can read (its config,
app-passwords.json, other users' homes) through WebDAV and every other API that opens files.Suggested fix
Open everything through
os.Root(Go ≥ 1.24; Cairn's go.mod is 1.25), which refuses to follow links out of the root — orLstateach segment and refuse links. Decide whether links inside the root are listed at all (cairn-desktop skips them). Add a test with a link pointing outside the root.Found while building the cairn-desktop WebDAV/tus client (phase 2, Cordy/cairn-desktop#29). Filed by Claude on behalf of @Cordy.
Fixed in v0.6.166 (PR #501, live on the dogfood).
The posix driver is rebuilt on
os.Rootas suggested: every operation resolves through the kernel's beneath-only semantics, so no path — lexical or via symlink — can leave the storage root. Policy decision on in-root links (the open question in the issue): links are not data. They are never listed (Listskips them), andStat/Open/Copyon a link answerErrNotFound, matching cairn-desktop's behaviour of skipping them.os.Rootescape errors also map toErrNotFoundso nothing about the outside filesystem is leaked. The Depth 0/Depth 1 disagreement is gone as a side effect — both now use Lstat semantics.Tests added (all witnessed red against the old driver first): out-of-root file link served-then-refused, out-of-root directory link mid-path, in-root link refused while the real file stays served.
SizeByPrefixnow walks via the driver's ownList, so the same policy applies to the space-size sweep.A
PUTonto a link path still replaces the link with a regular file (rename over the leaf), matching what you observed — writes never pass through a link.