add torrent_info constructor overloads to control torrent file limits

This commit is contained in:
Arvid Norberg 2019-07-14 11:33:46 -04:00 committed by Arvid Norberg
parent 7a20850632
commit 196588c7b8
4 changed files with 110 additions and 31 deletions

View File

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

View File

@ -58,40 +58,37 @@ std::vector<char> load_file(std::string const& filename)
int main(int argc, char* argv[]) try int main(int argc, char* argv[]) try
{ {
if (argc < 2 || argc > 4) { if (argc < 2 || argc > 5) {
std::cerr << "usage: dump_torrent torrent-file [total-items-limit] [recursion-limit]\n"; std::cerr << "usage: dump_torrent torrent-file [total-items-limit] [recursion-limit] [piece-count-limit]\n";
return 1; return 1;
} }
int item_limit = 1000000; lt::load_torrent_limits cfg;
int depth_limit = 1000;
if (argc > 2) item_limit = atoi(argv[2]); if (argc > 2) cfg.max_decode_tokens = atoi(argv[2]);
if (argc > 3) depth_limit = atoi(argv[3]); if (argc > 3) cfg.max_decode_depth = atoi(argv[3]);
if (argc > 4) cfg.max_pieces = atoi(argv[4]);
std::vector<char> buf = load_file(argv[1]); std::vector<char> buf = load_file(argv[1]);
lt::bdecode_node e;
int pos = -1; int pos = -1;
lt::error_code ec; lt::error_code ec;
std::cout << "decoding. recursion limit: " << depth_limit std::cout << "decoding. recursion limit: " << cfg.max_decode_depth
<< " total item count limit: " << item_limit << "\n"; << " total item count limit: " << cfg.max_decode_tokens << "\n";
int const ret = lt::bdecode(&buf[0], &buf[0] + buf.size(), e, ec, &pos lt::bdecode_node const e = lt::bdecode(buf, ec, &pos, cfg.max_decode_depth
, depth_limit, item_limit); , cfg.max_decode_tokens);
std::printf("\n\n----- raw info -----\n\n%s\n", print_entry(e).c_str()); std::printf("\n\n----- raw info -----\n\n%s\n", print_entry(e).c_str());
if (ret != 0) { if (ec) {
std::cerr << "failed to decode: '" << ec.message() << "' at character: " << pos<< "\n"; std::cerr << "failed to decode: '" << ec.message() << "' at character: " << pos<< "\n";
return 1; return 1;
} }
lt::torrent_info const t(e); lt::torrent_info const t(std::move(e), cfg);
e.clear(); buf.clear();
std::vector<char>().swap(buf);
// print info about torrent // print info about torrent
std::printf("\n\n----- torrent file info -----\n\n" std::printf("\n\n----- torrent file info -----\n\nnodes:\n");
"nodes:\n");
for (auto const& i : t.nodes()) for (auto const& i : t.nodes())
std::printf("%s: %d\n", i.first.c_str(), i.second); std::printf("%s: %d\n", i.first.c_str(), i.second);

View File

@ -111,6 +111,20 @@ namespace libtorrent {
// used to disambiguate a bencoded buffer and a filename // used to disambiguate a bencoded buffer and a filename
extern TORRENT_EXPORT from_span_t from_span; extern TORRENT_EXPORT from_span_t from_span;
// this object holds configuration options for limits to use when loading
// torrents. They are meant to prevent loading potentially malicious torrents
// that cause excessive memory allocations.
struct load_torrent_limits
{
int max_buffer_size = 6000000;
// the max number of pieces allowed in the torrent
int max_pieces = 0x100000;
// the max recursion depth in the bdecoded structure
int max_decode_depth = 100;
// the max number of bdecode tokens
int max_decode_tokens = 2000000;
};
// TODO: there may be some opportunities to optimize the size if torrent_info. // TODO: there may be some opportunities to optimize the size if torrent_info.
// specifically to turn some std::string and std::vector into pointers // specifically to turn some std::string and std::vector into pointers
class TORRENT_EXPORT torrent_info class TORRENT_EXPORT torrent_info
@ -157,6 +171,9 @@ namespace libtorrent {
: torrent_info(span<char const>{buffer, size}, from_span) {} : torrent_info(span<char const>{buffer, size}, from_span) {}
explicit torrent_info(span<char const> buffer, from_span_t); explicit torrent_info(span<char const> buffer, from_span_t);
explicit torrent_info(std::string const& filename); explicit torrent_info(std::string const& filename);
torrent_info(std::string const& filename, load_torrent_limits const& cfg);
torrent_info(span<char const> buffer, load_torrent_limits const& cfg, from_span_t);
torrent_info(bdecode_node const& torrent_file, load_torrent_limits const& cfg);
#endif // BOOST_NO_EXCEPTIONS #endif // BOOST_NO_EXCEPTIONS
torrent_info(torrent_info const& t); torrent_info(torrent_info const& t);
explicit torrent_info(sha1_hash const& info_hash); explicit torrent_info(sha1_hash const& info_hash);
@ -524,7 +541,11 @@ namespace libtorrent {
// where we only have the info-dict. The bdecode_node ``e`` points to a // where we only have the info-dict. The bdecode_node ``e`` points to a
// parsed info-dictionary. ``ec`` returns an error code if something // parsed info-dictionary. ``ec`` returns an error code if something
// fails (typically if the info dictionary is malformed). // fails (typically if the info dictionary is malformed).
// the `piece_limit` parameter allows limiting the amount of memory
// dedicated to loading the torrent, and fails for torrents that exceed
// the limit
bool parse_info_section(bdecode_node const& e, error_code& ec); bool parse_info_section(bdecode_node const& e, error_code& ec);
bool parse_info_section(bdecode_node const& e, error_code& ec, int piece_limit);
// This function looks up keys from the info-dictionary of the loaded // This function looks up keys from the info-dictionary of the loaded
// torrent file. It can be used to access extension values put in the // torrent file. It can be used to access extension values put in the
@ -551,11 +572,11 @@ namespace libtorrent {
// __ http://bittorrent.org/beps/bep_0030.html // __ http://bittorrent.org/beps/bep_0030.html
bool is_merkle_torrent() const { return !m_merkle_tree.empty(); } bool is_merkle_torrent() const { return !m_merkle_tree.empty(); }
bool parse_torrent_file(bdecode_node const& libtorrent, error_code& ec);
// if we're logging member offsets, we need access to them
private: private:
bool parse_torrent_file(bdecode_node const& libtorrent, error_code& ec);
bool parse_torrent_file(bdecode_node const& libtorrent, error_code& ec, int piece_limit);
void resolve_duplicate_filenames(); void resolve_duplicate_filenames();
// the slow path, in case we detect/suspect a name collision // the slow path, in case we detect/suspect a name collision

View File

@ -78,6 +78,14 @@ namespace libtorrent {
namespace { namespace {
// this is an arbitrary limit to avoid malicious torrents causing
// unreasaonably large allocations for the merkle hash tree
// the size of the tree would be max_pieces * sizeof(int) * 2
// which is about 8 MB with this limit
// TODO: remove this limit and the overloads that imply it, in favour of
// using load_torrent_limits
constexpr int default_piece_limit = 0x100000;
bool valid_path_character(std::int32_t const c) bool valid_path_character(std::int32_t const c)
{ {
#ifdef TORRENT_WINDOWS #ifdef TORRENT_WINDOWS
@ -539,16 +547,21 @@ namespace {
} }
int load_file(std::string const& filename, std::vector<char>& v int load_file(std::string const& filename, std::vector<char>& v
, error_code& ec) , error_code& ec, int const max_buffer_size = 80000000)
{ {
ec.clear(); ec.clear();
file f; file f;
if (!f.open(filename, open_mode::read_only, ec)) return -1; if (!f.open(filename, open_mode::read_only, ec)) return -1;
std::int64_t s = f.get_size(ec); std::int64_t const s = f.get_size(ec);
if (ec) return -1; if (ec) return -1;
if (s > max_buffer_size)
{
ec = errors::metadata_too_large;
return -1;
}
v.resize(std::size_t(s)); v.resize(std::size_t(s));
if (s == 0) return 0; if (s == 0) return 0;
std::int64_t read = f.readv(0, {v}, ec); std::int64_t const read = f.readv(0, {v}, ec);
if (read != s) return -3; if (read != s) return -3;
if (ec) return -3; if (ec) return -3;
return 0; return 0;
@ -869,6 +882,48 @@ namespace {
INVARIANT_CHECK; INVARIANT_CHECK;
} }
torrent_info::torrent_info(bdecode_node const& torrent_file
, load_torrent_limits const& cfg)
{
error_code ec;
if (!parse_torrent_file(torrent_file, ec, cfg.max_pieces))
aux::throw_ex<system_error>(ec);
INVARIANT_CHECK;
}
torrent_info::torrent_info(span<char const> buffer
, load_torrent_limits const& cfg, from_span_t)
{
error_code ec;
bdecode_node e = bdecode(buffer, ec, nullptr
, cfg.max_decode_depth, cfg.max_decode_tokens);
if (ec) aux::throw_ex<system_error>(ec);
if (!parse_torrent_file(e, ec, cfg.max_pieces))
aux::throw_ex<system_error>(ec);
INVARIANT_CHECK;
}
torrent_info::torrent_info(std::string const& filename
, load_torrent_limits const& cfg)
{
std::vector<char> buf;
error_code ec;
int ret = load_file(filename, buf, ec, cfg.max_buffer_size);
if (ret < 0) aux::throw_ex<system_error>(ec);
bdecode_node e = bdecode(buf, ec, nullptr, cfg.max_decode_depth
, cfg.max_decode_tokens);
if (ec) aux::throw_ex<system_error>(ec);
if (!parse_torrent_file(e, ec, cfg.max_pieces))
aux::throw_ex<system_error>(ec);
INVARIANT_CHECK;
}
#if TORRENT_ABI_VERSION == 1 #if TORRENT_ABI_VERSION == 1
torrent_info::torrent_info(std::wstring const& filename) torrent_info::torrent_info(std::wstring const& filename)
{ {
@ -1004,8 +1059,13 @@ namespace {
return m_info_dict.dict_find_string_value("ssl-cert"); return m_info_dict.dict_find_string_value("ssl-cert");
} }
bool torrent_info::parse_info_section(bdecode_node const& e, error_code& ec)
{
return parse_info_section(e, ec, default_piece_limit);
}
bool torrent_info::parse_info_section(bdecode_node const& info bool torrent_info::parse_info_section(bdecode_node const& info
, error_code& ec) , error_code& ec, int const max_pieces)
{ {
if (info.type() != bdecode_node::dict_t) if (info.type() != bdecode_node::dict_t)
{ {
@ -1129,12 +1189,6 @@ namespace {
return false; return false;
} }
// this is an arbitrary limit to avoid malicious torrents causing
// unreasaonably large allocations for the merkle hash tree
// the size of the tree would be max_pieces * sizeof(int) * 2
// which is about 6.3 MB with this limit
const int max_pieces = 0xC0000;
// we expect the piece hashes to be < 2 GB in size // we expect the piece hashes to be < 2 GB in size
if (files.num_pieces() >= std::numeric_limits<int>::max() / 20 if (files.num_pieces() >= std::numeric_limits<int>::max() / 20
|| files.num_pieces() > max_pieces) || files.num_pieces() > max_pieces)
@ -1315,6 +1369,12 @@ namespace {
bool torrent_info::parse_torrent_file(bdecode_node const& torrent_file bool torrent_info::parse_torrent_file(bdecode_node const& torrent_file
, error_code& ec) , error_code& ec)
{
return parse_torrent_file(torrent_file, ec, default_piece_limit);
}
bool torrent_info::parse_torrent_file(bdecode_node const& torrent_file
, error_code& ec, int const piece_limit)
{ {
if (torrent_file.type() != bdecode_node::dict_t) if (torrent_file.type() != bdecode_node::dict_t)
{ {
@ -1342,7 +1402,7 @@ namespace {
ec = errors::torrent_missing_info; ec = errors::torrent_missing_info;
return false; return false;
} }
if (!parse_info_section(info, ec)) return false; if (!parse_info_section(info, ec, piece_limit)) return false;
resolve_duplicate_filenames(); resolve_duplicate_filenames();
#ifndef TORRENT_DISABLE_MUTABLE_TORRENTS #ifndef TORRENT_DISABLE_MUTABLE_TORRENTS