Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Out-of-bounds write in parseBenc #3591

Closed
guidovranken opened this issue Aug 5, 2022 · 3 comments · Fixed by #3600
Closed

Out-of-bounds write in parseBenc #3591

guidovranken opened this issue Aug 5, 2022 · 3 comments · Fixed by #3600

Comments

@guidovranken
Copy link
Contributor

This code:

auto const n = std::size(value) / sizeof(tr_sha1_digest_t);
tm_.pieces_.resize(n);
std::copy_n(std::data(value), std::size(value), reinterpret_cast<char*>(std::data(tm_.pieces_)));
tm_.pieces_offset_ = context.tokenSpan().first;

Allocates enough room for n elements, but then proceeds to copy n elements AND the remaining data. So if std::size(value) is 21 (and sizeof(tr_sha1_digest_t) is 20), then it allocates 1 element (= 20 bytes), and copy 21 bytes to it.

Test for this issue:

diff --git a/tests/libtransmission/torrent-metainfo-test.cc b/tests/libtransmission/torrent-metainfo-test.cc
index 2253d41..ee208ea 100644
--- a/tests/libtransmission/torrent-metainfo-test.cc
+++ b/tests/libtransmission/torrent-metainfo-test.cc
@@ -255,5 +255,11 @@ TEST_F(TorrentMetainfoTest, GetRightStyleWebseedString)
     EXPECT_EQ("http://www.webseed-one.com/"sv, tm.webseed(0));
 }
 
+TEST_F(TorrentMetainfoTest, parseBencOOBWrite)
+{
+    auto tm = tr_torrent_metainfo{};
+    tm.parseBenc(tr_base64_decode("ZGg0OmluZm9kNjpwaWVjZXMzOkFpzQ=="));
+}
+
 } // namespace test
 } // namespace libtransmission

Potential fix (it addresses the issue, but I don't know if it's compliant with the relevant specs):

diff --git a/libtransmission/torrent-metainfo.cc b/libtransmission/torrent-metainfo.cc
index cec210d..1885902 100644
--- a/libtransmission/torrent-metainfo.cc
+++ b/libtransmission/torrent-metainfo.cc
@@ -455,10 +455,14 @@ struct MetainfoHandler final : public transmission::benc::BasicHandler<MaxBencDe
         }
         else if (pathIs(InfoKey, PiecesKey))
         {
-            auto const n = std::size(value) / sizeof(tr_sha1_digest_t);
-            tm_.pieces_.resize(n);
-            std::copy_n(std::data(value), std::size(value), reinterpret_cast<char*>(std::data(tm_.pieces_)));
-            tm_.pieces_offset_ = context.tokenSpan().first;
+            if (std::size(value) % sizeof(tr_sha1_digest_t) == 0) {
+                auto const n = std::size(value) / sizeof(tr_sha1_digest_t);
+                tm_.pieces_.resize(n);
+                std::copy_n(std::data(value), std::size(value), reinterpret_cast<char*>(std::data(tm_.pieces_)));
+                tm_.pieces_offset_ = context.tokenSpan().first;
+            } else {
+                unhandled = true;
+            }
         }
         else if (pathStartsWith(PieceLayersKey))
         {
@guidovranken
Copy link
Contributor Author

If you're satisfied with both the test and the fix, I'll submit a PR.

@guidovranken
Copy link
Contributor Author

A more lax approach:

diff --git a/libtransmission/torrent-metainfo.cc b/libtransmission/torrent-metainfo.cc
index cec210d..6470590 100644
--- a/libtransmission/torrent-metainfo.cc
+++ b/libtransmission/torrent-metainfo.cc
@@ -457,7 +457,10 @@ struct MetainfoHandler final : public transmission::benc::BasicHandler<MaxBencDe
         {
             auto const n = std::size(value) / sizeof(tr_sha1_digest_t);
             tm_.pieces_.resize(n);
-            std::copy_n(std::data(value), std::size(value), reinterpret_cast<char*>(std::data(tm_.pieces_)));
+            std::copy_n(
+                    std::data(value),
+                    n * sizeof(tr_sha1_digest_t),
+                    reinterpret_cast<char*>(std::data(tm_.pieces_)));
             tm_.pieces_offset_ = context.tokenSpan().first;
         }
         else if (pathStartsWith(PieceLayersKey))

This simply ignores the excess bytes.

@ckerr
Copy link
Member

ckerr commented Aug 5, 2022

At a minimum we should log m error. I’m inclined to say refuse to parse a torrent with the wrong length since it’s likely a sign of a corrupt torrent. That array should always be cleanly divisible by 20.

I’d be fine with a PR that checks this to safeguard against corrupt torrents.

guidovranken added a commit to guidovranken/transmission that referenced this issue Aug 6, 2022
ckerr pushed a commit to guidovranken/transmission that referenced this issue Aug 9, 2022
ckerr pushed a commit that referenced this issue Aug 9, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Development

Successfully merging a pull request may close this issue.

2 participants