Torrent enclosures write with overwrite enabled straight into a folder other feeds share, so one publisher can replace another feed's downloaded episodes #331

Closed
opened 2026-10-23 05:26:49 -07:00 by rays · 1 comment
Owner

Found by the security-audit skill (run-1, 2026-10-20). Source-read only: nothing was run, so this is a lead that still needs its local check, not a confirmed finding. Two independent verifiers did not disprove it.

Torrents::fetch adds every torrent with output_folder = dest_dir and overwrite: true (torrent.rs:64-67). Because output_folder is given, librqbit 9.0.1 uses it as is and adds no per-torrent subfolder (session.rs:1285-1291). With allow_overwrite, its filesystem storage opens each torrent file at output_folder/ with create(true), truncate(false), read(true), write(true) (storage/filesystem/fs.rs:141-148), the initializing phase resizes it to the torrent's length (initializing.rs:275) and checks the existing pieces, and the pieces that do not match are downloaded and written over the existing bytes. dest_dir is download_dir/folder_for(...) (main.rs:2333-2334): under the default organize = feed it is the sanitised feed title unless a folder override is set, and the title comes from the publisher's feed XML, so a publisher (or a household user adding their own feed) can give a feed the same title as another; under organize = date it is one MM-DD-YYYY folder shared by every feed that day. A torrent whose file names equal another feed's downloaded episode therefore rewrites that episode's bytes in place, and other subscribers then play the replaced content. HTTP downloads are protected by unique_path (download.rs:257-271); torrents have nothing like it. The recorded path of the attacker's torrent is dest_dir.join(name) (torrent.rs:106-107), the same file, so deleting or reaping the attacker's episode would also remove the victim's file. Torrents are enabled by default (config.rs:224). Single-file and per-file torrent paths cannot traverse, so this stays within download_dir; the separate multi-file info.name issue is a different root cause.

Claimed root cause. torrent.rs:66 sets overwrite: true and torrent.rs:65 gives each torrent the shared feed folder as its output folder with no subfolder of its own, while folder_for (download.rs:278-291) lets different feeds resolve to the same directory. Nothing makes a torrent's files unique or refuses to open an existing file the way unique_path does for HTTP downloads.

Local check. {'local': "In a sandboxed scratch daemon with loopback only and a scratch download_dir: feed A (HTTP enclosure, title 'Show') downloads victim.mp3 into download_dir/Show; record its SHA-256. Feed B, a second fixture feed also titled 'Show', offers a single-file torrent named victim.mp3 with different bytes, seeded by a local librqbit or another client on loopback. Download B's enclosure, wait for the torrent to finish, and compare the SHA-256 of download_dir/Show/victim.mp3 with A's original and check A's and B's enclosure rows point at the same path. A changed hash confirms it. Regression test: a torrent whose files would land on an existing file is refused or given its own subfolder (e.g. sub-directory per enclosure, or overwrite only when resuming the same info hash).", 'deployment': 'Owner-observed and non-destructive: check whether production sets organize = date or has two subscribed feeds whose sanitised titles collide (SELECT title, count() FROM feeds GROUP BY title HAVING count() > 1, read only), and whether any torrent enclosure is present; do not seed an attacking torrent against production.'}

Owner check (no probing). Owner-observed and non-destructive: check whether production sets organize = date or has two subscribed feeds whose sanitised titles collide (SELECT title, count() FROM feeds GROUP BY title HAVING count() > 1, read only), and whether any torrent enclosure is present; do not seed an attacking torrent against production.

Full detail: /config/security-audit-skill/ipx/run-1/NEEDS-VALIDATION.md (fingerprint ipx:torrent.rs:fetch:overwrite-shared-folder).

Found by the security-audit skill (run-1, 2026-10-20). Source-read only: nothing was run, so this is a lead that still needs its local check, not a confirmed finding. Two independent verifiers did not disprove it. Torrents::fetch adds every torrent with output_folder = dest_dir and overwrite: true (torrent.rs:64-67). Because output_folder is given, librqbit 9.0.1 uses it as is and adds no per-torrent subfolder (session.rs:1285-1291). With allow_overwrite, its filesystem storage opens each torrent file at output_folder/<relative path> with create(true), truncate(false), read(true), write(true) (storage/filesystem/fs.rs:141-148), the initializing phase resizes it to the torrent's length (initializing.rs:275) and checks the existing pieces, and the pieces that do not match are downloaded and written over the existing bytes. dest_dir is download_dir/folder_for(...) (main.rs:2333-2334): under the default organize = feed it is the sanitised feed title unless a folder override is set, and the title comes from the publisher's feed XML, so a publisher (or a household user adding their own feed) can give a feed the same title as another; under organize = date it is one MM-DD-YYYY folder shared by every feed that day. A torrent whose file names equal another feed's downloaded episode therefore rewrites that episode's bytes in place, and other subscribers then play the replaced content. HTTP downloads are protected by unique_path (download.rs:257-271); torrents have nothing like it. The recorded path of the attacker's torrent is dest_dir.join(name) (torrent.rs:106-107), the same file, so deleting or reaping the attacker's episode would also remove the victim's file. Torrents are enabled by default (config.rs:224). Single-file and per-file torrent paths cannot traverse, so this stays within download_dir; the separate multi-file info.name issue is a different root cause. **Claimed root cause.** torrent.rs:66 sets overwrite: true and torrent.rs:65 gives each torrent the shared feed folder as its output folder with no subfolder of its own, while folder_for (download.rs:278-291) lets different feeds resolve to the same directory. Nothing makes a torrent's files unique or refuses to open an existing file the way unique_path does for HTTP downloads. **Local check.** {'local': "In a sandboxed scratch daemon with loopback only and a scratch download_dir: feed A (HTTP enclosure, title 'Show') downloads victim.mp3 into download_dir/Show; record its SHA-256. Feed B, a second fixture feed also titled 'Show', offers a single-file torrent named victim.mp3 with different bytes, seeded by a local librqbit or another client on loopback. Download B's enclosure, wait for the torrent to finish, and compare the SHA-256 of download_dir/Show/victim.mp3 with A's original and check A's and B's enclosure rows point at the same path. A changed hash confirms it. Regression test: a torrent whose files would land on an existing file is refused or given its own subfolder (e.g. sub-directory per enclosure, or overwrite only when resuming the same info hash).", 'deployment': 'Owner-observed and non-destructive: check whether production sets organize = date or has two subscribed feeds whose sanitised titles collide (SELECT title, count(*) FROM feeds GROUP BY title HAVING count(*) > 1, read only), and whether any torrent enclosure is present; do not seed an attacking torrent against production.'} **Owner check (no probing).** Owner-observed and non-destructive: check whether production sets organize = date or has two subscribed feeds whose sanitised titles collide (SELECT title, count(*) FROM feeds GROUP BY title HAVING count(*) > 1, read only), and whether any torrent enclosure is present; do not seed an attacking torrent against production. Full detail: `/config/security-audit-skill/ipx/run-1/NEEDS-VALIDATION.md` (fingerprint `ipx:torrent.rs:fetch:overwrite-shared-folder`).
rays added the bug label 2026-10-23 05:26:49 -07:00
Author
Owner

Fixed in ec3dedf, with #318: every enclosure's torrent gets its own folder, so overwrite: true can only rewrite that enclosure's own files. Deployed to production (ipx -dev).

Fixed in ec3dedf, with #318: every enclosure's torrent gets its own folder, so `overwrite: true` can only rewrite that enclosure's own files. Deployed to production (ipx -dev).
rays closed this issue 2026-10-23 05:56:58 -07:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: rays/ipx#331