Skip to content

Commit c34b886

Browse files
committed
Merge #17095: util: Filter control characters out of log messages
d7820a1 util: Filter control characters out of log messages (Wladimir J. van der Laan) Pull request description: Belts and suspenders: make sure outgoing log messages don't contain potentially suspicious characters, such as terminal control codes. This escapes control characters except newline ('\n') in C syntax. It escapes instead of removes them to still allow for troubleshooting issues where they accidentally end up in strings (it is a debug log, after all). (more checks could be added such as UTF-8 validity and unicode code-point range checking—this is substantially more involved and would need to keep track of state between characters and even `LogPrint` calls as they could end up split up—but escape codes seem to be the most common attack vector for terminals.) ACKs for top commit: practicalswift: ACK d7820a1 - tested and works as expected :) Tree-SHA512: 0806265addebdcec1062a6def3e903555e62ba5e93967ce9ee6943d16462a222b3f41135a5bff0a76966ae9e7ed75f211d7785bceda788ae0b0654bf3fd891bf
2 parents 5a3dd93 + d7820a1 commit c34b886

File tree

2 files changed

+41
-1
lines changed

2 files changed

+41
-1
lines changed

src/logging.cpp

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -224,10 +224,32 @@ std::string BCLog::Logger::LogTimestampStr(const std::string& str)
224224
return strStamped;
225225
}
226226

227+
namespace BCLog {
228+
/** Belts and suspenders: make sure outgoing log messages don't contain
229+
* potentially suspicious characters, such as terminal control codes.
230+
*
231+
* This escapes control characters except newline ('\n') in C syntax.
232+
* It escapes instead of removes them to still allow for troubleshooting
233+
* issues where they accidentally end up in strings.
234+
*/
235+
std::string LogEscapeMessage(const std::string& str) {
236+
std::string ret;
237+
for (char ch_in : str) {
238+
uint8_t ch = (uint8_t)ch_in;
239+
if ((ch >= 32 || ch == '\n') && ch != '\x7f') {
240+
ret += ch_in;
241+
} else {
242+
ret += strprintf("\\x%02x", ch);
243+
}
244+
}
245+
return ret;
246+
}
247+
}
248+
227249
void BCLog::Logger::LogPrintStr(const std::string& str)
228250
{
229251
std::lock_guard<std::mutex> scoped_lock(m_cs);
230-
std::string str_prefixed = str;
252+
std::string str_prefixed = LogEscapeMessage(str);
231253

232254
if (m_log_threadnames && m_started_new_line) {
233255
str_prefixed.insert(0, "[" + util::ThreadGetInternalName() + "] ");

src/test/util_tests.cpp

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,11 @@
2626

2727
#include <boost/test/unit_test.hpp>
2828

29+
/* defined in logging.cpp */
30+
namespace BCLog {
31+
std::string LogEscapeMessage(const std::string& str);
32+
}
33+
2934
BOOST_FIXTURE_TEST_SUITE(util_tests, BasicTestingSetup)
3035

3136
BOOST_AUTO_TEST_CASE(util_criticalsection)
@@ -1696,4 +1701,17 @@ BOOST_AUTO_TEST_CASE(test_spanparsing)
16961701
BOOST_CHECK_EQUAL(SpanToStr(results[3]), "");
16971702
}
16981703

1704+
BOOST_AUTO_TEST_CASE(test_LogEscapeMessage)
1705+
{
1706+
// ASCII and UTF-8 must pass through unaltered.
1707+
BOOST_CHECK_EQUAL(BCLog::LogEscapeMessage("Valid log message貓"), "Valid log message貓");
1708+
// Newlines must pass through unaltered.
1709+
BOOST_CHECK_EQUAL(BCLog::LogEscapeMessage("Message\n with newlines\n"), "Message\n with newlines\n");
1710+
// Other control characters are escaped in C syntax.
1711+
BOOST_CHECK_EQUAL(BCLog::LogEscapeMessage("\x01\x7f Corrupted log message\x0d"), R"(\x01\x7f Corrupted log message\x0d)");
1712+
// Embedded NULL characters are escaped too.
1713+
const std::string NUL("O\x00O", 3);
1714+
BOOST_CHECK_EQUAL(BCLog::LogEscapeMessage(NUL), R"(O\x00O)");
1715+
}
1716+
16991717
BOOST_AUTO_TEST_SUITE_END()

0 commit comments

Comments
 (0)