Skip to content

Commit 9a595e0

Browse files
peterchavemeta-codesync[bot]
authored andcommitted
use after free fix for perf-client on shutdown (#218)
Summary: The perf client could crash on shutdown. SubscriberState handed the session a non-owning pointer to its own callback member, but the session keeps subgroup receivers alive after SubscriberState is destroyed, so a late callback dereferenced freed memory. The callback is now a shared_ptr that outlives SubscriberState, and the destructor detaches it so any callback arriving afterwards returns early instead of touching the dead object. Pull Request resolved: #218 Reviewed By: sandarsh Differential Revision: D116952494 Pulled By: afrind fbshipit-source-id: d03e01fc365b35271db7bcb4af331ed1b165cc9b
1 parent dc8bdf7 commit 9a595e0

2 files changed

Lines changed: 42 additions & 22 deletions

File tree

moxygen/moqtest/MoQPerfTestClient.cpp

Lines changed: 31 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -58,11 +58,12 @@ SubscriberState::SubscriberState(
5858
receiver_(
5959
std::make_shared<ObjectReceiver>(
6060
ObjectReceiver::SUBSCRIBE,
61-
std::shared_ptr<ObjectReceiverCallback>(
62-
std::shared_ptr<void>(),
63-
&callback_))) {}
61+
callback_)) {}
6462

6563
SubscriberState::~SubscriberState() {
64+
// The session may still hold subgroup receivers referencing callback_;
65+
// detach first so any late callback can't touch this destroyed object.
66+
callback_->detach();
6667
try {
6768
// Unsubscribe if we have a valid subHandle_
6869
if (subHandle_) {
@@ -193,9 +194,12 @@ ObjectReceiverCallback::FlowControlState SubscriberState::Callback::onObject(
193194
std::optional<TrackAlias> /* trackAlias */,
194195
const ObjectHeader& objHeader,
195196
Payload payload) {
196-
state_.objectsReceived_++;
197+
if (!state_) {
198+
return FlowControlState::UNBLOCKED;
199+
}
200+
state_->objectsReceived_++;
197201
if (payload) {
198-
state_.bytesReceived_ += payload->computeChainDataLength();
202+
state_->bytesReceived_ += payload->computeChainDataLength();
199203
}
200204

201205
if (auto sendTs =
@@ -205,15 +209,15 @@ ObjectReceiverCallback::FlowControlState SubscriberState::Callback::onObject(
205209
.count();
206210
if (nowMs >= *sendTs) {
207211
uint64_t latencyMs = nowMs - *sendTs;
208-
state_.totalLatencyMs_ += latencyMs;
209-
state_.latencyObjects_++;
210-
state_.testClient_.recordLatency(latencyMs);
212+
state_->totalLatencyMs_ += latencyMs;
213+
state_->latencyObjects_++;
214+
state_->testClient_.recordLatency(latencyMs);
211215
}
212216
}
213217

214218
// Update largest object seen for track restart detection
215219
AbsoluteLocation location(objHeader.group, objHeader.id);
216-
state_.testClient_.updateLargestObjectSeen(location);
220+
state_->testClient_.updateLargestObjectSeen(location);
217221

218222
return FlowControlState::UNBLOCKED;
219223
}
@@ -229,37 +233,46 @@ void SubscriberState::Callback::onEndOfStream() {
229233
}
230234

231235
void SubscriberState::Callback::onError(ResetStreamErrorCode code) {
232-
XLOG(ERR) << "Subscriber " << state_.id_
236+
if (!state_) {
237+
return;
238+
}
239+
XLOG(ERR) << "Subscriber " << state_->id_
233240
<< " received stream reset: " << static_cast<uint64_t>(code);
234241
// Don't unsubscribe immediately - let removeSubscriber handle it
235-
state_.testClient_.recordReset();
242+
state_->testClient_.recordReset();
236243
}
237244

238245
void SubscriberState::Callback::onPublishDone(PublishDone done) {
239-
XLOG(DBG1) << "Subscriber " << state_.id_
246+
if (!state_) {
247+
return;
248+
}
249+
XLOG(DBG1) << "Subscriber " << state_->id_
240250
<< " received PublishDone - status: "
241251
<< static_cast<uint32_t>(done.statusCode)
242252
<< ", reason: " << done.reasonPhrase;
243253

244254
// Library has already cleaned up subscription state; just release our handle
245-
state_.subHandle_.reset();
255+
state_->subHandle_.reset();
246256

247257
// Only signal completion if track ended naturally
248258
if (done.statusCode == PublishDoneStatusCode::TRACK_ENDED) {
249-
XLOG(DBG1) << "Subscriber " << state_.id_ << " - track ended naturally";
250-
state_.testClient_.completed();
259+
XLOG(DBG1) << "Subscriber " << state_->id_ << " - track ended naturally";
260+
state_->testClient_.completed();
251261
} else {
252262
// Other status codes (errors, going away, etc.) don't end the test
253-
XLOG(DBG1) << "Subscriber " << state_.id_
263+
XLOG(DBG1) << "Subscriber " << state_->id_
254264
<< " - PublishDone with non-TRACK_ENDED status, not ending test";
255265
}
256266
}
257267

258268
void SubscriberState::Callback::onAllDataReceived() {
259-
XLOG(DBG1) << "Subscriber " << state_.id_
269+
if (!state_) {
270+
return;
271+
}
272+
XLOG(DBG1) << "Subscriber " << state_->id_
260273
<< " - all data received, removing subscriber";
261274
// Remove this subscriber from the client's map now that all streams are done
262-
state_.testClient_.removeSubscriber(state_.id_);
275+
state_->testClient_.removeSubscriber(state_->id_);
263276
}
264277

265278
// ============================================================================

moxygen/moqtest/MoQPerfTestClient.h

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -59,10 +59,17 @@ class SubscriberState {
5959
bool hasError_{false};
6060

6161
private:
62-
// ObjectReceiverCallback implementation
62+
// ObjectReceiverCallback implementation.
63+
// The MoQ session keeps subgroup receivers (and therefore this callback)
64+
// alive past SubscriberState destruction, so detach() severs the back
65+
// pointer and later callbacks become no-ops.
6366
class Callback : public ObjectReceiverCallback {
6467
public:
65-
explicit Callback(SubscriberState& state) : state_(state) {}
68+
explicit Callback(SubscriberState& state) : state_(&state) {}
69+
70+
void detach() {
71+
state_ = nullptr;
72+
}
6673

6774
FlowControlState onObject(
6875
std::optional<TrackAlias> trackAlias,
@@ -79,10 +86,10 @@ class SubscriberState {
7986
void onAllDataReceived() override;
8087

8188
private:
82-
SubscriberState& state_;
89+
SubscriberState* state_;
8390
};
8491

85-
Callback callback_{*this};
92+
std::shared_ptr<Callback> callback_{std::make_shared<Callback>(*this)};
8693
std::shared_ptr<MoQFollyExecutorImpl> moqExecutor_;
8794
std::unique_ptr<MoQClientBase> moqClient_;
8895
std::shared_ptr<ObjectReceiver> receiver_;

0 commit comments

Comments
 (0)