cockroachdb / cockroachdb/cockroach
changefeedccl/avro: VARBIT memo reuse panic when bit-array length grows between rows
- Dominant language
- Go
- Stars
- 32.5k
- Forks
- 4.1k
- PR merge metrics
- PR metrics pending
Description
## Summary
The `BitFamily` encoder in `typeToSchema` (`pkg/ccl/changefeedccl/avro/avro.go:346-364`) panics with an index-out-of-range error when encoding consecutive VARBIT values where the second value requires more 64-bit words than the first. The memo-reuse logic at line 353 only handles truncation (memo longer than needed) but not growth (memo shorter than needed), causing a direct index write past the end of the reused slice. This crashes the changefeed job.
## Affected code
- `pkg/ccl/changefeedccl/avro/avro.go:353` -- Guard condition `len(signedLongs) > len(uints)+1` should also handle the `<` case
- `pkg/ccl/changefeedccl/avro/avro.go:362` -- Panic site: `signedLongs[idx+1] = int64(word)` with `idx+1 >= len(signedLongs)`
Note: Fixed-width `BIT(N)` columns are unaffected since all values produce the same number of encoding words. Only `VARBIT` columns trigger this bug.
## Reproduction
```sql
DROP TABLE IF EXISTS varbit_repro;
CREATE TABLE varbit_repro (
id INT PRIMARY KEY,
bits VARBIT
);
-- Row 1: short bit array (2 bits = 1 uint64 word, signedLongs length 2)
INSERT INTO varbit_repro VALUES (1, B'10');
-- Row 2: long bit array (70 bits = 2 uint64 words, signedLongs length 3)
INSERT INTO varbit_repro VALUES (2, B'1010101010101010101010101010101010101010101010101010101010101010101010');
-- Create a changefeed with Avro format to trigger the panic:
CREATE CHANGEFEED FOR varbit_repro
INTO 'null://'
WITH format = avro, confluent_schema_registry = 'http://localhost:8081';
-- Expected: changefeed processes both rows successfully
-- Actual: changefeed panics with "runtime error: index out of range [2] with length 2"
-- when encoding the second row's VARBIT value
DROP TABLE varbit_repro;
```
## Suggested fix direction
Replace the guard at line 353 to handle both truncation and growth:
```go
if memo != nil {
signedLongs = memo.([]interface{})
if len(signedLongs) >= len(uints)+1 {
signedLongs = signedLongs[:len(uints)+1]
} else {
signedLongs = make([]interface{}, len(uints)+1)
}
}
```
This mirrors the safe pattern used by the `ArrayFamily` encoder (line 735-786), which uses append-based growth and `i < len(avroArr)` guards to handle memo size differences correctly.
_This issue was found via automated deep static analysis._
Jira issue: CRDB-62038
Contributor guide
Assessment
This issue has not been assessed yet.