BUG #19763: ManualBug2 NULL pointer dereference in make_build_data via examine_expression bypass of lookup_var_a

From: PG Bug reporting form <noreply(at)postgresql(dot)org>
To: pgsql-bugs(at)lists(dot)postgresql(dot)org
Cc: 1217816127(at)qq(dot)com
Subject: BUG #19763: ManualBug2 NULL pointer dereference in make_build_data via examine_expression bypass of lookup_var_a
Date: 2026-10-15 15:05:00
Message-ID: 19763-14da3f96c02f07e8@postgresql.org
Views: Whole Thread | Raw Message | Download mbox | Resend email
Thread:
Lists: pgsql-bugs

The following bug has been logged on the website:

Bug reference: 19763
Logged by: Yuelin Wang
Email address: 1217816127(at)qq(dot)com
PostgreSQL version: 19rc1
Operating system: Linux (Ubuntu 24.04, x86_64)
Description:

### What happens?

`make_build_data()` in `src/backend/statistics/extended_stats.c` (line 2586)
calls `examine_expression(expr, stattarget)` for each expression in a
statistics object without checking for a NULL return value.
The function `examine_expression()` can return NULL when a typanalyze
callback returns false for the given `attstattarget` value.
The invariant violated is: every element of `data->stats[]` returned by
`make_build_data()` must be a valid non-NULL `VacAttrStats` pointer before
any stat builder iterates over the array.
The upstream guard in `BuildRelationExtStatistics()` calls
`lookup_var_attr_stats()` first (line 169) and returns early when it yields
NULL.
However, `lookup_var_attr_stats()` calls `examine_attribute(expr)` (line
776) which sets `attstattarget = -1`, whereas `make_build_data()` calls
`examine_expression(expr, stattarget)` (line 2586) which sets `attstattarget
= stattarget` (a positive integer).
A typanalyze callback that behaves differently depending on `attstattarget`
can therefore pass `examine_attribute` (returning true) but fail
`examine_expression` (returning false, causing NULL).
The NULL then propagates through `make_build_data()` into stat builders such
as `statext_ndistinct_build()`, `statext_mcv_build()`, and
`statext_dependencies_build()`, all of which dereference `data->stats[i]`
without a NULL check.

The crash path confirmed by ASAN is:

```
make_build_data extended_stats.c:2586 examine_expression returns NULL for
stattarget-sensitive typanalyze
data->stats[idx] = NULL
BuildRelationExtStatistics extended_stats.c:206 calls
statext_ndistinct_build(data)
statext_ndistinct_build mvdistinct.c:125 iterates over data->stats[]
ndistinct_for_combination mvdistinct.c:459 VacAttrStats *colstat =
data->stats[combination[i]] == NULL
typid = colstat->attrtypid <- NULL dereference (SEGFAULT)
```

### To Reproduce

First build and install the `test_custom_types` extension with an additional
`int_custom_typanalyze_stattarget_sensitive` function added to
`src/test/modules/test_custom_types/test_custom_types.c`.
The function returns true (with valid `compute_stats` and `minrows`) when
`stats->attstattarget < 0` (the value set by `examine_attribute`), and
returns false when `stats->attstattarget >= 0` (the value set by
`examine_expression` with the actual statistics target).

Start a fresh PostgreSQL ASAN instance and run:

```sql
SELECT version();
CREATE EXTENSION test_custom_types;
CREATE TABLE t1 (id int, data int_custom);
INSERT INTO t1 SELECT i, (i % 100)::text::int_custom FROM generate_series(1,
1000) i;
CREATE OR REPLACE FUNCTION func_ic(p int_custom) RETURNS int_custom LANGUAGE
plpgsql AS $$ BEGIN RETURN p; END; $$;
ALTER TYPE int_custom SET (ANALYZE =
int_custom_typanalyze_stattarget_sensitive);
CREATE STATISTICS st1 ON func_ic(data), (id) FROM t1;
ANALYZE t1;
SELECT 'survived' AS result;
```

Trigger output:

```
version
--------------------------------------------------------------------
PostgreSQL 20devel on x86_64-linux, compiled by gcc-13.3.0, 64-bit
(1 row)

CREATE EXTENSION
CREATE TABLE
INSERT 0 1000
CREATE FUNCTION
ALTER TYPE
CREATE STATISTICS
server closed the connection unexpectedly
This probably means the server terminated abnormally
before or while processing the request.
connection to server was lost
```

pg.log:

```
2026-10-15 08:38:12.423 +08 [2602104] LOG: starting PostgreSQL 20devel on
x86_64-linux, compiled by gcc-13.3.0, 64-bit
2026-10-15 08:38:12.423 +08 [2602104] LOG: listening on IPv4 address
"127.0.0.1", port 14064
2026-10-15 08:38:12.423 +08 [2602104] LOG: listening on Unix socket
"/tmp/.s.PGSQL.14064"
2026-10-15 08:38:12.432 +08 [2602109] LOG: database system was shut down at
2026-10-15 08:38:11 +08
2026-10-15 08:38:12.440 +08 [2602104] LOG: database system is ready to
accept connections
AddressSanitizer:DEADLYSIGNAL
2026-10-15 08:38:14.788 +08 [2602104] LOG: client backend (PID 2602233) was
terminated by signal 6: Aborted
2026-10-15 08:38:14.788 +08 [2602104] DETAIL: Failed process was running:
ANALYZE t1;
2026-10-15 08:38:14.788 +08 [2602104] LOG: terminating any other active
server processes
2026-10-15 08:38:14.793 +08 [2602104] LOG: all server processes terminated;
reinitializing
```

ASAN report:

```
==2602233==ERROR: AddressSanitizer: SEGV on unknown address 0x000000000004
(pc 0x594bbf1a8535 bp 0x7ffcec007d70 sp 0x7ffcec007ca0 T0)
==2602233==The signal is caused by a READ memory access.
==2602233==Hint: address points to the zero page.
#0 0x594bbf1a8535 in ndistinct_for_combination
../src/backend/statistics/mvdistinct.c:459
#1 0x594bbf1a8535 in statext_ndistinct_build
../src/backend/statistics/mvdistinct.c:125
#2 0x594bbf192013 in BuildRelationExtStatistics
../src/backend/statistics/extended_stats.c:206
#3 0x594bbeb807f7 in do_analyze_rel
../src/backend/commands/analyze.c:645
#4 0x594bbeb83d20 in analyze_rel ../src/backend/commands/analyze.c:299
#5 0x594bbecef696 in vacuum ../src/backend/commands/vacuum.c:653
#6 0x594bbecf0e6c in ExecVacuum ../src/backend/commands/vacuum.c:467
#7 0x594bbf286a15 in standard_ProcessUtility
../src/backend/tcop/utility.c:860
#8 0x594bbf282d46 in PortalRunUtility ../src/backend/tcop/pquery.c:1153
#9 0x594bbf282fa4 in PortalRunMulti ../src/backend/tcop/pquery.c:1311
#10 0x594bbf283c64 in PortalRun ../src/backend/tcop/pquery.c:788
#11 0x594bbf27a670 in exec_simple_query
../src/backend/tcop/postgres.c:1297
#12 0x594bbf27e970 in PostgresMain ../src/backend/tcop/postgres.c:4946
#13 0x594bbf27134c in BackendMain
../src/backend/tcop/backend_startup.c:124
#14 0x594bbf092b51 in postmaster_child_launch
../src/backend/postmaster/launch_backend.c:268
#15 0x594bbf099c76 in BackendStartup
../src/backend/postmaster/postmaster.c:3650
#16 0x594bbf099c76 in ServerLoop
../src/backend/postmaster/postmaster.c:1727
#17 0x594bbf09cb29 in PostmasterMain
../src/backend/postmaster/postmaster.c:1414
#18 0x594bbe88e20d in main ../src/backend/main/main.c:227
SUMMARY: AddressSanitizer: SEGV ../src/backend/statistics/mvdistinct.c:459
in ndistinct_for_combination
==2602233==ABORTING
```

### Environment

- OS: Ubuntu 24.04.2 LTS, x86_64 (Linux 6.8.0-55-generic)
- PostgreSQL Version: PostgreSQL 20devel on x86_64-linux, compiled by
gcc-13.3.0, 64-bit
- Client: psql, ASAN instrumented build (built from source with
-fsanitize=address)
- Hardware: N/A

### Identity Disclosure

- Full Name: Yuelin
- Affiliation: Individual

Responses

Browse pgsql-bugs by date

  From Date Subject
Next Message PG Bug reporting form 2026-10-15 15:05:32 BUG #19764: ManualBug3 Assert failure in pgstat_setup_shared_refs crashes initdb post-bootstrap subprocess
Previous Message PG Bug reporting form 2026-10-15 15:02:57 BUG #19762: ManualBug1 tcn: NULL pointer dereference in triggered_change_notification() via strcpy_quoted