sco-tester: test local and remote disconnecting simultaneously
authorPauli Virtanen <pav@iki.fi>
Tue, 1 Aug 2023 16:38:40 +0000 (19:38 +0300)
committerAyush Garg <ayush.garg@samsung.com>
Fri, 5 Jan 2024 13:34:03 +0000 (19:04 +0530)
Demonstrate a kernel race condition when remote side disconnects at the
same time as local side tries to cancel the connection. I.e.

[controller] > HCI Synchronous Connect Complete
[controller] > HCI Disconnection Complete (from remote)
[user] shutdown(sco_socket)
[kernel] hci_conn_abort(SCO handle)
[kernel] > HCI Create Connection Cancel
[kernel] < HCI Synchronous Connect Complete
[kernel] < HCI Disconnect Complete
[controller] < HCI Create Connection Cancel
[controller] > HCI Command Status (Create Connection Cancel)
[kernel] < HCI Command Status (Create Connection Cancel)

and then we get BUG: KASAN: slab-use-after-free in hci_conn_failed when
hci_conn_abort tries to delete the same connection a second time.

This type of crash is probably not limited to the sequence here, but for
this one it was possible to get the timing right in the emulator.

Add a test that hits this in the emulator environment (pretty narrow
window to hit on real hardware):

eSCO Simultaneous Disconnect - Failure

tools/sco-tester.c

index 4460632..f3de42c 100755 (executable)
@@ -29,6 +29,7 @@
 
 #include "src/shared/tester.h"
 #include "src/shared/mgmt.h"
+#include "src/shared/util.h"
 
 struct test_data {
        const void *test_data;
@@ -37,6 +38,7 @@ struct test_data {
        struct hciemu *hciemu;
        enum hciemu_type hciemu_type;
        unsigned int io_id;
+       int sk;
        bool disable_esco;
        bool enable_codecs;
 };
@@ -225,6 +227,7 @@ static void test_data_free(void *test_data)
                        break; \
                user->hciemu_type = HCIEMU_TYPE_BREDRLE; \
                user->io_id = 0; \
+               user->sk = -1; \
                user->test_data = data; \
                user->disable_esco = _disable_esco; \
                user->enable_codecs = _enable_codecs; \
@@ -250,6 +253,10 @@ static const struct sco_client_data connect_failure = {
        .expect_err = EOPNOTSUPP
 };
 
+static const struct sco_client_data connect_failure_reset = {
+       .expect_err = ECONNRESET
+};
+
 const uint8_t data[] = {0, 1, 2, 3, 4, 5, 6, 7, 8};
 
 static const struct sco_client_data connect_send_success = {
@@ -650,6 +657,8 @@ static void test_connect(const void *test_data)
                return;
        }
 
+       data->sk = sk;
+
        io = g_io_channel_unix_new(sk);
        g_io_channel_set_close_on_unref(io, TRUE);
 
@@ -745,6 +754,52 @@ static void test_connect_offload_msbc(const void *test_data)
 end:
        close(sk);
 }
+
+static bool hook_simult_disc(const void *msg, uint16_t len, void *user_data)
+{
+       const struct bt_hci_evt_sync_conn_complete *ev = msg;
+       struct test_data *data = tester_get_data();
+       struct bthost *bthost;
+
+       tester_print("Simultaneous disconnect");
+
+       if (len != sizeof(struct bt_hci_evt_sync_conn_complete)) {
+               tester_test_failed();
+               return true;
+       }
+
+       /* Disconnect from local and remote sides at the same time */
+       bthost = hciemu_client_get_host(data->hciemu);
+       bthost_hci_disconnect(bthost, le16_to_cpu(ev->handle), 0x13);
+
+       shutdown(data->sk, SHUT_RDWR);
+
+       return true;
+}
+
+static bool hook_delay_cmd(const void *data, uint16_t len, void *user_data)
+{
+       tester_print("Delaying emulator response...");
+       g_usleep(250000);
+       tester_print("Delaying emulator response... Done.");
+       return true;
+}
+
+static void test_connect_simult_disc(const void *test_data)
+{
+       struct test_data *data = tester_get_data();
+
+       /* Kernel shall not crash, but <= 6.5-rc1 crash */
+       hciemu_add_hook(data->hciemu, HCIEMU_HOOK_POST_EVT,
+                                       BT_HCI_EVT_SYNC_CONN_COMPLETE,
+                                       hook_simult_disc, NULL);
+       hciemu_add_hook(data->hciemu, HCIEMU_HOOK_PRE_CMD,
+                                       BT_HCI_CMD_CREATE_CONN_CANCEL,
+                                       hook_delay_cmd, NULL);
+
+       test_connect(test_data);
+}
+
 int main(int argc, char *argv[])
 {
        tester_init(&argc, &argv);
@@ -767,6 +822,10 @@ int main(int argc, char *argv[])
        test_sco("eSCO mSBC - Success", &connect_success, setup_powered,
                                                        test_connect_transp);
 
+       test_sco("eSCO Simultaneous Disconnect - Failure",
+                                       &connect_failure_reset, setup_powered,
+                                       test_connect_simult_disc);
+
        test_sco_11("SCO CVSD 1.1 - Success", &connect_success, setup_powered,
                                                        test_connect);