Skip to content

Segfault instead of 42501: calling a function without EXECUTE privilege after SET ROLE crashes the cluster (17.6.1.106, aarch64) #2366

Description

@adriano-linenform

Summary

On supabase/postgres:17.6.1.106, calling a function that the current role lacks EXECUTE privilege on, after SET ROLE, segfaults the backend instead of raising 42501 permission denied. The postmaster then terminates every other connection and the cluster enters crash recovery.

Because the whole cluster restarts, this is not a contained error: every in-flight connection dies, PostgREST loses its listener and re-initialises its pool, and unrelated requests fail with PGRST002 for the duration.

Reproduction

Four lines. No extension, no fixture, no SECURITY DEFINER, no SET clause on the function:

CREATE FUNCTION public.probe() RETURNS text LANGUAGE sql STABLE
  AS $$ SELECT 'ok'::text $$;

REVOKE EXECUTE ON FUNCTION public.probe() FROM PUBLIC, anon, authenticated;

SET ROLE authenticated;
SELECT public.probe();

Expected: ERROR: 42501: permission denied for function probe
Actual: the connection dies and the cluster recovers.

psql: server closed the connection unexpectedly

Server log:

LOG:  server process (PID …) was terminated by signal 11: Segmentation fault
DETAIL:  Failed process was running: SET ROLE authenticated; SELECT public.probe();
LOG:  terminating any other active server processes
LOG:  database system was not properly shut down; automatic recovery in progress

Environment

image public.ecr.aws/supabase/postgres:17.6.1.106
PostgreSQL 17.6 (aarch64-unknown-linux-gnu, gcc 15.2.0)
host Docker Desktop on macOS, Apple Silicon
Supabase CLI 2.98.2

What we ruled out

We hit this ~36 times across a working day and initially misdiagnosed it several ways. Each of these was excluded by measurement, in case it saves someone the same path:

  • Connection-pool exhaustion — sampled pg_stat_activity every 3 s across a full workload: peak 8 of PostgREST's 10, mostly 3, always returning to 1. Never at the limit.
  • A resource limitshared_buffers 128 MB, work_mem 4 MB, container using 312 MiB of 15.6 GiB with no hard limit.
  • JITjit_above_cost is 100000; representative plan costs here are ~24, so JIT never engages.
  • A specific extension (pgmq) — our first crashes all ran through pgmq.send(), but bare pgmq.send(...) as a privileged role is fine. The queue is incidental; the ACL is not.
  • SECURITY DEFINER — crashes with and without it.
  • A SET search_path clause on the function — crashes with and without it.

The single discriminator is the privilege. Adding GRANT EXECUTE ON FUNCTION public.probe() TO authenticated; makes every variant run cleanly; revoking it crashes reliably.

Impact

Any test or application path that asserts an unprivileged caller is refused will take the database down rather than receive the refusal. In our case two RLS regression tests exist specifically to prove that an unprivileged role cannot call two functions — so asserting a refusal is itself the crash, and the failures land on whichever unrelated suite happened to be running.

Happy to run further narrowing (e.g. against a stock PostgreSQL 17.6 image to confirm whether this is upstream or image-specific) if that would help.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions