2021-07-13 09:58:20

by Yun-hao Chung

[permalink] [raw]
Subject: [Bluez PATCH v3] a2dp: Fix crash in channel_free while waiting cmd resp

From: Yun-Hao Chung <[email protected]>

When channel_free is called and we are waiting for a command response
from the peer, bluez NULL the setup->session but would not free its
setup_cb. Since setup_cb holds a ref of setup, the setup wouldn't be
freed and if service_removed is called after channel_free, a2dp_cancel
tries to abort the ongoing avdtp commands, which accesses the
setup->session and triggers a crash.

This change finalizes all avdtp commands before assigning setup->session
to NULL in channel_free.

Crash stack trace:
0x000059f01943e688 (bluetoothd -avdtp.c:3690)
avdtp_abort
0x000059f01943928a (bluetoothd -a2dp.c:3069)
a2dp_cancel
0x000059f0194377fa (bluetoothd -sink.c:324)
sink_unregister
0x000059f01948715a (bluetoothd -service.c:177)
service_remove
0x000059f01948d77c (bluetoothd -device.c:5346)
device_remove
0x000059f019476d14 (bluetoothd -adapter.c:7202)
adapter_remove
0x000059f019476c3e (bluetoothd -adapter.c:10827)
adapter_cleanup
0x000059f01949d8d7 (bluetoothd -main.c:1114) main
0x0000787b36185d74 (libc.so.6 -libc-start.c:308)
__libc_start_main
0x000059f019433e39 (bluetoothd + 0x00026e39) _start
0x00007fff2d2c0127

Reviewed-by: Archie Pusaka <[email protected]>
---
There are two other options to fix this crash.
1. add a NULL check in a2dp_cancel before calling avdtp_abort.
2. call setup_cb_free to every setup_cb in setup->cb in channel_free.

Since each setup_cb needs setup->session, I think there is no need to
keep the setup_cb after assigning setup->session to NULL. So the first
option is not ideal. If the second option is adopted, there would be
some time that sink/source->connect_id/disconnect_id is not zero, but
there is no corresponding setup_cb.

Test steps:
Reproduce the crash with the following steps. Verify the crash is
no longer observed after this change.
1. ignore AVDTP_SET_CONFIGURATION resp by modifying avdtp.c
2. turn on a paired headset
3. check the bluetooth.log, while bluez is waiting for
AVDTP_SET_CONFIGURATION resp, stop bluetoothd immediately.
This will trigger:
session_cb (I/O error) -> connection_lost
-> avdtp_set_state (AVDTP_SESSION_STATE_DISCONNECTED)
-> avdtp_state_cb -> channel_remove -> channel_free
then:
adapter_cleanup -> adapter_remove -> device_remove -> service_remove
-> a2dp_sink_remove -> sink_unregister -> sink_free -> a2dp_cancel
4. check if bluetoothd crash
The above steps can trigger the crash 100%.

Changes in v3:
- Remove the duplicated finalize_all

Changes in v2:
- Implement helper function finalize_all
- add setup_ref before finalize_all, in case the setup is freed during
finalize

profiles/audio/a2dp.c | 37 ++++++++++++++++++++++++++++++++++++-
1 file changed, 36 insertions(+), 1 deletion(-)

diff --git a/profiles/audio/a2dp.c b/profiles/audio/a2dp.c
index d31ed845cbe7..39d5e3ef736c 100644
--- a/profiles/audio/a2dp.c
+++ b/profiles/audio/a2dp.c
@@ -404,6 +404,38 @@ static void finalize_discover(struct a2dp_setup *s)
}
}

+static void finalize_all(struct a2dp_setup *s)
+{
+ GSList *l;
+ struct avdtp_stream *stream = s->err ? NULL : s->stream;
+
+ for (l = s->cb; l != NULL; ) {
+ struct a2dp_setup_cb *cb = l->data;
+
+ l = l->next;
+
+ if (cb->discover_cb) {
+ cb->discover_cb(s->session, s->seps,
+ error_to_errno(s->err), cb->user_data);
+ } else if (cb->select_cb) {
+ cb->select_cb(s->session, s->seps, s->caps,
+ error_to_errno(s->err), cb->user_data);
+ } else if (cb->suspend_cb) {
+ cb->suspend_cb(s->session,
+ error_to_errno(s->err), cb->user_data);
+ } else if (cb->resume_cb) {
+ cb->resume_cb(s->session,
+ error_to_errno(s->err), cb->user_data);
+ } else if (cb->config_cb) {
+ cb->config_cb(s->session, s->seps, stream,
+ error_to_errno(s->err), cb->user_data);
+ } else
+ warn("setup_cb doesn't have any callback function");
+
+ setup_cb_free(cb);
+ }
+}
+
static struct a2dp_setup *find_setup_by_session(struct avdtp *session)
{
GSList *l;
@@ -1540,9 +1572,12 @@ static void channel_free(void *data)
setup = find_setup_by_session(chan->session);
if (setup) {
setup->chan = NULL;
+ setup_ref(setup);
+ /* Finalize pending commands before we NULL setup->session */
+ finalize_setup_errno(setup, -ENOTCONN, finalize_all, NULL);
avdtp_unref(setup->session);
setup->session = NULL;
- finalize_setup_errno(setup, -ENOTCONN, NULL);
+ setup_unref(setup);
}

g_free(chan);
--
2.32.0.93.g670b81a890-goog


2021-07-13 10:40:24

by bluez.test.bot

[permalink] [raw]
Subject: RE: [Bluez,v3] a2dp: Fix crash in channel_free while waiting cmd resp

This is automated email and please do not reply to this email!

Dear submitter,

Thank you for submitting the patches to the linux bluetooth mailing list.
This is a CI test results with your patch series:
PW Link:https://patchwork.kernel.org/project/bluetooth/list/?series=514653

---Test result---

Test Summary:
CheckPatch PASS 0.39 seconds
GitLint FAIL 0.13 seconds
Prep - Setup ELL PASS 48.01 seconds
Build - Prep PASS 0.11 seconds
Build - Configure PASS 8.45 seconds
Build - Make FAIL 156.82 seconds
Make Check FAIL 1.34 seconds
Make Distcheck PASS 241.40 seconds
Build w/ext ELL - Configure PASS 8.29 seconds
Build w/ext ELL - Make FAIL 144.34 seconds

Details
##############################
Test: CheckPatch - PASS
Desc: Run checkpatch.pl script with rule in .checkpatch.conf

##############################
Test: GitLint - FAIL
Desc: Run gitlint with rule in .gitlint
Output:
a2dp: Fix crash in channel_free while waiting cmd resp
14: B3 Line contains hard tab characters (\t): "0x000059f01943e688 (bluetoothd -avdtp.c:3690)"
16: B3 Line contains hard tab characters (\t): "0x000059f01943928a (bluetoothd -a2dp.c:3069)"
18: B3 Line contains hard tab characters (\t): "0x000059f0194377fa (bluetoothd -sink.c:324)"
20: B3 Line contains hard tab characters (\t): "0x000059f01948715a (bluetoothd -service.c:177)"
22: B3 Line contains hard tab characters (\t): "0x000059f01948d77c (bluetoothd -device.c:5346)"
24: B3 Line contains hard tab characters (\t): "0x000059f019476d14 (bluetoothd -adapter.c:7202)"
26: B3 Line contains hard tab characters (\t): "0x000059f019476c3e (bluetoothd -adapter.c:10827)"
28: B3 Line contains hard tab characters (\t): "0x000059f01949d8d7 (bluetoothd -main.c:1114) main"
29: B3 Line contains hard tab characters (\t): "0x0000787b36185d74 (libc.so.6 -libc-start.c:308)"
31: B3 Line contains hard tab characters (\t): "0x000059f019433e39 (bluetoothd + 0x00026e39) _start"


##############################
Test: Prep - Setup ELL - PASS
Desc: Clone, build, and install ELL

##############################
Test: Build - Prep - PASS
Desc: Prepare environment for build

##############################
Test: Build - Configure - PASS
Desc: Configure the BlueZ source tree

##############################
Test: Build - Make - FAIL
Desc: Build the BlueZ source tree
Output:
profiles/audio/a2dp.c: In function ‘finalize_all’:
profiles/audio/a2dp.c:421:31: error: passing argument 2 of ‘cb->select_cb’ from incompatible pointer type [-Werror=incompatible-pointer-types]
421 | cb->select_cb(s->session, s->seps, s->caps,
| ~^~~~~~
| |
| GSList * {aka struct _GSList *}
profiles/audio/a2dp.c:421:31: note: expected ‘struct a2dp_sep *’ but argument is of type ‘GSList *’ {aka ‘struct _GSList *’}
profiles/audio/a2dp.c:430:31: error: passing argument 2 of ‘cb->config_cb’ from incompatible pointer type [-Werror=incompatible-pointer-types]
430 | cb->config_cb(s->session, s->seps, stream,
| ~^~~~~~
| |
| GSList * {aka struct _GSList *}
profiles/audio/a2dp.c:430:31: note: expected ‘struct a2dp_sep *’ but argument is of type ‘GSList *’ {aka ‘struct _GSList *’}
profiles/audio/a2dp.c: In function ‘channel_free’:
profiles/audio/a2dp.c:1577:42: error: passing argument 3 of ‘finalize_setup_errno’ from incompatible pointer type [-Werror=incompatible-pointer-types]
1577 | finalize_setup_errno(setup, -ENOTCONN, finalize_all, NULL);
| ^~~~~~~~~~~~
| |
| void (*)(struct a2dp_setup *)
profiles/audio/a2dp.c:262:18: note: expected ‘GSourceFunc’ {aka ‘int (*)(void *)’} but argument is of type ‘void (*)(struct a2dp_setup *)’
262 | GSourceFunc cb1, ...)
| ~~~~~~~~~~~~^~~
cc1: all warnings being treated as errors
make[1]: *** [Makefile:8531: profiles/audio/bluetoothd-a2dp.o] Error 1
make: *** [Makefile:4134: all] Error 2


##############################
Test: Make Check - FAIL
Desc: Run 'make check'
Output:
profiles/audio/a2dp.c: In function ‘finalize_all’:
profiles/audio/a2dp.c:421:31: error: passing argument 2 of ‘cb->select_cb’ from incompatible pointer type [-Werror=incompatible-pointer-types]
421 | cb->select_cb(s->session, s->seps, s->caps,
| ~^~~~~~
| |
| GSList * {aka struct _GSList *}
profiles/audio/a2dp.c:421:31: note: expected ‘struct a2dp_sep *’ but argument is of type ‘GSList *’ {aka ‘struct _GSList *’}
profiles/audio/a2dp.c:430:31: error: passing argument 2 of ‘cb->config_cb’ from incompatible pointer type [-Werror=incompatible-pointer-types]
430 | cb->config_cb(s->session, s->seps, stream,
| ~^~~~~~
| |
| GSList * {aka struct _GSList *}
profiles/audio/a2dp.c:430:31: note: expected ‘struct a2dp_sep *’ but argument is of type ‘GSList *’ {aka ‘struct _GSList *’}
profiles/audio/a2dp.c: In function ‘channel_free’:
profiles/audio/a2dp.c:1577:42: error: passing argument 3 of ‘finalize_setup_errno’ from incompatible pointer type [-Werror=incompatible-pointer-types]
1577 | finalize_setup_errno(setup, -ENOTCONN, finalize_all, NULL);
| ^~~~~~~~~~~~
| |
| void (*)(struct a2dp_setup *)
profiles/audio/a2dp.c:262:18: note: expected ‘GSourceFunc’ {aka ‘int (*)(void *)’} but argument is of type ‘void (*)(struct a2dp_setup *)’
262 | GSourceFunc cb1, ...)
| ~~~~~~~~~~~~^~~
cc1: all warnings being treated as errors
make[1]: *** [Makefile:8531: profiles/audio/bluetoothd-a2dp.o] Error 1
make: *** [Makefile:10406: check] Error 2


##############################
Test: Make Distcheck - PASS
Desc: Run distcheck to check the distribution

##############################
Test: Build w/ext ELL - Configure - PASS
Desc: Configure BlueZ source with '--enable-external-ell' configuration

##############################
Test: Build w/ext ELL - Make - FAIL
Desc: Build BlueZ source with '--enable-external-ell' configuration
Output:
profiles/audio/a2dp.c: In function ‘finalize_all’:
profiles/audio/a2dp.c:421:31: error: passing argument 2 of ‘cb->select_cb’ from incompatible pointer type [-Werror=incompatible-pointer-types]
421 | cb->select_cb(s->session, s->seps, s->caps,
| ~^~~~~~
| |
| GSList * {aka struct _GSList *}
profiles/audio/a2dp.c:421:31: note: expected ‘struct a2dp_sep *’ but argument is of type ‘GSList *’ {aka ‘struct _GSList *’}
profiles/audio/a2dp.c:430:31: error: passing argument 2 of ‘cb->config_cb’ from incompatible pointer type [-Werror=incompatible-pointer-types]
430 | cb->config_cb(s->session, s->seps, stream,
| ~^~~~~~
| |
| GSList * {aka struct _GSList *}
profiles/audio/a2dp.c:430:31: note: expected ‘struct a2dp_sep *’ but argument is of type ‘GSList *’ {aka ‘struct _GSList *’}
profiles/audio/a2dp.c: In function ‘channel_free’:
profiles/audio/a2dp.c:1577:42: error: passing argument 3 of ‘finalize_setup_errno’ from incompatible pointer type [-Werror=incompatible-pointer-types]
1577 | finalize_setup_errno(setup, -ENOTCONN, finalize_all, NULL);
| ^~~~~~~~~~~~
| |
| void (*)(struct a2dp_setup *)
profiles/audio/a2dp.c:262:18: note: expected ‘GSourceFunc’ {aka ‘int (*)(void *)’} but argument is of type ‘void (*)(struct a2dp_setup *)’
262 | GSourceFunc cb1, ...)
| ~~~~~~~~~~~~^~~
cc1: all warnings being treated as errors
make[1]: *** [Makefile:8531: profiles/audio/bluetoothd-a2dp.o] Error 1
make: *** [Makefile:4134: all] Error 2




---
Regards,
Linux Bluetooth