Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ espresso = "3.7.0"
lifecycle = "2.9.3"
navigation = "2.9.4"
room = "2.7.2" # >=2.8.0 needs minSdk 23.
tox4j-android = "0.2.20"
tox4j-android = "0.2.22"
tox4j-core = "0.2.3"

[plugins]
Expand Down
3 changes: 2 additions & 1 deletion scripts/dependencies.mk
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,8 @@ $(PREFIX)/protobuf.stamp: $(SRCDIR)/protobuf $(TOOLCHAIN_FILE) $(PROTOC)
# toxcore

$(SRCDIR)/toxcore:
git clone --depth=1 --branch=v0.2.20 --recursive https://github.com/TokTok/c-toxcore $@
git clone --depth=1 --branch=v0.2.22 --recursive https://github.com/TokTok/c-toxcore $@
cd $@ && patch -p1 < $(CURDIR)/scripts/patches/fix-toxav-uaf-on-peer-offline.patch

$(PREFIX)/toxcore.stamp: $(foreach f,$(shell cd $(SRCDIR)/toxcore && git ls-files),$(SRCDIR)/toxcore/$f)
$(PREFIX)/toxcore.stamp: $(SRCDIR)/toxcore $(TOOLCHAIN_FILE) $(foreach i,libsodium opus libvpx,$(PREFIX)/$i.stamp)
Expand Down
178 changes: 178 additions & 0 deletions scripts/patches/fix-toxav-uaf-on-peer-offline.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
From dbb652239a228c7536ac473856bee42b6e3698c2 Mon Sep 17 00:00:00 2001
From: iphydf <iphydf@users.noreply.github.com>
Date: Sun, 15 Feb 2026 21:07:07 +0000
Subject: [PATCH] fix(av): Fix Use-After-Free when peer goes offline.

`msi_call_timeout` synchronously calls callbacks that eventually call
`call_remove`, which frees the `ToxAVCall` object and destroys its
mutex. `iterate_common` was attempting to unlock the mutex after the
call, leading to a Use-After-Free.
---
.../scenario_toxav_peer_offline_test.c | 142 ++++++++++++++++++
toxav/toxav.c | 3 +-
2 files changed, 144 insertions(+), 1 deletion(-)
create mode 100644 auto_tests/scenarios/scenario_toxav_peer_offline_test.c

diff --git a/auto_tests/scenarios/scenario_toxav_peer_offline_test.c b/auto_tests/scenarios/scenario_toxav_peer_offline_test.c
new file mode 100644
index 000000000..62ac4e75b
--- /dev/null
+++ b/auto_tests/scenarios/scenario_toxav_peer_offline_test.c
@@ -0,0 +1,142 @@
+#include "framework/framework.h"
+#include "../../toxav/toxav.h"
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+typedef struct {
+ uint32_t state;
+} AliceState;
+
+typedef struct {
+ uint32_t state;
+} BobState;
+
+static void on_call_state_alice(ToxAV *av, uint32_t friend_number, uint32_t state, void *user_data)
+{
+ ToxNode *self = (ToxNode *)user_data;
+ AliceState *cs = (AliceState *)tox_node_get_script_ctx(self);
+ tox_node_log(self, "Call state changed to %u", state);
+ cs->state = state;
+}
+
+static void on_call_state_bob(ToxAV *av, uint32_t friend_number, uint32_t state, void *user_data)
+{
+ ToxNode *self = (ToxNode *)user_data;
+ BobState *cs = (BobState *)tox_node_get_script_ctx(self);
+ tox_node_log(self, "Call state changed to %u", state);
+ cs->state = state;
+}
+
+static void on_audio_receive_dummy(ToxAV *av, uint32_t friend_number, int16_t const *pcm, size_t sample_count,
+ uint8_t channels, uint32_t sampling_rate, void *user_data)
+{
+ (void)av;
+ (void)friend_number;
+ (void)pcm;
+ (void)sample_count;
+ (void)channels;
+ (void)sampling_rate;
+ (void)user_data;
+}
+
+static void alice_script(ToxNode *self, void *ctx)
+{
+ AliceState *state = (AliceState *)ctx;
+ Tox *tox = tox_node_get_tox(self);
+ Toxav_Err_New av_err;
+ ToxAV *av = toxav_new(tox, &av_err);
+ ck_assert(av_err == TOXAV_ERR_NEW_OK);
+
+ toxav_callback_call_state(av, on_call_state_alice, self);
+ toxav_callback_audio_receive_frame(av, on_audio_receive_dummy, self);
+
+ tox_node_wait_for_self_connected(self);
+ tox_node_wait_for_friend_connected(self, 0);
+
+ tox_node_log(self, "Calling Bob...");
+ Toxav_Err_Call call_err;
+ toxav_call(av, 0, 48, 0, &call_err);
+ ck_assert(call_err == TOXAV_ERR_CALL_OK);
+
+ // Wait until call is active
+ while (!(state->state & TOXAV_FRIEND_CALL_STATE_SENDING_A) && tox_scenario_is_running(self)) {
+ toxav_iterate(av);
+ tox_scenario_yield(self);
+ }
+
+ tox_node_log(self, "Call is active, Alice deletes Bob to trigger offline branch in ToxAV...");
+ Tox_Err_Friend_Delete fr_err;
+ tox_friend_delete(tox, 0, &fr_err);
+ ck_assert(fr_err == TOX_ERR_FRIEND_DELETE_OK);
+
+ tox_node_log(self, "Alice deleted Bob, running toxav_iterate to trigger UAF...");
+ toxav_iterate(av);
+
+ tox_node_log(self, "Successfully finished toxav_iterate");
+ toxav_kill(av);
+}
+
+static void on_call_bob(ToxAV *av, uint32_t friend_number, bool audio_enabled, bool video_enabled, void *user_data)
+{
+ ToxNode *self = (ToxNode *)user_data;
+ tox_node_log(self, "Received call from Alice, answering...");
+ Toxav_Err_Answer err;
+ toxav_answer(av, friend_number, 48, 0, &err);
+ ck_assert(err == TOXAV_ERR_ANSWER_OK);
+}
+
+static void bob_script(ToxNode *self, void *ctx)
+{
+ BobState *state = (BobState *)ctx;
+ Tox *tox = tox_node_get_tox(self);
+ Toxav_Err_New av_err;
+ ToxAV *av = toxav_new(tox, &av_err);
+ ck_assert(av_err == TOXAV_ERR_NEW_OK);
+
+ toxav_callback_call(av, on_call_bob, self);
+ toxav_callback_call_state(av, on_call_state_bob, self);
+ toxav_callback_audio_receive_frame(av, on_audio_receive_dummy, self);
+
+ tox_node_wait_for_self_connected(self);
+ tox_node_wait_for_friend_connected(self, 0);
+
+ // Run until the call is finished (Alice deletes us or hangs up)
+ while (tox_scenario_is_running(self)) {
+ toxav_iterate(av);
+ if (state->state & (TOXAV_FRIEND_CALL_STATE_FINISHED | TOXAV_FRIEND_CALL_STATE_ERROR)) {
+ break;
+ }
+ tox_scenario_yield(self);
+ }
+
+ tox_node_log(self, "Bob script finished");
+ toxav_kill(av);
+}
+
+int main(int argc, char *argv[])
+{
+ ToxScenario *s = tox_scenario_new(argc, argv, 30000);
+
+ AliceState alice_state = {0};
+ BobState bob_state = {0};
+
+ tox_scenario_add_node(s, "Alice", alice_script, &alice_state, sizeof(AliceState));
+ tox_scenario_add_node(s, "Bob", bob_script, &bob_state, sizeof(BobState));
+
+ ToxNode *alice = tox_scenario_get_node(s, 0);
+ ToxNode *bob = tox_scenario_get_node(s, 1);
+
+ tox_node_bootstrap(alice, bob);
+ tox_node_friend_add(alice, bob);
+ tox_node_friend_add(bob, alice);
+
+ ToxScenarioStatus res = tox_scenario_run(s);
+ if (res != TOX_SCENARIO_DONE) {
+ fprintf(stderr, "Scenario failed with status %d\n", res);
+ return 1;
+ }
+
+ tox_scenario_free(s);
+ return 0;
+}
diff --git a/toxav/toxav.c b/toxav/toxav.c
index 7ab7577ad..c59f178df 100644
--- a/toxav/toxav.c
+++ b/toxav/toxav.c
@@ -528,8 +528,9 @@ static void iterate_common(ToxAV *_Nonnull av, bool audio)
const bool is_offline = tox_friend_get_connection_status(av->tox, fid, &f_con_query_error) == TOX_CONNECTION_NONE;

if (is_offline) {
- msi_call_timeout(i->msi_call->session, av->log, fid);
+ MSISession *session = i->msi_call->session;
pthread_mutex_unlock(i->toxav_call_mutex);
+ msi_call_timeout(session, av->log, fid);
pthread_mutex_lock(av->mutex);
break;
}
Loading