sql: Bound custom type resolution depth and total work#37581
Merged
Conversation
`scalar_type_from_catalog` resolves a custom type into an in-memory
`SqlScalarType` by recursing into its referenced sub-types. Two shapes of
user-created type made this pathological, both reachable at `CREATE TYPE`
plan time (and any later reference):
* A deep chain of LIST/MAP types (`l0 <- l1 <- ... <- lN`) recursed N deep
and overflowed the stack.
* A record type whose fields reference the same sub-type
(`CREATE TYPE tN AS (a t{N-1}, b t{N-1})`) resolves to a type tree that is
exponential in its depth, because record fields hold owned copies rather
than shared references. Building/cloning it exhausted memory and CPU.
Bound both: reject a type nested deeper than MAX_TYPE_NESTING_DEPTH, and cap
the total number of sub-type resolutions (MAX_TYPE_RESOLUTION_NODES) so an
exponentially large type is rejected before it is built, rather than after.
Both now return a graceful planning error.
Closes: SQL-514
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
`scalar_type_from_catalog` reset the node budget on every call, but `plan_create_type` and `CatalogType::desc` call it once per record field. A record whose fields are each just under the budget therefore passed creation, then materialized an unbounded type tree during sequencing and could exhaust environmentd's memory. This preserved the authenticated denial of service SQL-514 aimed to close. Thread one `TypeResolutionBudget` across all of a root type's children, charging the root itself and resolving children at depth one. Creation now rejects exactly the types a later direct resolution would reject, so `d16` (two `d15` fields), a wide record of `d14` fields, and `l128` (deep enough that its leaf resolves past the depth limit) are all refused up front rather than stored unusable. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The custom-type resolution depth and complexity bound rejects a type at plan time. Bootstrap re-plans every persisted `create_sql`, so a wide record or deep chain that an earlier version accepted would fail the new bound and abort environmentd, crash-looping on every restart. Lift the bound while re-planning persisted items, gated on `unsafe_enable_unbounded_custom_type_resolution`, which `enable_for_item_parsing` force-enables during bootstrap. A grandfathered type resolves to a finite tree, so it loads without materializing an unbounded one. A normal user session still applies the bound and returns the graceful planning error. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
mtabebe
approved these changes
Jul 13, 2026
mtabebe
left a comment
Contributor
There was a problem hiding this comment.
LGTM thanks for the tests and thorough comments. Nice clean error messages 👍
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
scalar_type_from_catalogresolves a custom type into an in-memorySqlScalarTypeby recursing into its referenced sub-types. Two shapes of user-created type made this pathological, both reachable atCREATE TYPEplan time (and any later reference):l0 <- l1 <- ... <- lN) recursed N deep and overflowed the stack.CREATE TYPE tN AS (a t{N-1}, b t{N-1})) resolves to a type tree that is exponential in its depth, because record fields hold owned copies rather than shared references. Building/cloning it exhausted memory and CPU.Bound both: reject a type nested deeper than MAX_TYPE_NESTING_DEPTH, and cap the total number of sub-type resolutions (MAX_TYPE_RESOLUTION_NODES) so an exponentially large type is rejected before it is built, rather than after. Both now return a graceful planning error.
Closes: SQL-514, SQL-513