Skip to content

fix(sync): reject out-of-range GCS parameters instead of decoding garbage - #894

Open
Chessing234 wants to merge 2 commits into
permissionlesstech:mainfrom
Chessing234:gcs-param-guard
Open

fix(sync): reject out-of-range GCS parameters instead of decoding garbage#894
Chessing234 wants to merge 2 commits into
permissionlesstech:mainfrom
Chessing234:gcs-param-guard

Conversation

@Chessing234

Copy link
Copy Markdown

GCSFilter.decodeToSortedSet takes p and m straight off the wire — a REQUEST_SYNC carries P as a uint8, so anything from 0 to 255 arrives — and decodes with them regardless.

  • Kotlin's shift operators use the low 6 bits of the shift count, so p >= 64 wraps and the decoder emits values that were never in the filter;
  • p = 0 makes every remainder a zero-width read, so the decoder manufactures a value per bit. On a 20-id filter that is 105 fabricated values.

The decoded set is what GossipSyncManager.handleRequestSync consults to decide whether a peer already has a packet, so fabricated values mean the responder withholds packets the peer actually lacks — the direction that loses messages.

iOS already guards this, with the reasoning spelled out in GCSFilter.swift:

// Reject out-of-range parameters rather than decoding garbage: callers
// treat the result as "peer has nothing" and fall back to sending data.
guard p >= 1, p <= maxP, m > 1 else { return [] }

This is the same guard, with the same bound (MAX_P = 32), so both platforms agree on what a filter means.

Verified locally with ./gradlew :app:testDebugUnitTest --tests "com.bitchat.android.sync.*":

  • before the guard, the new an out-of-range p decodes to nothing rather than to garbage fails with p=0 should decode to nothing, got 105 values;
  • after it, GCSFilterParameterTest is tests="3" failures="0" and the existing GCSFilterTest still passes tests="3" failures="0".

…bage

decodeToSortedSet takes p and m straight off the wire - REQUEST_SYNC
carries P as a uint8, so 0..255 all arrive - and decodes with them
regardless. Kotlin's shift operators use the low 6 bits of the count, so
p >= 64 wraps and the decoder emits values that were never in the filter;
p = 0 turns every remainder into a zero-width read and manufactures a
value per bit. On today's 20-id filter, p = 0 decodes 105 values.

Those values are read as "the peer already has this packet", so the
responder withholds packets the peer actually lacks - the failure
direction that loses messages.

Guard the parameters as iOS does in GCSFilter.decodeToSortedSet: p in
1..32 and m > 1, otherwise decode to nothing, which callers read as "peer
has nothing" and answer by sending the data.
Round trip with the built parameters, out-of-range p (0, 33, 64, 200,
255) decoding to nothing, and degenerate m (0, 1) decoding to nothing.
The p case fails without the guard: p = 0 yields 105 fabricated values.
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.

1 participant