With LDAP support enabled, we link the backend with libldap, and we link libpq with libldap_r. Modules like dblink and postgres_fdw link to libpq, so loading them results in a backend having both libldap and libdap_r loaded. Those libraries export the same symbols, and the load order rule gives priority to the libldap symbols. So far, so good. However, both libraries declare a GCC destructor, ldap_int_destroy_global_options(), to free memory reachable from a global variable, ldap_int_global_options. In OpenLDAP versions 2.4.24 through 2.4.31, that variable's structure type has an incompatible layout in libldap vs. libldap_r. When the libldap_r build of the destructor uses pointers from the ldap_int_global_options of libldap, SIGSEGV ensues. This does not arise if nothing in the process ever caused OpenLDAP to initialize itself, because the relevant bytes then happen to be all-zero in either structure layout. OpenLDAP 2.4.32 fixed this by making the libldap structure a strict prefix of the libldap_r structure. The OpenLDAP change log merely says "Fixed libldap sasl handling (ITS#7118, ITS#7133)"; here are the relevant commits:
http://www.openldap.org/devel/gitweb.cgi?p=openldap.git;a=commitdiff;h=270ef33acf18dc13bfd07f8a8e66b446f80e7d27 http://www.openldap.org/devel/gitweb.cgi?p=openldap.git;a=commitdiff;h=7ff18967d7d2e49baa9d80f1b9408b276f3982e0 You can cause the at-exit crash by building PostgreSQL against OpenLDAP 2.4.31, connecting with LDAP authentication, and issuing "LOAD 'dblink'". Alternately, connect with any authentication method and create a dblink connection that uses an LDAP-based pg_service.conf entry. I'm attaching a test suite patch to illustrate that second vector. The popularity of the affected OpenLDAP versions is not clear to me. RHEL 5, 6 and 7 all ship OpenLDAP either old enough or new enough to miss the problem. Debian wheezy ships 2.4.31, an affected version. I have not looked beyond that. I pondered what we could do short of treating this as a pure OpenLDAP bug for packagers to fix in their OpenLDAP builds, but I did not come up with anything singularly attractive. Some possibilities: 1. Link the backend with libldap_r, so we never face the mismatch. On some platforms, this means also linking in threading libraries. 2. Link a second copy of libpq against plain libldap and use that in server modules like dblink. 3. Wipe the memory of ldap_int_global_options so the destructor will have nothing to do. A challenge here is that we don't know the structure size; it's not part of any installed header, and it varies in response to OpenLDAP configuration options. Still, this is achievable in principle. This would be easy if the destructor function weren't static, alas. 4. Detect older OpenLDAP versions at runtime, just before we would otherwise initialize OpenLDAP, and raise an error. Possibly make the same check at compile time, for packager convenience. 5. Use only _exit(), not exit(). Hopefully I'm missing a great alternative, because each of those has something substantial to dislike about it. Thoughts welcome, especially from packagers dealing with platforms that use affected OpenLDAP versions. Thanks, nm -- Noah Misch EnterpriseDB http://www.enterprisedb.com
diff --git a/contrib/dblink/expected/dblink.out b/contrib/dblink/expected/dblink.out index f237c43..fd61985 100644 --- a/contrib/dblink/expected/dblink.out +++ b/contrib/dblink/expected/dblink.out @@ -103,6 +103,18 @@ SELECT * FROM dblink('SELECT * FROM foo') AS t(a int, b text, c text[]) WHERE t.a > 7; ERROR: connection not available +-- The first-level connection's backend will crash on exit given OpenLDAP +-- [2.4.24, 2.4.31]. Give the postmaster time to act on the crash. +SELECT pg_sleep(1) +FROM dblink('dbname=contrib_regression', $$SELECT * FROM + dblink('service=test_ldap dbname=contrib_regression', + 'select 1') t(c int)$$) +AS t(c int); + pg_sleep +---------- + +(1 row) + -- create a persistent connection SELECT dblink_connect('dbname=contrib_regression'); dblink_connect diff --git a/contrib/dblink/sql/dblink.sql b/contrib/dblink/sql/dblink.sql index 2a10760..fa98285 100644 --- a/contrib/dblink/sql/dblink.sql +++ b/contrib/dblink/sql/dblink.sql @@ -65,6 +65,14 @@ SELECT * FROM dblink('SELECT * FROM foo') AS t(a int, b text, c text[]) WHERE t.a > 7; +-- The first-level connection's backend will crash on exit given OpenLDAP +-- [2.4.24, 2.4.31]. Give the postmaster time to act on the crash. +SELECT pg_sleep(1) +FROM dblink('dbname=contrib_regression', $$SELECT * FROM + dblink('service=test_ldap dbname=contrib_regression', + 'select 1') t(c int)$$) +AS t(c int); + -- create a persistent connection SELECT dblink_connect('dbname=contrib_regression'); diff --git a/src/Makefile.global.in b/src/Makefile.global.in index 4b31c0a..19bfe75 100644 --- a/src/Makefile.global.in +++ b/src/Makefile.global.in @@ -480,7 +480,7 @@ endif pg_regress_locale_flags = $(if $(ENCODING),--encoding=$(ENCODING)) $(NOLOCALE) -pg_regress_check = $(top_builddir)/src/test/regress/pg_regress --inputdir=$(srcdir) --temp-install=./tmp_check --top-builddir=$(top_builddir) $(pg_regress_locale_flags) $(EXTRA_REGRESS_OPTS) +pg_regress_check = PGSERVICEFILE=$(abs_top_srcdir)/src/test/regress/pg_service.conf $(top_builddir)/src/test/regress/pg_regress --inputdir=$(srcdir) --temp-install=./tmp_check --top-builddir=$(top_builddir) $(pg_regress_locale_flags) $(EXTRA_REGRESS_OPTS) pg_regress_installcheck = $(top_builddir)/src/test/regress/pg_regress --inputdir=$(srcdir) --psqldir='$(PSQLDIR)' $(pg_regress_locale_flags) $(EXTRA_REGRESS_OPTS) pg_regress_clean_files = results/ regression.diffs regression.out tmp_check/ log/ diff --git a/src/test/regress/pg_service.conf b/src/test/regress/pg_service.conf new file mode 100644 index 0000000..8710172 --- /dev/null +++ b/src/test/regress/pg_service.conf @@ -0,0 +1,3 @@ +# Any URL that reaches no actual server is fine. +[test_ldap] +ldap://127.0.0.1:11111/base?attribute?one?filter
-- Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org) To make changes to your subscription: http://www.postgresql.org/mailpref/pgsql-hackers