{"record":{"id":"fa7155f5f78bc262","repo":"apache/cassandra","slug":"a-local-expiration-time-should-not-be-negative","errorCode":null,"errorMessage":"A local expiration time should not be negative","messagePattern":"A local expiration time should not be negative","errorType":"validation","errorClass":"MarshalException","httpStatus":null,"severity":"error","filePath":"src/java/org/apache/cassandra/db/LivenessInfo.java","lineNumber":336,"sourceCode":"        @Override\n        public void digest(Digest digest)\n        {\n            super.digest(digest);\n\n            // As of 5.0, local expiration times are encoded as unsigned integers on disk, so we can do the\n            // same thing here to populate the digest. This supports extended TTLs, but also maintains digest\n            // compatibility with previous versions, avoiding false digest mismatches during upgrades.\n            digest.updateWithInt(Cell.deletionTimeLongToUnsignedInteger(localExpirationTime));\n            digest.updateWithInt(ttl);\n        }\n\n        @Override\n        public void validate()\n        {\n            if (ttl < 0)\n                throw new MarshalException(\"A TTL should not be negative\");\n            if (localExpirationTime < 0)\n                throw new MarshalException(\"A local expiration time should not be negative\");\n        }\n\n        @Override\n        public int dataSize()\n        {\n            return super.dataSize()\n                 + TypeSizes.sizeof(ttl)\n                 + TypeSizes.sizeof(localExpirationTime);\n\n        }\n\n        @Override\n        public LivenessInfo withUpdatedTimestamp(long newTimestamp)\n        {\n            return new ExpiringLivenessInfo(newTimestamp, ttl, localExpirationTime);\n        }\n\n        @Override","sourceCodeStart":318,"sourceCodeEnd":354,"githubUrl":"https://github.com/apache/cassandra/blob/88fd0f6a0eaed8943f05ac9e8f947882b8ddc8f1/src/java/org/apache/cassandra/db/LivenessInfo.java#L318-L354","documentation":"Same validator as the TTL check: ExpiringLivenessInfo.validate() rejects a negative localExpirationTime, the wall-clock expiration timestamp computed as now + TTL. A negative value is impossible for valid expiration data, so MarshalException is thrown to keep corrupt values out of the storage engine.","triggerScenarios":"Deserializing a cell whose localExpirationTime field is negative (corrupt SSTable, bad internode message); constructing ExpiringLivenessInfo directly with a negative expiration; extreme clock manipulation producing negative epoch values.","commonSituations":"Disk corruption or failed compaction leftovers; hand-rolled serialization in tools/tests; system clock set before epoch in exotic environments.","solutions":["Run `nodetool scrub` on affected tables if negative values come from stored data.","Fix any code that constructs expiration times to compute now + ttl with non-negative inputs.","Verify system clocks (NTP) on nodes writing expiration data."],"exampleFix":"// before\nExpiringLivenessInfo exp = new ExpiringLivenessInfo(ttl, localExpirationTime, nowInSeconds); // localExpirationTime < 0 possible\n// after\nif (localExpirationTime < 0) throw new IllegalArgumentException(\"invalid expiration\");\nExpiringLivenessInfo exp = new ExpiringLivenessInfo(ttl, localExpirationTime, nowInSeconds);","handlingStrategy":"validation","validationCode":"if (localExpirationTime < 0) throw new IllegalArgumentException(\"localExpirationTime must be >= 0\");","typeGuard":"long safeExpiration(long ttl, long now) { return Math.max(0, now + Math.max(0, ttl)); }","tryCatchPattern":"try { readOrWrite(); } catch (MarshalException e) { if (e.getMessage().contains(\"expiration\")) { triggerScrubAndAlert(); } else throw e; }","preventionTips":["Keep node clocks NTP-synchronized.","Never hand-serialize expiration data; use the storage-engine APIs.","Scrub SSTables after suspected corruption before replaying data."],"tags":["expiration","marshal","validation","value-out-of-range"],"backgroundTag":"value-out-of-range","analyzedSha":"88fd0f6a0eaed8943f05ac9e8f947882b8ddc8f1","analyzedAt":"2026-09-10T07:29:22.284Z","contentChangedAt":"2026-09-10T07:29:22.284Z","schemaVersion":2},"datasetVersion":"2026-09-14T16:17:12.679Z"}