Skip to content

Commit 8b34dac

Browse files
gibber9809haiqi96LinZhihao-723
authored andcommitted
feat(core-clp): Add BoundedReader to prevent out-of-bound reads in segmented input streams. (y-scope#624)
Co-authored-by: haiqi96 <[email protected]> Co-authored-by: Lin Zhihao <[email protected]>
1 parent 13c7528 commit 8b34dac

File tree

5 files changed

+238
-0
lines changed

5 files changed

+238
-0
lines changed

components/core/CMakeLists.txt

+3
Original file line numberDiff line numberDiff line change
@@ -352,6 +352,8 @@ set(SOURCE_FILES_unitTest
352352
src/clp/aws/AwsAuthenticationSigner.cpp
353353
src/clp/aws/AwsAuthenticationSigner.hpp
354354
src/clp/aws/constants.hpp
355+
src/clp/BoundedReader.cpp
356+
src/clp/BoundedReader.hpp
355357
src/clp/BufferedFileReader.cpp
356358
src/clp/BufferedFileReader.hpp
357359
src/clp/BufferReader.cpp
@@ -571,6 +573,7 @@ set(SOURCE_FILES_unitTest
571573
submodules/sqlite3/sqlite3ext.h
572574
tests/LogSuppressor.hpp
573575
tests/test-Array.cpp
576+
tests/test-BoundedReader.cpp
574577
tests/test-BufferedFileReader.cpp
575578
tests/test-clp_s-end_to_end.cpp
576579
tests/test-EncodedVariableInterpreter.cpp
+43
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
#include "BoundedReader.hpp"
2+
3+
#include <cstddef>
4+
5+
#include "ErrorCode.hpp"
6+
7+
namespace clp {
8+
auto BoundedReader::try_seek_from_begin(size_t pos) -> ErrorCode {
9+
auto const next_pos = pos > m_bound ? m_bound : pos;
10+
if (auto const rc = m_reader->try_seek_from_begin(next_pos); ErrorCode_Success != rc) {
11+
m_curr_pos = ErrorCode_EndOfFile == rc ? next_pos : m_curr_pos;
12+
return rc;
13+
}
14+
m_curr_pos = next_pos;
15+
if (m_curr_pos >= m_bound) {
16+
return ErrorCode_EndOfFile;
17+
}
18+
return ErrorCode_Success;
19+
}
20+
21+
auto BoundedReader::try_read(char* buf, size_t num_bytes_to_read, size_t& num_bytes_read)
22+
-> ErrorCode {
23+
if (m_curr_pos == m_bound) {
24+
num_bytes_read = 0;
25+
return ErrorCode_EndOfFile;
26+
}
27+
28+
if ((m_curr_pos + num_bytes_to_read) > m_bound) {
29+
num_bytes_to_read = m_bound - m_curr_pos;
30+
}
31+
32+
auto const rc = m_reader->try_read(buf, num_bytes_to_read, num_bytes_read);
33+
m_curr_pos += num_bytes_read;
34+
if (ErrorCode_EndOfFile == rc) {
35+
if (0 == num_bytes_read) {
36+
return ErrorCode_EndOfFile;
37+
}
38+
} else if (ErrorCode_Success != rc) {
39+
return rc;
40+
}
41+
return ErrorCode_Success;
42+
}
43+
} // namespace clp
+89
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
#ifndef CLP_BOUNDEDREADER_HPP
2+
#define CLP_BOUNDEDREADER_HPP
3+
4+
#include <cstddef>
5+
#include <string>
6+
7+
#include "ErrorCode.hpp"
8+
#include "ReaderInterface.hpp"
9+
10+
namespace clp {
11+
/**
12+
* BoundedReader is a ReaderInterface designed to wrap other ReaderInterfaces and prevent users
13+
* from reading or seeking beyond a certain point in the underlying input stream.
14+
*
15+
* This is useful when the underlying input stream is divided into several logical segments and we
16+
* want to prevent a reader for an earlier segment consuming any bytes from a later segment. In
17+
* particular, reading part of a later segment may force the reader for that later segment to seek
18+
* backwards, which can be either inefficient or impossible for certain kinds of input streams.
19+
*/
20+
class BoundedReader : public ReaderInterface {
21+
public:
22+
// Constructor
23+
explicit BoundedReader(ReaderInterface* reader, size_t bound)
24+
: m_reader{reader},
25+
m_bound{bound} {
26+
if (nullptr == m_reader) {
27+
throw ReaderInterface::OperationFailed(ErrorCode_BadParam, __FILE__, __LINE__);
28+
}
29+
m_curr_pos = m_reader->get_pos();
30+
if (m_curr_pos > m_bound) {
31+
throw ReaderInterface::OperationFailed(ErrorCode_BadParam, __FILE__, __LINE__);
32+
}
33+
}
34+
35+
// Methods implementing the ReaderInterface
36+
/**
37+
* Tries to get the current position of the read head in the underlying reader.
38+
* @param pos Returns the position of the underlying reader's head
39+
* @return ErrorCode_Success on success
40+
* @return ErrorCode_errno on failure
41+
*/
42+
[[nodiscard]] auto try_get_pos(size_t& pos) -> ErrorCode override {
43+
return m_reader->try_get_pos(pos);
44+
}
45+
46+
/**
47+
* Tries to seek to the given position, limited by the bound.
48+
* @param pos
49+
* @return ErrorCode_Success on success
50+
* @return ErrorCode_EndOfFile on EOF or if trying to seek beyond the checkpoint
51+
* @return ErrorCode_errno on failure
52+
*/
53+
[[nodiscard]] auto try_seek_from_begin(size_t pos) -> ErrorCode override;
54+
55+
/**
56+
* Tries to read up to a given number of bytes from the file, limited by the bound.
57+
* @param buf
58+
* @param num_bytes_to_read The number of bytes to try and read
59+
* @param num_bytes_read The actual number of bytes read
60+
* @return ErrorCode_errno on error
61+
* @return ErrorCode_EndOfFile on EOF or trying to read after hitting checkpoint
62+
* @return ErrorCode_Success on success
63+
*/
64+
[[nodiscard]] auto
65+
try_read(char* buf, size_t num_bytes_to_read, size_t& num_bytes_read) -> ErrorCode override;
66+
67+
/**
68+
* This function is unsupported because BoundedReader can not delegate to a potentially
69+
* efficient implementation in the underlying reader, as the underlying reader's implementation
70+
* will not respect the bound.
71+
* @return ErrorCode_Unsupported
72+
*/
73+
[[nodiscard]] auto try_read_to_delimiter(
74+
[[maybe_unused]] char delim,
75+
[[maybe_unused]] bool keep_delimiter,
76+
[[maybe_unused]] bool append,
77+
[[maybe_unused]] std::string& str
78+
) -> ErrorCode override {
79+
return ErrorCode_Unsupported;
80+
}
81+
82+
private:
83+
ReaderInterface* m_reader{nullptr};
84+
size_t m_bound{};
85+
size_t m_curr_pos{};
86+
};
87+
} // namespace clp
88+
89+
#endif // CLP_BOUNDEDREADER_HPP

components/core/src/clp/StringReader.cpp

+4
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,10 @@ ErrorCode StringReader::try_read(char* buf, size_t num_bytes_to_read, size_t& nu
4141
}
4242

4343
ErrorCode StringReader::try_seek_from_begin(size_t pos) {
44+
if (pos > input_string.size()) {
45+
this->pos = input_string.size();
46+
return ErrorCode_EndOfFile;
47+
}
4448
this->pos = pos;
4549
return ErrorCode_Success;
4650
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
#include <array>
2+
#include <cstddef>
3+
#include <string>
4+
#include <string_view>
5+
6+
#include <Catch2/single_include/catch2/catch.hpp>
7+
8+
#include "../src/clp/BoundedReader.hpp"
9+
#include "../src/clp/ErrorCode.hpp"
10+
#include "../src/clp/StringReader.hpp"
11+
12+
TEST_CASE("Test Bounded Reader", "[BoundedReader]") {
13+
constexpr std::string_view cTestString{"0123456789"};
14+
15+
SECTION("BoundedReader does not support try_read_to_delimiter") {
16+
clp::StringReader string_reader;
17+
string_reader.open(std::string{cTestString});
18+
clp::BoundedReader bounded_reader{&string_reader, cTestString.size()};
19+
std::string tmp;
20+
REQUIRE(clp::ErrorCode_Unsupported
21+
== bounded_reader.try_read_to_delimiter('0', false, false, tmp));
22+
}
23+
24+
SECTION("BoundedReader does not allow reads beyond end of underlying stream.") {
25+
clp::StringReader string_reader;
26+
string_reader.open(std::string{cTestString});
27+
clp::BoundedReader bounded_reader{&string_reader, cTestString.size() + 1};
28+
std::array<char, cTestString.size() + 1> buf{};
29+
size_t num_bytes_read{};
30+
auto rc = bounded_reader.try_read(buf.data(), cTestString.size() + 1, num_bytes_read);
31+
REQUIRE(clp::ErrorCode_Success == rc);
32+
REQUIRE(num_bytes_read == cTestString.size());
33+
REQUIRE(cTestString.size() == string_reader.get_pos());
34+
REQUIRE(cTestString.size() == bounded_reader.get_pos());
35+
}
36+
37+
SECTION("BoundedReader does not allow reads beyond checkpoint.") {
38+
clp::StringReader string_reader;
39+
string_reader.open(std::string{cTestString});
40+
clp::BoundedReader bounded_reader{&string_reader, 1};
41+
std::array<char, cTestString.size()> buf{};
42+
size_t num_bytes_read{};
43+
auto rc = bounded_reader.try_read(buf.data(), cTestString.size(), num_bytes_read);
44+
REQUIRE(clp::ErrorCode_Success == rc);
45+
REQUIRE(1 == num_bytes_read);
46+
REQUIRE(1 == string_reader.get_pos());
47+
REQUIRE(1 == bounded_reader.get_pos());
48+
rc = bounded_reader.try_read(buf.data(), 1, num_bytes_read);
49+
REQUIRE(clp::ErrorCode_EndOfFile == rc);
50+
REQUIRE(0 == num_bytes_read);
51+
REQUIRE(1 == string_reader.get_pos());
52+
REQUIRE(1 == bounded_reader.get_pos());
53+
}
54+
55+
SECTION("BoundedReader does allow reads before checkpoint.") {
56+
clp::StringReader string_reader;
57+
string_reader.open(std::string{cTestString});
58+
clp::BoundedReader bounded_reader{&string_reader, 1};
59+
char buf{};
60+
size_t num_bytes_read{};
61+
auto rc = bounded_reader.try_read(&buf, 1, num_bytes_read);
62+
REQUIRE(clp::ErrorCode_Success == rc);
63+
REQUIRE(1 == num_bytes_read);
64+
REQUIRE(1 == string_reader.get_pos());
65+
REQUIRE(1 == bounded_reader.get_pos());
66+
}
67+
68+
SECTION("BoundedReader does not allow seeks beyond end of underlying stream.") {
69+
clp::StringReader string_reader;
70+
string_reader.open(std::string{cTestString});
71+
clp::BoundedReader bounded_reader{&string_reader, cTestString.size() + 1};
72+
auto rc = bounded_reader.try_seek_from_begin(cTestString.size() + 1);
73+
REQUIRE(clp::ErrorCode_EndOfFile == rc);
74+
REQUIRE(cTestString.size() == string_reader.get_pos());
75+
REQUIRE(cTestString.size() == bounded_reader.get_pos());
76+
}
77+
78+
SECTION("BoundedReader does not allow seeks beyond checkpoint.") {
79+
clp::StringReader string_reader;
80+
string_reader.open(std::string{cTestString});
81+
clp::BoundedReader bounded_reader{&string_reader, 1};
82+
size_t num_bytes_read{};
83+
auto rc = bounded_reader.try_seek_from_begin(cTestString.size());
84+
REQUIRE(clp::ErrorCode_EndOfFile == rc);
85+
REQUIRE(1 == string_reader.get_pos());
86+
REQUIRE(1 == bounded_reader.get_pos());
87+
}
88+
89+
SECTION("BoundedReader does allow seeks before checkpoint.") {
90+
clp::StringReader string_reader;
91+
string_reader.open(std::string{cTestString});
92+
clp::BoundedReader bounded_reader{&string_reader, 2};
93+
size_t num_bytes_read{};
94+
auto rc = bounded_reader.try_seek_from_begin(1);
95+
REQUIRE(clp::ErrorCode_Success == rc);
96+
REQUIRE(1 == string_reader.get_pos());
97+
REQUIRE(1 == bounded_reader.get_pos());
98+
}
99+
}

0 commit comments

Comments
 (0)