Skip to content

fix: add input validation to prevent remote crash from empty/malforme…#912

Merged
zxzxwu merged 2 commits intogoogle:mainfrom
ibondarenko1:fix/empty-pdu-crash
Apr 20, 2026
Merged

fix: add input validation to prevent remote crash from empty/malforme…#912
zxzxwu merged 2 commits intogoogle:mainfrom
ibondarenko1:fix/empty-pdu-crash

Conversation

@ibondarenko1
Copy link
Copy Markdown
Contributor

…d PDUs

Add length checks in from_bytes() for ATT and SMP protocol parsers to prevent IndexError crashes from empty PDUs sent by remote Bluetooth devices. Also add buffer size limit and UTF-8 error handling in HFP protocol to prevent memory exhaustion and decode crashes.

  • bumble/att.py: validate PDU is non-empty before accessing pdu[0]
  • bumble/smp.py: validate PDU is non-empty before accessing pdu[0]
  • bumble/hfp.py: limit buffer to 64KB, handle invalid UTF-8 gracefully

These issues can be triggered by a remote Bluetooth device sending malformed packets, causing denial of service on the host.

@google-cla
Copy link
Copy Markdown

google-cla Bot commented Apr 16, 2026

Thanks for your pull request! It looks like this may be your first contribution to a Google open source project. Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA).

View this failed invocation of the CLA check for more information.

For the most up to date status, view the checks section at the bottom of the pull request.

…d PDUs

Add length checks in from_bytes() for ATT and SMP protocol parsers
to prevent IndexError crashes from empty PDUs sent by remote Bluetooth
devices. Also add buffer size limit and UTF-8 error handling in HFP
protocol to prevent memory exhaustion and decode crashes.

- bumble/att.py: validate PDU is non-empty before accessing pdu[0]
- bumble/smp.py: validate PDU is non-empty before accessing pdu[0]
- bumble/hfp.py: limit buffer to 64KB, handle invalid UTF-8 gracefully

These issues can be triggered by a remote Bluetooth device sending
malformed packets, causing denial of service on the host.
Copy link
Copy Markdown
Collaborator

@zxzxwu zxzxwu left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for your PR, but I am wondering when do we need such handling?

For single packet parsing, we won't crash because there is a try-except umbrella at the packet sink root.

For HFP, despite the issue is possible, "normal" products qualified by SIG and authenticated by users should not send such invalid packets, and users can still define their own handling.

Comment thread bumble/att.py Outdated
@classmethod
def from_bytes(cls, pdu: bytes) -> ATT_PDU:
if not pdu:
raise ValueError("Empty ATT PDU")
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment thread bumble/smp.py Outdated
@classmethod
def from_bytes(cls, pdu: bytes) -> SMP_Command:
if not pdu:
raise ValueError("Empty SMP PDU")
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ditto

Comment thread bumble/hfp.py Outdated
Comment on lines +93 to +95
if len(self.buffer) > 65536:
logger.warning("HFP buffer overflow, truncating")
self.buffer = self.buffer[-65536:]
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we can just abort new packets after overflow?

Suggested change
if len(self.buffer) > 65536:
logger.warning("HFP buffer overflow, truncating")
self.buffer = self.buffer[-65536:]
MAX_LINE_LENGTH = 65536
...
if len(self.buffer) > MAX_LINE_LENGTH:
logger.error("HFP buffer overflow, abort")
return

…ffer overflow

- att.py: raise core.InvalidPacketError instead of generic ValueError
- smp.py: raise core.InvalidPacketError instead of generic ValueError
- hfp.py: add MAX_BUFFER_SIZE class constant (64KB)
- hfp.py: drop incoming data when it would overflow buffer instead of
  truncating, preserving existing partial-packet state

Per review comments on PR google#912 by @zxzxwu.
@ibondarenko1
Copy link
Copy Markdown
Contributor Author

Thanks for the review @zxzxwu — pushed follow-up commit addressing both points:

  1. att.py / smp.py: switched from ValueError to core.InvalidPacketError so the exception fits the existing taxonomy and is caught by the packet-sink umbrella try/except as you noted.

  2. hfp.py: replaced truncation with abort-on-overflow using a MAX_BUFFER_SIZE class constant (64KB). When incoming data would overflow, we log a warning and drop only the new data via early return, keeping existing partial-packet state intact so a valid in-flight AT command can still parse.

Happy to adjust naming or buffer size if preferred.

@ibondarenko1
Copy link
Copy Markdown
Contributor Author

@googlebot I signed it!

@ibondarenko1
Copy link
Copy Markdown
Contributor Author

Gentle ping @zxzxwu — review fixes are in commit 444f43f, CLA is green, and checks pass. Let me know if any further changes are needed before merge. Thanks!

@ibondarenko1 ibondarenko1 requested a review from zxzxwu April 17, 2026 22:39
@zxzxwu zxzxwu merged commit bf0784d into google:main Apr 20, 2026
55 checks passed
zxzxwu pushed a commit that referenced this pull request Apr 26, 2026
DataElement.from_bytes -> list_from_bytes -> (SEQUENCE/ALTERNATIVE
constructor dispatches back to list_from_bytes) had no depth limit. A
malicious SDP peer could send a PDU of a few kilobytes containing ~1000
nested SEQUENCE tags and exhaust the Python recursion stack, crashing the
host with an unhandled RecursionError propagating out of the SDP handler.

Reachable via: any remote Bluetooth device that Bumble performs SDP
service discovery against (default during Classic connection setup).

Same family as PR #912 (ATT_PDU.from_bytes empty PDU IndexError) - remote
unchecked-input parser crash in the Bluetooth stack.

Fix: thread-local depth counter, cap nesting at 32 (well above anything a
legitimate service record uses). Added two regression tests covering the
deep-nesting reject path and normal 16-level-nested SEQUENCE parsing.

Reproducer (4.5 KB payload, deterministic crash on 0.0.228):

    from bumble.sdp import DataElement
    inner = b"\x35\x00"
    for _ in range(1500):
        size = len(inner)
        if size < 65535:
            inner = bytes([0x36, (size >> 8) & 0xFF, size & 0xFF]) + inner
    DataElement.from_bytes(inner)  # RecursionError before fix

Signed-off-by: ibondarenko1 <ibondarenko1@users.noreply.github.com>
zxzxwu pushed a commit that referenced this pull request Apr 27, 2026
A remote peer can send an AVDTP frame shorter than the assembler expects.
The current MessageAssembler.on_pdu() unconditionally accesses pdu[0],
pdu[1], and (for START packets) pdu[2], so a 0-, 1-, or 2-byte frame
raises IndexError. The exception propagates up through L2CAP's read loop
and tears down the channel — same DoS class as #912 (empty ATT PDU) and
#914 (unbounded SDP recursion).

Fix: validate length before each access. Empty PDUs and packets shorter
than the type-specific minimum are logged and dropped; the assembler
stays alive so the L2CAP channel is not torn down.

- bumble/avdtp.py: length guards in MessageAssembler.on_pdu before
  accessing pdu[0], pdu[1], pdu[2].
- tests/avdtp_test.py: regression test covering empty PDU, 1-byte SINGLE,
  1-byte START, 2-byte START — all four would have raised IndexError
  pre-fix; assembler now drops without raising.
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 this pull request may close these issues.

3 participants