Skip to content

Commit 47262e3

Browse files
optbuilder: gate crdb_internal builtins from unauthorized access
The epic below outlines a set of work to prevent users from unauthorized access to what we deem unsafe internals. These unsafe internals lie mostly in the crdb_internal schema and the system database. This PR makes it so crdb_internal builtins are disallowed from external use unless the specified override is enabled. Fixes: #151501 Epic: CRDB-24527 Release note (ops change): restricts access to all crdb_internal builtins unless session variable `allow_unsafe_internals` is set to true, or if the caller is internal.
1 parent 8e688ef commit 47262e3

File tree

4 files changed

+116
-75
lines changed

4 files changed

+116
-75
lines changed

pkg/sql/authorization_test.go

Lines changed: 87 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -311,7 +311,7 @@ func checkUnsafeErr(t *testing.T, err error) {
311311
t.Fatal("expected unsafe access error, got", err)
312312
}
313313

314-
func TestCheckUnsafeSystemAccess(t *testing.T) {
314+
func TestCheckUnsafeInternalsAccess(t *testing.T) {
315315
defer leaktest.AfterTest(t)()
316316
defer log.Scope(t).Close(t)
317317

@@ -321,92 +321,105 @@ func TestCheckUnsafeSystemAccess(t *testing.T) {
321321
})
322322
defer s.Stopper().Stop(ctx)
323323

324-
q := "SELECT * FROM system.namespace"
325-
326-
t.Run("as an external querier", func(t *testing.T) {
327-
for _, test := range []struct {
328-
AllowUnsafeInternals bool
329-
Passes bool
330-
}{
331-
{AllowUnsafeInternals: false, Passes: false},
332-
{AllowUnsafeInternals: true, Passes: true},
333-
} {
334-
t.Run(fmt.Sprintf("%t", test), func(t *testing.T) {
335-
conn := s.SQLConn(t)
336-
_, err := conn.Exec("SET allow_unsafe_internals = $1", test.AllowUnsafeInternals)
337-
require.NoError(t, err)
338-
339-
_, err = conn.Query(q)
340-
if test.Passes {
324+
t.Run("accessing the system database", func(t *testing.T) {
325+
q := "SELECT * FROM system.namespace"
326+
327+
t.Run("as an external querier", func(t *testing.T) {
328+
for _, test := range []struct {
329+
AllowUnsafeInternals bool
330+
Passes bool
331+
}{
332+
{AllowUnsafeInternals: false, Passes: false},
333+
{AllowUnsafeInternals: true, Passes: true},
334+
} {
335+
t.Run(fmt.Sprintf("%t", test), func(t *testing.T) {
336+
conn := s.SQLConn(t)
337+
_, err := conn.Exec("SET allow_unsafe_internals = $1", test.AllowUnsafeInternals)
341338
require.NoError(t, err)
342-
} else {
343-
checkUnsafeErr(t, err)
344-
}
345-
})
346-
}
347-
})
348339

349-
t.Run("as an internal querier", func(t *testing.T) {
350-
for _, test := range []struct {
351-
AllowUnsafeInternals bool
352-
Passes bool
353-
}{
354-
{AllowUnsafeInternals: false, Passes: true},
355-
{AllowUnsafeInternals: true, Passes: true},
356-
} {
357-
t.Run(fmt.Sprintf("%t", test), func(t *testing.T) {
358-
idb := s.InternalDB().(isql.DB)
359-
err := idb.Txn(ctx, func(ctx context.Context, txn isql.Txn) error {
360-
txn.SessionData().LocalOnlySessionData.AllowUnsafeInternals = test.AllowUnsafeInternals
361-
362-
_, err := txn.QueryBuffered(ctx, "internal-query", txn.KV(), q)
363-
return err
340+
_, err = conn.Query(q)
341+
if test.Passes {
342+
require.NoError(t, err)
343+
} else {
344+
checkUnsafeErr(t, err)
345+
}
364346
})
347+
}
348+
})
365349

366-
require.NoError(t, err)
350+
t.Run("as an internal querier", func(t *testing.T) {
351+
for _, test := range []struct {
352+
AllowUnsafeInternals bool
353+
Passes bool
354+
}{
355+
{AllowUnsafeInternals: false, Passes: true},
356+
{AllowUnsafeInternals: true, Passes: true},
357+
} {
358+
t.Run(fmt.Sprintf("%t", test), func(t *testing.T) {
359+
idb := s.InternalDB().(isql.DB)
360+
err := idb.Txn(ctx, func(ctx context.Context, txn isql.Txn) error {
361+
txn.SessionData().LocalOnlySessionData.AllowUnsafeInternals = test.AllowUnsafeInternals
362+
363+
_, err := txn.QueryBuffered(ctx, "internal-query", txn.KV(), q)
364+
return err
365+
})
367366

368-
if test.Passes {
369367
require.NoError(t, err)
370-
} else {
371-
checkUnsafeErr(t, err)
372-
}
373-
})
374-
}
375-
})
376-
377-
}
378-
379-
func TestCheckUnsafeCRDBInternalAccess(t *testing.T) {
380-
defer leaktest.AfterTest(t)()
381-
defer log.Scope(t).Close(t)
382368

383-
ctx := context.Background()
384-
s := serverutils.StartServerOnly(t, base.TestServerArgs{
385-
DefaultTestTenant: base.TestControlsTenantsExplicitly,
369+
if test.Passes {
370+
require.NoError(t, err)
371+
} else {
372+
checkUnsafeErr(t, err)
373+
}
374+
})
375+
}
376+
})
386377
})
387-
defer s.Stopper().Stop(ctx)
388378

389-
// Test that with allow_unsafe_internals = false:
390-
// - Supported tables (zones) are allowed
391-
// - Unsupported tables (gossip_alerts) are denied
379+
t.Run("accessing the crdb_internal schema", func(t *testing.T) {
380+
t.Run("supported table allowed", func(t *testing.T) {
381+
conn := s.SQLConn(t)
382+
_, err := conn.Exec("SET allow_unsafe_internals = false")
383+
require.NoError(t, err)
392384

393-
t.Run("supported table allowed", func(t *testing.T) {
394-
conn := s.SQLConn(t)
395-
_, err := conn.Exec("SET allow_unsafe_internals = false")
396-
require.NoError(t, err)
385+
// Supported crdb_internal tables should be allowed even when allow_unsafe_internals = false
386+
_, err = conn.Query("SELECT * FROM crdb_internal.zones")
387+
require.NoError(t, err, "supported crdb_internal table (zones) should be accessible when allow_unsafe_internals = false")
388+
})
397389

398-
// Supported crdb_internal tables should be allowed even when allow_unsafe_internals = false
399-
_, err = conn.Query("SELECT * FROM crdb_internal.zones")
400-
require.NoError(t, err, "supported crdb_internal table (zones) should be accessible when allow_unsafe_internals = false")
390+
t.Run("unsupported table denied", func(t *testing.T) {
391+
conn := s.SQLConn(t)
392+
_, err := conn.Exec("SET allow_unsafe_internals = false")
393+
require.NoError(t, err)
394+
395+
// Unsupported crdb_internal tables should be denied when allow_unsafe_internals = false
396+
_, err = conn.Query("SELECT * FROM crdb_internal.gossip_alerts")
397+
checkUnsafeErr(t, err)
398+
})
401399
})
402400

403-
t.Run("unsupported table denied", func(t *testing.T) {
404-
conn := s.SQLConn(t)
405-
_, err := conn.Exec("SET allow_unsafe_internals = false")
406-
require.NoError(t, err)
401+
// The functionality for this lies in the pkg/sql/optbuilder/scalar.go
402+
// file, but it is tested here as that package does not setup a test
403+
// server.
404+
t.Run("accessing crdb_internal builtins", func(t *testing.T) {
405+
t.Run("non crdb_internal builtin allowed", func(t *testing.T) {
406+
conn := s.SQLConn(t)
407+
_, err := conn.Exec("SET allow_unsafe_internals = false")
408+
require.NoError(t, err)
409+
410+
// Non crdb_internal tables should be allowed.
411+
_, err = conn.Query("SELECT * FROM generate_series(1,5)")
412+
require.NoError(t, err)
413+
})
414+
415+
t.Run("crdb_internal builtin not allowed", func(t *testing.T) {
416+
conn := s.SQLConn(t)
417+
_, err := conn.Exec("SET allow_unsafe_internals = false")
418+
require.NoError(t, err)
407419

408-
// Unsupported crdb_internal tables should be denied when allow_unsafe_internals = false
409-
_, err = conn.Query("SELECT * FROM crdb_internal.gossip_alerts")
410-
checkUnsafeErr(t, err)
420+
// Unsupported crdb_internal builtins should be denied.
421+
_, err = conn.Query("SELECT * FROM crdb_internal.tenant_span_stats()")
422+
checkUnsafeErr(t, err)
423+
})
411424
})
412425
}

pkg/sql/opt/optbuilder/BUILD.bazel

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,7 @@ go_library(
101101
"//pkg/sql/sqltelemetry",
102102
"//pkg/sql/syntheticprivilege",
103103
"//pkg/sql/types",
104+
"//pkg/sql/unsafesql",
104105
"//pkg/util",
105106
"//pkg/util/buildutil",
106107
"//pkg/util/errorutil",

pkg/sql/opt/optbuilder/scalar.go

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,15 @@ import (
2020
"github.com/cockroachdb/cockroach/pkg/sql/pgwire/pgcode"
2121
"github.com/cockroachdb/cockroach/pkg/sql/pgwire/pgerror"
2222
"github.com/cockroachdb/cockroach/pkg/sql/privilege"
23+
"github.com/cockroachdb/cockroach/pkg/sql/sem/catconstants"
2324
"github.com/cockroachdb/cockroach/pkg/sql/sem/eval"
2425
"github.com/cockroachdb/cockroach/pkg/sql/sem/tree"
2526
"github.com/cockroachdb/cockroach/pkg/sql/sem/tree/treebin"
2627
"github.com/cockroachdb/cockroach/pkg/sql/sem/tree/treecmp"
2728
"github.com/cockroachdb/cockroach/pkg/sql/sqlerrors"
2829
"github.com/cockroachdb/cockroach/pkg/sql/sqltelemetry"
2930
"github.com/cockroachdb/cockroach/pkg/sql/types"
31+
"github.com/cockroachdb/cockroach/pkg/sql/unsafesql"
3032
"github.com/cockroachdb/cockroach/pkg/util/errorutil"
3133
"github.com/cockroachdb/cockroach/pkg/util/errorutil/unimplemented"
3234
"github.com/cockroachdb/errors"
@@ -547,6 +549,11 @@ func (b *Builder) buildFunction(
547549
if overload.HasSQLBody() {
548550
return b.buildUDF(f, def, inScope, outScope, outCol, colRefs)
549551
}
552+
if b.isUnsafeBuiltin(overload, def) {
553+
if err := unsafesql.CheckInternalsAccess(b.evalCtx.SessionData()); err != nil {
554+
panic(err)
555+
}
556+
}
550557
b.factory.Metadata().AddBuiltin(f.Func.ReferenceByName)
551558

552559
if overload.Class == tree.AggregateClass {
@@ -878,6 +885,22 @@ func (b *Builder) constructUnary(
878885
panic(errors.AssertionFailedf("unhandled unary operator: %s", redact.Safe(un)))
879886
}
880887

888+
// isUnsafeBuiltin returns true if the given function definition
889+
// is a CRDB internal builtin function.
890+
func (b *Builder) isUnsafeBuiltin(
891+
overload *tree.Overload, def *tree.ResolvedFunctionDefinition,
892+
) bool {
893+
if overload.Type != tree.BuiltinRoutine {
894+
return false
895+
}
896+
for _, o := range def.Overloads {
897+
if o.Schema == catconstants.CRDBInternalSchemaName {
898+
return true
899+
}
900+
}
901+
return false
902+
}
903+
881904
// ScalarBuilder is a specialized variant of Builder that can be used to create
882905
// a scalar from a TypedExpr. This is used to build scalar expressions for
883906
// testing. It is also used temporarily to interface with the old planning code.

pkg/sql/sem/eval/context.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -465,10 +465,14 @@ func MakeTestingEvalContext(st *cluster.Settings) Context {
465465
// MemoryMonitor. Ownership of the memory monitor is transferred to the
466466
// EvalContext so do not start or close the memory monitor.
467467
func MakeTestingEvalContextWithMon(st *cluster.Settings, monitor *mon.BytesMonitor) Context {
468+
sessionData := &sessiondata.SessionData{}
469+
// Set defaults that match what the session variables system expects.
470+
// allow_unsafe_internals defaults to true.
471+
sessionData.AllowUnsafeInternals = true
468472
ctx := Context{
469473
Codec: keys.SystemSQLCodec,
470474
Txn: &kv.Txn{},
471-
SessionDataStack: sessiondata.NewStack(&sessiondata.SessionData{}),
475+
SessionDataStack: sessiondata.NewStack(sessionData),
472476
Settings: st,
473477
NodeID: base.TestingIDContainer,
474478
}

0 commit comments

Comments
 (0)