From 1f243405e3acb14f461244e6a8ac462fd302dec1 Mon Sep 17 00:00:00 2001 From: Drew Kimball Date: Wed, 20 Sep 2023 01:08:47 -0600 Subject: [PATCH] sql: return expected user-facing error for invalid unnest arguments Previously, the `unnest` builtin function could trigger an internal error when passed multiple non array-type arguments. This is because it only checked whether the arguments were `NULL` when determining whether they were of a valid type. This is not a problem for some types, like `INT`, because they will prevent the function overloads from being resolved. However, since `TEXT` arguments can be cast to `ARRAY` types, function overload resolution succeeds. Since `unnest` only checked for `NULL`, it would assume that the arguments were array types, and attempt to retrieve the (nil) array contents. For the single-argument case this wasn't a problem because nil is used to signal invalid arguments, anyway. However, the multiple-argument case wraps the array contents of each argument type into a tuple, resulting in a tuple-type of nil types. This caused a nil-pointer dereference later down the line. This patch prevents the internal error by checking directly that the arguments are `ARRAY` types, to ensure that the array contents are non-nil. If the check fails, the (nil) `tree.UnknownReturnType` type is returned, which signals an invalid type. That results in an expected, user-facing error instead of an internal error. The `information_schema._pg_expandarray` builtin function had a similar vulnerability. This patch fixes that as well. Fixes #110952 Release note (bug fix): Fixed an edge case in the `unnest` and `information_schema._pg_expandarray` builtin functions that could cause an internal error when passed string arguments that could be cast to an array. --- pkg/sql/logictest/testdata/logic_test/srfs | 44 ++++++++++++++++++++++ pkg/sql/sem/builtins/generator_builtins.go | 6 +-- 2 files changed, 47 insertions(+), 3 deletions(-) diff --git a/pkg/sql/logictest/testdata/logic_test/srfs b/pkg/sql/logictest/testdata/logic_test/srfs index 19244444b8d4..7d7593dd3f88 100644 --- a/pkg/sql/logictest/testdata/logic_test/srfs +++ b/pkg/sql/logictest/testdata/logic_test/srfs @@ -1406,3 +1406,47 @@ NULL NULL 4 5 + +# Regression test for #110952 - providing invalid arguments to unnest should +# not trigger an internal error. +statement error pgcode 42804 pq: could not determine polymorphic type: unnest\(string\) +SELECT unnest('{}'); + +statement error pgcode 42804 pq: could not determine polymorphic type: unnest\(string, string, string\) +SELECT unnest('{}', '{}', '{}'); + +statement error pgcode 42883 pq: unknown signature: unnest\(int\) +SELECT unnest(1); + +statement error pgcode 42883 pq: unknown signature: unnest\(int, int, int\) +SELECT unnest(1, 2, 3); + +statement error pgcode 42804 pq: could not determine polymorphic type: unnest\(unknown\) +SELECT unnest(NULL); + +statement error pgcode 42804 pq: could not determine polymorphic type: unnest\(unknown, unknown, unknown\) +SELECT unnest(NULL, NULL, NULL); + +# pg_expandarray handles return types similarly to unnest. +statement error pgcode 42804 pq: could not determine polymorphic type: information_schema._pg_expandarray\(string\) +SELECT information_schema._pg_expandarray('{}'); + +query T +SELECT unnest('{1}'::int[], '{2}', '{3}'); +---- +(1,2,3) + +query T +SELECT unnest('{1}'::int[], '{}', '{}'); +---- +(1,,) + +query T +SELECT unnest('{1}', '{2}', '{3}'::int[]); +---- +(1,2,3) + +query T +SELECT unnest('{}', '{}', '{3}'::int[]); +---- +(,,3) diff --git a/pkg/sql/sem/builtins/generator_builtins.go b/pkg/sql/sem/builtins/generator_builtins.go index e39d456124b7..1db4ca17d7fd 100644 --- a/pkg/sql/sem/builtins/generator_builtins.go +++ b/pkg/sql/sem/builtins/generator_builtins.go @@ -299,7 +299,7 @@ var generators = map[string]builtinDefinition{ makeGeneratorOverloadWithReturnType( tree.ParamTypes{{Name: "input", Typ: types.AnyArray}}, func(args []tree.TypedExpr) *types.T { - if len(args) == 0 || args[0].ResolvedType().Family() == types.UnknownFamily { + if len(args) == 0 || args[0].ResolvedType().Family() != types.ArrayFamily { return tree.UnknownReturnType } return args[0].ResolvedType().ArrayContents() @@ -319,7 +319,7 @@ var generators = map[string]builtinDefinition{ returnTypes := make([]*types.T, len(args)) labels := make([]string, len(args)) for i, arg := range args { - if arg.ResolvedType().Family() == types.UnknownFamily { + if arg.ResolvedType().Family() != types.ArrayFamily { return tree.UnknownReturnType } returnTypes[i] = arg.ResolvedType().ArrayContents() @@ -337,7 +337,7 @@ var generators = map[string]builtinDefinition{ makeGeneratorOverloadWithReturnType( tree.ParamTypes{{Name: "input", Typ: types.AnyArray}}, func(args []tree.TypedExpr) *types.T { - if len(args) == 0 || args[0].ResolvedType().Family() == types.UnknownFamily { + if len(args) == 0 || args[0].ResolvedType().Family() != types.ArrayFamily { return tree.UnknownReturnType } t := args[0].ResolvedType().ArrayContents()