diff --git a/ChangeLog b/ChangeLog index a3d410964..2892e045a 100644 --- a/ChangeLog +++ b/ChangeLog @@ -71,6 +71,7 @@ release 0.14.4 * improved handling of out-of-memory conditions in disk I/O thread * fixed bug when force-checking a torrent with partial pieces * fixed memory leak in disk cache + * fixed torrent file path vulnerability release 0.14.3 diff --git a/src/torrent_info.cpp b/src/torrent_info.cpp index b69efd046..4b914a006 100644 --- a/src/torrent_info.cpp +++ b/src/torrent_info.cpp @@ -163,6 +163,16 @@ namespace libtorrent if (!verify_encoding(p)) target.path = p; } + bool valid_path_element(std::string const& element) + { + if (element.empty() + || element == "." || element == ".." + || element[0] == '/' || element[0] == '\\' + || element[element.size()-1] == ':') + return false; + return true; + } + void trim_path_element(std::string& path_element) { #ifdef FILENAME_MAX @@ -189,6 +199,20 @@ namespace libtorrent } } + fs::path sanitize_path(fs::path const& p) + { + fs::path new_path; + for (fs::path::const_iterator i = p.begin(); i != p.end(); ++i) + { + if (!valid_path_element(*i)) continue; + std::string pe = *i; + trim_path_element(pe); + new_path /= pe; + } + TORRENT_ASSERT(!new_path.is_complete()); + return new_path; + } + bool extract_single_file(lazy_entry const& dict, file_entry& target , std::string const& root_dir) { @@ -215,12 +239,11 @@ namespace libtorrent return false; std::string path_element = p->list_at(i)->string_value(); trim_path_element(path_element); - if (path_element != "..") - target.path /= path_element; + target.path /= path_element; } + target.path = sanitize_path(target.path); verify_encoding(target); - if (target.path.is_complete()) - return false; + TORRENT_ASSERT(!target.path.is_complete()); // bitcomet pad file if (target.path.string().find("_____padding_file_") != std::string::npos) @@ -568,34 +591,9 @@ namespace libtorrent return false; } - fs::path tmp = name; - if (tmp.is_complete()) - { - name = tmp.leaf(); - trim_path_element(name); - } -#if BOOST_VERSION < 103600 - else if (tmp.has_branch_path()) -#else - else if (tmp.has_parent_path()) -#endif - { - fs::path p; - for (fs::path::iterator i = tmp.begin() - , end(tmp.end()); i != end; ++i) - { - if (*i == "." || *i == "..") continue; - std::string path_element = *i; - trim_path_element(path_element); - p /= path_element; - } - name = p.string(); - } - else - { - trim_path_element(name); - } - if (name == ".." || name == ".") + name = sanitize_path(name).string(); + + if (!valid_path_element(name)) { ec = error_code(errors::torrent_invalid_name, libtorrent_category); return false; diff --git a/test/test_primitives.cpp b/test/test_primitives.cpp index ceb32e81c..8a598b75c 100644 --- a/test/test_primitives.cpp +++ b/test/test_primitives.cpp @@ -55,6 +55,10 @@ using namespace libtorrent; using namespace boost::tuples; using boost::bind; +namespace libtorrent { + fs::path sanitize_path(fs::path const& p); +} + sha1_hash to_hash(char const* s) { sha1_hash ret; @@ -355,6 +359,17 @@ int test_main() { using namespace libtorrent; + TEST_CHECK(sanitize_path("/a/b/c").string() == "a/b/c"); + TEST_CHECK(sanitize_path("a/../c").string() == "a/c"); + TEST_CHECK(sanitize_path("/.././c").string() == "c"); + TEST_CHECK(sanitize_path("dev:").string() == ""); + TEST_CHECK(sanitize_path("c:/b").string() == "b"); +#ifdef TORRENT_WINDOWS + TEST_CHECK(sanitize_path("c:\\.\\c").string() == "c"); +#else + TEST_CHECK(sanitize_path("//./c").string() == "c"); +#endif + // make sure the time classes have correct semantics TEST_CHECK(total_milliseconds(milliseconds(100)) == 100); @@ -690,7 +705,7 @@ int test_main() torrent["info"] = info; torrent_info ti2(torrent); std::cerr << ti2.name() << std::endl; - TEST_CHECK(ti2.name() == "test3"); + TEST_CHECK(ti2.name() == "test1/test2/test3"); info["name.utf-8"] = "test2/../test3/.././../../test4"; torrent["info"] = info;