Subject: BUG #19095: Test if function exit() is used fail when linked static

From: PG Bug reporting form <[email-masked]>
Date: Mon, 27 Oct 2025 07:56:38 +0000

The following bug has been logged on the website:

Bug reference:      19095
Logged by:          Torsten Rupp
Email address:      [email-masked]
PostgreSQL version: 15.14
Operating system:   Linux
Description:        

Note: occur from version 15.14 or newer.

In src/interfaces/libpq/Makefile is a test if the function "exit()" (or in
general: a function exists with the name part "exit") is used:

libpq-refs-stamp: $(shlib)
ifneq ($(enable_coverage), yes)
ifeq (,$(filter aix solaris,$(PORTNAME)))
        @if nm -A -u $< 2>/dev/null | grep -v __cxa_atexit | grep exit; then
\
                echo 'libpq must not be calling any function which invokes
exit'; exit 1; \
        fi
endif
endif

This test fail if libpq is linked static to an application when e. g.
libcrypto is also linked static into libpq which add indirectly a call to
"pthread_exit()".

Possible fix: exclude pthread_exit(), too (like __cxa_atexit), e.g.:

libpq-refs-stamp: $(shlib)
ifneq ($(enable_coverage), yes)
ifeq (,$(filter aix solaris,$(PORTNAME)))
        @if nm -A -u $< 2>/dev/null | grep -v __cxa_atexit | grep -v
pthread_exit | grep exit; then \
                echo 'libpq must not be calling any function which invokes
exit'; exit 1; \
        fi
endif
endif

---

From: Michael Paquier <[email-masked]>
Date: Tue, 28 Oct 2025 12:16:49 +0900

> Possible fix: exclude pthread_exit(), too (like __cxa_atexit), e.g.:

Previous discussions around this check:
- 936f56988741, with:
https://www.postgresql.org/message-id/[email-masked]
- dc227eb82ea8, with:
https://www.postgresql.org/message-id/[email-masked]

We have usually used the buildfarm to decide how much restriction we
should put into this one, for good historical reasons because we
should never exit() directly from libpq, like this one:
https://www.postgresql.org/message-id/[email-masked]

Treating pthread_exit() as an exception sounds like it may be a good
thing anyway: we don't rely on it in the code core.

Now I am not completely sure how much we should care about considering
that any of that as something we need to tweak in the core code.  The
use of static libraries are usually discouraged, because it makes the
handling of package dependencies more complicated if some
sub-libraries need to be upgraded following a CVE-class issue, and
here you are pointing at what looks like a custom static library build
of libcrypto on Linux.

Opinions from others are welcome, mine counts like -0.5.
--
Michael

---

From: Torsten Rupp <[email-masked]>
Date: Sun, 9 Nov 2025 09:33:16 +0100

Dear developers,

I opened this request for a behavior concerning functions with the name 

>                  echo 'libpq must not be calling any function which invokes

BTW: if you wonder about static linkage: I'm aware of the disadvantages, 
but I use static linkage for a backup tool which should run e. g. in any 
live Linux from a USB medium, thus it should have as less dependencies 
to shared libraries as possible. A non-static version of the tool is 
also available. The issue does not occur with shared libraries, because 
then no function with the name part "exit" is linked into libpq.

Thank you for your attention.

Best regards, Torsten Rupp

---

From: BharatDB <[email-masked]>
Date: Wed, 12 Nov 2025 11:23:10 +0530

>
>
> Hello Hackers,
>
> While reproducing a static linking issue between libpq and libcrypto.a,
> I observed that the Makefile's symbol check incorrectly reports missing
> exit() symbols because 'grep exit' also matches 'atexit' and
> 'OPENSSL_atexit', etc.
>
> As discussed in the thread by Michael Paquier
> (https://www.postgresql.org/message-id/aQA1obboZFjqjaBI%40paquier.xyz),
> it seems a related fix was committed earlier. However, I was able to
> reproduce this issue again using PostgreSQL 18 (latest release).
>
> Steps to reproduce:
>   nm -A -u ./src/interfaces/libpq/libpq.a
> /usr/lib/x86_64-linux-gnu/libcrypto.a 2>/dev/null | grep -v __cxa_atexit |
> grep exit
>
> Output:
>   /usr/lib/x86_64-linux-gnu/libcrypto.a:libcrypto-lib-init.o:
>     U atexit
>   /usr/lib/x86_64-linux-gnu/libcrypto.a:libdefault-lib-rand_unix.o:
>     U OPENSSL_atexit
>
> This falsely triggers an undefined exit() error.
> [1]Changing the grep pattern to match the exact symbol ('grep -x exit')
> prevents
> such false positives.

[2]Alternatively, excluding pthread_exit() (similar to
> __cxa_atexit) would also avoid the issue.
>
>   But [1] will handle all the false positives instead of just pthread_exit
.LGTM

Expected (after fix):
>   no output — no false positives.
>
> Patch attached.I would love to hear any suggestions from the committers.
>
  Thanks in advance

> Best regards,
> Vasuki M
    BharatDB,

> CDAC Chennai.
From 6ba784e27f6d2a7023828263f988b7a2716fe614 Mon Sep 17 00:00:00 2001
From: BharatDBPG <[email-masked]>
Date: Tue, 11 Nov 2025 17:36:29 +0530
Subject: [PATCH] Fix: prevent false exit() match in libpq static link check

The previous Makefile check used 'grep exit', which also matched
symbols like 'atexit' and 'OPENSSL_atexit' from libcrypto.a.
This caused false positive link failures when statically linking
libpq with OpenSSL.

Use 'grep -x exit' to match only the exact symbol name.

Signed-off-by: BharatDBPG <[email-masked]>
---
 src/interfaces/libpq/Makefile | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/interfaces/libpq/Makefile b/src/interfaces/libpq/Makefile
index da66500..8cff30b 100644
--- a/src/interfaces/libpq/Makefile
+++ b/src/interfaces/libpq/Makefile
@@ -144,7 +144,7 @@ $(stlib): $(OBJS_STATIC)
 libpq-refs-stamp: $(shlib)
 ifneq ($(enable_coverage), yes)
 ifeq (,$(filter solaris,$(PORTNAME)))
-	@if nm -A -u $< 2>/dev/null | grep -v -e __cxa_atexit -e __tsan_func_exit | grep exit; then \
+	@if nm -A -u $< 2>/dev/null | grep -v -e __cxa_atexit -e __tsan_func_exit | grep -x exit; then \
 		echo 'libpq must not be calling any function which invokes exit'; exit 1; \
 	fi
 endif
-- 
2.43.0

---

From: BharatDB <[email-masked]>
Date: Wed, 12 Nov 2025 11:24:31 +0530

---------- Forwarded message ---------
From: BharatDB <[email-masked]>
Date: Wed, Nov 12, 2025 at 11:23 AM
Subject: Re: BUG #19095: Test if function exit() is used fail when linked
static
To: <[email-masked]>, <[email-masked]>, <
[email-masked]>
Cc: VASUKI M <[email-masked]>

.LGTM

  Thanks in advance

    BharatDB,

---

From: Tom Lane <[email-masked]>
Date: Wed, 12 Nov 2025 01:38:47 -0500

BharatDB <[email-masked]> writes:
>> prevents such false positives.

We might as well remove the test entirely as do that; it would
fail to detect "_exit" for example.

Additionally, I don't have a lot of faith in "grep -x" being
universally portable.  POSIX 2018 does specify that switch, but
it mentions that it is "historically available only with fgrep".

Personally I'm okay with whitelisting pthread_exit() as
Torsten suggested.

BTW, it looks like libpq's meson.build is missing this check.

			regards, tom lane

---

From: Daniel Gustafsson <[email-masked]>
Date: Wed, 12 Nov 2025 09:13:09 +0100

> On 12 Nov 2025, at 07:38, Tom Lane <[email-masked]> wrote:

> Personally I'm okay with whitelisting pthread_exit() as

+1, we already have a few whitelisted entries and pthread_exit seems perfectly
reasonable to add to that list.

--
Daniel Gustafsson

---

From: Michael Paquier <[email-masked]>
Date: Wed, 12 Nov 2025 17:15:15 +0900

> reasonable to add to that list.

WFM.
--
Michael

---

From: Daniel Gustafsson <[email-masked]>
Date: Fri, 14 Nov 2025 13:11:15 +0100

> On 12 Nov 2025, at 09:15, Michael Paquier <[email-masked]> wrote:
> 

The attached trivial diff adds this to the whitelist clause in the Makefile.  I
experimented with adding this to Meson, and while it's trivial enough to do the
run_command with libpq_so.full_path, it's less clear to me exactly where in the
build it should be added.  I've pinged my colleague Bilal who is much better at
Meson than me to collaborate on that as a separate fix.

--
Daniel Gustafsson

---

From: Andres Freund <[email-masked]>
Date: Fri, 14 Nov 2025 09:01:06 -0500

Hi,
> Meson than me to collaborate on that as a separate fix.

For meson we'll have to filter where we test this more strictly - it'll
e.g. not work on windows, because there's no nm, perhaps no grep, etc.

But more generally: If we allow pthread_exit(), what's the point of this test?
That's one of the functions we better avoid calling, no?

ISTM that if we do want to continue having this test, the issue is that we're
testing the shared library - which will have already linked against static
libraries like the sanitizer ones or in this case libcrypto. What we ought to
do is to test the .o files constituting libpq.so, rather than the already
linked .so. That way we will find our own calls to exit etc, but not ones in
static libraries.

Greetings,

Andres Freund

---

From: Tom Lane <[email-masked]>
Date: Fri, 14 Nov 2025 09:40:46 -0500

> That's one of the functions we better avoid calling, no?

ATM it's not something we'd be tempted to call, but I take your point.

> linked .so. That way we will find our own calls to exit etc, but not ones in

My recollection is that that doesn't help as much as you'd think.
__tsan_func_exit, for one, can get injected into our own .o files
if we build with appropriate sanitizers enabled.

			regards, tom lane

---

From: BharatDB <[email-masked]>
Date: Wed, 19 Nov 2025 14:38:37 +0530

Hi Andres and hackers,
I came up with the solution
— short follow-up with what I changed and how the Meson check actually runs.

Summary of what I did
  - Added a Meson custom_target in src/interfaces/libpq/meson.build that
    scans libpq's object files for direct exit() references.
  - Added pthread_exit to the Makefile whitelist (the Makefile test
    already existed; this just updates the whitelist).
  -Added the custom target in the top level meson.build

Where the Meson check lives and when it runs
  - I put the custom_target in src/interfaces/libpq/meson.build,
    immediately after the libpq shared/static library targets are
    defined and after the declare_dependency for libpq.  That is the
    correct location in the libpq build file.
  - The custom_target declares `depends: [libpq_st, libpq_so]`.  This
    is important: it tells Meson to build those targets first so the
    .o files are present in the build directory before the check runs.
    The check itself does not scan libpq_st or libpq_so; it scans
    files in build/src/interfaces/libpq/*.o.
  - The command uses `find <builddir>/src/interfaces/libpq -name '*.o'`
    and runs `nm -u` on each .o, piping through `grep -v` for the
    whitelisted names and finally checking for exit.
  - The check is skipped for:
      * cross-builds
      * when coverage (b_coverage) is enabled
      * on Windows (no nm/grep in the same form there) as Andrew questioned

Why pthread_exit is whitelisted
  - pthread_exit can legitimately appear in a few build/runtime
    configurations (thread runtimes or link-time glue), and the
    Makefile test was updated to whitelist it.  The Meson check has the
    same whitelist so both build systems behave the same.
  - Whitelisting pthread_exit doesn't remove the value of the test:
    it only avoids false positives for legitimate thread shutdown code.
    We still catch direct exit()/ _exit()/abort()-style calls as tom said.

How to reproduce locally
  - From repo root:
      rm -rf build
      meson setup build
      cd build
      ninja
    The custom_target runs as part of the normal build and will fail
    the build if any .o contains an un-whitelisted exit() reference.

On Fri, Nov 14, 2025 at 7:31 PM Andres Freund <[email-masked]> wrote:

But more generally: If we allow pthread_exit(), what's the point of this
> test?
 I agree it's worth questioning which functions we allow.  The
 current choice (whitelist pthread_exit) mirrors the Makefile
 behavior, avoids false positives, and keeps the test focused on
 direct process-termination calls authored in our code.  If the
 community prefers a stricter policy so we can adjust the whitelist.

> linked .so. That way we will find our own calls to exit etc, but not ones

TBH After so many test runs I have finalized
Why scan .o files (not the final .so)?
  - A shared library is usually linked with other static libraries
    (libcrypto, sanitizer runtimes).  If we scan the final .so we'll
    see references that originate in those static libraries and produce
    false positives.
  - If we scan the .o files that make up libpq, we only inspect our
    own compilation units and will catch only exit() calls introduced
    by our code.

HTH! I attached the patch and also added the meson test output before
/after of any file containing an 'exit' explicitly; it fails the
build[ninja].

Regards,
Vasuki M
BharatDB[CDAC chennai]
From f711555fd2b69e74a33312f5ba8750b8fec97f1f Mon Sep 17 00:00:00 2001
From: BharatDBPG <[email-masked]>
Date: Wed, 19 Nov 2025 12:17:43 +0530
Subject: [PATCH] libpq: Add exit() function check for Meson build and
 whitelist pthread_exit()
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

The Makefile-based build already performs a safety check to ensure that
libpq does not accidentally reference exit() or related termination
functions.

Meson, however, did not run this check. As a result, Meson builds could
miss accidental references to exit()-family functions which ideally
should never be called inside libpq.

This patch adds the missing scan to the Meson build by:
  • Scanning the libpq .o files using nm and filtering through the same
    whitelist logic as the Makefile.
  • Adding pthread_exit() to the Meson whitelist, matching the behavior
    of the existing Makefile check.

With this change, both Makefile and Meson builds apply the same
validation for unwanted exit() usage.

Signed-off-by: Vasuki[BharatDBPG] <[email-masked]>
---
 meson.build                      |  1 +
 src/interfaces/libpq/Makefile    |  7 ++--
 src/interfaces/libpq/meson.build | 70 +++++++++++++++-----------------
 3 files changed, 37 insertions(+), 41 deletions(-)

diff --git a/meson.build b/meson.build
index 24aeffe..e20bfe6 100644
--- a/meson.build
+++ b/meson.build
@@ -3812,6 +3812,7 @@ alias_target('bin', bin_targets + [libpq_st])
 alias_target('pl', pl_targets)
 alias_target('contrib', contrib_targets)
 alias_target('testprep', testprep_targets)
+alias_target('run-check-libpq', [check_exit_target])
 
 alias_target('world', all_built, docs)
 alias_target('install-world', install_quiet, installdocs)
diff --git a/src/interfaces/libpq/Makefile b/src/interfaces/libpq/Makefile
index 3bd0e4f..1ad3c60 100644
--- a/src/interfaces/libpq/Makefile
+++ b/src/interfaces/libpq/Makefile
@@ -134,9 +134,10 @@ $(stlib): $(OBJS_STATIC)
 # build toolchains insert abort() calls, e.g. to implement assert().)
 # If nm doesn't exist or doesn't work on shlibs, this test will do nothing,
 # which is fine.  The exclusion of __cxa_atexit is necessary on OpenBSD,
-# which seems to insert references to that even in pure C code. Excluding
-# __tsan_func_exit is necessary when using ThreadSanitizer data race detector
-# which use this function for instrumentation of function exit.
+# which seems to insert references to that even in pure C code.  Excluding
+# __tsan_func_exit is necessary when using ThreadSanitizer, which emits this
+# symbol as part of its instrumentation of function exits.  Excluding
+# pthread_exit allows legitimate thread shutdown paths used on some builds.
 # Skip the test when profiling, as gcc may insert exit() calls for that.
 # Also skip the test on platforms where libpq infrastructure may be provided
 # by statically-linked libraries, as we can't expect them to honor this
diff --git a/src/interfaces/libpq/meson.build b/src/interfaces/libpq/meson.build
index 4b1e8a2..4822814 100644
--- a/src/interfaces/libpq/meson.build
+++ b/src/interfaces/libpq/meson.build
@@ -1,5 +1,4 @@
 # Copyright (c) 2022-2025, PostgreSQL Global Development Group
-
 libpq_sources = files(
   'fe-auth-oauth.c',
   'fe-auth-scram.c',
@@ -85,6 +84,38 @@ libpq = declare_dependency(
   include_directories: [include_directories('.')]
 )
 
+# Sanity check to ensure libpq does not contain any unintended references
+# to exit() in its object files.  Client libraries must not terminate the
+# calling process, so we scan all libpq .o files with 'nm' and fail the
+# build if a direct exit() reference is found.  Certain harmless symbols
+# (__cxa_atexit, __tsan_func_exit, pthread_exit) are whitelisted.
+# Skip on cross-builds, sanitizer coverage, and Windows
+
+if not meson.is_cross_build() and not get_option('b_coverage') and host_system != 'windows'
+  check_exit_target = custom_target(
+    'check-libpq-no-exit',
+    output: 'libpq-nm-stamp',
+    depends: [libpq_st, libpq_so],
+    build_by_default: true,
+    command: [
+      'bash','-eu', '-c',
+      '''
+      echo "Checking that libpq object files do not reference exit()..."  
+      obj_files=$(find src/interfaces/libpq -type f -name "*.o")
+      for f in $obj_files; do
+        if nm -u "$f" 2>/dev/null \
+            | grep -v -E '__cxa_atexit|__tsan_func_exit|pthread_exit' \
+            | grep exit; then
+          echo "ERROR: exit()-related reference found in: $f"
+          exit 1
+        fi
+      done            
+      touch  @OUTPUT@
+      '''.format(meson.current_build_dir())
+    ],
+  )
+
+endif
 
 private_deps = [
   frontend_stlib_code,
@@ -147,41 +178,4 @@ tests += {
   },
 }
 
-# Verify that libpq does not reference functions that may invoke exit().
-#
-# This check parallels the Makefile logic used in the autoconf build system.
-# 
-# The following symbols are considered safe and therefore ignored:
-#   - __cxa_atexit      : used for C++ static destructors
-#   - __tsan_func_exit  : thread sanitizer instrumentation
-#   - pthread_exit      : used by thread runtimes, harmless here
-#
-# The test runs only for native builds (not cross-builds) and when code
-# coverage is disable
-
-if not meson.is_cross_build() and not get_option('b_coverage') and host_system != 'sunos'
-  check_exit_target = custom_target(
-    'check-libpq-no-exit',
-    output: 'libpq-refs-stamp',
-    depends: libpq_so,
-    build_by_default: true,
-    command: [
-      'bash', '-c',
-      '''
-      echo "Running exit() reference check for libpq..."
-      if nm -A -u @0@ 2>/dev/null | \
-         grep -v -e __cxa_atexit -e __tsan_func_exit -e pthread_exit | \
-         grep exit; then
-         echo "ERROR: libpq must not be calling any function which invokes exit()"
-         exit 1
-      else
-         echo "SUCCESS: No exit() references found in libpq"
-      fi
-      '''.format(libpq_so.full_path())
-    ],
-    build_always_stale: true,
-    capture: false
-  )
-endif
-
 subdir('po', if_found: libintl)
-- 
2.43.0

---

From: BharatDB <[email-masked]>
Date: Wed, 19 Nov 2025 17:59:09 +0530

Sorry,for the wrong patch this is the correct one.kindly find the attached
patch for testing
I would love to hear the feedback from you committers.

regards,
Vasuki M
BharatDB,C-DAC Chennai.

> /after of any file containing an 'exit' explicitly; it fails the
From f0679266bda765f4e1e15bfee97d2817d3bd78ea Mon Sep 17 00:00:00 2001
From: BharatDBPG <[email-masked]>
Date: Wed, 19 Nov 2025 17:43:53 +0530
Subject: [PATCH] libpq: Add exit() function check for Meson build and
 whitelist pthread_exit()
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

With this change, both Makefile and Meson builds apply the same
validation for unwanted exit() usage.
---
 meson.build                      |  1 +
 src/interfaces/libpq/Makefile    |  5 +++--
 src/interfaces/libpq/meson.build | 30 ++++++++++++++++++++++++++++++
 3 files changed, 34 insertions(+), 2 deletions(-)

diff --git a/meson.build b/meson.build
index c1e17aa3040..47588ead9fb 100644
--- a/meson.build
+++ b/meson.build
@@ -3820,6 +3820,7 @@ alias_target('bin', bin_targets + [libpq_st])
 alias_target('pl', pl_targets)
 alias_target('contrib', contrib_targets)
 alias_target('testprep', testprep_targets)
+alias_target('run-check-libpq', [check_exit_target])
 
 alias_target('world', all_built, docs)
 alias_target('install-world', install_quiet, installdocs)
diff --git a/src/interfaces/libpq/Makefile b/src/interfaces/libpq/Makefile
index da6650066d4..7b20d84d51d 100644
--- a/src/interfaces/libpq/Makefile
+++ b/src/interfaces/libpq/Makefile
@@ -137,14 +137,15 @@ $(stlib): $(OBJS_STATIC)
 # which seems to insert references to that even in pure C code. Excluding
 # __tsan_func_exit is necessary when using ThreadSanitizer data race detector
 # which use this function for instrumentation of function exit.
-# Skip the test when profiling, as gcc may insert exit() calls for that.
+#Excluding pthread_exit allows legitimate thread shutdown paths used on some builds.
+#Skip the test when profiling, as gcc may insert exit() calls for that.
 # Also skip the test on platforms where libpq infrastructure may be provided
 # by statically-linked libraries, as we can't expect them to honor this
 # coding rule.
 libpq-refs-stamp: $(shlib)
 ifneq ($(enable_coverage), yes)
 ifeq (,$(filter solaris,$(PORTNAME)))
-	@if nm -A -u $< 2>/dev/null | grep -v -e __cxa_atexit -e __tsan_func_exit | grep exit; then \
+	@if nm -A -u $< 2>/dev/null | grep -v -e __cxa_atexit -e __tsan_func_exit -e pthread_exit | grep exit; then \
 		echo 'libpq must not be calling any function which invokes exit'; exit 1; \
 	fi
 endif
diff --git a/src/interfaces/libpq/meson.build b/src/interfaces/libpq/meson.build
index a74e885b169..30324731e9c 100644
--- a/src/interfaces/libpq/meson.build
+++ b/src/interfaces/libpq/meson.build
@@ -85,6 +85,36 @@ libpq = declare_dependency(
   include_directories: [include_directories('.')]
 )
 
+        if nm -u "$f" 2>/dev/null| grep -v -E '__cxa_atexit|__tsan_func_exit|pthread_exit'| grep exit; then
   libpq_deps,
-- 
2.43.0