Harden raw KV deserialization#8102
Conversation
Validate fixed-width, size-prefixed, vector, and public-domain lengths before advancing pointers, allocating, or copying. Add regressions for truncated and invalid input. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: bc1a467e-7d08-4580-886e-64af3a0e514c
There was a problem hiding this comment.
Pull request overview
This PR hardens CCF’s raw KV deserialisation path (used by RawKvStoreDeserialiser) by adding strict bounds checks before cursor advancement and by rejecting malformed size-prefixed/vector/array reads and invalid public-domain slices, with regression tests covering truncated and inconsistent inputs.
Changes:
- Add
RawReaderinvariants and pre-read bounds checks (fixed-size, size-prefixed, arrays/vectors), plus invalid-argument rejection for null buffers with non-zero sizes. - Validate
public_domain_lengthagainst remaining entry size before initialising the public-domain reader. - Add focused
kv_testcases for truncated fixed-width reads, inconsistent size prefixes, non-divisible vector payloads, and malformed public-domain payloads (including truncated claims digests).
Custom instructions used:
.github/copilot-instructions.md.github/instructions/reviewing.instructions.md
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated no comments.
| File | Description |
|---|---|
| src/kv/test/kv_serialisation.cpp | Adds regression tests for truncated/malformed raw reads and invalid public-domain entry layouts. |
| src/kv/raw_serialise.h | Hardens RawReader with cursor/size invariants, explicit bounds checks, and safer vector/array reads. |
| src/kv/generic_serialise_wrapper.h | Rejects public-domain lengths that exceed remaining entry bytes before advancing/initialising the reader. |
DescriptionComparing 2 available runs from this branch (#8102) against the trend of the last 30 Each chart plots every benchmark as an axis, with values normalized so 100 is the EWMA baseline of recent Axis labels show the latest branch value and its difference from the main EWMA baseline, where 0% is on the baseline. They are coloured green where the latest run improves on the baseline, red where it regresses, and grey where the difference is within one std dev of the baseline (within noise). Higher is better for throughput and rate, lower for latency and memory. Throughput (tx/s)---
config:
radar:
width: 620
height: 620
marginTop: 90
marginRight: 220
marginBottom: 60
marginLeft: 220
axisLabelFactor: 1.12
curveTension: 0.08
theme: base
themeCSS: |
.radarCurve-0{fill:color-mix(in srgb, #62B5E5 13%, var(--color-canvas-default,var(--bgColor-default,#fff)))!important;fill-opacity:1!important;stroke:none!important;stroke-width:0!important}
.radarCurve-1{fill:color-mix(in srgb, #62B5E5 40%, var(--color-canvas-default,var(--bgColor-default,#fff)))!important;fill-opacity:1!important;stroke:none!important;stroke-width:0!important}
.radarCurve-2{fill:color-mix(in srgb, #62B5E5 13%, var(--color-canvas-default,var(--bgColor-default,#fff)))!important;fill-opacity:1!important;stroke:none!important;stroke-width:0!important}
.radarCurve-3{fill:var(--color-canvas-default,var(--bgColor-default,#fff))!important;fill-opacity:1!important;stroke:none!important;stroke-width:0!important}
.radarAxisLabel,.radarTitle{fill:var(--color-fg-default,var(--fgColor-default,#111827))!important;color:var(--color-fg-default,var(--fgColor-default,#111827))!important}
.radarCurve-4{stroke-width:1.5px!important;stroke-opacity:0.50!important}
.radarCurve-5{stroke-width:1.75px!important;stroke-opacity:1.00!important}
.radarAxisLabel:nth-of-type(1){fill:#808A94!important}
.radarAxisLabel:nth-of-type(2){fill:#808A94!important}
.radarAxisLabel:nth-of-type(3){fill:#808A94!important}
.radarAxisLabel:nth-of-type(4){fill:#808A94!important}
.radarAxisLabel:nth-of-type(5){fill:#808A94!important}
.radarAxisLabel:nth-of-type(6){fill:#808A94!important}
.radarAxisLabel:nth-of-type(7){fill:#808A94!important}
themeVariables:
cScale0: "#62B5E5"
cScale1: "#62B5E5"
cScale2: "#62B5E5"
cScale3: "#62B5E5"
cScale4: "#F97316"
cScale5: "#F97316"
radar:
axisColor: "#9CA3AF"
graticuleColor: "#E5E7EB"
graticuleOpacity: 0
axisStrokeWidth: 1
curveOpacity: 0
---
radar-beta
axis b0["Basic: 67,671 tx/s ▬ +4%"]
axis b1["Basic Blocking: 1,019 tx/s ▬ +2%"]
axis b2["Basic JS: 4,758 tx/s ▬ +3%"]
axis b3["Basic Multi-Threaded: 86,789 tx/s ▬ +4%"]
axis b4["Historical Queries: 207,968 tx/s ▬ +5%"]
axis b5["Logging: 63,038 tx/s ▬ +4%"]
axis b6["Logging JWT: 9,661 tx/s ▬ -2%"]
curve stddev2_high["main EWMA + 2 std dev"]{116.88, 106.01, 116.04, 116.03, 125.20, 117.42, 117.69}
curve stddev1_high["main EWMA + 1 std dev"]{108.44, 103.01, 108.02, 108.01, 112.60, 108.71, 108.84}
curve stddev1_low["main EWMA - 1 std dev"]{91.56, 96.99, 91.98, 91.99, 87.40, 91.29, 91.16}
curve stddev2_low["main EWMA - 2 std dev"]{83.12, 93.99, 83.96, 83.97, 74.80, 82.58, 82.31}
curve branch_0["#8102 (1 run earlier)"]{104.45, 101.81, 101.15, 104.33, 108.76, 103.96, 104.70}
curve branch_1["#8102"]{103.94, 101.70, 102.69, 103.52, 104.58, 103.95, 98.05}
graticule polygon
max 143
min 57
ticks 0
showLegend false
Latency (ms)---
config:
radar:
width: 620
height: 620
marginTop: 90
marginRight: 220
marginBottom: 60
marginLeft: 220
axisLabelFactor: 1.12
curveTension: 0.08
theme: base
themeCSS: |
.radarCurve-0{fill:color-mix(in srgb, #62B5E5 13%, var(--color-canvas-default,var(--bgColor-default,#fff)))!important;fill-opacity:1!important;stroke:none!important;stroke-width:0!important}
.radarCurve-1{fill:color-mix(in srgb, #62B5E5 40%, var(--color-canvas-default,var(--bgColor-default,#fff)))!important;fill-opacity:1!important;stroke:none!important;stroke-width:0!important}
.radarCurve-2{fill:color-mix(in srgb, #62B5E5 13%, var(--color-canvas-default,var(--bgColor-default,#fff)))!important;fill-opacity:1!important;stroke:none!important;stroke-width:0!important}
.radarCurve-3{fill:var(--color-canvas-default,var(--bgColor-default,#fff))!important;fill-opacity:1!important;stroke:none!important;stroke-width:0!important}
.radarAxisLabel,.radarTitle{fill:var(--color-fg-default,var(--fgColor-default,#111827))!important;color:var(--color-fg-default,var(--fgColor-default,#111827))!important}
.radarCurve-4{stroke-width:1.5px!important;stroke-opacity:0.50!important}
.radarCurve-5{stroke-width:1.75px!important;stroke-opacity:1.00!important}
.radarAxisLabel:nth-of-type(1){fill:#808A94!important}
.radarAxisLabel:nth-of-type(2){fill:#808A94!important}
.radarAxisLabel:nth-of-type(3){fill:#808A94!important}
themeVariables:
cScale0: "#62B5E5"
cScale1: "#62B5E5"
cScale2: "#62B5E5"
cScale3: "#62B5E5"
cScale4: "#F97316"
cScale5: "#F97316"
radar:
axisColor: "#9CA3AF"
graticuleColor: "#E5E7EB"
graticuleOpacity: 0
axisStrokeWidth: 1
curveOpacity: 0
---
radar-beta
axis b0["Commit Latency 16ms: 4.34 ms ▬ -19%"]
axis b1["Commit Latency 1ms: 1.83 ms ▬ +1%"]
axis b2["Commit Latency 256ms: 208 ms ▬ 0%"]
curve stddev2_high["main EWMA + 2 std dev"]{139.15, 107.78, 101.45}
curve stddev1_high["main EWMA + 1 std dev"]{119.58, 103.89, 100.72}
curve stddev1_low["main EWMA - 1 std dev"]{80.42, 96.11, 99.28}
curve stddev2_low["main EWMA - 2 std dev"]{60.85, 92.22, 98.55}
curve branch_0["#8102 (1 run earlier)"]{92.44, 100.55, 99.67}
curve branch_1["#8102"]{80.61, 101.26, 99.94}
graticule polygon
max 167
min 33
ticks 0
showLegend false
Memory (bytes)---
config:
radar:
width: 620
height: 620
marginTop: 90
marginRight: 220
marginBottom: 60
marginLeft: 220
axisLabelFactor: 1.12
curveTension: 0.08
theme: base
themeCSS: |
.radarCurve-0{fill:color-mix(in srgb, #62B5E5 13%, var(--color-canvas-default,var(--bgColor-default,#fff)))!important;fill-opacity:1!important;stroke:none!important;stroke-width:0!important}
.radarCurve-1{fill:color-mix(in srgb, #62B5E5 40%, var(--color-canvas-default,var(--bgColor-default,#fff)))!important;fill-opacity:1!important;stroke:none!important;stroke-width:0!important}
.radarCurve-2{fill:color-mix(in srgb, #62B5E5 13%, var(--color-canvas-default,var(--bgColor-default,#fff)))!important;fill-opacity:1!important;stroke:none!important;stroke-width:0!important}
.radarCurve-3{fill:var(--color-canvas-default,var(--bgColor-default,#fff))!important;fill-opacity:1!important;stroke:none!important;stroke-width:0!important}
.radarAxisLabel,.radarTitle{fill:var(--color-fg-default,var(--fgColor-default,#111827))!important;color:var(--color-fg-default,var(--fgColor-default,#111827))!important}
.radarCurve-4{stroke-width:1.5px!important;stroke-opacity:0.50!important}
.radarCurve-5{stroke-width:1.75px!important;stroke-opacity:1.00!important}
.radarAxisLabel:nth-of-type(1){fill:#808A94!important}
.radarAxisLabel:nth-of-type(2){fill:#E5484D!important}
.radarAxisLabel:nth-of-type(3){fill:#808A94!important}
.radarAxisLabel:nth-of-type(4){fill:#808A94!important}
.radarAxisLabel:nth-of-type(5){fill:#808A94!important}
.radarAxisLabel:nth-of-type(6){fill:#E5484D!important}
themeVariables:
cScale0: "#62B5E5"
cScale1: "#62B5E5"
cScale2: "#62B5E5"
cScale3: "#62B5E5"
cScale4: "#F97316"
cScale5: "#F97316"
radar:
axisColor: "#9CA3AF"
graticuleColor: "#E5E7EB"
graticuleOpacity: 0
axisStrokeWidth: 1
curveOpacity: 0
---
radar-beta
axis b0["Basic: 89.9 MiB ▬ +3%"]
axis b1["Basic Blocking: 72.2 MiB ▲ 1%"]
axis b2["Basic JS: 69.4 MiB ▬ -2%"]
axis b3["Basic Multi-Threaded: 88.4 MiB ▬ -1%"]
axis b4["Logging: 75.7 MiB ▬ 0%"]
axis b5["Logging JWT: 69.2 MiB ▲ 1%"]
curve stddev2_high["main EWMA + 2 std dev"]{106.07, 100.96, 104.08, 102.31, 102.26, 101.47}
curve stddev1_high["main EWMA + 1 std dev"]{103.03, 100.48, 102.04, 101.15, 101.13, 100.73}
curve stddev1_low["main EWMA - 1 std dev"]{96.97, 99.52, 97.96, 98.85, 98.87, 99.27}
curve stddev2_low["main EWMA - 2 std dev"]{93.93, 99.04, 95.92, 97.69, 97.74, 98.53}
curve branch_0["#8102 (1 run earlier)"]{94.14, 99.75, 101.70, 98.74, 100.94, 100.59}
curve branch_1["#8102"]{102.61, 101.14, 98.03, 99.03, 99.97, 101.15}
graticule polygon
max 111
min 89
ticks 0
showLegend false
Rate (ops/s)---
config:
radar:
width: 620
height: 620
marginTop: 90
marginRight: 220
marginBottom: 60
marginLeft: 220
axisLabelFactor: 1.12
curveTension: 0.08
theme: base
themeCSS: |
.radarCurve-0{fill:color-mix(in srgb, #62B5E5 13%, var(--color-canvas-default,var(--bgColor-default,#fff)))!important;fill-opacity:1!important;stroke:none!important;stroke-width:0!important}
.radarCurve-1{fill:color-mix(in srgb, #62B5E5 40%, var(--color-canvas-default,var(--bgColor-default,#fff)))!important;fill-opacity:1!important;stroke:none!important;stroke-width:0!important}
.radarCurve-2{fill:color-mix(in srgb, #62B5E5 13%, var(--color-canvas-default,var(--bgColor-default,#fff)))!important;fill-opacity:1!important;stroke:none!important;stroke-width:0!important}
.radarCurve-3{fill:var(--color-canvas-default,var(--bgColor-default,#fff))!important;fill-opacity:1!important;stroke:none!important;stroke-width:0!important}
.radarAxisLabel,.radarTitle{fill:var(--color-fg-default,var(--fgColor-default,#111827))!important;color:var(--color-fg-default,var(--fgColor-default,#111827))!important}
.radarCurve-4{stroke-width:1.5px!important;stroke-opacity:0.50!important}
.radarCurve-5{stroke-width:1.75px!important;stroke-opacity:1.00!important}
.radarAxisLabel:nth-of-type(1){fill:#808A94!important}
.radarAxisLabel:nth-of-type(2){fill:#808A94!important}
.radarAxisLabel:nth-of-type(3){fill:#E5484D!important}
.radarAxisLabel:nth-of-type(4){fill:#808A94!important}
.radarAxisLabel:nth-of-type(5){fill:#808A94!important}
.radarAxisLabel:nth-of-type(6){fill:#808A94!important}
themeVariables:
cScale0: "#62B5E5"
cScale1: "#62B5E5"
cScale2: "#62B5E5"
cScale3: "#62B5E5"
cScale4: "#F97316"
cScale5: "#F97316"
radar:
axisColor: "#9CA3AF"
graticuleColor: "#E5E7EB"
graticuleOpacity: 0
axisStrokeWidth: 1
curveOpacity: 0
---
radar-beta
axis b0["CHAMP get: 37,604,201 ops/s ▬ +1%"]
axis b1["CHAMP put: 5,558,632 ops/s ▬ +3%"]
axis b2["KV deserialisation: 1,432,460 ops/s ▼ 10%"]
axis b3["KV serialisation: 1,483,680 ops/s ▬ +3%"]
axis b4["KV snapshot deserialis...: 4,204 ops/s ▬ +3%"]
axis b5["KV snapshot serialisation: 4,458 ops/s ▬ 0%"]
curve stddev2_high["main EWMA + 2 std dev"]{115.06, 115.24, 115.08, 114.62, 114.84, 118.54}
curve stddev1_high["main EWMA + 1 std dev"]{107.53, 107.62, 107.54, 107.31, 107.42, 109.27}
curve stddev1_low["main EWMA - 1 std dev"]{92.47, 92.38, 92.46, 92.69, 92.58, 90.73}
curve stddev2_low["main EWMA - 2 std dev"]{84.94, 84.76, 84.92, 85.38, 85.16, 81.46}
curve branch_0["#8102 (1 run earlier)"]{101.49, 102.97, 92.38, 101.55, 103.68, 105.47}
curve branch_1["#8102"]{100.61, 102.89, 90.38, 103.21, 102.76, 100.03}
graticule polygon
max 132
min 68
ticks 0
showLegend false
|
Summary
Context
Extracted from #8092 so this generic parser hardening can be reviewed and landed independently. #8092 should be rebased onto this change: its recovery path parses untrusted snapshot and ledger bytes through
RawKvStoreDeserialiser, so it relies on these checks as a prerequisite.Validation
kv_test