CASSANDRA-21549: Fix deserialization of column masks in cluster metadata - #4997
Open
SEPURI-SAI-KRISHNA wants to merge 1 commit into
Open
Conversation
smiklosovic
reviewed
Aug 7, 2026
patch by Sepuri Sai Krishna; reviewed by TBD for CASSANDRA-21549 Assisted-by: Claude Code:claude-opus-5
SEPURI-SAI-KRISHNA
force-pushed
the
sai-krishna/CASSANDRA-21549/cassandra-6.0
branch
from
August 7, 2026 15:00
f0e2e63 to
364ec72
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
CASSANDRA-21549
Problem
ColumnMask.Serializer.deserializehas two independent defects:new ArrayList<>(numArgs + 1)sets the capacity, not the size, so the list is empty andargTypes.set(0, columnType)throwsIndexOutOfBoundsException. This fires for every maskedcolumn, including
MASKED WITH DEFAULT.(
valuePresent ? null : read(..)). Besides losing the value, this desynchronises the inputstream and corrupts every field deserialized afterwards.
Impact
ColumnMask.serializeris reached throughColumnMetadata → TableMetadata → Tables → KeyspaceMetadata → DistributedSchema → ClusterMetadata.Ordinary DDL never hits it, because schema changes propagate as transformations that each node
replays locally. It is reached once the schema is embedded in a serialized
ClusterMetadata, i.e.for cluster metadata snapshots, which are read back on startup and when a lagging peer catches up
from the CMS.
Restarting a node that has a masked column and a stored snapshot fails during startup:
Note that
MetadataSnapshots.getSnapshotonly catchesIOException, so this unchecked exceptionpropagates rather than degrading to a null snapshot.
Reproduction
Tests
Existing DDM tests miss this because none of them force a metadata snapshot.
SchemaMetadataSerializationTest— three round-trip cases: a masking function with no partialarguments, with partial arguments, and with a null partial argument.
ColumnMaskMetadataSnapshotTest(new dtest) — snapshot read-back, and node restart after asnapshot.
Verified on
cassandra-6.0, unpatched vs patched:SchemaMetadataSerializationTestColumnMaskMetadataSnapshotTestColumnMaskTest(unit)ColumnMaskTest(distributed)ClusterMetadataSerializerTestIsolating defect 1 alone makes the same tests fail with
EOFException: EOF after 43 bytes out of 1024, confirming defect 2 independently.ant jar,ant checkstyleandant checkstyle-testare clean.Branch
Based on
cassandra-6.0. Not applicable tocassandra-5.0, which has noColumnMask.Serializer—dynamic data masking shipped in 5.0, but the schema is only serialized into cluster metadata from
6.0 onwards. Needs merging forward to
trunk.Prepared with AI assistance (Claude Opus 5); the commit carries an
Assisted-by:trailer.