From 2ef52074d48f167742aaaf58f2e22298720d6013 Mon Sep 17 00:00:00 2001 From: jianminzhao <76990468+jianminzhao@users.noreply.github.com> Date: Fri, 7 Aug 2026 18:04:47 -0700 Subject: [PATCH] CBL-8733: Pusher UAF in ChangesFeed's DB-change observer The root cause is that the changes notifier is not timely removed from the SequenceTracker. We remove it whent the status turns to Stoppped. The removal and the use of the notifier callback are under Collection's lock on the SequenceTracker; the removal has to wait for the callback to complete. --- Replicator/ChangesFeed.cc | 2 ++ Replicator/ChangesFeed.hh | 2 ++ Replicator/Pusher.cc | 5 +++++ Replicator/Pusher.hh | 2 ++ 4 files changed, 11 insertions(+) diff --git a/Replicator/ChangesFeed.cc b/Replicator/ChangesFeed.cc index dd0d649b59..dd96611f31 100644 --- a/Replicator/ChangesFeed.cc +++ b/Replicator/ChangesFeed.cc @@ -252,6 +252,8 @@ namespace litecore::repl { return true; } + void ChangesFeed::stopObserving() { _changeObserver.reset(); } + // Overridden by ReplicatorChangesFeed bool ChangesFeed::getRemoteRevID(RevToSend* rev, C4Document* doc) const { return true; } diff --git a/Replicator/ChangesFeed.hh b/Replicator/ChangesFeed.hh index 979295e352..78bf695f33 100644 --- a/Replicator/ChangesFeed.hh +++ b/Replicator/ChangesFeed.hh @@ -84,6 +84,8 @@ namespace litecore::repl { /** Returns true if the given rev matches the push filters. */ [[nodiscard]] virtual bool shouldPushRev(RevToSend* NONNULL) const; + void stopObserving(); + protected: std::string loggingClassName() const override { return "ChangesFeed"; } diff --git a/Replicator/Pusher.cc b/Replicator/Pusher.cc index 4cb7e13d98..73c50488d7 100644 --- a/Replicator/Pusher.cc +++ b/Replicator/Pusher.cc @@ -615,6 +615,11 @@ namespace litecore::repl { return level; } + void Pusher::changedStatus() { + if ( status().level == kC4Stopped ) _changesFeed.stopObserving(); + Worker::changedStatus(); + } + void Pusher::afterEvent() { // If I would otherwise go idle or stop, but there are revs I want to retry, restart them: if ( !_revsToRetry.empty() && connected() && !isBusy() ) retryRevs(std::move(_revsToRetry), false); diff --git a/Replicator/Pusher.hh b/Replicator/Pusher.hh index 9237724f94..44ce9f4cc5 100644 --- a/Replicator/Pusher.hh +++ b/Replicator/Pusher.hh @@ -63,6 +63,8 @@ namespace litecore::repl { void _connectionClosed() override; ActivityLevel computeActivityLevel(std::string* reason) const override; + void changedStatus() override; + private: void _start(); bool isBusy(std::string* reason = nullptr) const;