Skip to content

Commit abaf943

Browse files
author
MarcoFalke
committed
Merge bitcoin/bitcoin#24231: streams: Fix read-past-the-end and integer overflows
fa1b89a scripted-diff: Rename nReadPos to m_read_pos in streams.h (MarcoFalke) fa56c79 Make CDataStream work properly on 64-bit systems (MarcoFalke) fab02f7 streams: Fix read-past-the-end and integer overflows (MarcoFalke) Pull request description: This is a follow-up to commit e26b620 with the following fixes: * Fix unsigned integer overflow in `ignore()`, when `nReadPos` wraps. * Fix unsigned integer overflow in `read()`, when `nReadPos` wraps. * Fix read-past-the-end in `read()`, when `nReadPos` wraps. This shouldn't be remote-exploitable, because it requires a stream of more than 1GB of size. However, it might be exploitable if the attacker controls the datadir (I haven't checked). A unit test for the overflow in `ignore()` looks like following. It is left as an excercise to the reader to replace `foo.ignore(7)` with the appropriate call to `read()` to reproduce the overflow and read-error in `read()`. ```diff diff --git a/src/test/coins_tests.cpp b/src/test/coins_tests.cpp index 922fd8e..ec6ea93919 100644 --- a/src/test/coins_tests.cpp +++ b/src/test/coins_tests.cpp @@ -534,6 +534,20 @@ BOOST_AUTO_TEST_CASE(ccoins_serialization) } catch (const std::ios_base::failure&) { } + CDataStream foo{0, 0}; + auto size{std::numeric_limits<uint32_t>::max()}; + foo.resize(size); + BOOST_CHECK_EQUAL(foo.size(), size); + foo.ignore(std::numeric_limits<int32_t>::max()); + size -= std::numeric_limits<int32_t>::max(); + BOOST_CHECK_EQUAL(foo.size(), size); + foo.ignore(std::numeric_limits<int32_t>::max()); + size -= std::numeric_limits<int32_t>::max(); + BOOST_CHECK_EQUAL(foo.size(), size); + BOOST_CHECK_EQUAL(foo.size(), 1); + foo.ignore(7); // Should overflow, as the size is only 1 + BOOST_CHECK_EQUAL(foo.size(), uint32_t(1 - 7)); + // Very large scriptPubKey (3*10^9 bytes) past the end of the stream CDataStream tmp(SER_DISK, CLIENT_VERSION); uint64_t x = 3000000000ULL; ``` ACKs for top commit: klementtan: Code Review ACK fa1b89a: Tree-SHA512: 67f0a1baafe88eaf1dc844ac55b638d5cf168a18c945e3bf7a2cb03c9a5976674a8e3af2487d8a2c3eae21e5c0e7a519c8b16ee7f104934442e2769d100660e9
2 parents 3de5fcc + fa1b89a commit abaf943

File tree

2 files changed

+50
-52
lines changed

2 files changed

+50
-52
lines changed

src/streams.h

Lines changed: 49 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
#include <serialize.h>
1010
#include <span.h>
1111
#include <support/allocators/zeroafterfree.h>
12+
#include <util/overflow.h>
1213

1314
#include <algorithm>
1415
#include <assert.h>
@@ -186,7 +187,7 @@ class CDataStream
186187
protected:
187188
using vector_type = SerializeData;
188189
vector_type vch;
189-
unsigned int nReadPos{0};
190+
vector_type::size_type m_read_pos{0};
190191

191192
int nType;
192193
int nVersion;
@@ -229,37 +230,37 @@ class CDataStream
229230
//
230231
// Vector subset
231232
//
232-
const_iterator begin() const { return vch.begin() + nReadPos; }
233-
iterator begin() { return vch.begin() + nReadPos; }
233+
const_iterator begin() const { return vch.begin() + m_read_pos; }
234+
iterator begin() { return vch.begin() + m_read_pos; }
234235
const_iterator end() const { return vch.end(); }
235236
iterator end() { return vch.end(); }
236-
size_type size() const { return vch.size() - nReadPos; }
237-
bool empty() const { return vch.size() == nReadPos; }
238-
void resize(size_type n, value_type c = value_type{}) { vch.resize(n + nReadPos, c); }
239-
void reserve(size_type n) { vch.reserve(n + nReadPos); }
240-
const_reference operator[](size_type pos) const { return vch[pos + nReadPos]; }
241-
reference operator[](size_type pos) { return vch[pos + nReadPos]; }
242-
void clear() { vch.clear(); nReadPos = 0; }
243-
value_type* data() { return vch.data() + nReadPos; }
244-
const value_type* data() const { return vch.data() + nReadPos; }
237+
size_type size() const { return vch.size() - m_read_pos; }
238+
bool empty() const { return vch.size() == m_read_pos; }
239+
void resize(size_type n, value_type c = value_type{}) { vch.resize(n + m_read_pos, c); }
240+
void reserve(size_type n) { vch.reserve(n + m_read_pos); }
241+
const_reference operator[](size_type pos) const { return vch[pos + m_read_pos]; }
242+
reference operator[](size_type pos) { return vch[pos + m_read_pos]; }
243+
void clear() { vch.clear(); m_read_pos = 0; }
244+
value_type* data() { return vch.data() + m_read_pos; }
245+
const value_type* data() const { return vch.data() + m_read_pos; }
245246

246247
inline void Compact()
247248
{
248-
vch.erase(vch.begin(), vch.begin() + nReadPos);
249-
nReadPos = 0;
249+
vch.erase(vch.begin(), vch.begin() + m_read_pos);
250+
m_read_pos = 0;
250251
}
251252

252253
bool Rewind(std::optional<size_type> n = std::nullopt)
253254
{
254255
// Total rewind if no size is passed
255256
if (!n) {
256-
nReadPos = 0;
257+
m_read_pos = 0;
257258
return true;
258259
}
259260
// Rewind by n characters if the buffer hasn't been compacted yet
260-
if (*n > nReadPos)
261+
if (*n > m_read_pos)
261262
return false;
262-
nReadPos -= *n;
263+
m_read_pos -= *n;
263264
return true;
264265
}
265266

@@ -281,36 +282,32 @@ class CDataStream
281282
if (dst.size() == 0) return;
282283

283284
// Read from the beginning of the buffer
284-
unsigned int nReadPosNext = nReadPos + dst.size();
285-
if (nReadPosNext > vch.size()) {
285+
auto next_read_pos{CheckedAdd(m_read_pos, dst.size())};
286+
if (!next_read_pos.has_value() || next_read_pos.value() > vch.size()) {
286287
throw std::ios_base::failure("CDataStream::read(): end of data");
287288
}
288-
memcpy(dst.data(), &vch[nReadPos], dst.size());
289-
if (nReadPosNext == vch.size())
290-
{
291-
nReadPos = 0;
289+
memcpy(dst.data(), &vch[m_read_pos], dst.size());
290+
if (next_read_pos.value() == vch.size()) {
291+
m_read_pos = 0;
292292
vch.clear();
293293
return;
294294
}
295-
nReadPos = nReadPosNext;
295+
m_read_pos = next_read_pos.value();
296296
}
297297

298-
void ignore(int nSize)
298+
void ignore(size_t num_ignore)
299299
{
300300
// Ignore from the beginning of the buffer
301-
if (nSize < 0) {
302-
throw std::ios_base::failure("CDataStream::ignore(): nSize negative");
301+
auto next_read_pos{CheckedAdd(m_read_pos, num_ignore)};
302+
if (!next_read_pos.has_value() || next_read_pos.value() > vch.size()) {
303+
throw std::ios_base::failure("CDataStream::ignore(): end of data");
303304
}
304-
unsigned int nReadPosNext = nReadPos + nSize;
305-
if (nReadPosNext >= vch.size())
306-
{
307-
if (nReadPosNext > vch.size())
308-
throw std::ios_base::failure("CDataStream::ignore(): end of data");
309-
nReadPos = 0;
305+
if (next_read_pos.value() == vch.size()) {
306+
m_read_pos = 0;
310307
vch.clear();
311308
return;
312309
}
313-
nReadPos = nReadPosNext;
310+
m_read_pos = next_read_pos.value();
314311
}
315312

316313
void write(Span<const value_type> src)
@@ -594,7 +591,7 @@ class CBufferedFile
594591

595592
FILE *src; //!< source file
596593
uint64_t nSrcPos; //!< how many bytes have been read from source
597-
uint64_t nReadPos; //!< how many bytes have been read from this
594+
uint64_t m_read_pos; //!< how many bytes have been read from this
598595
uint64_t nReadLimit; //!< up to which position we're allowed to read
599596
uint64_t nRewind; //!< how many bytes we guarantee to rewind
600597
std::vector<std::byte> vchBuf; //!< the buffer
@@ -604,7 +601,7 @@ class CBufferedFile
604601
bool Fill() {
605602
unsigned int pos = nSrcPos % vchBuf.size();
606603
unsigned int readNow = vchBuf.size() - pos;
607-
unsigned int nAvail = vchBuf.size() - (nSrcPos - nReadPos) - nRewind;
604+
unsigned int nAvail = vchBuf.size() - (nSrcPos - m_read_pos) - nRewind;
608605
if (nAvail < readNow)
609606
readNow = nAvail;
610607
if (readNow == 0)
@@ -619,7 +616,7 @@ class CBufferedFile
619616

620617
public:
621618
CBufferedFile(FILE* fileIn, uint64_t nBufSize, uint64_t nRewindIn, int nTypeIn, int nVersionIn)
622-
: nType(nTypeIn), nVersion(nVersionIn), nSrcPos(0), nReadPos(0), nReadLimit(std::numeric_limits<uint64_t>::max()), nRewind(nRewindIn), vchBuf(nBufSize, std::byte{0})
619+
: nType(nTypeIn), nVersion(nVersionIn), nSrcPos(0), m_read_pos(0), nReadLimit(std::numeric_limits<uint64_t>::max()), nRewind(nRewindIn), vchBuf(nBufSize, std::byte{0})
623620
{
624621
if (nRewindIn >= nBufSize)
625622
throw std::ios_base::failure("Rewind limit must be less than buffer size");
@@ -648,56 +645,56 @@ class CBufferedFile
648645

649646
//! check whether we're at the end of the source file
650647
bool eof() const {
651-
return nReadPos == nSrcPos && feof(src);
648+
return m_read_pos == nSrcPos && feof(src);
652649
}
653650

654651
//! read a number of bytes
655652
void read(Span<std::byte> dst)
656653
{
657-
if (dst.size() + nReadPos > nReadLimit) {
654+
if (dst.size() + m_read_pos > nReadLimit) {
658655
throw std::ios_base::failure("Read attempted past buffer limit");
659656
}
660657
while (dst.size() > 0) {
661-
if (nReadPos == nSrcPos)
658+
if (m_read_pos == nSrcPos)
662659
Fill();
663-
unsigned int pos = nReadPos % vchBuf.size();
660+
unsigned int pos = m_read_pos % vchBuf.size();
664661
size_t nNow = dst.size();
665662
if (nNow + pos > vchBuf.size())
666663
nNow = vchBuf.size() - pos;
667-
if (nNow + nReadPos > nSrcPos)
668-
nNow = nSrcPos - nReadPos;
664+
if (nNow + m_read_pos > nSrcPos)
665+
nNow = nSrcPos - m_read_pos;
669666
memcpy(dst.data(), &vchBuf[pos], nNow);
670-
nReadPos += nNow;
667+
m_read_pos += nNow;
671668
dst = dst.subspan(nNow);
672669
}
673670
}
674671

675672
//! return the current reading position
676673
uint64_t GetPos() const {
677-
return nReadPos;
674+
return m_read_pos;
678675
}
679676

680677
//! rewind to a given reading position
681678
bool SetPos(uint64_t nPos) {
682679
size_t bufsize = vchBuf.size();
683680
if (nPos + bufsize < nSrcPos) {
684681
// rewinding too far, rewind as far as possible
685-
nReadPos = nSrcPos - bufsize;
682+
m_read_pos = nSrcPos - bufsize;
686683
return false;
687684
}
688685
if (nPos > nSrcPos) {
689686
// can't go this far forward, go as far as possible
690-
nReadPos = nSrcPos;
687+
m_read_pos = nSrcPos;
691688
return false;
692689
}
693-
nReadPos = nPos;
690+
m_read_pos = nPos;
694691
return true;
695692
}
696693

697694
//! prevent reading beyond a certain position
698695
//! no argument removes the limit
699696
bool SetLimit(uint64_t nPos = std::numeric_limits<uint64_t>::max()) {
700-
if (nPos < nReadPos)
697+
if (nPos < m_read_pos)
701698
return false;
702699
nReadLimit = nPos;
703700
return true;
@@ -714,12 +711,12 @@ class CBufferedFile
714711
void FindByte(uint8_t ch)
715712
{
716713
while (true) {
717-
if (nReadPos == nSrcPos)
714+
if (m_read_pos == nSrcPos)
718715
Fill();
719-
if (vchBuf[nReadPos % vchBuf.size()] == std::byte{ch}) {
716+
if (vchBuf[m_read_pos % vchBuf.size()] == std::byte{ch}) {
720717
break;
721718
}
722-
nReadPos++;
719+
m_read_pos++;
723720
}
724721
}
725722
};

src/util/overflow.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
#define BITCOIN_UTIL_OVERFLOW_H
77

88
#include <limits>
9+
#include <optional>
910
#include <type_traits>
1011

1112
template <class T>

0 commit comments

Comments
 (0)