Skip to content

Commit 196588c

Browse files
committed
add torrent_info constructor overloads to control torrent file limits
1 parent 7a20850 commit 196588c

File tree

4 files changed

+110
-31
lines changed

4 files changed

+110
-31
lines changed

ChangeLog

+1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
* add torrent_info constructor overloads to control torrent file limits
12
* feature to disable DHT, PEX and LSD per torrent
23
* fix issue where trackers from magnet links were not included in create_torrent()
34
* make peer_info::client a byte array in python binding

examples/dump_torrent.cpp

+14-17
Original file line numberDiff line numberDiff line change
@@ -58,40 +58,37 @@ std::vector<char> load_file(std::string const& filename)
5858

5959
int main(int argc, char* argv[]) try
6060
{
61-
if (argc < 2 || argc > 4) {
62-
std::cerr << "usage: dump_torrent torrent-file [total-items-limit] [recursion-limit]\n";
61+
if (argc < 2 || argc > 5) {
62+
std::cerr << "usage: dump_torrent torrent-file [total-items-limit] [recursion-limit] [piece-count-limit]\n";
6363
return 1;
6464
}
6565

66-
int item_limit = 1000000;
67-
int depth_limit = 1000;
66+
lt::load_torrent_limits cfg;
6867

69-
if (argc > 2) item_limit = atoi(argv[2]);
70-
if (argc > 3) depth_limit = atoi(argv[3]);
68+
if (argc > 2) cfg.max_decode_tokens = atoi(argv[2]);
69+
if (argc > 3) cfg.max_decode_depth = atoi(argv[3]);
70+
if (argc > 4) cfg.max_pieces = atoi(argv[4]);
7171

7272
std::vector<char> buf = load_file(argv[1]);
73-
lt::bdecode_node e;
7473
int pos = -1;
7574
lt::error_code ec;
76-
std::cout << "decoding. recursion limit: " << depth_limit
77-
<< " total item count limit: " << item_limit << "\n";
78-
int const ret = lt::bdecode(&buf[0], &buf[0] + buf.size(), e, ec, &pos
79-
, depth_limit, item_limit);
75+
std::cout << "decoding. recursion limit: " << cfg.max_decode_depth
76+
<< " total item count limit: " << cfg.max_decode_tokens << "\n";
77+
lt::bdecode_node const e = lt::bdecode(buf, ec, &pos, cfg.max_decode_depth
78+
, cfg.max_decode_tokens);
8079

8180
std::printf("\n\n----- raw info -----\n\n%s\n", print_entry(e).c_str());
8281

83-
if (ret != 0) {
82+
if (ec) {
8483
std::cerr << "failed to decode: '" << ec.message() << "' at character: " << pos<< "\n";
8584
return 1;
8685
}
8786

88-
lt::torrent_info const t(e);
89-
e.clear();
90-
std::vector<char>().swap(buf);
87+
lt::torrent_info const t(std::move(e), cfg);
88+
buf.clear();
9189

9290
// print info about torrent
93-
std::printf("\n\n----- torrent file info -----\n\n"
94-
"nodes:\n");
91+
std::printf("\n\n----- torrent file info -----\n\nnodes:\n");
9592
for (auto const& i : t.nodes())
9693
std::printf("%s: %d\n", i.first.c_str(), i.second);
9794

include/libtorrent/torrent_info.hpp

+24-3
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,20 @@ namespace libtorrent {
111111
// used to disambiguate a bencoded buffer and a filename
112112
extern TORRENT_EXPORT from_span_t from_span;
113113

114+
// this object holds configuration options for limits to use when loading
115+
// torrents. They are meant to prevent loading potentially malicious torrents
116+
// that cause excessive memory allocations.
117+
struct load_torrent_limits
118+
{
119+
int max_buffer_size = 6000000;
120+
// the max number of pieces allowed in the torrent
121+
int max_pieces = 0x100000;
122+
// the max recursion depth in the bdecoded structure
123+
int max_decode_depth = 100;
124+
// the max number of bdecode tokens
125+
int max_decode_tokens = 2000000;
126+
};
127+
114128
// TODO: there may be some opportunities to optimize the size if torrent_info.
115129
// specifically to turn some std::string and std::vector into pointers
116130
class TORRENT_EXPORT torrent_info
@@ -157,6 +171,9 @@ namespace libtorrent {
157171
: torrent_info(span<char const>{buffer, size}, from_span) {}
158172
explicit torrent_info(span<char const> buffer, from_span_t);
159173
explicit torrent_info(std::string const& filename);
174+
torrent_info(std::string const& filename, load_torrent_limits const& cfg);
175+
torrent_info(span<char const> buffer, load_torrent_limits const& cfg, from_span_t);
176+
torrent_info(bdecode_node const& torrent_file, load_torrent_limits const& cfg);
160177
#endif // BOOST_NO_EXCEPTIONS
161178
torrent_info(torrent_info const& t);
162179
explicit torrent_info(sha1_hash const& info_hash);
@@ -524,7 +541,11 @@ namespace libtorrent {
524541
// where we only have the info-dict. The bdecode_node ``e`` points to a
525542
// parsed info-dictionary. ``ec`` returns an error code if something
526543
// fails (typically if the info dictionary is malformed).
544+
// the `piece_limit` parameter allows limiting the amount of memory
545+
// dedicated to loading the torrent, and fails for torrents that exceed
546+
// the limit
527547
bool parse_info_section(bdecode_node const& e, error_code& ec);
548+
bool parse_info_section(bdecode_node const& e, error_code& ec, int piece_limit);
528549

529550
// This function looks up keys from the info-dictionary of the loaded
530551
// torrent file. It can be used to access extension values put in the
@@ -551,11 +572,11 @@ namespace libtorrent {
551572
// __ http://bittorrent.org/beps/bep_0030.html
552573
bool is_merkle_torrent() const { return !m_merkle_tree.empty(); }
553574

554-
bool parse_torrent_file(bdecode_node const& libtorrent, error_code& ec);
555-
556-
// if we're logging member offsets, we need access to them
557575
private:
558576

577+
bool parse_torrent_file(bdecode_node const& libtorrent, error_code& ec);
578+
bool parse_torrent_file(bdecode_node const& libtorrent, error_code& ec, int piece_limit);
579+
559580
void resolve_duplicate_filenames();
560581

561582
// the slow path, in case we detect/suspect a name collision

src/torrent_info.cpp

+71-11
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,14 @@ namespace libtorrent {
7878

7979
namespace {
8080

81+
// this is an arbitrary limit to avoid malicious torrents causing
82+
// unreasaonably large allocations for the merkle hash tree
83+
// the size of the tree would be max_pieces * sizeof(int) * 2
84+
// which is about 8 MB with this limit
85+
// TODO: remove this limit and the overloads that imply it, in favour of
86+
// using load_torrent_limits
87+
constexpr int default_piece_limit = 0x100000;
88+
8189
bool valid_path_character(std::int32_t const c)
8290
{
8391
#ifdef TORRENT_WINDOWS
@@ -539,16 +547,21 @@ namespace {
539547
}
540548

541549
int load_file(std::string const& filename, std::vector<char>& v
542-
, error_code& ec)
550+
, error_code& ec, int const max_buffer_size = 80000000)
543551
{
544552
ec.clear();
545553
file f;
546554
if (!f.open(filename, open_mode::read_only, ec)) return -1;
547-
std::int64_t s = f.get_size(ec);
555+
std::int64_t const s = f.get_size(ec);
548556
if (ec) return -1;
557+
if (s > max_buffer_size)
558+
{
559+
ec = errors::metadata_too_large;
560+
return -1;
561+
}
549562
v.resize(std::size_t(s));
550563
if (s == 0) return 0;
551-
std::int64_t read = f.readv(0, {v}, ec);
564+
std::int64_t const read = f.readv(0, {v}, ec);
552565
if (read != s) return -3;
553566
if (ec) return -3;
554567
return 0;
@@ -869,6 +882,48 @@ namespace {
869882
INVARIANT_CHECK;
870883
}
871884

885+
torrent_info::torrent_info(bdecode_node const& torrent_file
886+
, load_torrent_limits const& cfg)
887+
{
888+
error_code ec;
889+
if (!parse_torrent_file(torrent_file, ec, cfg.max_pieces))
890+
aux::throw_ex<system_error>(ec);
891+
892+
INVARIANT_CHECK;
893+
}
894+
895+
torrent_info::torrent_info(span<char const> buffer
896+
, load_torrent_limits const& cfg, from_span_t)
897+
{
898+
error_code ec;
899+
bdecode_node e = bdecode(buffer, ec, nullptr
900+
, cfg.max_decode_depth, cfg.max_decode_tokens);
901+
if (ec) aux::throw_ex<system_error>(ec);
902+
903+
if (!parse_torrent_file(e, ec, cfg.max_pieces))
904+
aux::throw_ex<system_error>(ec);
905+
906+
INVARIANT_CHECK;
907+
}
908+
909+
torrent_info::torrent_info(std::string const& filename
910+
, load_torrent_limits const& cfg)
911+
{
912+
std::vector<char> buf;
913+
error_code ec;
914+
int ret = load_file(filename, buf, ec, cfg.max_buffer_size);
915+
if (ret < 0) aux::throw_ex<system_error>(ec);
916+
917+
bdecode_node e = bdecode(buf, ec, nullptr, cfg.max_decode_depth
918+
, cfg.max_decode_tokens);
919+
if (ec) aux::throw_ex<system_error>(ec);
920+
921+
if (!parse_torrent_file(e, ec, cfg.max_pieces))
922+
aux::throw_ex<system_error>(ec);
923+
924+
INVARIANT_CHECK;
925+
}
926+
872927
#if TORRENT_ABI_VERSION == 1
873928
torrent_info::torrent_info(std::wstring const& filename)
874929
{
@@ -1004,8 +1059,13 @@ namespace {
10041059
return m_info_dict.dict_find_string_value("ssl-cert");
10051060
}
10061061

1062+
bool torrent_info::parse_info_section(bdecode_node const& e, error_code& ec)
1063+
{
1064+
return parse_info_section(e, ec, default_piece_limit);
1065+
}
1066+
10071067
bool torrent_info::parse_info_section(bdecode_node const& info
1008-
, error_code& ec)
1068+
, error_code& ec, int const max_pieces)
10091069
{
10101070
if (info.type() != bdecode_node::dict_t)
10111071
{
@@ -1129,12 +1189,6 @@ namespace {
11291189
return false;
11301190
}
11311191

1132-
// this is an arbitrary limit to avoid malicious torrents causing
1133-
// unreasaonably large allocations for the merkle hash tree
1134-
// the size of the tree would be max_pieces * sizeof(int) * 2
1135-
// which is about 6.3 MB with this limit
1136-
const int max_pieces = 0xC0000;
1137-
11381192
// we expect the piece hashes to be < 2 GB in size
11391193
if (files.num_pieces() >= std::numeric_limits<int>::max() / 20
11401194
|| files.num_pieces() > max_pieces)
@@ -1315,6 +1369,12 @@ namespace {
13151369

13161370
bool torrent_info::parse_torrent_file(bdecode_node const& torrent_file
13171371
, error_code& ec)
1372+
{
1373+
return parse_torrent_file(torrent_file, ec, default_piece_limit);
1374+
}
1375+
1376+
bool torrent_info::parse_torrent_file(bdecode_node const& torrent_file
1377+
, error_code& ec, int const piece_limit)
13181378
{
13191379
if (torrent_file.type() != bdecode_node::dict_t)
13201380
{
@@ -1342,7 +1402,7 @@ namespace {
13421402
ec = errors::torrent_missing_info;
13431403
return false;
13441404
}
1345-
if (!parse_info_section(info, ec)) return false;
1405+
if (!parse_info_section(info, ec, piece_limit)) return false;
13461406
resolve_duplicate_filenames();
13471407

13481408
#ifndef TORRENT_DISABLE_MUTABLE_TORRENTS

0 commit comments

Comments
 (0)