Skip to content

Commit a5db6e0

Browse files
committed
sqlliveness: detect and handle invalid SessionIDs
Previously, the code for checking if sessions are alive supported non-RBR-encoded session IDs. However, in version 24.1, we removed this support without adding proper handling for invalid IDs, potentially leading to finalization failures during upgrades (if stale session IDs existed). This patch adds logic to treat invalid session IDs, which will allow upgrades to occur if stale session IDs exist.s Fixes: #127061 Release note: None
1 parent 0963c2b commit a5db6e0

File tree

15 files changed

+138
-22
lines changed

15 files changed

+138
-22
lines changed

pkg/ccl/logictestccl/tests/3node-tenant/generated_test.go

Lines changed: 7 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pkg/ccl/logictestccl/tests/local-read-committed/generated_test.go

Lines changed: 7 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pkg/sql/catalog/lease/lease_internal_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1709,8 +1709,8 @@ func TestLeaseCountDetailSessionBased(t *testing.T) {
17091709
version := 1
17101710
region := enum.One
17111711
_, err := executor.Exec(ctx, "add-rows-for-test", nil,
1712-
fmt.Sprintf("INSERT INTO system.lease VALUES (%d, %d, %s, '%s', '\\x%x')",
1713-
descID, version, nodeID, session.ID(), region))
1712+
fmt.Sprintf("INSERT INTO system.lease VALUES (%d, %d, %s, '\\x%x', '\\x%x')",
1713+
descID, version, nodeID, session.ID().UnsafeBytes(), region))
17141714
if err != nil {
17151715
return err
17161716
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
# Validate that invalid sessionID's are always
2+
# considered dead.
3+
subtest invalid_sessions
4+
5+
# Legacy non-RBR format
6+
query B
7+
select crdb_internal.sql_liveness_is_alive(x'1f915e98f96145a5baa9f3a42c378eb6');
8+
----
9+
false
10+
11+
# Wrong length
12+
query B
13+
select crdb_internal.sql_liveness_is_alive(x'deadbeef');
14+
----
15+
false
16+
17+
subtest end
18+
19+
20+
subtest valid_sessions
21+
22+
# Sanity: All sessions are alive in sqlliveness.
23+
query I
24+
SELECT count(*) FROM system.sqlliveness WHERE crdb_internal.sql_liveness_is_alive(session_id) = false;
25+
----
26+
0
27+
28+
query B
29+
SELECT count(*) > 0 FROM system.sqlliveness WHERE crdb_internal.sql_liveness_is_alive(session_id) = true;
30+
----
31+
true
32+
33+
subtest end
34+
35+
36+

pkg/sql/logictest/tests/fakedist-disk/generated_test.go

Lines changed: 7 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pkg/sql/logictest/tests/fakedist-vec-off/generated_test.go

Lines changed: 7 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pkg/sql/logictest/tests/fakedist/generated_test.go

Lines changed: 7 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pkg/sql/logictest/tests/local-legacy-schema-changer/generated_test.go

Lines changed: 7 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pkg/sql/logictest/tests/local-mixed-23.2/generated_test.go

Lines changed: 7 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pkg/sql/logictest/tests/local-vec-off/generated_test.go

Lines changed: 7 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pkg/sql/logictest/tests/local/generated_test.go

Lines changed: 7 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pkg/sql/schemachanger/comparator_generated_test.go

Lines changed: 5 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pkg/sql/sqlliveness/slstorage/key_encoder.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import (
2424
type keyCodec interface {
2525
encode(sid sqlliveness.SessionID) (roachpb.Key, string, error)
2626
decode(key roachpb.Key) (sqlliveness.SessionID, error)
27+
validate(session sqlliveness.SessionID) error
2728

2829
// indexPrefix returns the prefix for an encoded key. encode() will return
2930
// something with the prefix and decode will expect a key with this prefix.
@@ -37,6 +38,10 @@ type rbrEncoder struct {
3738
rbrIndex roachpb.Key
3839
}
3940

41+
func (e *rbrEncoder) validate(session sqlliveness.SessionID) error {
42+
return ValidateSessionID(session)
43+
}
44+
4045
func (e *rbrEncoder) encode(session sqlliveness.SessionID) (roachpb.Key, string, error) {
4146
region, _, err := SafeDecodeSessionID(session)
4247
if err != nil {

pkg/sql/sqlliveness/slstorage/sessionid.go

Lines changed: 15 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -67,18 +67,8 @@ func MakeSessionID(region []byte, id uuid.UUID) (sqlliveness.SessionID, error) {
6767
// not be mutated.
6868
func UnsafeDecodeSessionID(session sqlliveness.SessionID) (region, id []byte, err error) {
6969
b := session.UnsafeBytes()
70-
if len(b) == legacyLen {
71-
return nil, nil, errors.Newf("unexpected legacy SessionID format")
72-
}
73-
if len(b) < minimumNonLegacyLen {
74-
// The smallest valid v1 session id is a [version, 1, single_byte_region, uuid...],
75-
// which is three bytes larger than a uuid.
76-
return nil, nil, errors.New("session id is too short")
77-
}
78-
79-
// Decode the version.
80-
if b[0] != sessionIDVersion {
81-
return nil, nil, errors.Newf("invalid session id version: %d", b[0])
70+
if err = ValidateSessionID(sqlliveness.SessionID(b)); err != nil {
71+
return nil, nil, err
8272
}
8373
regionLen := int(b[1])
8474
rest := b[2:]
@@ -91,24 +81,30 @@ func UnsafeDecodeSessionID(session sqlliveness.SessionID) (region, id []byte, er
9181
return rest[:regionLen], rest[regionLen:], nil
9282
}
9383

94-
// SafeDecodeSessionID decodes the region and id from the SessionID.
95-
func SafeDecodeSessionID(session sqlliveness.SessionID) (region, id string, err error) {
84+
// ValidateSessionID validates that the SessionID has the correct format.
85+
func ValidateSessionID(session sqlliveness.SessionID) error {
9686
if len(session) == legacyLen {
97-
return "", "", errors.Newf("unexpected legacy SessionID format")
87+
return errors.Newf("unexpected legacy SessionID format")
9888
}
9989
if len(session) < minimumNonLegacyLen {
10090
// The smallest valid v1 session id is a [version, 1, single_byte_region, uuid...],
10191
// which is three bytes larger than a uuid.
102-
return "", "", errors.New("session id is too short")
92+
return errors.New("session id is too short")
10393
}
104-
10594
// Decode the version.
10695
if session[0] != sessionIDVersion {
107-
return "", "", errors.Newf("invalid session id version: %d", session[0])
96+
return errors.Newf("invalid session id version: %d", session[0])
97+
}
98+
return nil
99+
}
100+
101+
// SafeDecodeSessionID decodes the region and id from the SessionID.
102+
func SafeDecodeSessionID(session sqlliveness.SessionID) (region, id string, err error) {
103+
if err = ValidateSessionID(session); err != nil {
104+
return "", "", err
108105
}
109106
regionLen := int(session[1])
110107
rest := session[2:]
111-
112108
// Decode and validate the length of the region.
113109
if len(rest) != regionLen+uuid.Size {
114110
return "", "", errors.Newf("session id with length %d is the wrong size to include a region with length %d", len(session), regionLen)

pkg/sql/sqlliveness/slstorage/slstorage.go

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -200,7 +200,15 @@ const (
200200
func (s *Storage) isAlive(
201201
ctx context.Context, sid sqlliveness.SessionID, syncOrAsync readType,
202202
) (alive bool, _ error) {
203-
203+
// Confirm the session ID has the correct format, and if it
204+
// doesn't then we can consider it as dead without any extra
205+
// work.
206+
if err := s.keyCodec.validate(sid); err != nil {
207+
// This SessionID may be invalid because of the wrong format
208+
// so consider it as dead.
209+
//nolint:returnerrcheck
210+
return false, nil
211+
}
204212
// If wait is false, alive is set and future is unset.
205213
// If wait is true, alive is unset and future is set.
206214
alive, wait, future, err := func() (bool, bool, singleflight.Future, error) {
@@ -318,6 +326,9 @@ func (s *Storage) deleteOrFetchSession(
318326
ctx = multitenant.WithTenantCostControlExemption(ctx)
319327
livenessProber := regionliveness.NewLivenessProber(s.db, s.codec, nil, s.settings)
320328
k, regionPhysicalRep, err := s.keyCodec.encode(sid)
329+
if err != nil {
330+
return false, hlc.Timestamp{}, err
331+
}
321332
if err := s.txn(ctx, func(ctx context.Context, txn *kv.Txn) error {
322333
// Reset captured variable in case of retry.
323334
deleted, expiration, prevExpiration = false, hlc.Timestamp{}, hlc.Timestamp{}

0 commit comments

Comments
 (0)