File tree 5 files changed +53
-5
lines changed 5 files changed +53
-5
lines changed Original file line number Diff line number Diff line change @@ -153,7 +153,7 @@ bool Session::Accept(Connection& conn)
153
153
}
154
154
155
155
const std::string& peer_dest =
156
- conn.sock .RecvUntilTerminator (' \n ' , MAX_WAIT_FOR_IO, *m_interrupt);
156
+ conn.sock .RecvUntilTerminator (' \n ' , MAX_WAIT_FOR_IO, *m_interrupt, MAX_MSG_SIZE );
157
157
158
158
conn.peer = CService (DestB64ToAddr (peer_dest), Params ().GetDefaultPort ());
159
159
@@ -252,7 +252,7 @@ Session::Reply Session::SendRequestAndGetReply(const Sock& sock,
252
252
// signaled.
253
253
static constexpr auto recv_timeout = 3min;
254
254
255
- reply.full = sock.RecvUntilTerminator (' \n ' , recv_timeout, *m_interrupt);
255
+ reply.full = sock.RecvUntilTerminator (' \n ' , recv_timeout, *m_interrupt, MAX_MSG_SIZE );
256
256
257
257
for (const auto & kv : spanparsing::Split (reply.full , ' ' )) {
258
258
const auto & pos = std::find (kv.begin (), kv.end (), ' =' );
Original file line number Diff line number Diff line change @@ -40,6 +40,14 @@ struct Connection {
40
40
41
41
namespace sam {
42
42
43
+ /* *
44
+ * The maximum size of an incoming message from the I2P SAM proxy (in bytes).
45
+ * Used to avoid a runaway proxy from sending us an "unlimited" amount of data without a terminator.
46
+ * The longest known message is ~1400 bytes, so this is high enough not to be triggered during
47
+ * normal operation, yet low enough to avoid a malicious proxy from filling our memory.
48
+ */
49
+ static constexpr size_t MAX_MSG_SIZE{65536 };
50
+
43
51
/* *
44
52
* I2P SAM session.
45
53
*/
Original file line number Diff line number Diff line change 4
4
5
5
#include < compat.h>
6
6
#include < test/util/setup_common.h>
7
+ #include < threadinterrupt.h>
7
8
#include < util/sock.h>
8
9
#include < util/system.h>
9
10
10
11
#include < boost/test/unit_test.hpp>
11
12
13
+ #include < cassert>
12
14
#include < thread>
13
15
14
16
using namespace std ::chrono_literals;
@@ -144,6 +146,35 @@ BOOST_AUTO_TEST_CASE(wait)
144
146
waiter.join ();
145
147
}
146
148
149
+ BOOST_AUTO_TEST_CASE (recv_until_terminator_limit)
150
+ {
151
+ constexpr auto timeout = 1min; // High enough so that it is never hit.
152
+ CThreadInterrupt interrupt;
153
+ int s[2 ];
154
+ CreateSocketPair (s);
155
+
156
+ Sock sock_send (s[0 ]);
157
+ Sock sock_recv (s[1 ]);
158
+
159
+ std::thread receiver ([&sock_recv, &timeout, &interrupt]() {
160
+ constexpr size_t max_data{10 };
161
+ bool threw_as_expected{false };
162
+ // BOOST_CHECK_EXCEPTION() writes to some variables shared with the main thread which
163
+ // creates a data race. So mimic it manually.
164
+ try {
165
+ sock_recv.RecvUntilTerminator (' \n ' , timeout, interrupt, max_data);
166
+ } catch (const std::runtime_error& e) {
167
+ threw_as_expected = HasReason (" too many bytes without a terminator" )(e);
168
+ }
169
+ assert (threw_as_expected);
170
+ });
171
+
172
+ BOOST_REQUIRE_NO_THROW (sock_send.SendComplete (" 1234567" , timeout, interrupt));
173
+ BOOST_REQUIRE_NO_THROW (sock_send.SendComplete (" 89a\n " , timeout, interrupt));
174
+
175
+ receiver.join ();
176
+ }
177
+
147
178
#endif /* WIN32 */
148
179
149
180
BOOST_AUTO_TEST_SUITE_END ()
Original file line number Diff line number Diff line change @@ -175,7 +175,8 @@ void Sock::SendComplete(const std::string& data,
175
175
176
176
std::string Sock::RecvUntilTerminator (uint8_t terminator,
177
177
std::chrono::milliseconds timeout,
178
- CThreadInterrupt& interrupt) const
178
+ CThreadInterrupt& interrupt,
179
+ size_t max_data) const
179
180
{
180
181
const auto deadline = GetTime<std::chrono::milliseconds>() + timeout;
181
182
std::string data;
@@ -190,9 +191,14 @@ std::string Sock::RecvUntilTerminator(uint8_t terminator,
190
191
// at a time is about 50 times slower.
191
192
192
193
for (;;) {
194
+ if (data.size () >= max_data) {
195
+ throw std::runtime_error (
196
+ strprintf (" Received too many bytes without a terminator (%u)" , data.size ()));
197
+ }
198
+
193
199
char buf[512 ];
194
200
195
- const ssize_t peek_ret{Recv (buf, sizeof (buf), MSG_PEEK)};
201
+ const ssize_t peek_ret{Recv (buf, std::min ( sizeof (buf), max_data - data. size () ), MSG_PEEK)};
196
202
197
203
switch (peek_ret) {
198
204
case -1 : {
Original file line number Diff line number Diff line change @@ -135,13 +135,16 @@ class Sock
135
135
* @param[in] terminator Character up to which to read from the socket.
136
136
* @param[in] timeout Timeout for the entire operation.
137
137
* @param[in] interrupt If this is signaled then the operation is canceled.
138
+ * @param[in] max_data The maximum amount of data (in bytes) to receive. If this many bytes
139
+ * are received and there is still no terminator, then this method will throw an exception.
138
140
* @return The data that has been read, without the terminating character.
139
141
* @throws std::runtime_error if the operation cannot be completed. In this case some bytes may
140
142
* have been consumed from the socket.
141
143
*/
142
144
virtual std::string RecvUntilTerminator (uint8_t terminator,
143
145
std::chrono::milliseconds timeout,
144
- CThreadInterrupt& interrupt) const ;
146
+ CThreadInterrupt& interrupt,
147
+ size_t max_data) const ;
145
148
146
149
/* *
147
150
* Check if still connected.
You can’t perform that action at this time.
0 commit comments