Skip to content

CASSANDRA-21549: Fix deserialization of column masks in cluster metadata - #4997

Open
SEPURI-SAI-KRISHNA wants to merge 1 commit into
apache:cassandra-6.0from
SEPURI-SAI-KRISHNA:sai-krishna/CASSANDRA-21549/cassandra-6.0
Open

CASSANDRA-21549: Fix deserialization of column masks in cluster metadata#4997
SEPURI-SAI-KRISHNA wants to merge 1 commit into
apache:cassandra-6.0from
SEPURI-SAI-KRISHNA:sai-krishna/CASSANDRA-21549/cassandra-6.0

Conversation

@SEPURI-SAI-KRISHNA

Copy link
Copy Markdown

CASSANDRA-21549

Problem

ColumnMask.Serializer.deserialize has two independent defects:

  1. new ArrayList<>(numArgs + 1) sets the capacity, not the size, so the list is empty and
    argTypes.set(0, columnType) throws IndexOutOfBoundsException. This fires for every masked
    column, including MASKED WITH DEFAULT.
  2. The ternary reading each partial argument value is inverted
    (valuePresent ? null : read(..)). Besides losing the value, this desynchronises the input
    stream and corrupts every field deserialized afterwards.

Impact

ColumnMask.serializer is reached through
ColumnMetadata → 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:

java.lang.IndexOutOfBoundsException: Index 0 out of bounds for length 0
	at java.base/java.util.ArrayList.set(ArrayList.java:470)
	at org.apache.cassandra.cql3.functions.masking.ColumnMask$Serializer.deserialize(ColumnMask.java:318)
	at org.apache.cassandra.schema.ColumnMetadata$Serializer.deserialize(ColumnMetadata.java:819)
	...
	at org.apache.cassandra.tcm.MetadataSnapshots$SystemKeyspaceMetadataSnapshots.getLatestSnapshot(MetadataSnapshots.java:131)
	at org.apache.cassandra.tcm.log.SystemKeyspaceStorage.getPersistedLogState(SystemKeyspaceStorage.java:133)
	at org.apache.cassandra.tcm.log.LocalLog.replayPersisted(LocalLog.java:584)
	at org.apache.cassandra.tcm.Startup.initializeAsNonCmsNode(Startup.java:185)
	at org.apache.cassandra.tcm.Startup.initialize(Startup.java:120)

Note that MetadataSnapshots.getSnapshot only catches IOException, so this unchecked exception
propagates rather than degrading to a null snapshot.

Reproduction

CREATE TABLE ks.t (k int PRIMARY KEY, v text MASKED WITH mask_inner(2, 1));
nodetool cms snapshot
# restart the node -> startup fails with the stack above

Tests

Existing DDM tests miss this because none of them force a metadata snapshot.

  • SchemaMetadataSerializationTest — three round-trip cases: a masking function with no partial
    arguments, with partial arguments, and with a null partial argument.
  • ColumnMaskMetadataSnapshotTest (new dtest) — snapshot read-back, and node restart after a
    snapshot.

Verified on cassandra-6.0, unpatched vs patched:

Suite Patched Unpatched
SchemaMetadataSerializationTest 11/11 pass 3 errors
ColumnMaskMetadataSnapshotTest 2/2 pass 2 errors
ColumnMaskTest (unit) 17/17 pass
ColumnMaskTest (distributed) 2/2 pass
ClusterMetadataSerializerTest 2/2 pass

Isolating defect 1 alone makes the same tests fail with
EOFException: EOF after 43 bytes out of 1024, confirming defect 2 independently.

ant jar, ant checkstyle and ant checkstyle-test are clean.

Branch

Based on cassandra-6.0. Not applicable to cassandra-5.0, which has no ColumnMask.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.

patch by Sepuri Sai Krishna; reviewed by TBD for CASSANDRA-21549

Assisted-by: Claude Code:claude-opus-5
@SEPURI-SAI-KRISHNA
SEPURI-SAI-KRISHNA force-pushed the sai-krishna/CASSANDRA-21549/cassandra-6.0 branch from f0e2e63 to 364ec72 Compare August 7, 2026 15:00
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