NULL pointer dereference in syslogger with load_libraries() and -DEXEC_BACKEND at startup

First seen: 2026-05-25 07:45:41+00:00 · Messages: 13 · Participants: 5

Latest Update

2026-05-27 · claude-opus-4-6

NULL Pointer Dereference in Syslogger with EXEC_BACKEND at Startup

Core Problem

A NULL pointer dereference occurs in PostgreSQL's syslogger process when all of the following conditions are met:

  1. A module is loaded via shared_preload_libraries that emits log messages during _PG_init()
  2. logging_collector = on
  3. Built with -DEXEC_BACKEND (Windows-style process spawning, also used on some test configurations)
  4. log_min_messages = debug1 or lower (to trigger log output from load_libraries)

The crash happens because write_syslogger_file() attempts to call fwrite() with a NULL logfile pointer. The syslogger process believes it should write directly to its log file (bypassing the pipe), but the file hasn't been opened yet at that point in the startup sequence.

Architectural Context

The Syslogger's Direct-Write Path

PostgreSQL's logging architecture has two paths for getting log messages to disk:

  1. Normal path: Backend processes write log chunks to syslogPipe[], and the syslogger process reads from the pipe and writes to files.
  2. Direct path: When code is executing inside the syslogger process itself, it bypasses the pipe and writes directly to the log file via write_syslogger_file().

The direct path is selected in send_message_to_server_log() (elog.c) by checking:

if (MyBackendType == B_LOGGER)
    write_syslogger_file(buf.data, buf.len, LOG_DESTINATION_STDERR);

This has been a safe assumption historically because MyBackendType was only set to B_LOGGER deep within SysLoggerMain(), well after the log files were opened.

The Breaking Change: Commit 0c8e082fba8d

Commit 0c8e082fba8d ("Assign 'backend' type earlier during process start-up", by Álvaro Herrera, 2026-02-04) moved the assignment of MyBackendType earlier in the startup sequence for all backend types. This was architecturally motivated — knowing the backend type earlier enables better initialization decisions.

However, for the syslogger (B_LOGGER), this created a window where:

The log file opening happens in SysLogger_Start()logfile_open(), which occurs later in SysLoggerMain(). The startup order in the EXEC_BACKEND case is:

SubPostmasterMain()
  → MyBackendType = B_LOGGER  [NEW - from 0c8e082fba8d]
  → process_shared_preload_libraries()  ← crash window
  → ...
  → SysLoggerMain()
    → logfile_open()  ← files actually opened here
    → main loop

Historical Assumption

The comment in syslogger.c (dating back to commit bff84a547d71, refined in dc686681e079 for jsonlog) explicitly states: "dump the data to syslogFile (which is always open)". This assumption held for many years because the backend type was only known after log files were set up.

Proposed Solutions and Discussion

Solution 1: Guard on FILE pointer being non-NULL (Rejected)

Ayush Tiwari and Kyotaro Horiguchi suggested treating logfile == NULL as an error case and falling back to write_stderr(). Michael Paquier firmly rejected this: the NULL file is not an OS-level write failure but a logic bug in Postgres's startup sequencing. Adding a NULL guard would mask the real problem.

Solution 2: Exception for B_LOGGER in early MyBackendType assignment (Rejected)

Euler Santos suggested not setting MyBackendType for B_LOGGER in the early path and keeping the old assignment location. Both Michael Paquier and Euler himself considered this "ugly and hackish."

Solution 3: New boolean syslogger_setup_done (Accepted)

Álvaro Herrera proposed a new boolean flag that is set at the same point where MyBackendType was previously assigned (i.e., after log files are opened in SysLoggerMain()). The check in send_message_to_server_log() becomes:

if (MyBackendType == B_LOGGER && syslogger_setup_done)
    write_syslogger_file(buf.data, buf.len, LOG_DESTINATION_STDERR);

This cleanly separates "I am the syslogger process" (MyBackendType) from "the syslogger's file descriptors are ready" (syslogger_setup_done).

Solution 4: Reuse redirection_done (Considered and Rejected)

Euler Santos suggested reusing the existing redirection_done flag. Álvaro explained why this is incorrect:

Remaining Work

Michael Paquier identified additional hardening needed:

  1. csvlog.c and jsonlog.c paths should also use syslogger_setup_done instead of relying solely on MyBackendType
  2. Comments should document the flag's purpose and the invariant it protects

Why This Matters