Skip to content

Commit 3a69141

Browse files
authored
Fix telnet off by one error. (#1930)
* Fix telnet off by one error. * Added additional bounds checking for dataPos. * Improved bounds checking on telnet sequence parsing. - Replaced `isCommandField` -> `isTelnetCommand` - Replaced `isDataField` -> `isTelnetData` - Refactored `getNextCommand` and `getNextDataField` with an improved loop. * Lint * Changed isTelnetData to return `false` on nullptr input. * debug log fix * Added error handling for invalid telnet sequence when searching for next field. * Add error handling for toString(). * Added more error handling. * Move dataPos to be entirely inside the `removeEscapeCharacters` branch. * Added error handling. * Refactored to remove exception throwing in relatively common error operation. Telnet packets can commonly be incomplete due to being based on TCP stream protocol. Parsing errors near the end of the stream are expected to be common, thus throwing exception for them is not recommended. * Fixed switch unhandled value. * Suppressed gcc unused function warning. * Removed redundant else. * Revert "Suppressed gcc unused function warning." This reverts commit 47b35a5. * Removed unused function. * Replaced `dataPos` range assertion with assertion macro. * Fix assert condition. * Remove unnecessary local function.
1 parent b274b01 commit 3a69141

File tree

4 files changed

+209
-53
lines changed

4 files changed

+209
-53
lines changed

Common++/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ add_library(
1414

1515
set(
1616
public_headers
17+
header/AssertionUtils.h
1718
header/DeprecationUtils.h
1819
header/GeneralUtils.h
1920
header/IpAddress.h

Common++/header/AssertionUtils.h

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
#pragma once
2+
3+
/// @file
4+
/// @brief This file contains internal assertion utilities used in PcapPlusPlus for debugging.
5+
6+
#ifndef PCPP_ASSERT_USE_C_ASSERT
7+
# define PCPP_ASSERT_USE_C_ASSERT 0
8+
#endif // !PCPP_ASSERT_USE_C_ASSERT
9+
10+
#include <stdexcept>
11+
#if PCPP_ASSERT_USE_C_ASSERT
12+
# include <cassert>
13+
#else
14+
# include <string>
15+
# include <sstream>
16+
#endif // PCPP_ASSERT_USE_C_ASSERT
17+
18+
namespace pcpp
19+
{
20+
namespace internal
21+
{
22+
/// @brief A custom assertion error class derived from std::logic_error to be used with PCPP_ASSERT.
23+
class AssertionError : public std::logic_error
24+
{
25+
public:
26+
using std::logic_error::logic_error;
27+
};
28+
} // namespace internal
29+
} // namespace pcpp
30+
31+
#ifndef NDEBUG
32+
# if PCPP_ASSERT_USE_C_ASSERT
33+
# define PCPP_ASSERT(condition, message) assert((condition) && (message))
34+
# else // !PCPP_ASSERT_USE_C_ASSERT
35+
# define PCPP_ASSERT(condition, message) \
36+
do \
37+
{ \
38+
if (!(condition)) \
39+
{ \
40+
std::stringstream ss; \
41+
ss << "[PCPP] Assertion failed on [" << __FILE__ << ":" << __LINE__ << "] with: " << (message); \
42+
throw pcpp::internal::AssertionError(ss.str()); \
43+
} \
44+
} while (false)
45+
# endif // PCPP_ASSERT_USE_C_ASSERT
46+
#else
47+
# define PCPP_ASSERT(condition, message) (void)0;
48+
#endif // !NDEBUG

Packet++/header/TelnetLayer.h

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,6 @@ namespace pcpp
1515
// Position iterator for next command
1616
size_t lastPositionOffset;
1717

18-
// Checks if position is a data field
19-
bool isDataField(uint8_t* pos) const;
20-
// Checks if position is a command field
21-
bool isCommandField(uint8_t* pos) const;
2218
// Returns distance to next IAC
2319
size_t distanceToNextIAC(uint8_t* startPos, size_t maxLength);
2420
// Returns length of provided field

Packet++/src/TelnetLayer.cpp

Lines changed: 160 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -3,22 +3,72 @@
33
#include "TelnetLayer.h"
44
#include "Logger.h"
55
#include "GeneralUtils.h"
6+
#include "AssertionUtils.h"
67
#include <cstring>
8+
#include <iterator>
9+
#include <algorithm>
710

811
namespace pcpp
912
{
10-
11-
bool TelnetLayer::isDataField(uint8_t* pos) const
13+
namespace
1214
{
13-
// "FF FF" means data
14-
return pos[0] != static_cast<int>(TelnetCommand::InterpretAsCommand) ||
15-
pos[1] == static_cast<int>(TelnetCommand::InterpretAsCommand);
16-
}
15+
/// @brief An enum representing the type of a Telnet sequence
16+
enum class TelnetSequenceType
17+
{
18+
/// @brief An unknown sequence type. Usually means parsing error.
19+
/// Commonly happens when an IAC symbol is found at the end of the buffer without a following byte.
20+
Unknown,
21+
/// @brief A telnet command sequence. Starts with IAC followed by a command code.
22+
Command,
23+
/// @brief A telnet data sequence. Either does not start with IAC or starts with IAC IAC.
24+
UserData,
25+
};
26+
27+
/// @brief Checks if a given sequence matches Telnet command or data pattern.
28+
/// @param first Start of the sequence to check
29+
/// @param maxCount Maximum number of bytes to check
30+
/// @return The type of the Telnet sequence. Unknown if the sequence does not match either command or data
31+
/// pattern or if parsing error occurs.
32+
TelnetSequenceType getTelnetSequenceType(uint8_t const* first, size_t maxCount)
33+
{
34+
if (first == nullptr || maxCount == 0)
35+
{
36+
PCPP_LOG_DEBUG("Checking empty or null buffer for telnet sequence type");
37+
return TelnetSequenceType::Unknown;
38+
}
1739

18-
bool TelnetLayer::isCommandField(uint8_t* pos) const
19-
{
20-
return !isDataField(pos);
21-
}
40+
// If first byte is not "FF" it's data
41+
if (*first != static_cast<int>(TelnetLayer::TelnetCommand::InterpretAsCommand))
42+
{
43+
return TelnetSequenceType::UserData;
44+
}
45+
46+
// IAC must be followed by another octet
47+
if (maxCount <= 1)
48+
{
49+
PCPP_LOG_DEBUG("Telnet Parse Error: IAC (FF) must always be followed by another octet");
50+
return TelnetSequenceType::Unknown;
51+
}
52+
53+
if (first[1] == static_cast<int>(TelnetLayer::TelnetCommand::InterpretAsCommand))
54+
{
55+
// "FF FF" means data continue
56+
return TelnetSequenceType::UserData;
57+
}
58+
59+
// "FF X" where X != "FF" means command
60+
return TelnetSequenceType::Command;
61+
}
62+
63+
/// @brief Checks if a given sequence matches Telnet command pattern.
64+
/// @param first Start of the sequence to check
65+
/// @param maxCount Maximum number of bytes to check
66+
/// @return True if the buffer matches Telnet command pattern, false otherwise
67+
bool isTelnetCommand(uint8_t const* first, size_t maxCount)
68+
{
69+
return getTelnetSequenceType(first, maxCount) == TelnetSequenceType::Command;
70+
}
71+
} // namespace
2272

2373
size_t TelnetLayer::distanceToNextIAC(uint8_t* startPos, size_t maxLength)
2474
{
@@ -64,36 +114,65 @@ namespace pcpp
64114

65115
uint8_t* TelnetLayer::getNextDataField(uint8_t* pos, size_t len)
66116
{
67-
size_t offset = 0;
68-
while (offset < len)
69-
{
70-
// Move to next field
71-
size_t length = getFieldLen(pos, len - offset);
72-
pos += length;
73-
offset += length;
117+
// This assumes `pos` points to the start of a valid field.
118+
auto const endIt = pos + len;
74119

75-
if (isDataField(pos))
120+
// Advance to the next field, as we are skipping the current one from the search.
121+
pos += getFieldLen(pos, len);
122+
123+
while (pos < endIt)
124+
{
125+
// Check if the current field is data
126+
switch (getTelnetSequenceType(pos, std::distance(pos, endIt)))
127+
{
128+
case TelnetSequenceType::Unknown:
129+
{
130+
PCPP_LOG_DEBUG("Telnet Parse Error: Unknown sequence found during data field search.");
131+
return nullptr;
132+
}
133+
case TelnetSequenceType::UserData:
76134
return pos;
135+
default:
136+
break; // continue searching
137+
}
138+
139+
// If not data, move to next field
140+
pos += getFieldLen(pos, std::distance(pos, endIt));
77141
}
78142

143+
// If we got here, no data field has been found before the end of the buffer
79144
return nullptr;
80145
}
81146

82147
uint8_t* TelnetLayer::getNextCommandField(uint8_t* pos, size_t len)
83148
{
84-
size_t offset = 0;
85-
while (offset < len)
86-
{
87-
// Move to next field
88-
size_t length = getFieldLen(pos, len - offset);
89-
pos += length;
90-
offset += length;
149+
// This assumes `pos` points to the start of a valid field.
150+
auto const endIt = pos + len;
151+
152+
// Advance to the next field, as we are skipping the current one from the search.
153+
pos += getFieldLen(pos, len);
91154

92-
if ((static_cast<size_t>(pos - m_Data) <= (m_DataLen - 2)) &&
93-
isCommandField(pos)) // Need at least 2 bytes for command
155+
while (pos < endIt)
156+
{
157+
// Check if the current field is command
158+
switch (getTelnetSequenceType(pos, std::distance(pos, endIt)))
159+
{
160+
case TelnetSequenceType::Unknown:
161+
{
162+
PCPP_LOG_DEBUG("Telnet Parse Error: Unknown sequence found during command field search.");
163+
return nullptr;
164+
}
165+
case TelnetSequenceType::Command:
94166
return pos;
167+
default:
168+
break; // continue searching
169+
}
170+
171+
// If not command, move to next field
172+
pos += getFieldLen(pos, std::distance(pos, endIt));
95173
}
96174

175+
// If we got here, no command field has been found before the end of the buffer
97176
return nullptr;
98177
}
99178

@@ -117,36 +196,59 @@ namespace pcpp
117196

118197
std::string TelnetLayer::getDataAsString(bool removeEscapeCharacters)
119198
{
120-
uint8_t* dataPos = nullptr;
121-
if (isDataField(m_Data))
122-
dataPos = m_Data;
123-
else
124-
dataPos = getNextDataField(m_Data, m_DataLen);
125-
126-
if (!dataPos)
199+
if (m_Data == nullptr)
127200
{
128-
PCPP_LOG_DEBUG("Packet does not have a data field");
129-
return std::string();
201+
PCPP_LOG_DEBUG("Layer does not have data");
202+
return {};
130203
}
131204

132205
// Convert to string
133206
if (removeEscapeCharacters)
134207
{
135-
std::stringstream ss;
136-
for (size_t idx = 0; idx < m_DataLen - (dataPos - m_Data) + 1; ++idx)
208+
uint8_t* dataPos = nullptr;
209+
switch (getTelnetSequenceType(m_Data, m_DataLen))
210+
{
211+
case TelnetSequenceType::Unknown:
212+
{
213+
PCPP_LOG_DEBUG("Telnet Parse Error: Unknown sequence found during data string extraction.");
214+
return {};
215+
}
216+
case TelnetSequenceType::UserData:
217+
dataPos = m_Data;
218+
break;
219+
case TelnetSequenceType::Command:
220+
dataPos = getNextDataField(m_Data, m_DataLen);
221+
break;
222+
default:
223+
throw std::logic_error("Unsupported sequence type");
224+
}
225+
226+
if (!dataPos)
137227
{
138-
if (int(dataPos[idx]) < 127 && int(dataPos[idx]) > 31) // From SPACE to ~
139-
ss << dataPos[idx];
228+
PCPP_LOG_DEBUG("Packet does not have a data field");
229+
return std::string();
140230
}
141-
return ss.str();
231+
232+
PCPP_ASSERT(dataPos >= m_Data && dataPos < (m_Data + m_DataLen),
233+
"Data position is out of bounds, this should never happen!");
234+
235+
// End of range is corrected by the advance offset.
236+
auto const* beginIt = dataPos;
237+
auto const* endIt = dataPos + m_DataLen - std::distance(m_Data, dataPos);
238+
239+
std::string result;
240+
std::copy_if(beginIt, endIt, std::back_inserter(result), [](char ch) -> bool {
241+
return ch > 31 && ch < 127; // From SPACE to ~
242+
});
243+
return result;
142244
}
143245
return std::string((char*)m_Data, m_DataLen);
144246
}
145247

146248
size_t TelnetLayer::getTotalNumberOfCommands()
147249
{
148250
size_t ctr = 0;
149-
if (isCommandField(m_Data))
251+
if (isTelnetCommand(m_Data, m_DataLen))
150252
++ctr;
151253

152254
uint8_t* pos = m_Data;
@@ -167,7 +269,7 @@ namespace pcpp
167269
return 0;
168270

169271
size_t ctr = 0;
170-
if (isCommandField(m_Data) && m_Data[1] == static_cast<int>(command))
272+
if (isTelnetCommand(m_Data, m_DataLen) && m_Data[1] == static_cast<int>(command))
171273
++ctr;
172274

173275
uint8_t* pos = m_Data;
@@ -185,7 +287,7 @@ namespace pcpp
185287
TelnetLayer::TelnetCommand TelnetLayer::getFirstCommand()
186288
{
187289
// If starts with command
188-
if (isCommandField(m_Data))
290+
if (isTelnetCommand(m_Data, m_DataLen))
189291
return static_cast<TelnetCommand>(m_Data[1]);
190292

191293
// Check is there any command
@@ -200,7 +302,7 @@ namespace pcpp
200302
if (lastPositionOffset == SIZE_MAX)
201303
{
202304
lastPositionOffset = 0;
203-
if (isCommandField(m_Data))
305+
if (isTelnetCommand(m_Data, m_DataLen))
204306
return static_cast<TelnetLayer::TelnetCommand>(m_Data[1]);
205307
}
206308

@@ -231,7 +333,7 @@ namespace pcpp
231333
return TelnetOption::TelnetOptionNoOption;
232334
}
233335

234-
if (isCommandField(m_Data) && m_Data[1] == static_cast<int>(command))
336+
if (isTelnetCommand(m_Data, m_DataLen) && m_Data[1] == static_cast<int>(command))
235337
return static_cast<TelnetOption>(getSubCommand(m_Data, getFieldLen(m_Data, m_DataLen)));
236338

237339
uint8_t* pos = m_Data;
@@ -271,7 +373,7 @@ namespace pcpp
271373
return nullptr;
272374
}
273375

274-
if (isCommandField(m_Data) && m_Data[1] == static_cast<int>(command))
376+
if (isTelnetCommand(m_Data, m_DataLen) && m_Data[1] == static_cast<int>(command))
275377
{
276378
size_t lenBuffer = getFieldLen(m_Data, m_DataLen);
277379
uint8_t* posBuffer = getCommandData(m_Data, lenBuffer);
@@ -473,9 +575,18 @@ namespace pcpp
473575

474576
std::string TelnetLayer::toString() const
475577
{
476-
if (isDataField(m_Data))
578+
// TODO: Perhaps print the entire sequence of commands and data?
579+
switch (getTelnetSequenceType(m_Data, m_DataLen))
580+
{
581+
case TelnetSequenceType::Unknown:
582+
return "Telnet Unknown";
583+
case TelnetSequenceType::Command:
584+
return "Telnet Control";
585+
case TelnetSequenceType::UserData:
477586
return "Telnet Data";
478-
return "Telnet Control";
587+
default:
588+
throw std::logic_error("Unsupported sequence type");
589+
}
479590
}
480591

481592
} // namespace pcpp

0 commit comments

Comments
 (0)