Skip to content

Keep plugin directories inside the top directory through links - #3489

Merged
xirvik merged 4 commits into
Novik:masterfrom
xirvik:fix/plugin-directories-top-directory-links
Oct 30, 2026
Merged

xirvik merged 4 commits into
Novik:masterfrom
xirvik:fix/plugin-directories-top-directory-links

Conversation

@xirvik

@xirvik xirvik commented Oct 30, 2026

Copy link
Copy Markdown
Collaborator

Why

#3488 fixed datadir; #3474 also names autotools, unpack and create. Each checked its directory with correctDirectory(), which compares it as written, while the operations follow symbolic links. A link inside $topDirectory that points out of it had finished downloads moved, archives extracted, files hashed (and, once seeded, served) and watched torrents loaded and removed wherever it pointed. httprpc's setsettings had the same gap for rTorrent's default directory, where downloads added without a directory of their own are written.

What changes

Each directory is checked with rTorrent::isInsideTopDirectory() where it is used rather than only where it is saved, since a link can be made after the settings are: autotools' check.php, move.php and watch.php (watch directory, torrents reached through a link, data directory); unpack's final output directory in the automatic and both manual unpacks; create's path; httprpc's sdirectory. Torrent::scandir() leaves out a link that resolves outside the top directory. A top directory of / and a remote rTorrent are unchanged.

How to test

cd tests && bash php-test.sh. tests/plugins/{httprpc/HttprpcSetDirectoryTest,unpack/UnpackTopDirectoryTest,create/CreateTopDirectoryTest,autotools/AutoToolsTopDirectoryTest}.php build a real tree with a link out, a link that stays inside and a dangling link, and drive the plugins' own scripts; each fails without its fix, and the inside cases pass either way.

Closes #3474.

setsettings checked sdirectory with correctDirectory(), which compares
the directory as written. A download added without a directory of its
own is written to rtorrent's default directory through any symbolic
link along it, so a link inside $topDirectory that points out of it had
those downloads written wherever it pointed.

sdirectory now also has to pass rTorrent::isInsideTopDirectory(), as
add directories do; a refused value is skipped as before. The XML-RPC
proxy already resolves links for directory.default.set.
The output directory was checked with correctDirectory(), which compares
it as written, and only when it came from the plugin's settings. unrar
and unzip write through any symbolic link along it, so a link inside
$topDirectory that points out of it had archives extracted wherever it
pointed, and a download's own directory reached through such a link was
not checked at all.

The final directory -- the setting with the label and name added, or
the download's own directory -- now has to pass
rTorrent::isInsideTopDirectory() before the extraction is started, in
the automatic unpack and in both manual ones. A manual unpack that is
refused says so.
create checked the path it hashes with correctDirectory(), which
compares it as written, and Torrent::scandir() followed every symbolic
link in a folder except those back into the folder itself. A link inside
$topDirectory that points out of it -- the path itself, or a link inside
a folder -- had the file it points at hashed, and with "start seeding"
served by rtorrent.

action.php now also requires the path to pass
rTorrent::isInsideTopDirectory() and answers "Incorrect directory" as
before, and Torrent::scandir() leaves out a link that resolves outside
the top directory. A top directory of "/" is unchanged.
The finished and watch directories were checked with
correctDirectory(), which compares them as written, and only when the
settings were saved. The move, the scan, the touch and the load follow
symbolic links, so a link inside $topDirectory that points out of it,
made at any time, had finished downloads moved and torrents loaded and
removed wherever it pointed.

The directories are now checked with rTorrent::isInsideTopDirectory()
where they are used: check.php leaves a download where its data is when
its destination resolves outside the top directory, move.php does not
move, copy or link there, and watch.php skips a watch directory, a
torrent reached through a link and a data directory that resolve
outside it.
@xirvik
xirvik merged commit 2446f48 into Novik:master Oct 30, 2026
10 checks passed
@xirvik
xirvik deleted the fix/plugin-directories-top-directory-links branch October 30, 2026 01:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Authenticated symlink jail escape in the datadir plugin (arbitrary file relocation outside topDirectory)

1 participant