Skip to content

age_to*list functions crash the server (SIGSEGV) on array-shaped SQL literals #2563

Description

@hkjiang26

AGE Version: apache/age master @ cfd3b63 (2026-08-14), extension 1.8.0, on PostgreSQL 18.6

Installation Method: Docker

API: SQL (psql)

Steps to reproduce

  1. On a fresh database, run (no data required):
LOAD 'age';
SET search_path = ag_catalog, public;
SELECT ag_catalog.age_tobooleanlist('[true]');

All four age_to*list functions crash deterministically on array-shaped literal arguments (12 shapes verified):

Statement Result
age_tobooleanlist('[true]'), ('[null]'), ('[true, null, false]') SIGSEGV
age_tofloatlist('[true]'), ('[1]'), ('[null]') SIGSEGV
age_tointegerlist('[true]'), ('[null]') SIGSEGV
age_tostringlist('[true]'), ('[null]'), ('[1, null, "a"]') SIGSEGV

Other array shapes ('[1]', '["a"]', '[1.5]' for the mismatched functions) give a bogus ERROR: toBooleanList() argument must resolve to a list or null instead — same root cause, non-crashing bit pattern.

Expected behavior

The Cypher equivalents work correctly and define the expected behavior:

SELECT * FROM cypher('g', $$ RETURN toBooleanList([true]) $$) AS (r agtype);
-- r = [true]

The SQL form should either return the same result or raise a clean error; it must never crash the backend.

Actual behavior

psql: server closed the connection unexpectedly
        This probably means the server terminated abnormally
        before or while processing the request.
psql: error: connection to server was lost

Server log (docker logs):

LOG:  client backend (PID 14030) was terminated by signal 11: Segmentation fault
LOG:  all server processes terminated; reinitializing

Root cause (in src/backend/utils/adt/agtype.c): type confusion at the SQL boundary. For VARIADIC "any" functions, PostgreSQL resolves an unknown-type literal to text (verified with the same-signature builtin json_build_array('[true]')["[true]"]), i.e. the function receives a text[] (ArrayType) datum, not agtype. The C functions read the argument with AG_GET_ARG_AGTYPE_P(0) without ever checking get_fn_expr_argtype(), so the text[] datum is reinterpreted as an agtype container. Depending on the literal's byte pattern the garbage either passes the AGT_ROOT_IS_ARRAY root check and crashes while iterating elements (the 12 shapes above), or fails it and produces the bogus argument must resolve to a list or null error ('[1]', '["a"]', '[1.5]'). Passing an explicit ::agtype cast works correctly — age_tobooleanlist('[true]'::agtype) returns [true] — and the explicit VARIADIC ARRAY[...] form hits the same reinterpretation. EXPLAIN (VERBOSE) of a crashing call also crashes the backend.

Cypher is safe: literal lists are built through agtype_build_list (null elements get real value slots) and the transform passes a typed agtype expression, so the C code never sees a text[]; the PREPARE/EXECUTE parameter path was verified safe as well. Trigger surface = AGE's public SQL API (shared-instance DoS, same class as the ? crash).

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions