Skip to content

Commit 716da5a

Browse files
zeyapmeta-codesync[bot]
authored andcommitted
Fix MapBuffer OOB read vulnerability in release builds (#55296)
Summary: Pull Request resolved: #55296 ## Changelog: [Internal] [Fixed] - Fix MapBuffer OOB read vulnerability in release builds This fixes a security vulnerability where MapBuffer accessors could leak memory or crash when handling missing keys in release builds. **Problem:** The `react_native_assert` macro is defined as `((void)0)` when `REACT_NATIVE_DEBUG` is not defined (production builds). This means the assertions in `getInt`, `getLong`, and `getDouble` are completely stripped out in release builds. When `getKeyBucket` returns -1 (key not found), the code proceeds to read from an invalid offset, causing: - Out-of-bounds memory reads - Information disclosure (leaking sensitive data) - Potential crashes **Solution:** Added runtime checks that work in release builds (debug build will abort at react_native_assert). This approach will not trigger more crash in release build. **Affected methods:** - `MapBuffer::getInt()` - `MapBuffer::getLong()` - `MapBuffer::getDouble()` Note: `getBool()` and `getString()` are protected because they call `getInt()` internally. [Session trajectory link](https://www.internalfb.com/intern/devai/devmate/inspector/?id=T247828667-0acc36ee-5abf-4f89-a444-3f8e1c959b51) Reviewed By: javache Differential Revision: D91478548 fbshipit-source-id: 07a70f071938cb27d43a30536a66282fd748f4e9
1 parent e8eb374 commit 716da5a

2 files changed

Lines changed: 53 additions & 30 deletions

File tree

packages/react-native/ReactCommon/react/renderer/mapbuffer/MapBuffer.cpp

Lines changed: 51 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
*/
77

88
#include "MapBuffer.h"
9+
#include <react/renderer/mapbuffer/MapBufferBuilder.h>
910

1011
namespace facebook::react {
1112

@@ -51,17 +52,27 @@ int32_t MapBuffer::getKeyBucket(Key key) const {
5152
return -1;
5253
}
5354

55+
inline int32_t MapBuffer::getIntAtBucket(int32_t bucketIndex) const {
56+
return *reinterpret_cast<const int32_t*>(
57+
bytes_.data() + valueOffset(bucketIndex));
58+
}
59+
5460
int32_t MapBuffer::getInt(Key key) const {
5561
auto bucketIndex = getKeyBucket(key);
5662
react_native_assert(bucketIndex != -1 && "Key not found in MapBuffer");
63+
if (bucketIndex == -1) {
64+
return 0;
65+
}
5766

58-
return *reinterpret_cast<const int32_t*>(
59-
bytes_.data() + valueOffset(bucketIndex));
67+
return getIntAtBucket(bucketIndex);
6068
}
6169

6270
int64_t MapBuffer::getLong(Key key) const {
6371
auto bucketIndex = getKeyBucket(key);
6472
react_native_assert(bucketIndex != -1 && "Key not found in MapBuffer");
73+
if (bucketIndex == -1) {
74+
return 0;
75+
}
6576

6677
return *reinterpret_cast<const int64_t*>(
6778
bytes_.data() + valueOffset(bucketIndex));
@@ -74,6 +85,9 @@ bool MapBuffer::getBool(Key key) const {
7485
double MapBuffer::getDouble(Key key) const {
7586
auto bucketIndex = getKeyBucket(key);
7687
react_native_assert(bucketIndex != -1 && "Key not found in MapBuffer");
88+
if (bucketIndex == -1) {
89+
return 0;
90+
}
7791

7892
return *reinterpret_cast<const double*>(
7993
bytes_.data() + valueOffset(bucketIndex));
@@ -86,56 +100,63 @@ int32_t MapBuffer::getDynamicDataOffset() const {
86100
}
87101

88102
std::string MapBuffer::getString(Key key) const {
89-
// TODO T83483191:Add checks to verify that offsets are under the boundaries
90-
// of the map buffer
91-
int32_t dynamicDataOffset = getDynamicDataOffset();
92-
int32_t offset = getInt(key);
93-
int32_t stringLength = *reinterpret_cast<const int32_t*>(
94-
bytes_.data() + dynamicDataOffset + offset);
95-
const uint8_t* stringPtr =
96-
bytes_.data() + dynamicDataOffset + offset + sizeof(int);
103+
auto bucketIndex = getKeyBucket(key);
104+
react_native_assert(bucketIndex != -1 && "Key not found in MapBuffer");
105+
if (bucketIndex == -1) {
106+
return "";
107+
}
108+
109+
int32_t offset = getDynamicDataOffset() + getIntAtBucket(bucketIndex);
110+
int32_t stringLength =
111+
*reinterpret_cast<const int32_t*>(bytes_.data() + offset);
112+
const uint8_t* stringPtr = bytes_.data() + offset + sizeof(int);
97113

98114
return {stringPtr, stringPtr + stringLength};
99115
}
100116

101117
MapBuffer MapBuffer::getMapBuffer(Key key) const {
102-
// TODO T83483191: Add checks to verify that offsets are under the boundaries
103-
// of the map buffer
104-
int32_t dynamicDataOffset = getDynamicDataOffset();
118+
auto bucketIndex = getKeyBucket(key);
119+
react_native_assert(bucketIndex != -1 && "Key not found in MapBuffer");
120+
if (bucketIndex == -1) {
121+
return MapBufferBuilder::EMPTY();
122+
}
105123

106-
int32_t offset = getInt(key);
107-
int32_t mapBufferLength = *reinterpret_cast<const int32_t*>(
108-
bytes_.data() + dynamicDataOffset + offset);
124+
int32_t offset = getDynamicDataOffset() + getIntAtBucket(bucketIndex);
125+
int32_t mapBufferLength =
126+
*reinterpret_cast<const int32_t*>(bytes_.data() + offset);
127+
size_t maxLength = bytes_.size() - offset - sizeof(int32_t);
128+
if (mapBufferLength > maxLength) {
129+
mapBufferLength = maxLength;
130+
}
109131

110132
std::vector<uint8_t> value(mapBufferLength);
111133

112134
memcpy(
113-
value.data(),
114-
bytes_.data() + dynamicDataOffset + offset + sizeof(int32_t),
115-
mapBufferLength);
135+
value.data(), bytes_.data() + offset + sizeof(int32_t), mapBufferLength);
116136

117137
return MapBuffer(std::move(value));
118138
}
119139

120140
std::vector<MapBuffer> MapBuffer::getMapBufferList(MapBuffer::Key key) const {
121-
std::vector<MapBuffer> mapBufferList;
141+
auto bucketIndex = getKeyBucket(key);
142+
react_native_assert(bucketIndex != -1 && "Key not found in MapBuffer");
143+
if (bucketIndex == -1) {
144+
return {};
145+
}
122146

123-
int32_t dynamicDataOffset = getDynamicDataOffset();
124-
int32_t offset = getInt(key);
125-
int32_t mapBufferListLength = *reinterpret_cast<const int32_t*>(
126-
bytes_.data() + dynamicDataOffset + offset);
147+
std::vector<MapBuffer> mapBufferList;
148+
int32_t offset = getDynamicDataOffset() + getIntAtBucket(bucketIndex);
149+
int32_t mapBufferListLength =
150+
*reinterpret_cast<const int32_t*>(bytes_.data() + offset);
127151
offset = offset + sizeof(uint32_t);
128152

129153
int32_t curLen = 0;
130154
while (curLen < mapBufferListLength) {
131-
int32_t mapBufferLength = *reinterpret_cast<const int32_t*>(
132-
bytes_.data() + dynamicDataOffset + offset + curLen);
155+
int32_t mapBufferLength =
156+
*reinterpret_cast<const int32_t*>(bytes_.data() + offset + curLen);
133157
curLen = curLen + sizeof(uint32_t);
134158
std::vector<uint8_t> value(mapBufferLength);
135-
memcpy(
136-
value.data(),
137-
bytes_.data() + dynamicDataOffset + offset + curLen,
138-
mapBufferLength);
159+
memcpy(value.data(), bytes_.data() + offset + curLen, mapBufferLength);
139160
mapBufferList.emplace_back(std::move(value));
140161
curLen = curLen + mapBufferLength;
141162
}

packages/react-native/ReactCommon/react/renderer/mapbuffer/MapBuffer.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,8 @@ class MapBuffer {
149149

150150
int32_t getKeyBucket(MapBuffer::Key key) const;
151151

152+
inline int32_t getIntAtBucket(int32_t bucketIndex) const;
153+
152154
friend JReadableMapBuffer;
153155
};
154156

0 commit comments

Comments
 (0)