Skip to content

Commit 01061ea

Browse files
authored
Firestore: string_format.h: fix use-after-free bug when internally formatting strings (#14306)
1 parent f7ab2d4 commit 01061ea

File tree

3 files changed

+79
-16
lines changed

3 files changed

+79
-16
lines changed

Firestore/CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
# Unreleased
2+
- [fixed] Fixed use-after-free bug when internally formatting strings. (#14306)
3+
14
# 11.6.0
25
- [fixed] Add conditional `Sendable` conformance so `ServerTimestamp<T>` is
36
`Sendable` if `T` is `Sendable`. (#14042)

Firestore/core/src/util/string_format.h

Lines changed: 36 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -62,11 +62,20 @@ struct FormatChoice<5> {};
6262
* formatting of the value as an unsigned integer.
6363
* * Otherwise the value is interpreted as anything absl::AlphaNum accepts.
6464
*/
65-
class FormatArg : public absl::AlphaNum {
65+
class FormatArg final : public absl::AlphaNum {
6666
public:
6767
template <typename T>
68-
FormatArg(T&& value) // NOLINT(runtime/explicit)
69-
: FormatArg{std::forward<T>(value), internal::FormatChoice<0>{}} {
68+
FormatArg(
69+
T&& value,
70+
// TODO(b/388888512) Remove the usage of StringifySink since it is not
71+
// part of absl's public API. Moreover, subclassing AlphaNum is not
72+
// supported either, so find a way to do this without these two caveats.
73+
// See https://github.com/firebase/firebase-ios-sdk/pull/14331 for a
74+
// partial proposal.
75+
absl::strings_internal::StringifySink&& sink =
76+
{}) // NOLINT(runtime/explicit)
77+
: FormatArg{std::forward<T>(value), std::move(sink),
78+
internal::FormatChoice<0>{}} {
7079
}
7180

7281
private:
@@ -79,7 +88,9 @@ class FormatArg : public absl::AlphaNum {
7988
*/
8089
template <typename T,
8190
typename = typename std::enable_if<std::is_same<bool, T>{}>::type>
82-
FormatArg(T bool_value, internal::FormatChoice<0>)
91+
FormatArg(T bool_value,
92+
absl::strings_internal::StringifySink&&,
93+
internal::FormatChoice<0>)
8394
: AlphaNum(bool_value ? "true" : "false") {
8495
}
8596

@@ -90,15 +101,19 @@ class FormatArg : public absl::AlphaNum {
90101
template <
91102
typename T,
92103
typename = typename std::enable_if<objc::is_objc_pointer<T>{}>::type>
93-
FormatArg(T object, internal::FormatChoice<1>)
104+
FormatArg(T object,
105+
absl::strings_internal::StringifySink&&,
106+
internal::FormatChoice<1>)
94107
: AlphaNum(MakeStringView([object description])) {
95108
}
96109

97110
/**
98111
* Creates a FormatArg from any Objective-C Class type. Objective-C Class
99112
* types are a special struct that aren't of a type derived from NSObject.
100113
*/
101-
FormatArg(Class object, internal::FormatChoice<1>)
114+
FormatArg(Class object,
115+
absl::strings_internal::StringifySink&&,
116+
internal::FormatChoice<1>)
102117
: AlphaNum(MakeStringView(NSStringFromClass(object))) {
103118
}
104119
#endif
@@ -108,15 +123,20 @@ class FormatArg : public absl::AlphaNum {
108123
* handled specially to avoid ambiguity with generic pointers, which are
109124
* handled differently.
110125
*/
111-
FormatArg(std::nullptr_t, internal::FormatChoice<2>) : AlphaNum("null") {
126+
FormatArg(std::nullptr_t,
127+
absl::strings_internal::StringifySink&&,
128+
internal::FormatChoice<2>)
129+
: AlphaNum("null") {
112130
}
113131

114132
/**
115133
* Creates a FormatArg from a character string literal. This is
116134
* handled specially to avoid ambiguity with generic pointers, which are
117135
* handled differently.
118136
*/
119-
FormatArg(const char* string_value, internal::FormatChoice<3>)
137+
FormatArg(const char* string_value,
138+
absl::strings_internal::StringifySink&&,
139+
internal::FormatChoice<3>)
120140
: AlphaNum(string_value == nullptr ? "null" : string_value) {
121141
}
122142

@@ -125,16 +145,21 @@ class FormatArg : public absl::AlphaNum {
125145
* hexadecimal integer literal.
126146
*/
127147
template <typename T>
128-
FormatArg(T* pointer_value, internal::FormatChoice<4>)
129-
: AlphaNum(absl::Hex(reinterpret_cast<uintptr_t>(pointer_value))) {
148+
FormatArg(T* pointer_value,
149+
absl::strings_internal::StringifySink&& sink,
150+
internal::FormatChoice<4>)
151+
: AlphaNum(absl::Hex(reinterpret_cast<uintptr_t>(pointer_value)),
152+
std::move(sink)) {
130153
}
131154

132155
/**
133156
* As a final fallback, creates a FormatArg from any type of value that
134157
* absl::AlphaNum accepts.
135158
*/
136159
template <typename T>
137-
FormatArg(T&& value, internal::FormatChoice<5>)
160+
FormatArg(T&& value,
161+
absl::strings_internal::StringifySink&&,
162+
internal::FormatChoice<5>)
138163
: AlphaNum(std::forward<T>(value)) {
139164
}
140165
};

Firestore/core/test/unit/util/string_format_test.cc

Lines changed: 40 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,39 @@
1616

1717
#include "Firestore/core/src/util/string_format.h"
1818

19+
#include <algorithm>
20+
#include <sstream>
21+
#include <string>
22+
1923
#include "absl/strings/string_view.h"
24+
#include "gmock/gmock.h"
2025
#include "gtest/gtest.h"
2126

2227
namespace firebase {
2328
namespace firestore {
2429
namespace util {
2530

31+
namespace {
32+
33+
using testing::HasSubstr;
34+
using testing::MatchesRegex;
35+
36+
std::string lowercase(const std::string& str) {
37+
std::string lower_str = str;
38+
std::transform(lower_str.begin(), lower_str.end(), lower_str.begin(),
39+
[](auto c) { return std::tolower(c); });
40+
return lower_str;
41+
}
42+
43+
template <typename T>
44+
std::string hex_address(const T* ptr) {
45+
std::ostringstream os;
46+
os << std::hex << reinterpret_cast<uintptr_t>(ptr);
47+
return os.str();
48+
}
49+
50+
} // namespace
51+
2652
TEST(StringFormatTest, Empty) {
2753
EXPECT_EQ("", StringFormat(""));
2854
EXPECT_EQ("", StringFormat("%s", std::string().c_str()));
@@ -72,14 +98,23 @@ TEST(StringFormatTest, Bool) {
7298
EXPECT_EQ("Hello false", StringFormat("Hello %s", false));
7399
}
74100

75-
TEST(StringFormatTest, Pointer) {
76-
// pointers implicitly convert to bool. Make sure this doesn't happen in
77-
// this API.
78-
int value = 4;
79-
EXPECT_NE("Hello true", StringFormat("Hello %s", &value));
101+
TEST(StringFormatTest, NullPointer) {
102+
// pointers implicitly convert to bool. Make sure this doesn't happen here.
80103
EXPECT_EQ("Hello null", StringFormat("Hello %s", nullptr));
81104
}
82105

106+
TEST(StringFormatTest, NonNullPointer) {
107+
// pointers implicitly convert to bool. Make sure this doesn't happen here.
108+
int value = 4;
109+
110+
const std::string formatted_string = StringFormat("Hello %s", &value);
111+
112+
const std::string hex_address_regex = "(0x)?[0123456789abcdefABCDEF]+";
113+
EXPECT_THAT(formatted_string, MatchesRegex("Hello " + hex_address_regex));
114+
const std::string expected_hex_address = lowercase(hex_address(&value));
115+
EXPECT_THAT(lowercase(formatted_string), HasSubstr(expected_hex_address));
116+
}
117+
83118
TEST(StringFormatTest, Mixed) {
84119
EXPECT_EQ("string=World, bool=true, int=42, float=1.5",
85120
StringFormat("string=%s, bool=%s, int=%s, float=%s", "World", true,

0 commit comments

Comments
 (0)