Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Puzzled by HID-over-GATT, feature, report ID #15

Closed
Dvad opened this issue May 26, 2020 · 5 comments
Closed

Puzzled by HID-over-GATT, feature, report ID #15

Dvad opened this issue May 26, 2020 · 5 comments

Comments

@Dvad
Copy link

Dvad commented May 26, 2020

I have been playing a bit with a HOGP device (microsoft surface dial) under linux and found a strange behavior when using linux's hidraw interface to get and send feature reports. I am sharing this with you because it is puzzling me, and because I might have found a bug in bluez but do not have enough familiarity with Bluetooth stack to recognize it.

What I do:

  1. I send a HID feature report with report_id 1 and the following sequence of bytes [255, 0, 1, 3, 1, 80, 0] as payload. This is a totally valid payload for the device, although its meaning does not matter here.
  2. I request just after a HID feature report with same id and look at the payload.

What I expect:
The buffer should contain the byte I just sent [255, 0, 1, 3, 1, 80, 0] as detailed in hidraw docs :

HIDIOCGFEATURE(len): Get a Feature Report
[...] The report will be returned starting at
the first byte of the buffer (ie: the report number is not returned).

What I observe:
The returned buffer contains [0, 1, 3, 1, 80, 0, 0]. It seems to be shifted by one byte and truncated (255 is not here anymore).


I did some investigation, and ended up -- after trying to find anything in linux hidp module 🤦‍♂️ -- in the implementation of HOGP in bluez.
I found this in get_report_cb :

--len;
++pdu;
if (hog->has_report_id && len > 0) {
--len;
++pdu;
}

I am not familiar at all with the bluetooth stack so I don't quite understand why the two first bytes are removed from the buffer here. However, this seems to be the only place that could cause my issue in the entire stack.
After digging a bit more, I also read here that in the case of HOGP, the report id of HID are not serialized with the data sent over the air. (which I also kind of decrypt from the section 4.8.1 of the HOGP spec but it is not clear).

That makes me wonder if the report_id is really present in pdu buffer, and if the code should rather be:

	--len;
	++pdu;
        // the following is useless as the HID is not returned over GATT
	//if (hog->has_report_id && len > 0) {
	//	--len;
	//	++pdu;
	//}

And indeed, testing this gives me the expected behavior.

This solution looks very hacky dark magic to me : I guess such a deep change in the stack would break other things.
What do you think? Should I look elsewhere? The only alternative I can think of is that I am dealing with a non-compliant device. 🙃


The following python code snippet can be used to reproduce. It will be surely not useful for someone without the device, posting it for completeness.

# hidraw comes from
# https://github.com/vpelletier/python-hidraw simple
# passtrhough around hidraw ioctl, seems fine
import hidraw
import struct
a = hidraw.HIDRaw(open("/dev/hidraw0", "w+b"))

# byte payload, no report-id, will be added just before sendinf
toSend = struct.pack(forma_inv, 255, 0 , 1, 3, 1, 80, 0)

# 1 = report_id
a.sendFeatureReport(toSend, 1)

# 1 = report_id, 6 = size of expected buffer - 1 (weirdness of hidraw)
ret = a.getFeatureReport(1, length=6)  

print("Payload sent", struct.unpack("BBBBBBB", toSend))
print("Payload received", struct.unpack("BBBBBBB", ret)) 

print

Payload sent (255, 0, 1, 3, 1, 80, 0)
Payload received (0, 1, 3, 1, 80, 0, 0)
@Vudentz
Copy link
Contributor

Vudentz commented May 27, 2020

I wonder if the original intend was to actually skip the report_id in case there are multiple reports over the same characteristic, which I believe it would be against the spec, so I guess we either have to fix has_report_id to really detect the presence of report_id on the payload or just assume there is never suppose to be a report_id which is basically what you did.

@Dvad
Copy link
Author

Dvad commented May 28, 2020

Hi @Vudentz thanks for reading me and for maintaining this project!

That would makes sense. I wonder how many client rely on this specific behaviour, I expected plenty, but maybe getting features report are not so common. I would actually not really need it for writing this device driver and was just hacking around when I discovered it.

How easy it would be to safely detect if report_id is actually there?
Just testing the first byte of the payload would not be safe, as even having report id here might be a coincidence with a valid report.
Maybe we can detect the fact that multiple reports goes over the same characteristics somehow ?

@abcminiuser
Copy link
Contributor

I just ran into this as well - in my case, I have a conformant device that does not include the report ID in the bluetooth characteristic, and as a result the returned report data has the first data byte lost entirely.

I suggest that we don't massage the returned buffer, just give back the read bytes as-is. This is perhaps safest as it gives the widest range of compatibility and moves the responsibility of dealing with device-specific quirks upstream where it it more able to handle it. In Linux, that means either the regular hid input subsystem would need to length check and deal with the report ID byte if it was included, or in the case of hidraw, the end-user application would have to do the check.

As it stands now we have the worst of both worlds: non-compliant devices' data is returned correctly, while compliant devices have the first data byte discarded, breaking user applications.

@abcminiuser
Copy link
Contributor

This should now be resolved by 35a2c50.

@Dvad
Copy link
Author

Dvad commented Nov 23, 2020

Awesome! Thanks @abcminiuser.

@Dvad Dvad closed this as completed Nov 23, 2020
BluezTestBot pushed a commit that referenced this issue Mar 29, 2022
This fixes the following error for invalid read access when registering
filter for incoming messages:

140632==ERROR: AddressSanitizer: stack-buffer-overflow on address...
 #0 0x7f60c185741d in MemcmpInterceptorCommon(...
    #1 0x7f60c1857af8 in __interceptor_memcmp (/lib64/libasan.so...
    #2 0x55a10101536e in find_by_filter mesh/mesh-io-unit.c:494
    #3 0x55a1010d8c46 in l_queue_remove_if ell/queue.c:517
    #4 0x55a101014ebd in recv_register mesh/mesh-io-unit.c:506
    #5 0x55a10102946f in mesh_net_attach mesh/net.c:2885
    #6 0x55a101086f64 in send_reply mesh/dbus.c:153
    #7 0x55a101124c3d in handle_method_return ell/dbus.c:216
    #8 0x55a10112c8ef in message_read_handler ell/dbus.c:276
    #9 0x55a1010dae20 in io_callback ell/io.c:120
    #10 0x55a1010dff7e in l_main_iterate ell/main.c:478
    #11 0x55a1010e06e3 in l_main_run ell/main.c:525
    #12 0x55a1010e06e3 in l_main_run ell/main.c:507
    #13 0x55a1010e0bfc in l_main_run_with_signal ell/main.c:647
    #14 0x55a10100316e in main mesh/main.c:292
    #15 0x7f60c0c6855f in __libc_start_call_main (/lib64/libc.so.6+...
    #16 0x7f60c0c6860b in __libc_start_main_alias_1 (/lib64/libc.so.6+...
    #17 0x55a101003ce4 in _start (/home/istotlan/bluez/mesh/bluetooth-m...
BluezTestBot pushed a commit that referenced this issue Sep 20, 2023
Primary/Secundary Counters are supposed to be 16 bytes values, if the
server has implemented them incorrectly it may lead to the following
crash:

=================================================================
==31860==ERROR: AddressSanitizer: heap-buffer-overflow on address
0x607000001878 at pc 0x7f95a1575638 bp 0x7fff58c6bb80 sp 0x7fff58c6b328

 READ of size 48 at 0x607000001878 thread T0
     #0 0x7f95a1575637 in MemcmpInterceptorCommon(void*, int (*)(void const*, void const*, unsigned long), void const*, void const*, unsigned long) ../../../../src/libsanitizer/sanitizer_common/sanitizer_common_interceptors.inc:860
     #1 0x7f95a1575ba6 in __interceptor_memcmp ../../../../src/libsanitizer/sanitizer_common/sanitizer_common_interceptors.inc:892
     #2 0x7f95a1575ba6 in __interceptor_memcmp ../../../../src/libsanitizer/sanitizer_common/sanitizer_common_interceptors.inc:887
     #3 0x564df69c77a0 in read_version obexd/client/pbap.c:288
     #4 0x564df69c77a0 in read_return_apparam obexd/client/pbap.c:352
     #5 0x564df69c77a0 in phonebook_size_callback obexd/client/pbap.c:374
     #6 0x564df69bea3c in session_terminate_transfer obexd/client/session.c:921
     #7 0x564df69d56b0 in get_xfer_progress_first obexd/client/transfer.c:729
     #8 0x564df698b9ee in handle_response gobex/gobex.c:1140
     #9 0x564df698cdea in incoming_data gobex/gobex.c:1385
     #10 0x7f95a12fdc43 in g_main_context_dispatch (/lib/x86_64-linux-gnu/libglib-2.0.so.0+0x55c43)
     #11 0x7f95a13526c7  (/lib/x86_64-linux-gnu/libglib-2.0.so.0+0xaa6c7)
     #12 0x7f95a12fd2b2 in g_main_loop_run (/lib/x86_64-linux-gnu/libglib-2.0.so.0+0x552b2)
     #13 0x564df6977d41 in main obexd/src/main.c:307
     #14 0x7f95a10a7d8f in __libc_start_call_main ../sysdeps/nptl/libc_start_call_main.h:58
     #15 0x7f95a10a7e3f in __libc_start_main_impl ../csu/libc-start.c:392
     #16 0x564df6978704 in _start (/usr/local/libexec/bluetooth/obexd+0x8b704)
 0x607000001878 is located 0 bytes to the right of 72-byte region [0x607000001830,0x607000001878)

 allocated by thread T0 here:
     #0 0x7f95a1595a37 in __interceptor_calloc ../../../../src/libsanitizer/asan/asan_malloc_linux.cpp:154
     #1 0x564df69c8b6a in pbap_probe obexd/client/pbap.c:1259
BluezTestBot pushed a commit that referenced this issue Feb 2, 2024
This fixes the following crash when a broadcast stream setup is
pending and the device is remove:

bluetoothd[37]: src/device.c:device_free() 0x89a500
bluetoothd[37]: GLib: Invalid file descriptor.
bluetoothd[37]: ++++++++ backtrace ++++++++
bluetoothd[37]: #1  g_logv+0x270 (/usr/lib64/libglib-2.0.so.0.7600.6) [0x7feb557e3120]
bluetoothd[37]: #2  g_log+0x93 (/usr/lib64/libglib-2.0.so.0.7600.6) [0x7feb557e3403]
bluetoothd[37]: #3  g_io_channel_error_from_errno+0x4a (/usr/lib64/libglib-2.0.so.0.7600.6) [0x7feb557cd9da]
bluetoothd[37]: #4  g_io_unix_close+0x53 (/usr/lib64/libglib-2.0.so.0.7600.6) [0x7feb55839d53]
bluetoothd[37]: #5  g_io_channel_shutdown+0x10f (/usr/lib64/libglib-2.0.so.0.7600.6) [0x7feb557cdf7f]
bluetoothd[37]: #6  g_io_channel_unref+0x39 (/usr/lib64/libglib-2.0.so.0.7600.6) [0x7feb557ce1e9]
bluetoothd[37]: #7  g_source_unref_internal+0x24f (/usr/lib64/libglib-2.0.so.0.7600.6) [0x7feb557db79f]
bluetoothd[37]: #8  g_main_context_dispatch+0x288 (/usr/lib64/libglib-2.0.so.0.7600.6) [0x7feb557dd638]
bluetoothd[37]: #9  g_main_context_iterate.isra.0+0x318 (/usr/lib64/libglib-2.0.so.0.7600.6) [0x7feb5583b6b8]
bluetoothd[37]: #10 g_main_loop_run+0x7f (/usr/lib64/libglib-2.0.so.0.7600.6) [0x7feb557dcaff]
bluetoothd[37]: #11 mainloop_run+0x15 (src/shared/mainloop-glib.c:68) [0x662e65]
bluetoothd[37]: #12 mainloop_run_with_signal+0x128 (src/shared/mainloop-notify.c:190) [0x663368]
bluetoothd[37]: #13 main+0x154b (src/main.c:1454) [0x41521b]
bluetoothd[37]: #14 __libc_start_call_main+0x7a (/usr/lib64/libc.so.6) [0x7feb54e1fb8a]
bluetoothd[37]: #15 __libc_start_main@@GLIBC_2.34+0x8b (/usr/lib64/libc.so.6) [0x7feb54e1fc4b]
bluetoothd[37]: #16 _start+0x25 (src/main.c:1197) [0x416305]
bluetoothd[37]: +++++++++++++++++++++++++++
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants