Skip to content

Commit 8524b83

Browse files
committed
batcheval: prevent excising non-user keys
This commit performs some checks in the Excise command evaluation. It prevents the command to run against non-user keys. Excising non-global keys is not allowed as it would leave the replica in a bad state. For example, we don't want to allow excising a range descriptor. References: #143135 Release note: None
1 parent d2acf29 commit 8524b83

File tree

3 files changed

+123
-3
lines changed

3 files changed

+123
-3
lines changed

pkg/kv/kvserver/batcheval/BUILD.bazel

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,7 @@ go_test(
112112
"cmd_delete_range_gchint_test.go",
113113
"cmd_delete_range_test.go",
114114
"cmd_end_transaction_test.go",
115+
"cmd_excise_test.go",
115116
"cmd_export_test.go",
116117
"cmd_get_test.go",
117118
"cmd_is_span_empty_test.go",

pkg/kv/kvserver/batcheval/cmd_excise.go

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import (
1414
"github.com/cockroachdb/cockroach/pkg/kv/kvserver/kvserverpb"
1515
"github.com/cockroachdb/cockroach/pkg/roachpb"
1616
"github.com/cockroachdb/cockroach/pkg/storage"
17+
"github.com/cockroachdb/errors"
1718
)
1819

1920
func init() {
@@ -22,10 +23,33 @@ func init() {
2223

2324
// EvalExcise evaluates a Excise command.
2425
func EvalExcise(
25-
_ context.Context, _ storage.ReadWriter, cArgs CommandArgs, _ kvpb.Response,
26+
_ context.Context, _ storage.ReadWriter, cArgs CommandArgs, resp kvpb.Response,
2627
) (result.Result, error) {
2728
args := cArgs.Args.(*kvpb.ExciseRequest)
28-
start, end := storage.MVCCKey{Key: args.Key}, storage.MVCCKey{Key: args.EndKey}
29+
start, end := args.Key, args.EndKey
30+
31+
// Verify that the start and end keys are for global key space. Excising
32+
// non-global keys is not allowed as it would leave the replica in a bad
33+
// state. For example, we don't want to allow excising a range descriptor.
34+
rStart, err := keys.Addr(start)
35+
if err != nil {
36+
return result.Result{}, err
37+
}
38+
39+
rEnd, err := keys.Addr(end)
40+
if err != nil {
41+
return result.Result{}, err
42+
}
43+
44+
if !start.Equal(rStart.AsRawKey()) {
45+
return result.Result{},
46+
errors.Errorf("excise can only be run against global keys, but found start key: %s", start)
47+
}
48+
49+
if !end.Equal(rEnd.AsRawKey()) {
50+
return result.Result{},
51+
errors.Errorf("excise can only be run against global keys, but found end key: %s", end)
52+
}
2953

3054
// Since we can't know the exact range stats, mark it as an estimate.
3155
cArgs.Stats.ContainsEstimates++
@@ -36,7 +60,7 @@ func EvalExcise(
3660
return result.Result{
3761
Replicated: kvserverpb.ReplicatedEvalResult{
3862
Excise: &kvserverpb.ReplicatedEvalResult_Excise{
39-
Span: roachpb.Span{Key: start.Key, EndKey: end.Key},
63+
Span: roachpb.Span{Key: start, EndKey: end},
4064
LockTableSpan: roachpb.Span{Key: ltStart, EndKey: ltEnd},
4165
},
4266
},
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
// Copyright 2025 The Cockroach Authors.
2+
//
3+
// Use of this software is governed by the CockroachDB Software License
4+
// included in the /LICENSE file.
5+
6+
package batcheval_test
7+
8+
import (
9+
"context"
10+
"testing"
11+
12+
"github.com/cockroachdb/cockroach/pkg/keys"
13+
"github.com/cockroachdb/cockroach/pkg/kv/kvpb"
14+
"github.com/cockroachdb/cockroach/pkg/kv/kvserver/batcheval"
15+
"github.com/cockroachdb/cockroach/pkg/roachpb"
16+
"github.com/cockroachdb/cockroach/pkg/storage/enginepb"
17+
"github.com/cockroachdb/cockroach/pkg/util/hlc"
18+
"github.com/cockroachdb/cockroach/pkg/util/leaktest"
19+
"github.com/cockroachdb/cockroach/pkg/util/log"
20+
"github.com/cockroachdb/cockroach/pkg/util/timeutil"
21+
"github.com/stretchr/testify/require"
22+
)
23+
24+
// TestExciseEval tests basic Excise evaluation.
25+
func TestExciseEval(t *testing.T) {
26+
defer leaktest.AfterTest(t)()
27+
defer log.Scope(t).Close(t)
28+
29+
ctx := context.Background()
30+
clock := hlc.NewClockForTesting(timeutil.NewManualTime(timeutil.Now()))
31+
evalCtx := (&batcheval.MockEvalCtx{Clock: clock}).EvalContext()
32+
33+
testCases := []struct {
34+
name string
35+
startKey roachpb.Key
36+
endKey roachpb.Key
37+
expectErr string
38+
}{
39+
{
40+
// Valid range.
41+
startKey: roachpb.Key("a"),
42+
endKey: roachpb.Key("z"),
43+
expectErr: "",
44+
},
45+
{
46+
// Only the start key is a non-user key.
47+
startKey: keys.RangeDescriptorKey(roachpb.RKey("a")),
48+
endKey: roachpb.Key("z"),
49+
expectErr: "excise can only be run against global keys",
50+
},
51+
{
52+
// Only the end key is a non-user key.
53+
startKey: roachpb.Key("a"),
54+
endKey: keys.RangeDescriptorKey(roachpb.RKey("z")),
55+
expectErr: "excise can only be run against global keys",
56+
},
57+
{
58+
// Both keys are non-user keys.
59+
startKey: keys.RangeDescriptorKey(roachpb.RKey("a")),
60+
endKey: keys.RangeDescriptorKey(roachpb.RKey("z")),
61+
expectErr: "excise can only be run against global keys",
62+
},
63+
}
64+
65+
for _, tc := range testCases {
66+
resp := &kvpb.ExciseResponse{}
67+
res, err := batcheval.EvalExcise(ctx, nil, batcheval.CommandArgs{
68+
EvalCtx: evalCtx,
69+
Stats: &enginepb.MVCCStats{},
70+
Args: &kvpb.ExciseRequest{
71+
RequestHeader: kvpb.RequestHeader{
72+
Key: tc.startKey,
73+
EndKey: tc.endKey,
74+
},
75+
},
76+
}, resp)
77+
78+
// If there are no errors, we expect the result to be populated.
79+
if tc.expectErr == "" {
80+
require.NoError(t, err)
81+
82+
userSpan := roachpb.Span{Key: tc.startKey, EndKey: tc.endKey}
83+
ltStart, _ := keys.LockTableSingleKey(tc.startKey, nil)
84+
ltEnd, _ := keys.LockTableSingleKey(tc.endKey, nil)
85+
LockTableSpan := roachpb.Span{Key: ltStart, EndKey: ltEnd}
86+
87+
require.NotNil(t, res.Replicated.Excise)
88+
require.Equal(t, userSpan, res.Replicated.Excise.Span)
89+
require.Equal(t, LockTableSpan, res.Replicated.Excise.LockTableSpan)
90+
} else {
91+
require.Regexp(t, tc.expectErr, err)
92+
require.Nil(t, res.Replicated.Excise)
93+
}
94+
}
95+
}

0 commit comments

Comments
 (0)