age_to*list functions crash the server (SIGSEGV) on array-shaped SQL literals
- Dominant language
- C
- Stars
- 4.8k
- Forks
- 523
- Avg merge
- 1d 2h
- Merged PRs (30d)
- 9
Description
**AGE Version:** apache/age master @ cfd3b634 (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):
```sql
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:
```sql
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).
Contributor guide
Research direction
Start in src/backend/utils/adt/agtype.c, at the age_to*list functions and their AG_GET_ARG_AGTYPE_P argument handling; compare the received argument type with get_fn_expr_argtype(). Run the listed SQL reproducer, including the explicit ::agtype form. Done means array-shaped literals no longer crash the PostgreSQL backend and either return the expected list or raise a clean error.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- c, postgresql
- Domain
- backend-api-design, databases, security
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 65/100