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:
- A module is loaded via
shared_preload_librariesthat emits log messages during_PG_init() logging_collector = on- Built with
-DEXEC_BACKEND(Windows-style process spawning, also used on some test configurations) log_min_messages = debug1or lower (to trigger log output fromload_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:
- Normal path: Backend processes write log chunks to
syslogPipe[], and the syslogger process reads from the pipe and writes to files. - 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:
MyBackendType == B_LOGGERis true (set inSubPostmasterMain())process_shared_preload_libraries()runs and calls_PG_init()on preloaded modules- A module logs a message (e.g.,
pgstat_register_kind()emitting a DEBUG/LOG message) send_message_to_server_log()seesMyBackendType == B_LOGGERand callswrite_syslogger_file()- But
syslogFileis still NULL becauseSysLoggerMain()hasn't opened it yet
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:
redirection_doneindicates thatsyslogPipe[]is set up for chunk transmission — a different semantic- It is designed to be transmitted from postmaster to children (global state), while the new flag is local to the syslogger process
- On syslogger restart, inherited
redirection_donecould be stale/incorrect - Mixing purposes of flags is error-prone
Remaining Work
Michael Paquier identified additional hardening needed:
- csvlog.c and jsonlog.c paths should also use
syslogger_setup_doneinstead of relying solely onMyBackendType - Comments should document the flag's purpose and the invariant it protects
Why This Matters
- The logging subsystem must be robust by design — it's the last line of defense for diagnosing problems
- EXEC_BACKEND is the mandatory path on Windows, so this affects all Windows PostgreSQL deployments using
logging_collector+ preloaded libraries - The fix preserves the architectural benefit of early backend type assignment while restoring the safety invariant for syslogger file readiness