Skip to content

Check attribute read and write permissions in the GATT server - #998

Open
deadcaf3 wants to merge 2 commits into
google:mainfrom
deadcaf3:worktree-fix-gatt-perm-check
Open

deadcaf3 wants to merge 2 commits into
google:mainfrom
deadcaf3:worktree-fix-gatt-perm-check

Conversation

@deadcaf3

@deadcaf3 deadcaf3 commented Oct 4, 2026

Copy link
Copy Markdown
Contributor

Summary

The GATT server never returned Read Not Permitted or Write Not Permitted. A peer
could write an attribute that has no write permission and read one that has no
read permission. Only the encryption, authentication and authorization
requirements were enforced. This resolves the three # TODO: check permissions
comments in gatt_server.py.

Behavior

Request Attribute lacks the permission
Read, Read Blob, Read By Type, Read Multiple, Read Multiple Variable Read Not Permitted
Find By Type Value attribute is skipped
Write, Prepare Write Write Not Permitted
Write Command ignored

Notes for review

  • The check is in the server's request handlers rather than in
    Attribute.read_value, because notifications and indications read the value
    through Attribute.read_value, and characteristics such as the Heart Rate
    Measurement are notify-only with no permissions.
  • A *_REQUIRES_* permission implies the access it protects. Several profiles
    (MCP, AICS, CSIP, VOCS, HAP, VCS) declare for example
    READ_REQUIRES_ENCRYPTION without READABLE, and they keep working.
  • Read Multiple and Read Multiple Variable now return an error response when
    reading an attribute fails. Before, the error was raised inside the handler
    task and no response was sent, for example for an attribute that requires
    encryption on an unencrypted link.
  • tests/profiles/heart_rate_service_test.py was reading the Heart Rate
    Measurement, which is notify-only. It now subscribes and checks a
    notification.

Compatibility

Servers that declare a property without the matching permission (for example
WRITE with only READABLE) will now have those requests rejected. There is no
such declaration in bumble, apps or examples.

Testing

invoke project.pre-commit passes. Three tests added in tests/gatt_test.py.

The server never returned Read Not Permitted or Write Not Permitted: an
attribute could be written without any write permission, and read without
any read permission. Only the encryption, authentication and authorization
requirements were enforced.

Requests from a peer are now checked before the attribute is accessed:
- Read, Read Blob, Read By Type, Read Multiple and Read Multiple Variable
  return Read Not Permitted.
- Find By Type Value skips attributes that cannot be read.
- Write and Prepare Write return Write Not Permitted.
- Write Command is ignored.

A security requirement implies the access that it protects, so that
permissions like READ_REQUIRES_ENCRYPTION alone keep working. Notifications
and indications are not affected, since the value is then read locally.

Read Multiple and Read Multiple Variable now also return an error response
when reading an attribute fails, instead of not responding.

The heart rate test was reading the measurement characteristic, which can
only be notified. It now checks a notification.
zxzxwu
zxzxwu previously approved these changes Oct 5, 2026

@zxzxwu zxzxwu left a comment

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.

LGTM, thanks!

@zxzxwu
zxzxwu self-requested a review October 5, 2026 08:43
@zxzxwu
zxzxwu dismissed their stale review October 5, 2026 09:12

Holding off on approval to discuss backward compatibility for quirk testing.

@zxzxwu zxzxwu left a comment

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.

One concern here: since Bumble is widely used as a testing tool to simulate device quirks and non-standard behaviors, some users/tests may intentionally (or inadvertently, like heart_rate_service_test.py) read/write attributes without matching permission bits set, or rely on CharacteristicValue(read=..., write=...) callbacks to return custom ATT_Error codes.

Enforcing this unconditionally by default might break existing quirk tests. Perhaps we could either make strict permission checking configurable/optional on the GATT server, or discuss how to preserve flexibility for testing?

Bumble is also used to simulate devices that do not follow the
specification, and existing tests may read or write attributes that do not
have the matching permission, or rely on a value callback to return its
own error code.

Server.strict_permissions, True by default, controls whether READABLE and
WRITEABLE are required for reads and writes from a peer. When it is False,
those checks are skipped, as before, and the value callbacks are always
reached.
@deadcaf3

deadcaf3 commented Oct 5, 2026

Copy link
Copy Markdown
Contributor Author

Makes sense, 6ea8ae7 adds Server.strict_permissions (default True). As perms aren't sent ota, a test ca simulate any behaviour by setting the bit sand returning whatever error it wants from the cb. In tree, heart rate test relied on old behaviour only. On by default so server follows the spec out the box, happy to flip to false if you'd rather keep old behaviour unless opted in.

This branch has not been deployed

No deployments
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.

2 participants