Patchwork ld.so: Enable preloading of new symbol versions [BZ #24974]

login
register
mail settings
Submitter Florian Weimer
Date Sept. 6, 2019, 3:59 p.m.
Message ID <877e6le7pj.fsf@oldenburg2.str.redhat.com>
Download mbox | patch
Permalink /patch/34412/
State New
Headers show

Comments

Florian Weimer - Sept. 6, 2019, 3:59 p.m.
This commit adds the --no-version-coverage-check option to the
dynamic loader, and the LD_NO_VERSION_COVERAGE_CHECK environment
variable.

The new _dl_no_version_coverage_check field in struct rtld_global_ro
lands in previously-unused padding (on x86-64).

2019-09-06  Florian Weimer  <fweimer@redhat.com>

	[BZ #24974]
	* elf/rtld.c (dl_main): Add --no-version-coverage-check option.
	(process_envvars): Handle LD_NO_VERSION_COVERAGE_CHECK.
	* sysdeps/generic/ldsodefs.h (struct rtld_global_ro): Add
	_dl_no_version_coverage_check field.
	* elf/dl-version.c (match_symbol): Check it before reporting an
	error.
	* elf/Makefile [$(build-shared)] (tests): Add
	tst-no-version-coverage-lazy, tst-no-version-coverage-now.
	(modules-names): Add tst-no-version-coverage-linkmod,
	tst-no-version-coverage-runmod, tst-no-version-coverage-preloadmod.
	(LDFLAGS-tst-no-version-coverage-linkmod.so): Set version map and
	soname.
	(LDFLAGS-tst-no-version-coverage-runmod.so): Likewise.
	(LDFLAGS-tst-no-version-coverage-preloadmod.so): Likewise.
	(LDFLAGS-tst-no-version-coverage-lazy): Enable lazy binding.
	(tst-no-version-coverage-lazy): Link with
	tst-no-version-coverage-linkmod.so.
	(tst-no-version-coverage-lazy-ENV): Disable symbol version
	coverage check.
	(tst-no-version-coverage-lazy.out): Depend on
	tst-no-version-coverage-runmod.so.
	(LDFLAGS-tst-no-version-coverage-now): Disable lazy binding.
	(tst-no-version-coverage-now): Link with
	tst-no-version-coverage-linkmod.so.
	(tst-no-version-coverage-now-ENV): Preload
	tst-no-version-coverage-preloadmod.so.  Disable symbol version
	coverage check.
	(tst-no-version-coverage-now.out): Depend on
	tst-no-version-coverage-preloadmod.so,
	tst-no-version-coverage-runmod.so.
	* elf/tst-no-version-coverage-lazy.c: New file.
	* elf/tst-no-version-coverage-linkmod.c:: Likewise.
	* elf/tst-no-version-coverage-linkmod.map: Likewise.
	* elf/tst-no-version-coverage-now.c: Likewise.
	* elf/tst-no-version-coverage-preloadmod.c: Likewise.
	* elf/tst-no-version-coverage-preloadmod.map: Likewise.
	* elf/tst-no-version-coverage-runmod.c: Likewise.
	* elf/tst-no-version-coverage-runmod.map: Likewise.
Carlos O'Donell - Sept. 7, 2019, 3:52 a.m.
On 9/6/19 11:59 AM, Florian Weimer wrote:
> This commit adds the --no-version-coverage-check option to the
> dynamic loader, and the LD_NO_VERSION_COVERAGE_CHECK environment
> variable.
> 
> The new _dl_no_version_coverage_check field in struct rtld_global_ro
> lands in previously-unused padding (on x86-64).

At a high level I like where you are going with this design.

I have some high level questions to ask, that way we can set the tone
for the overall design of the dynamic loader.

(a) Not a tunable, but use some tunable framework.

I assume this isn't a tunable because it changes the semantics of the
dynamic loader.

I would like us to avoid adding another env var since it will become
another variable that downstream needs to learn to "clean" from the
environment if they want to manage runtime behaviour.

My idea is that if we are going to add a new env var that it should
be the *last* one we add.

We should add a GLIBC_LDSO="glibc.ld.no_version_coverage_check=1" and
then extend GLIBC_LDSO to contain all existing legacy env vars etc.
You can reuse all of the tunables code to manage *one* env var that
can alter the behaviour of the dynamic loader, and this includes
reusing the well tested handling of security context and clearing.

There are ~20 env vars that can be cleaned up and put into legacy
handling like this.

(b) When does a user know to use this env var?

- Is one scenario like this:

  - They have an old library that doesn't provide all the symbols they need.
  - They implement a preload that provides the missing functionality.
  - They disable the check with an env var and run a newer program using 
    a mix of the old library and preload to provide all the required symbols.

Is that a valid scenario? We should document something like this in the NEWS
entry.

What other useful scenarios are supported by the change?

(c) Why not remove the check entirely?

What are the pros/cons of keeping or removing the check?

Cheers,
Carlos.
Florian Weimer - Sept. 9, 2019, 12:01 p.m.
* Carlos O'Donell:

> (a) Not a tunable, but use some tunable framework.
>
> I assume this isn't a tunable because it changes the semantics of the
> dynamic loader.
>
> I would like us to avoid adding another env var since it will become
> another variable that downstream needs to learn to "clean" from the
> environment if they want to manage runtime behaviour.

We have basically reserved the LD_ prefix for these knobs.  It is
relatively easy to strip all environment variables which start with
"LD_" (very easy if you use posix_spawn or execve with an explicit
envp).

If we stuff everything into a single environment variable, applications
would have to write a parser for the variable contents even if they just
want to add a single directory to LD_LIBRARY_PATH.  This doesn't strike
me as a good idea.

> (b) When does a user know to use this env var?

When application developers who ship a glibc compatibility shim tell
them to.

> - Is one scenario like this:
>
>   - They have an old library that doesn't provide all the symbols they need.
>   - They implement a preload that provides the missing functionality.
>   - They disable the check with an env var and run a newer program using 
>     a mix of the old library and preload to provide all the required symbols.
>
> Is that a valid scenario? We should document something like this in
> the NEWS entry.

The NEWS entry already references this scenario (LD_PRELOAD of new
library versions).  If we had documentation for the dynamic loader, I
would have added it there.

We could also add a new DT_ tag to the LD_PRELOAD library and disable
the check for the process if we encounter such a library.  That would
simplify things for users.

> What other useful scenarios are supported by the change?

I don't know of anything else.

> (c) Why not remove the check entirely?

I don't know if anyone uses Solaris-style versions (without associated
symbols).  They would completely break as a result, in the sense that
the version check for them is completely gone.

Even without that, with lazy binding, users will start to see crashes in
programs if their glibc (or libstdc++) is too old, instead of them
failing to start at all.  I don't think that would be an improvement.

Thanks,
Florian
Carlos O'Donell - Sept. 9, 2019, 1:57 p.m.
On 9/9/19 8:01 AM, Florian Weimer wrote:
> * Carlos O'Donell:
> 
>> (a) Not a tunable, but use some tunable framework.
>>
>> I assume this isn't a tunable because it changes the semantics of the
>> dynamic loader.
>>
>> I would like us to avoid adding another env var since it will become
>> another variable that downstream needs to learn to "clean" from the
>> environment if they want to manage runtime behaviour.
> 
> We have basically reserved the LD_ prefix for these knobs.  It is
> relatively easy to strip all environment variables which start with
> "LD_" (very easy if you use posix_spawn or execve with an explicit
> envp).

Let me explore the solution space a bit here, I don't disagree with you, but
I'm playing a bit of devil's advocate. My own opinion is that changes of this
nature need more public discourse on the pros and cons. Sot let me lecture a
bit.

Yes, users can do `unset "${!LD@}"` to unset all such environment variables,
and in all the years I've been working with downstream packages I've seen
this construct, and its variants, used very few times. 

It is not used, IMO, because it is not a conservative action to take on the
part of the ISV.

Example from OpenEmbedded:

106 # autotools.bbclass sets the _FOR_BUILD variables, but for some reason we need
107 # to unset LD_LIBRARY_PATH.
108 export CC_FOR_BUILD = "LD_LIBRARY_PATH= ${BUILD_CC}"

Developers tend to remove env vars one at a time when they impact their use
cases, rather than blanket removal of all LD_* env vars.

I also do not think that LD_* is reserved for glibc, we now have shared
semantics with musl, and soon llvm's libc, and I fully expect they will create
their own environment variables here with an LD_* prefix. This means that we
might be removing env vars that are used by musl or llvm-libc and we should not
do that.

We did the right thing with tunables to use a single variable with N=V pairs,
but in that case we envisioned a *huge* number of tunables that were set as a
collection, and could be unset without impacting the ability to run the program.

We have more isolation using a GLIBC_* prefix which is clearly the variables
used by only glibc.

The counter argument here is that GLIBC_LDSO is a single monolithic variable
that is either set or unset, and that could be problematic for developers who
need to alter only part of it via the command line.

If I have to weigh between the two:

(a) One env var for multiple variables.

vs.

(b) One env var per variable.

I think it comes out slightly in favour of (b), one env var per variable,
as you suggest, but for the following reasons:

(1) These variables have direct influence over the operation of the software
    and there is an easy shell-based mechanism for setting or unsetting the
    values.

(2) The variables can be set or unset one at a time allowing easy and fine
    grained control. As is seen in the OE example. The variables are orthogonal
    to each other.

(3) We have no expectation that we will grow the list of LD_* vars in any
    appreciable way, compared to tunables which can change with each release.

So I think we are on the same page here, use one env var for this behaviour.

However, we should be cautious of musl and llvm-libc to ensure that we don't
have problems with the LD_* namespace.
 
> If we stuff everything into a single environment variable, applications
> would have to write a parser for the variable contents even if they just
> want to add a single directory to LD_LIBRARY_PATH.  This doesn't strike
> me as a good idea.

One already has to have a sufficiently complex parser to handle : splitting,
along with spaces and escaping, extending the parser to handle N=V is no
that much additional complication. In-line editing of LD_LIBRARY_PATH via
shell is not trivial, but setting and unsetting is easy.

I would *support* making APIs to properly handle env-var => array, and
array => env-var support, so one can insert into LD_LIBRARY_PATH or
GLIBC_TUNABLES easily from a C API.

e.g. https://github.com/sloria/environs
     https://12factor.net/config

In summary:

- Unsetting a single env var is easy.
- Setting it to an entirely new value is easy.
- Appending to the env var is easy.
- Rewriting the env var is hard.

This still weighs in favour of a single env var per control.

>> (b) When does a user know to use this env var?
> 
> When application developers who ship a glibc compatibility shim tell
> them to.

We should call this out in the NEWS entry then to make it clear how the
new feature is used.

>> - Is one scenario like this:
>>
>>   - They have an old library that doesn't provide all the symbols they need.
>>   - They implement a preload that provides the missing functionality.
>>   - They disable the check with an env var and run a newer program using 
>>     a mix of the old library and preload to provide all the required symbols.
>>
>> Is that a valid scenario? We should document something like this in
>> the NEWS entry.
> 
> The NEWS entry already references this scenario (LD_PRELOAD of new
> library versions).  If we had documentation for the dynamic loader, I
> would have added it there.

Thanks, I was just practicing 3 way communication here to try and
rewrite in my own words what I understood. Given that you seem to agree
I guess I understood it correctly.

We have proposed initial documentation for the dynamic loader:
https://sourceware.org/ml/libc-alpha/2017-10/msg00582.html

Someone needs to review it, and integrate it, and then we'd have a
place to put this.

I do not block your suggested fix, but it would be nice, as a senior
developer, to help us make incremental progress on this issue.

> We could also add a new DT_ tag to the LD_PRELOAD library and disable
> the check for the process if we encounter such a library.  That would
> simplify things for users.

Exactly! I was just going to suggest this, because if the use case is
designed for this exact scenario then the developer of the shim can
build the shim in a certain way to make it easier for the user.

The semantics of the new DT_ tag would be interesting, because we could
do something like:

DT_PROVIDE_VERSIONS="libc.so.6=GLIBC_2.18,GLIBC_2.19:libpthread=GLIBC_2.18,GLIBC_2.19"

Encoding at least some of the .gnu.version_r information in DT_VERSIONS
to allow us to make the error checking more robust.

This is just a thought.

>> What other useful scenarios are supported by the change?
> 
> I don't know of anything else.

Fine by me, the use case we already have is quite useful.

>> (c) Why not remove the check entirely?
> 
> I don't know if anyone uses Solaris-style versions (without associated
> symbols).  They would completely break as a result, in the sense that
> the version check for them is completely gone.

I don't follow, could you expand on this a bit, or verify that I understand
you correctly?

A Solaris-style version is where we have possibly one version recorded
per library, and it's the oldest version that covers the called APIs.

Which could be implemented as just having .gnu.version_r entries without
the associated symbol versions? Right?

I have never seen a Linux-based implementation of this, but I agree that
removing the check entirely would not support such an alternative use.

> Even without that, with lazy binding, users will start to see crashes in
> programs if their glibc (or libstdc++) is too old, instead of them
> failing to start at all.  I don't think that would be an improvement.

I agree. I wanted to ask the question because I think it's an interesting
thought experiment.

In summary:

- It is a conscious choice to use a new env var for the reasons outlined
  above.

- We should investigate using a new DT_* tag to remove the env var and
  make the solution transparent to users.

- The check should not be removed entirely because it is useful for
  normal builds and checking (error is up front at startup), and it
  supports Solaris-style implementations.

I think the real question is this:

Do we avoid the env var and try to implement it all as a DT_* tag?
Florian Weimer - Sept. 9, 2019, 3:09 p.m.
* Carlos O'Donell:

> I also do not think that LD_* is reserved for glibc, we now have shared
> semantics with musl, and soon llvm's libc, and I fully expect they will create
> their own environment variables here with an LD_* prefix. This means that we
> might be removing env vars that are used by musl or llvm-libc and we should not
> do that.

Sure, it's a shared namespace, like pretty much everything else.  I'd
love to coordinate such changes across libcs, but we don't have such a
forum today.

>> If we stuff everything into a single environment variable, applications
>> would have to write a parser for the variable contents even if they just
>> want to add a single directory to LD_LIBRARY_PATH.  This doesn't strike
>> me as a good idea.
>
> One already has to have a sufficiently complex parser to handle : splitting,
> along with spaces and escaping, extending the parser to handle N=V is no
> that much additional complication. In-line editing of LD_LIBRARY_PATH via
> shell is not trivial, but setting and unsetting is easy.

We'll need string quoting because any option separator we choose can in
paths.  At that point, you will be hard-pressed to implement this
processin using shell scripts.

> I would *support* making APIs to properly handle env-var => array, and
> array => env-var support, so one can insert into LD_LIBRARY_PATH or
> GLIBC_TUNABLES easily from a C API.
>
> e.g. https://github.com/sloria/environs
>      https://12factor.net/config

What's missing from the envz functions?

>> We could also add a new DT_ tag to the LD_PRELOAD library and disable
>> the check for the process if we encounter such a library.  That would
>> simplify things for users.
>
> Exactly! I was just going to suggest this, because if the use case is
> designed for this exact scenario then the developer of the shim can
> build the shim in a certain way to make it easier for the user.
>
> The semantics of the new DT_ tag would be interesting, because we could
> do something like:
>
> DT_PROVIDE_VERSIONS="libc.so.6=GLIBC_2.18,GLIBC_2.19:libpthread=GLIBC_2.18,GLIBC_2.19"
>
> Encoding at least some of the .gnu.version_r information in DT_VERSIONS
> to allow us to make the error checking more robust.
>
> This is just a thought.

I'll have to verify that this actually works out with the order things
are processed in the dynamic linker.

If it's more than a flag, I think we'd need a new data structure because
we have two arbitrary strings, soname and symbol version, and no clear
way to separate them.

>>> (c) Why not remove the check entirely?
>> 
>> I don't know if anyone uses Solaris-style versions (without associated
>> symbols).  They would completely break as a result, in the sense that
>> the version check for them is completely gone.
>
> I don't follow, could you expand on this a bit, or verify that I understand
> you correctly?
>
> A Solaris-style version is where we have possibly one version recorded
> per library, and it's the oldest version that covers the called APIs.
>
> Which could be implemented as just having .gnu.version_r entries without
> the associated symbol versions? Right?
>
> I have never seen a Linux-based implementation of this, but I agree that
> removing the check entirely would not support such an alternative use.

binutils has some remnants there.  I have never used it.

> I think the real question is this:
>
> Do we avoid the env var and try to implement it all as a DT_* tag?

For something that's just a hack to avoid rebasing glibc?  I don't think
so.  I suspect everyone would be better off if we could deliver
functionality provided by newer glibc versions in more direct ways.

A mere flag in DT_* might hit a sweet spot in terms of implementation
complexity and user convenience, though.

Thanks,
Florian

Patch

diff --git a/NEWS b/NEWS
index a64b89986a..4038bb3d5c 100644
--- a/NEWS
+++ b/NEWS
@@ -21,6 +21,14 @@  Major new features:
   18661-1:2014 and TS 18661-3:2015 as amended by the resolution of
   Clarification Request 13 to TS 18661-3.
 
+* A new command line option, --no-version-coverage-check, and a new
+  environment variable, LD_NO_VERSION_COVERAGE_CHECK=1, have been added to
+  the dynamic linker.  If enabled, it is possible to load objects which do
+  not provide the full set of version definitions required by other objects.
+  Instead, it is expected that the missing symbols and their versions are
+  provided by another shared object, via LD_PRELOAD.  In effect, this
+  permits adding new symbol versions to existing shared objects at run time.
+
 Deprecated and removed features, and other changes affecting compatibility:
 
 * The totalorder and totalordermag functions, and the corresponding
diff --git a/elf/Makefile b/elf/Makefile
index d470e41402..8ed23f6986 100644
--- a/elf/Makefile
+++ b/elf/Makefile
@@ -192,7 +192,8 @@  tests += restest1 preloadtest loadfail multiload origtest resolvfail \
 	 tst-latepthread tst-tls-manydynamic tst-nodelete-dlclose \
 	 tst-debug1 tst-main1 tst-absolute-sym tst-absolute-zero tst-big-note \
 	 tst-unwind-ctor tst-unwind-main tst-audit13 \
-	 tst-sonamemove-link tst-sonamemove-dlopen tst-dlopen-aout
+	 tst-sonamemove-link tst-sonamemove-dlopen tst-dlopen-aout \
+	 tst-no-version-coverage-lazy tst-no-version-coverage-now
 #	 reldep9
 tests-internal += loadtest unload unload2 circleload1 \
 	 neededtest neededtest2 neededtest3 neededtest4 \
@@ -279,7 +280,10 @@  modules-names = testobj1 testobj2 testobj3 testobj4 testobj5 testobj6 \
 		tst-main1mod tst-libc_dlvsym-dso tst-absolute-sym-lib \
 		tst-absolute-zero-lib tst-big-note-lib tst-unwind-ctor-lib \
 		tst-audit13mod1 tst-sonamemove-linkmod1 \
-		tst-sonamemove-runmod1 tst-sonamemove-runmod2
+		tst-sonamemove-runmod1 tst-sonamemove-runmod2 \
+		tst-no-version-coverage-linkmod \
+		tst-no-version-coverage-runmod \
+		tst-no-version-coverage-preloadmod
 # Most modules build with _ISOMAC defined, but those filtered out
 # depend on internal headers.
 modules-names-tests = $(filter-out ifuncmod% tst-libc_dlvsym-dso tst-tlsmod%,\
@@ -1432,6 +1436,32 @@  $(objpfx)tst-sonamemove-dlopen.out: \
   $(objpfx)tst-sonamemove-runmod1.so \
   $(objpfx)tst-sonamemove-runmod2.so
 
+LDFLAGS-tst-no-version-coverage-linkmod.so = \
+  -Wl,--version-script=tst-no-version-coverage-linkmod.map \
+  -Wl,-soname,tst-no-version-coverage-runmod.so
+LDFLAGS-tst-no-version-coverage-runmod.so = \
+  -Wl,--version-script=tst-no-version-coverage-runmod.map \
+  -Wl,-soname,tst-no-version-coverage-runmod.so
+LDFLAGS-tst-no-version-coverage-preloadmod.so = \
+  -Wl,--version-script=tst-no-version-coverage-preloadmod.map \
+  -Wl,-soname,tst-no-version-coverage-preloadmod.so
+LDFLAGS-tst-no-version-coverage-lazy = -Wl,-z,lazy
+$(objpfx)tst-no-version-coverage-lazy: \
+  $(objpfx)tst-no-version-coverage-linkmod.so
+tst-no-version-coverage-lazy-ENV = \
+  LD_NO_VERSION_COVERAGE_CHECK=1
+$(objpfx)tst-no-version-coverage-lazy.out: \
+  $(objpfx)tst-no-version-coverage-runmod.so
+LDFLAGS-tst-no-version-coverage-now = -Wl,-z,now
+$(objpfx)tst-no-version-coverage-now: \
+  $(objpfx)tst-no-version-coverage-linkmod.so
+tst-no-version-coverage-now-ENV = \
+  LD_NO_VERSION_COVERAGE_CHECK=1 \
+  LD_PRELOAD=tst-no-version-coverage-preloadmod.so
+$(objpfx)tst-no-version-coverage-now.out: \
+  $(objpfx)tst-no-version-coverage-preloadmod.so \
+  $(objpfx)tst-no-version-coverage-runmod.so
+
 # Override -z defs, so that we can reference an undefined symbol.
 # Force lazy binding for the same reason.
 LDFLAGS-tst-latepthreadmod.so = \
diff --git a/elf/dl-version.c b/elf/dl-version.c
index 53c0af3d15..e54b2cd7ff 100644
--- a/elf/dl-version.c
+++ b/elf/dl-version.c
@@ -125,6 +125,11 @@  checking for version `%s' in file %s [%lu] required by file %s [%lu]\n",
       def = (ElfW(Verdef) *) ((char *) def + def->vd_next);
     }
 
+  /* If the dynamic linker was told not check for version coverage, do
+     not report this error.  */
+  if (GLRO (dl_no_version_coverage_check))
+    return 0;
+
   /* Symbol not found.  If it was a weak reference it is not fatal.  */
   if (__glibc_likely (weak))
     {
diff --git a/elf/rtld.c b/elf/rtld.c
index c9490ff694..aed1b0d98c 100644
--- a/elf/rtld.c
+++ b/elf/rtld.c
@@ -1197,6 +1197,12 @@  dl_main (const ElfW(Phdr) *phdr,
 	    _dl_argc -= 2;
 	    _dl_argv += 2;
 	  }
+	else if (strcmp (_dl_argv[1], "--no-version-coverage-check") == 0)
+	  {
+	    GLRO (dl_no_version_coverage_check) = true;
+	    --_dl_argc;
+	    ++_dl_argv;
+	  }
 	else
 	  break;
 
@@ -1226,7 +1232,11 @@  of this helper program; chances are you did not intend to run this program.\n\
   --inhibit-rpath LIST  ignore RUNPATH and RPATH information in object names\n\
 			in LIST\n\
   --audit LIST          use objects named in LIST as auditors\n\
-  --preload LIST        preload objects named in LIST\n");
+  --preload LIST        preload objects named in LIST\n\
+  --no-version-coverage-check\n\
+                        allow loading of objects which reference symbol\n\
+                        versions which are missing from other objects\n\
+");
 
       ++_dl_skip_args;
       --_dl_argc;
@@ -2677,6 +2687,12 @@  process_envvars (enum mode *modep)
 	    mode = trace;
 	  break;
 
+	case 25:
+	  if (!__libc_enable_secure
+	      && memcmp (envline, "NO_VERSION_COVERAGE_CHECK", 25) == 0)
+	    GLRO (dl_no_version_coverage_check) = envline[26] == '1';
+	  break;
+
 	  /* We might have some extra environment variable to handle.  This
 	     is tricky due to the pre-processing of the length of the name
 	     in the switch statement here.  The code here assumes that added
diff --git a/elf/tst-no-version-coverage-lazy.c b/elf/tst-no-version-coverage-lazy.c
new file mode 100644
index 0000000000..71de1ac935
--- /dev/null
+++ b/elf/tst-no-version-coverage-lazy.c
@@ -0,0 +1,44 @@ 
+/* Test for the no-version-coverage mode in ld.so, with lazy binding.
+   Copyright (C) 2019 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <http://www.gnu.org/licenses/>.  */
+
+/* Defined in tst-no-version-coverage-linkmod.so and in
+   tst-no-version-coverage-runmod.so.  */
+void version_1_function (void);
+
+/* Only defined in tst-no-version-coverage-linkmod.so.  */
+void version_2_function (void);
+
+__attribute__ ((noinline, noclone, weak))
+void
+compiler_barrier (int call_2)
+{
+  version_1_function ();
+  if (call_2)
+    /* This function is not defined at run time, but the test should
+       still pass due to lazy binding.  */
+    version_2_function ();
+}
+
+static int
+do_test (void)
+{
+  compiler_barrier (0);
+  return 0;
+}
+
+#include <support/test-driver.c>
diff --git a/elf/tst-no-version-coverage-linkmod.c b/elf/tst-no-version-coverage-linkmod.c
new file mode 100644
index 0000000000..fb6c1a3a98
--- /dev/null
+++ b/elf/tst-no-version-coverage-linkmod.c
@@ -0,0 +1,29 @@ 
+/* Link interface for exercising the no-version-coverage mode in ld.so.
+   Copyright (C) 2019 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <http://www.gnu.org/licenses/>.  */
+
+/* This function is still present at run time.  */
+void
+version_1_function (void)
+{
+}
+
+/* This function will be removed at run time.  */
+void
+version_2_function (void)
+{
+}
diff --git a/elf/tst-no-version-coverage-linkmod.map b/elf/tst-no-version-coverage-linkmod.map
new file mode 100644
index 0000000000..c1126d8940
--- /dev/null
+++ b/elf/tst-no-version-coverage-linkmod.map
@@ -0,0 +1,7 @@ 
+VERSION_1 {
+  version_1_function;
+};
+
+VERSION_2 {
+  version_2_function;
+} VERSION_1;
diff --git a/elf/tst-no-version-coverage-now.c b/elf/tst-no-version-coverage-now.c
new file mode 100644
index 0000000000..3133c46d3b
--- /dev/null
+++ b/elf/tst-no-version-coverage-now.c
@@ -0,0 +1,35 @@ 
+/* Test for the no-version-coverage mode in ld.so, with BIND_NOW.
+   Copyright (C) 2019 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <http://www.gnu.org/licenses/>.  */
+
+/* Defined in tst-no-version-coverage-linkmod.so and in
+   tst-no-version-coverage-runmod.so.  */
+void version_1_function (void);
+
+/* Defined in tst-no-version-coverage-linkmod.so and in
+   tst-no-version-coverage-preloadmod.so.  */
+void version_2_function (void);
+
+static int
+do_test (void)
+{
+  version_1_function ();
+  version_2_function ();
+  return 0;
+}
+
+#include <support/test-driver.c>
diff --git a/elf/tst-no-version-coverage-preloadmod.c b/elf/tst-no-version-coverage-preloadmod.c
new file mode 100644
index 0000000000..4bff8c496c
--- /dev/null
+++ b/elf/tst-no-version-coverage-preloadmod.c
@@ -0,0 +1,23 @@ 
+/* Preloaded DSO for exercising the no-version-coverage mode in ld.so.
+   Copyright (C) 2019 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <http://www.gnu.org/licenses/>.  */
+
+/* This function supplies the required function.  */
+void
+version_2_function (void)
+{
+}
diff --git a/elf/tst-no-version-coverage-preloadmod.map b/elf/tst-no-version-coverage-preloadmod.map
new file mode 100644
index 0000000000..7613b6d7de
--- /dev/null
+++ b/elf/tst-no-version-coverage-preloadmod.map
@@ -0,0 +1,3 @@ 
+VERSION_2 {
+  version_2_function;
+};
diff --git a/elf/tst-no-version-coverage-runmod.c b/elf/tst-no-version-coverage-runmod.c
new file mode 100644
index 0000000000..39d9d063e3
--- /dev/null
+++ b/elf/tst-no-version-coverage-runmod.c
@@ -0,0 +1,23 @@ 
+/* Run-time DSO for exercising the no-version-coverage mode in ld.so.
+   Copyright (C) 2019 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <http://www.gnu.org/licenses/>.  */
+
+/* This function is still present at run time.  */
+void
+version_1_function (void)
+{
+}
diff --git a/elf/tst-no-version-coverage-runmod.map b/elf/tst-no-version-coverage-runmod.map
new file mode 100644
index 0000000000..c5d9e29554
--- /dev/null
+++ b/elf/tst-no-version-coverage-runmod.map
@@ -0,0 +1,3 @@ 
+VERSION_1 {
+  version_1_function;
+};
diff --git a/sysdeps/generic/ldsodefs.h b/sysdeps/generic/ldsodefs.h
index 1e193b05b0..4c75e207e9 100644
--- a/sysdeps/generic/ldsodefs.h
+++ b/sysdeps/generic/ldsodefs.h
@@ -629,6 +629,13 @@  struct rtld_global_ro
   /* List of auditing interfaces.  */
   struct audit_ifaces *_dl_audit;
   unsigned int _dl_naudit;
+#endif /* SHARED */
+
+  /* If true, allow references to versioned symbols which are not
+     defined.  */
+  bool _dl_no_version_coverage_check;
+
+#ifdef SHARED
 };
 # define __rtld_global_attribute__
 # if IS_IN (rtld)
@@ -645,7 +652,7 @@  extern const struct rtld_global_ro _rtld_global_ro
     attribute_relro __rtld_global_attribute__;
 # endif
 # undef __rtld_global_attribute__
-#endif
+#endif /* SHARED */
 #undef EXTERN
 
 #ifndef SHARED