Skip to content

Commit 44725b8

Browse files
christophpurrermeta-codesync[bot]
authored andcommitted
Fix race condition in ImageFetcher when registering/unregistering commitHooks (facebook#53862)
Summary: Pull Request resolved: facebook#53862 Recently, we observed a `use-after-free race condition` where the ImageFetcher object was destroyed while it was still registered as a UIManagerCommitHook. The crash occurred in `std::vector::size()` at line 635 when accessing corrupted memory. Changelog: [Internal] Reviewed By: lenaic Differential Revision: D82846245 fbshipit-source-id: fe0d9156ec6bc004339ed987bb5937699ebdf23b
1 parent 83fbb0b commit 44725b8

File tree

2 files changed

+44
-26
lines changed

2 files changed

+44
-26
lines changed

packages/react-native/ReactCommon/react/renderer/imagemanager/platform/android/react/renderer/imagemanager/ImageFetcher.cpp

Lines changed: 37 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,39 @@
1010
#include <glog/logging.h>
1111
#include <react/common/mapbuffer/JReadableMapBuffer.h>
1212
#include <react/renderer/imagemanager/conversions.h>
13+
#include <react/renderer/uimanager/UIManagerCommitHook.h>
1314

1415
namespace facebook::react {
1516

17+
class ImageFetcherCommitHook : public UIManagerCommitHook {
18+
public:
19+
explicit ImageFetcherCommitHook(ImageFetcher* fetcher) : fetcher_(fetcher) {}
20+
21+
RootShadowNode::Unshared shadowTreeWillCommit(
22+
const ShadowTree& /*shadowTree*/,
23+
const RootShadowNode::Shared& /*oldRootShadowNode*/,
24+
const RootShadowNode::Unshared& newRootShadowNode,
25+
const ShadowTree::CommitOptions& /*commitOptions*/) noexcept override {
26+
if (fetcher_ != nullptr) {
27+
fetcher_->flushImageRequests();
28+
}
29+
return newRootShadowNode;
30+
}
31+
32+
void commitHookWasRegistered(
33+
const UIManager& /*uiManager*/) noexcept override {}
34+
35+
void commitHookWasUnregistered(
36+
const UIManager& /*uiManager*/) noexcept override {}
37+
38+
void invalidate() {
39+
fetcher_ = nullptr;
40+
}
41+
42+
private:
43+
ImageFetcher* fetcher_;
44+
};
45+
1646
ImageFetcher::ImageFetcher(
1747
std::shared_ptr<const ContextContainer> contextContainer)
1848
: contextContainer_(std::move(contextContainer)) {
@@ -22,20 +52,24 @@ ImageFetcher::ImageFetcher(
2252
->find<std::shared_ptr<UIManagerCommitHookManager>>(
2353
std::string(UIManagerCommitHookManagerKey));
2454
uiManagerCommitHookManager.has_value()) {
25-
(*uiManagerCommitHookManager)->registerCommitHook(*this);
55+
commitHook_ = std::make_unique<ImageFetcherCommitHook>(this);
56+
(*uiManagerCommitHookManager)->registerCommitHook(*commitHook_);
2657
}
2758
}
2859
}
2960

3061
ImageFetcher::~ImageFetcher() {
31-
if (ReactNativeFeatureFlags::enableImagePrefetchingJNIBatchingAndroid()) {
62+
if (ReactNativeFeatureFlags::enableImagePrefetchingJNIBatchingAndroid() &&
63+
commitHook_ != nullptr) {
64+
commitHook_->invalidate();
3265
if (auto uiManagerCommitHookManager =
3366
contextContainer_
3467
->find<std::shared_ptr<UIManagerCommitHookManager>>(
3568
std::string(UIManagerCommitHookManagerKey));
3669
uiManagerCommitHookManager.has_value()) {
37-
(*uiManagerCommitHookManager)->unregisterCommitHook(*this);
70+
(*uiManagerCommitHookManager)->unregisterCommitHook(*commitHook_);
3871
}
72+
commitHook_ = nullptr;
3973
}
4074
}
4175

@@ -58,15 +92,6 @@ ImageRequest ImageFetcher::requestImage(
5892
return {imageSource, telemetry};
5993
}
6094

61-
RootShadowNode::Unshared ImageFetcher::shadowTreeWillCommit(
62-
const ShadowTree& /*shadowTree*/,
63-
const RootShadowNode::Shared& /*oldRootShadowNode*/,
64-
const RootShadowNode::Unshared& newRootShadowNode,
65-
const ShadowTree::CommitOptions& /*commitOptions*/) noexcept {
66-
flushImageRequests();
67-
return newRootShadowNode;
68-
}
69-
7095
void ImageFetcher::flushImageRequests() {
7196
if (items_.empty()) {
7297
return;

packages/react-native/ReactCommon/react/renderer/imagemanager/platform/android/react/renderer/imagemanager/ImageFetcher.h

Lines changed: 7 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -11,17 +11,19 @@
1111
#include <react/renderer/imagemanager/ImageRequest.h>
1212
#include <react/renderer/imagemanager/ImageRequestParams.h>
1313
#include <react/renderer/mounting/ShadowTree.h>
14-
#include <react/renderer/uimanager/UIManagerCommitHook.h>
1514
#include <react/utils/ContextContainer.h>
15+
#include <memory>
1616
#include <unordered_map>
1717
#include <vector>
1818

1919
namespace facebook::react {
2020

21-
class ImageFetcher : public UIManagerCommitHook {
21+
class ImageFetcherCommitHook;
22+
23+
class ImageFetcher {
2224
public:
2325
ImageFetcher(std::shared_ptr<const ContextContainer> contextContainer);
24-
~ImageFetcher() override;
26+
~ImageFetcher();
2527
ImageFetcher(const ImageFetcher&) = delete;
2628
ImageFetcher& operator=(const ImageFetcher&) = delete;
2729
ImageFetcher(ImageFetcher&&) = delete;
@@ -33,21 +35,12 @@ class ImageFetcher : public UIManagerCommitHook {
3335
const ImageRequestParams& imageRequestParams,
3436
Tag tag);
3537

36-
void commitHookWasRegistered(const UIManager& uiManager) noexcept override {}
37-
38-
void commitHookWasUnregistered(const UIManager& uiManager) noexcept override {
39-
}
40-
41-
RootShadowNode::Unshared shadowTreeWillCommit(
42-
const ShadowTree& shadowTree,
43-
const RootShadowNode::Shared& oldRootShadowNode,
44-
const RootShadowNode::Unshared& newRootShadowNode,
45-
const ShadowTree::CommitOptions& commitOptions) noexcept override;
46-
4738
private:
39+
friend class ImageFetcherCommitHook;
4840
void flushImageRequests();
4941

5042
std::unordered_map<SurfaceId, std::vector<ImageRequestItem>> items_;
5143
std::shared_ptr<const ContextContainer> contextContainer_;
44+
std::unique_ptr<ImageFetcherCommitHook> commitHook_;
5245
};
5346
} // namespace facebook::react

0 commit comments

Comments
 (0)