Fix a use-after-free bug in which the string passed to `AtLocation` may be
referenced after it is destroyed. While the string does live until the end of
the full statement, logging (previously occurred) in the destructor of the
`LogMessage` which may be constructed before the temporary string (and thus
destroyed after the temporary string's destructor).
This change moves logging to occur inside the internal `Voidify`. This does not
affect when logging occurs relative to any other observable, other than the
fact that it runs later than any temporary destructors in the log statement.
PiperOrigin-RevId: 729708145
Change-Id: I9b90367d7a5f4ed3cf091d4d33756262acc03ec6
diff --git a/absl/log/CMakeLists.txt b/absl/log/CMakeLists.txt
index a511f03..01e2b28 100644
--- a/absl/log/CMakeLists.txt
+++ b/absl/log/CMakeLists.txt
@@ -379,6 +379,7 @@
${ABSL_DEFAULT_LINKOPTS}
DEPS
absl::config
+ absl::core_headers
)
absl_cc_library(
diff --git a/absl/log/internal/BUILD.bazel b/absl/log/internal/BUILD.bazel
index 594f867..d3f4a8f 100644
--- a/absl/log/internal/BUILD.bazel
+++ b/absl/log/internal/BUILD.bazel
@@ -415,7 +415,10 @@
hdrs = ["voidify.h"],
copts = ABSL_DEFAULT_COPTS,
linkopts = ABSL_DEFAULT_LINKOPTS,
- deps = ["//absl/base:config"],
+ deps = [
+ "//absl/base:config",
+ "//absl/base:core_headers",
+ ],
)
cc_library(
diff --git a/absl/log/internal/conditions.h b/absl/log/internal/conditions.h
index 9dc15db..97edf65 100644
--- a/absl/log/internal/conditions.h
+++ b/absl/log/internal/conditions.h
@@ -65,7 +65,7 @@
switch (0) \
case 0: \
default: \
- !(condition) ? (void)0 : ::absl::log_internal::Voidify()&&
+ !(condition) ? (void)0 : ::absl::log_internal::Voidify() &&
// `ABSL_LOG_INTERNAL_STATEFUL_CONDITION` applies a condition like
// `ABSL_LOG_INTERNAL_STATELESS_CONDITION` but adds to that a series of variable
@@ -96,7 +96,8 @@
for (const uint32_t COUNTER ABSL_ATTRIBUTE_UNUSED = \
absl_log_internal_stateful_condition_state.counter(); \
absl_log_internal_stateful_condition_do_log; \
- absl_log_internal_stateful_condition_do_log = false)
+ absl_log_internal_stateful_condition_do_log = false) \
+ ::absl::log_internal::Voidify() &&
// `ABSL_LOG_INTERNAL_CONDITION_*` serve to combine any conditions from the
// macro (e.g. `LOG_IF` or `VLOG`) with inherent conditions (e.g.
diff --git a/absl/log/internal/log_message.cc b/absl/log/internal/log_message.cc
index 9e7722d..8dc5722 100644
--- a/absl/log/internal/log_message.cc
+++ b/absl/log/internal/log_message.cc
@@ -291,16 +291,8 @@
LogMessage::LogMessage(absl::Nonnull<const char*> file, int line, ErrorTag)
: LogMessage(file, line, absl::LogSeverity::kError) {}
-LogMessage::~LogMessage() {
-#ifdef ABSL_MIN_LOG_LEVEL
- if (data_->entry.log_severity() <
- static_cast<absl::LogSeverity>(ABSL_MIN_LOG_LEVEL) &&
- data_->entry.log_severity() < absl::LogSeverity::kFatal) {
- return;
- }
-#endif
- Flush();
-}
+// This cannot go in the header since LogMessageData is defined in this file.
+LogMessage::~LogMessage() = default;
LogMessage& LogMessage::AtLocation(absl::string_view file, int line) {
data_->entry.full_filename_ = file;
@@ -691,7 +683,6 @@
}
LogMessageFatal::~LogMessageFatal() {
- Flush();
FailWithoutStackTrace();
}
@@ -700,7 +691,6 @@
: LogMessage(file, line, absl::LogSeverity::kFatal) {}
LogMessageDebugFatal::~LogMessageDebugFatal() {
- Flush();
FailWithoutStackTrace();
}
@@ -711,7 +701,6 @@
}
LogMessageQuietlyDebugFatal::~LogMessageQuietlyDebugFatal() {
- Flush();
FailQuietly();
}
@@ -729,7 +718,6 @@
}
LogMessageQuietlyFatal::~LogMessageQuietlyFatal() {
- Flush();
FailQuietly();
}
#if defined(_MSC_VER) && !defined(__clang__)
diff --git a/absl/log/internal/log_message.h b/absl/log/internal/log_message.h
index 4986e7e..8acc4a1 100644
--- a/absl/log/internal/log_message.h
+++ b/absl/log/internal/log_message.h
@@ -17,12 +17,12 @@
// -----------------------------------------------------------------------------
//
// This file declares `class absl::log_internal::LogMessage`. This class more or
-// less represents a particular log message. LOG/CHECK macros create a
-// temporary instance of `LogMessage` and then stream values to it. At the end
-// of the LOG/CHECK statement, LogMessage instance goes out of scope and
-// `~LogMessage` directs the message to the registered log sinks.
-// Heap-allocation of `LogMessage` is unsupported. Construction outside of a
-// `LOG` macro is unsupported.
+// less represents a particular log message. LOG/CHECK macros create a temporary
+// instance of `LogMessage` and then stream values to it. At the end of the
+// LOG/CHECK statement, the LogMessage is voidified by operator&&, and `Flush()`
+// directs the message to the registered log sinks. Heap-allocation of
+// `LogMessage` is unsupported. Construction outside of a `LOG` macro is
+// unsupported.
#ifndef ABSL_LOG_INTERNAL_LOG_MESSAGE_H_
#define ABSL_LOG_INTERNAL_LOG_MESSAGE_H_
@@ -188,6 +188,9 @@
template <typename T>
LogMessage& operator<<(const T& v) ABSL_ATTRIBUTE_NOINLINE;
+ // Dispatches the completed `absl::LogEntry` to applicable `absl::LogSink`s.
+ void Flush();
+
// Note: We explicitly do not support `operator<<` for non-const references
// because it breaks logging of non-integer bitfield types (i.e., enums).
@@ -200,11 +203,6 @@
// the process with an error exit code.
[[noreturn]] static void FailQuietly();
- // Dispatches the completed `absl::LogEntry` to applicable `absl::LogSink`s.
- // This might as well be inlined into `~LogMessage` except that
- // `~LogMessageFatal` needs to call it early.
- void Flush();
-
// After this is called, failures are done as quiet as possible for this log
// message.
void SetFailQuietly();
diff --git a/absl/log/internal/nullstream.h b/absl/log/internal/nullstream.h
index 973e91a..c87f9aa 100644
--- a/absl/log/internal/nullstream.h
+++ b/absl/log/internal/nullstream.h
@@ -79,6 +79,7 @@
return *this;
}
NullStream& InternalStream() { return *this; }
+ void Flush() {}
};
template <typename T>
inline NullStream& operator<<(NullStream& str, const T&) {
diff --git a/absl/log/internal/voidify.h b/absl/log/internal/voidify.h
index 8f62da2..f42859e 100644
--- a/absl/log/internal/voidify.h
+++ b/absl/log/internal/voidify.h
@@ -16,13 +16,15 @@
// File: log/internal/voidify.h
// -----------------------------------------------------------------------------
//
-// This class is used to explicitly ignore values in the conditional logging
-// macros. This avoids compiler warnings like "value computed is not used" and
-// "statement has no effect".
+// This class does the dispatching of the completed `absl::LogEntry` to
+// applicable `absl::LogSink`s, and is used to explicitly ignore values in the
+// conditional logging macros. This avoids compiler warnings like "value
+// computed is not used" and "statement has no effect".
#ifndef ABSL_LOG_INTERNAL_VOIDIFY_H_
#define ABSL_LOG_INTERNAL_VOIDIFY_H_
+#include "absl/base/attributes.h"
#include "absl/base/config.h"
namespace absl {
@@ -34,7 +36,11 @@
// This has to be an operator with a precedence lower than << but higher than
// ?:
template <typename T>
- void operator&&(const T&) const&& {}
+ ABSL_ATTRIBUTE_COLD void operator&&(T&& message) const&& {
+ // The dispatching of the completed `absl::LogEntry` to applicable
+ // `absl::LogSink`s happens here.
+ message.Flush();
+ }
};
} // namespace log_internal
diff --git a/absl/log/log_modifier_methods_test.cc b/absl/log/log_modifier_methods_test.cc
index fc98e1f..4cee0c0 100644
--- a/absl/log/log_modifier_methods_test.cc
+++ b/absl/log/log_modifier_methods_test.cc
@@ -15,6 +15,8 @@
#include <errno.h>
+#include <string>
+
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "absl/log/internal/test_actions.h"
@@ -77,6 +79,14 @@
<< "hello world";
}
+TEST(TailCallsModifiesTest, AtLocationFileLineLifetime) {
+ // The macro takes care to not use this temporary after its lifetime.
+ // The only salient expectation is "no sanitizer diagnostics".
+ LOG(INFO).AtLocation(std::string("/my/very/very/very_long_source_file.cc"),
+ 777)
+ << "hello world";
+}
+
TEST(TailCallsModifiesTest, NoPrefix) {
absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected);