Skip to content

Commit dbbcc69

Browse files
authored
logging: fix delegating log sink races (envoyproxy#12298)
This fixes two different issues: 1) Previously there was no locking around log sink replacement, so it was possibles for a log sink to get removed by one thread while getting written to by another thread. 2) Even with locking, the base class destructor pattern would do the swap after the derived class was destroyed, leading to undefined behavior. This was easy to reproduce in cx_limit_integration_test but is an issue anywhere the log expectations are used, or previously in the death test stderr workaround (EXPECT_DEATH_LOG_TO_STDERR) for coverage which has been removed because coverage no longer logs to a file and instead logs to stderr like the rest of the tests. Fixes envoyproxy#11841 Risk Level: Medium, code is a bit scary, though only really in tests Testing: Existing tests Docs Changes: N/A Release Notes: N/A Signed-off-by: Matt Klein <[email protected]>
1 parent 2e939b6 commit dbbcc69

26 files changed

+141
-115
lines changed

source/common/common/logger.cc

+29-6
Original file line numberDiff line numberDiff line change
@@ -19,18 +19,34 @@ namespace Logger {
1919
StandardLogger::StandardLogger(const std::string& name)
2020
: Logger(std::make_shared<spdlog::logger>(name, Registry::getSink())) {}
2121

22-
SinkDelegate::SinkDelegate(DelegatingLogSinkSharedPtr log_sink)
23-
: previous_delegate_(log_sink->delegate()), log_sink_(log_sink) {
24-
log_sink->setDelegate(this);
25-
}
22+
SinkDelegate::SinkDelegate(DelegatingLogSinkSharedPtr log_sink) : log_sink_(log_sink) {}
2623

2724
SinkDelegate::~SinkDelegate() {
28-
assert(log_sink_->delegate() == this); // Ensures stacked allocation of delegates.
25+
// The previous delegate should have never been set or should have been reset by now via
26+
// restoreDelegate();
27+
assert(previous_delegate_ == nullptr);
28+
}
29+
30+
void SinkDelegate::setDelegate() {
31+
// There should be no previous delegate before this call.
32+
assert(previous_delegate_ == nullptr);
33+
previous_delegate_ = log_sink_->delegate();
34+
log_sink_->setDelegate(this);
35+
}
36+
37+
void SinkDelegate::restoreDelegate() {
38+
// Ensures stacked allocation of delegates.
39+
assert(log_sink_->delegate() == this);
2940
log_sink_->setDelegate(previous_delegate_);
41+
previous_delegate_ = nullptr;
3042
}
3143

3244
StderrSinkDelegate::StderrSinkDelegate(DelegatingLogSinkSharedPtr log_sink)
33-
: SinkDelegate(log_sink) {}
45+
: SinkDelegate(log_sink) {
46+
setDelegate();
47+
}
48+
49+
StderrSinkDelegate::~StderrSinkDelegate() { restoreDelegate(); }
3450

3551
void StderrSinkDelegate::log(absl::string_view msg) {
3652
Thread::OptionalLockGuard guard(lock_);
@@ -60,6 +76,13 @@ void DelegatingLogSink::log(const spdlog::details::log_msg& msg) {
6076
}
6177
lock.Release();
6278

79+
// Hold the sink mutex while performing the actual logging. This prevents the sink from being
80+
// swapped during an individual log event.
81+
// TODO(mattklein123): In production this lock will never be contended. In practice, thread
82+
// protection is really only needed in tests. It would be nice to figure out a test-only
83+
// mechanism for this that does not require extra locking that we don't explicitly need in the
84+
// prod code.
85+
absl::ReaderMutexLock sink_lock(&sink_mutex_);
6386
if (should_escape_) {
6487
sink_->log(escapeLogLine(msg_view));
6588
} else {

source/common/common/logger.h

+28-6
Original file line numberDiff line numberDiff line change
@@ -104,10 +104,21 @@ class SinkDelegate : NonCopyable {
104104
virtual void flush() PURE;
105105

106106
protected:
107+
// Swap the current log sink delegate for this one. This should be called by the derived class
108+
// constructor immediately before returning. This is required to match restoreDelegate(),
109+
// otherwise it's possible for the previous delegate to get set in the base class constructor,
110+
// the derived class constructor throws, and cleanup becomes broken.
111+
void setDelegate();
112+
113+
// Swap the current log sink (this) for the previous one. This should be called by the derived
114+
// class destructor in the body. This is critical as otherwise it's possible for a log message
115+
// to get routed to a partially destructed sink.
116+
void restoreDelegate();
117+
107118
SinkDelegate* previousDelegate() { return previous_delegate_; }
108119

109120
private:
110-
SinkDelegate* previous_delegate_;
121+
SinkDelegate* previous_delegate_{nullptr};
111122
DelegatingLogSinkSharedPtr log_sink_;
112123
};
113124

@@ -117,6 +128,7 @@ class SinkDelegate : NonCopyable {
117128
class StderrSinkDelegate : public SinkDelegate {
118129
public:
119130
explicit StderrSinkDelegate(DelegatingLogSinkSharedPtr log_sink);
131+
~StderrSinkDelegate() override;
120132

121133
// SinkDelegate
122134
void log(absl::string_view msg) override;
@@ -141,7 +153,10 @@ class DelegatingLogSink : public spdlog::sinks::sink {
141153

142154
// spdlog::sinks::sink
143155
void log(const spdlog::details::log_msg& msg) override;
144-
void flush() override { sink_->flush(); }
156+
void flush() override {
157+
absl::ReaderMutexLock lock(&sink_mutex_);
158+
sink_->flush();
159+
}
145160
void set_pattern(const std::string& pattern) override {
146161
set_formatter(spdlog::details::make_unique<spdlog::pattern_formatter>(pattern));
147162
}
@@ -180,13 +195,20 @@ class DelegatingLogSink : public spdlog::sinks::sink {
180195

181196
DelegatingLogSink() = default;
182197

183-
void setDelegate(SinkDelegate* sink) { sink_ = sink; }
184-
SinkDelegate* delegate() { return sink_; }
198+
void setDelegate(SinkDelegate* sink) {
199+
absl::WriterMutexLock lock(&sink_mutex_);
200+
sink_ = sink;
201+
}
202+
SinkDelegate* delegate() {
203+
absl::ReaderMutexLock lock(&sink_mutex_);
204+
return sink_;
205+
}
185206

186-
SinkDelegate* sink_{nullptr};
207+
SinkDelegate* sink_ ABSL_GUARDED_BY(sink_mutex_){nullptr};
208+
absl::Mutex sink_mutex_;
187209
std::unique_ptr<StderrSinkDelegate> stderr_sink_; // Builtin sink to use as a last resort.
188210
std::unique_ptr<spdlog::formatter> formatter_ ABSL_GUARDED_BY(format_mutex_);
189-
absl::Mutex format_mutex_; // direct absl reference to break build cycle.
211+
absl::Mutex format_mutex_;
190212
bool should_escape_{false};
191213
};
192214

source/common/common/logger_delegates.cc

+5-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,11 @@ namespace Logger {
1313
FileSinkDelegate::FileSinkDelegate(const std::string& log_path,
1414
AccessLog::AccessLogManager& log_manager,
1515
DelegatingLogSinkSharedPtr log_sink)
16-
: SinkDelegate(log_sink), log_file_(log_manager.createAccessLog(log_path)) {}
16+
: SinkDelegate(log_sink), log_file_(log_manager.createAccessLog(log_path)) {
17+
setDelegate();
18+
}
19+
20+
FileSinkDelegate::~FileSinkDelegate() { restoreDelegate(); }
1721

1822
void FileSinkDelegate::log(absl::string_view msg) {
1923
// Log files have internal locking to ensure serial, non-interleaved

source/common/common/logger_delegates.h

+1-3
Original file line numberDiff line numberDiff line change
@@ -14,16 +14,14 @@
1414
namespace Envoy {
1515
namespace Logger {
1616

17-
class DelegatingLogSink;
18-
using DelegatingLogSinkSharedPtr = std::shared_ptr<DelegatingLogSink>;
19-
2017
/**
2118
* SinkDelegate that writes log messages to a file.
2219
*/
2320
class FileSinkDelegate : public SinkDelegate {
2421
public:
2522
FileSinkDelegate(const std::string& log_path, AccessLog::AccessLogManager& log_manager,
2623
DelegatingLogSinkSharedPtr log_sink);
24+
~FileSinkDelegate() override;
2725

2826
// SinkDelegate
2927
void log(absl::string_view msg) override;

test/common/buffer/owned_impl_test.cc

-1
Original file line numberDiff line numberDiff line change
@@ -1034,7 +1034,6 @@ TEST_F(OwnedImplTest, ReadReserveAndCommit) {
10341034
}
10351035

10361036
TEST(OverflowDetectingUInt64, Arithmetic) {
1037-
Logger::StderrSinkDelegate stderr_sink(Logger::Registry::getSink()); // For coverage build.
10381037
OverflowDetectingUInt64 length;
10391038
length += 1;
10401039
length -= 1;

test/common/common/assert_test.cc

-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
namespace Envoy {
88

99
TEST(ReleaseAssertDeathTest, VariousLogs) {
10-
Logger::StderrSinkDelegate stderr_sink(Logger::Registry::getSink()); // For coverage build.
1110
EXPECT_DEATH({ RELEASE_ASSERT(0, ""); }, ".*assert failure: 0.*");
1211
EXPECT_DEATH({ RELEASE_ASSERT(0, "With some logs"); },
1312
".*assert failure: 0. Details: With some logs.*");

test/common/common/logger_test.cc

+10-8
Original file line numberDiff line numberDiff line change
@@ -8,38 +8,40 @@
88
namespace Envoy {
99
namespace Logger {
1010

11-
class LoggerEscapeTest : public testing::Test {};
11+
TEST(LoggerTest, StackingStderrSinkDelegate) {
12+
StderrSinkDelegate stacked(Envoy::Logger::Registry::getSink());
13+
}
1214

13-
TEST_F(LoggerEscapeTest, LinuxEOL) {
15+
TEST(LoggerEscapeTest, LinuxEOL) {
1416
EXPECT_EQ("line 1 \\n line 2\n", DelegatingLogSink::escapeLogLine("line 1 \n line 2\n"));
1517
}
1618

17-
TEST_F(LoggerEscapeTest, WindowEOL) {
19+
TEST(LoggerEscapeTest, WindowEOL) {
1820
EXPECT_EQ("line 1 \\n line 2\r\n", DelegatingLogSink::escapeLogLine("line 1 \n line 2\r\n"));
1921
}
2022

21-
TEST_F(LoggerEscapeTest, NoTrailingWhitespace) {
23+
TEST(LoggerEscapeTest, NoTrailingWhitespace) {
2224
EXPECT_EQ("line 1 \\n line 2", DelegatingLogSink::escapeLogLine("line 1 \n line 2"));
2325
}
2426

25-
TEST_F(LoggerEscapeTest, NoWhitespace) {
27+
TEST(LoggerEscapeTest, NoWhitespace) {
2628
EXPECT_EQ("line1", DelegatingLogSink::escapeLogLine("line1"));
2729
}
2830

29-
TEST_F(LoggerEscapeTest, AnyTrailingWhitespace) {
31+
TEST(LoggerEscapeTest, AnyTrailingWhitespace) {
3032
EXPECT_EQ("line 1 \\t tab 1 \\n line 2\t\n",
3133
DelegatingLogSink::escapeLogLine("line 1 \t tab 1 \n line 2\t\n"));
3234
}
3335

34-
TEST_F(LoggerEscapeTest, WhitespaceOnly) {
36+
TEST(LoggerEscapeTest, WhitespaceOnly) {
3537
// 8 spaces
3638
EXPECT_EQ(" ", DelegatingLogSink::escapeLogLine(" "));
3739

3840
// Any whitespace characters
3941
EXPECT_EQ("\r\n\t \r\n \n", DelegatingLogSink::escapeLogLine("\r\n\t \r\n \n"));
4042
}
4143

42-
TEST_F(LoggerEscapeTest, Empty) { EXPECT_EQ("", DelegatingLogSink::escapeLogLine("")); }
44+
TEST(LoggerEscapeTest, Empty) { EXPECT_EQ("", DelegatingLogSink::escapeLogLine("")); }
4345

4446
} // namespace Logger
4547
} // namespace Envoy

test/common/http/header_map_impl_test.cc

+4-4
Original file line numberDiff line numberDiff line change
@@ -1005,14 +1005,14 @@ TEST(HeaderMapImplTest, TestAppendHeader) {
10051005
TEST(TestHeaderMapImplDeathTest, TestHeaderLengthChecks) {
10061006
HeaderString value;
10071007
value.setCopy("some;");
1008-
EXPECT_DEATH_LOG_TO_STDERR(value.append(nullptr, std::numeric_limits<uint32_t>::max()),
1009-
"Trying to allocate overly large headers.");
1008+
EXPECT_DEATH(value.append(nullptr, std::numeric_limits<uint32_t>::max()),
1009+
"Trying to allocate overly large headers.");
10101010

10111011
std::string source("hello");
10121012
HeaderString reference;
10131013
reference.setReference(source);
1014-
EXPECT_DEATH_LOG_TO_STDERR(reference.append(nullptr, std::numeric_limits<uint32_t>::max()),
1015-
"Trying to allocate overly large headers.");
1014+
EXPECT_DEATH(reference.append(nullptr, std::numeric_limits<uint32_t>::max()),
1015+
"Trying to allocate overly large headers.");
10161016
}
10171017

10181018
TEST(HeaderMapImplTest, PseudoHeaderOrder) {

test/common/http/http2/metadata_encoder_decoder_test.cc

-1
Original file line numberDiff line numberDiff line change
@@ -333,7 +333,6 @@ using MetadataEncoderDecoderDeathTest = MetadataEncoderDecoderTest;
333333

334334
// Crash if a caller tries to pack more frames than the encoder has data for.
335335
TEST_F(MetadataEncoderDecoderDeathTest, PackTooManyFrames) {
336-
Logger::StderrSinkDelegate stderr_sink(Logger::Registry::getSink()); // For coverage build.
337336
MetadataMap metadata_map = {
338337
{"header_key1", std::string(5, 'a')},
339338
{"header_key2", std::string(5, 'b')},

test/common/network/address_impl_test.cc

+8-9
Original file line numberDiff line numberDiff line change
@@ -448,9 +448,9 @@ TEST(AddressFromSockAddrDeathTest, IPv4) {
448448
EXPECT_EQ(1, inet_pton(AF_INET, "1.2.3.4", &sin.sin_addr));
449449
sin.sin_port = htons(6502);
450450

451-
EXPECT_DEATH_LOG_TO_STDERR(addressFromSockAddr(ss, 1), "ss_len");
452-
EXPECT_DEATH_LOG_TO_STDERR(addressFromSockAddr(ss, sizeof(sockaddr_in) - 1), "ss_len");
453-
EXPECT_DEATH_LOG_TO_STDERR(addressFromSockAddr(ss, sizeof(sockaddr_in) + 1), "ss_len");
451+
EXPECT_DEATH(addressFromSockAddr(ss, 1), "ss_len");
452+
EXPECT_DEATH(addressFromSockAddr(ss, sizeof(sockaddr_in) - 1), "ss_len");
453+
EXPECT_DEATH(addressFromSockAddr(ss, sizeof(sockaddr_in) + 1), "ss_len");
454454

455455
EXPECT_EQ("1.2.3.4:6502", addressFromSockAddr(ss, sizeof(sockaddr_in))->asString());
456456

@@ -467,9 +467,9 @@ TEST(AddressFromSockAddrDeathTest, IPv6) {
467467
EXPECT_EQ(1, inet_pton(AF_INET6, "01:023::00Ef", &sin6.sin6_addr));
468468
sin6.sin6_port = htons(32000);
469469

470-
EXPECT_DEATH_LOG_TO_STDERR(addressFromSockAddr(ss, 1), "ss_len");
471-
EXPECT_DEATH_LOG_TO_STDERR(addressFromSockAddr(ss, sizeof(sockaddr_in6) - 1), "ss_len");
472-
EXPECT_DEATH_LOG_TO_STDERR(addressFromSockAddr(ss, sizeof(sockaddr_in6) + 1), "ss_len");
470+
EXPECT_DEATH(addressFromSockAddr(ss, 1), "ss_len");
471+
EXPECT_DEATH(addressFromSockAddr(ss, sizeof(sockaddr_in6) - 1), "ss_len");
472+
EXPECT_DEATH(addressFromSockAddr(ss, sizeof(sockaddr_in6) + 1), "ss_len");
473473

474474
EXPECT_EQ("[1:23::ef]:32000", addressFromSockAddr(ss, sizeof(sockaddr_in6))->asString());
475475

@@ -490,9 +490,8 @@ TEST(AddressFromSockAddrDeathTest, Pipe) {
490490

491491
StringUtil::strlcpy(sun.sun_path, "/some/path", sizeof sun.sun_path);
492492

493-
EXPECT_DEATH_LOG_TO_STDERR(addressFromSockAddr(ss, 1), "ss_len");
494-
EXPECT_DEATH_LOG_TO_STDERR(addressFromSockAddr(ss, offsetof(struct sockaddr_un, sun_path)),
495-
"ss_len");
493+
EXPECT_DEATH(addressFromSockAddr(ss, 1), "ss_len");
494+
EXPECT_DEATH(addressFromSockAddr(ss, offsetof(struct sockaddr_un, sun_path)), "ss_len");
496495

497496
socklen_t ss_len = offsetof(struct sockaddr_un, sun_path) + 1 + strlen(sun.sun_path);
498497
EXPECT_EQ("/some/path", addressFromSockAddr(ss, ss_len)->asString());

test/common/network/connection_impl_test.cc

+1-1
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ TEST_P(ConnectionImplDeathTest, BadFd) {
8585
Event::DispatcherPtr dispatcher(api->allocateDispatcher("test_thread"));
8686
IoHandlePtr io_handle = std::make_unique<IoSocketHandleImpl>();
8787
StreamInfo::StreamInfoImpl stream_info(dispatcher->timeSource());
88-
EXPECT_DEATH_LOG_TO_STDERR(
88+
EXPECT_DEATH(
8989
ConnectionImpl(*dispatcher,
9090
std::make_unique<ConnectionSocketImpl>(std::move(io_handle), nullptr, nullptr),
9191
Network::Test::createRawBufferSocket(), stream_info, false),

test/common/network/listener_impl_test.cc

+1-1
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ INSTANTIATE_TEST_SUITE_P(IpVersions, ListenerImplDeathTest,
5959
testing::ValuesIn(TestEnvironment::getIpVersionsForTest()),
6060
TestUtility::ipTestParamsToString);
6161
TEST_P(ListenerImplDeathTest, ErrorCallback) {
62-
EXPECT_DEATH_LOG_TO_STDERR(errorCallbackTest(GetParam()), ".*listener accept failure.*");
62+
EXPECT_DEATH(errorCallbackTest(GetParam()), ".*listener accept failure.*");
6363
}
6464

6565
class TestListenerImpl : public ListenerImpl {

test/common/network/udp_listener_impl_test.cc

-1
Original file line numberDiff line numberDiff line change
@@ -390,7 +390,6 @@ TEST_P(UdpListenerImplTest, SendData) {
390390
* The send fails because the server_socket is created with bind=false.
391391
*/
392392
TEST_P(UdpListenerImplTest, SendDataError) {
393-
Logger::StderrSinkDelegate stderr_sink(Logger::Registry::getSink()); // For coverage build.
394393
const std::string payload("hello world");
395394
Buffer::InstancePtr buffer(new Buffer::OwnedImpl());
396395
buffer->add(payload);

test/common/signal/signals_test.cc

+9-7
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,11 @@ namespace Envoy {
2626
#ifndef ASANITIZED
2727
TEST(SignalsDeathTest, InvalidAddressDeathTest) {
2828
SignalAction actions;
29-
EXPECT_DEATH_LOG_TO_STDERR(
29+
EXPECT_DEATH(
3030
[]() -> void {
3131
// Oops!
3232
volatile int* nasty_ptr = reinterpret_cast<int*>(0x0);
33+
// NOLINTNEXTLINE(clang-analyzer-core.NullDereference)
3334
*(nasty_ptr) = 0;
3435
}(),
3536
"backtrace.*Segmentation fault");
@@ -44,10 +45,11 @@ TEST(SignalsDeathTest, RegisteredHandlerTest) {
4445
SignalAction::registerFatalErrorHandler(handler);
4546
SignalAction actions;
4647
// Make sure the fatal error log "HERE" registered above is logged on fatal error.
47-
EXPECT_DEATH_LOG_TO_STDERR(
48+
EXPECT_DEATH(
4849
[]() -> void {
4950
// Oops!
5051
volatile int* nasty_ptr = reinterpret_cast<int*>(0x0);
52+
// NOLINTNEXTLINE(clang-analyzer-core.NullDereference)
5153
*(nasty_ptr) = 0;
5254
}(),
5355
"HERE");
@@ -56,7 +58,7 @@ TEST(SignalsDeathTest, RegisteredHandlerTest) {
5658

5759
TEST(SignalsDeathTest, BusDeathTest) {
5860
SignalAction actions;
59-
EXPECT_DEATH_LOG_TO_STDERR(
61+
EXPECT_DEATH(
6062
[]() -> void {
6163
// Bus error is tricky. There's one way that can work on POSIX systems
6264
// described below but it depends on mmaping a file. Just make it easy and
@@ -72,7 +74,7 @@ TEST(SignalsDeathTest, BusDeathTest) {
7274

7375
TEST(SignalsDeathTest, BadMathDeathTest) {
7476
SignalAction actions;
75-
EXPECT_DEATH_LOG_TO_STDERR(
77+
EXPECT_DEATH(
7678
[]() -> void {
7779
// It turns out to be really hard to not have the optimizer get rid of a
7880
// division by zero. Just raise the signal for this test.
@@ -85,7 +87,7 @@ TEST(SignalsDeathTest, BadMathDeathTest) {
8587
// Unfortunately we don't have a reliable way to do this on other platforms
8688
TEST(SignalsDeathTest, IllegalInstructionDeathTest) {
8789
SignalAction actions;
88-
EXPECT_DEATH_LOG_TO_STDERR(
90+
EXPECT_DEATH(
8991
[]() -> void {
9092
// Intel defines the "ud2" opcode to be an invalid instruction:
9193
__asm__("ud2");
@@ -96,7 +98,7 @@ TEST(SignalsDeathTest, IllegalInstructionDeathTest) {
9698

9799
TEST(SignalsDeathTest, AbortDeathTest) {
98100
SignalAction actions;
99-
EXPECT_DEATH_LOG_TO_STDERR([]() -> void { abort(); }(), "backtrace.*Abort(ed)?");
101+
EXPECT_DEATH([]() -> void { abort(); }(), "backtrace.*Abort(ed)?");
100102
}
101103

102104
TEST(SignalsDeathTest, RestoredPreviousHandlerDeathTest) {
@@ -108,7 +110,7 @@ TEST(SignalsDeathTest, RestoredPreviousHandlerDeathTest) {
108110
// goes out of scope, NOT the default.
109111
}
110112
// Outer SignalAction should be active again:
111-
EXPECT_DEATH_LOG_TO_STDERR([]() -> void { abort(); }(), "backtrace.*Abort(ed)?");
113+
EXPECT_DEATH([]() -> void { abort(); }(), "backtrace.*Abort(ed)?");
112114
}
113115

114116
#endif

test/common/singleton/manager_impl_test.cc

+1-2
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,7 @@ static void deathTestWorker() {
1818
}
1919

2020
TEST(SingletonManagerImplDeathTest, NotRegistered) {
21-
EXPECT_DEATH_LOG_TO_STDERR(deathTestWorker(),
22-
"invalid singleton name 'foo'. Make sure it is registered.");
21+
EXPECT_DEATH(deathTestWorker(), "invalid singleton name 'foo'. Make sure it is registered.");
2322
}
2423

2524
SINGLETON_MANAGER_REGISTRATION(test);

0 commit comments

Comments
 (0)