V5 [PATCH 2/2] ldconfig/x86: Add ISA level check to glibc-hwcaps

Message ID 20201206144952.2109594-3-hjl.tools@gmail.com
State Superseded
Headers

Commit Message

H.J. Lu Dec. 6, 2020, 2:49 p.m. UTC
  Add ISA level check to detect misplaced shared objects with incompatible
ISA level requirement in glibc-hwcaps subdirectories:

/sbin/ldconfig: /usr/lib64/glibc-hwcaps/x86-64-v2/libx86-64-isa-level.so: skipped, ISA level mismatch (x86-64-v4 > x86-64-v2)
---
 elf/ldconfig.c                                |  9 +-
 elf/readelflib.c                              | 83 ++++++++++++++++++-
 elf/readlib.c                                 |  7 +-
 elf/tst-glibc-hwcaps-2-cache.c                | 45 ++++++++++
 .../etc/ld.so.conf                            |  2 +
 .../postclean.req                             |  0
 elf/tst-glibc-hwcaps-2-cache.script           |  6 ++
 sysdeps/generic/ldconfig.h                    |  9 +-
 sysdeps/generic/read-prop.h                   | 32 +++++++
 sysdeps/unix/sysv/linux/x86/read-prop.h       | 57 +++++++++++++
 sysdeps/unix/sysv/linux/x86/readelflib.c      | 67 +++++++++++++--
 sysdeps/unix/sysv/linux/x86_64/Makefile       | 26 ++++++
 .../sysv/linux/x86_64/tst-glibc-hwcaps-2.c    | 50 +++++++++++
 .../linux/x86_64/x86-64-isa-level-VALUE.c     |  4 +
 14 files changed, 379 insertions(+), 18 deletions(-)
 create mode 100644 elf/tst-glibc-hwcaps-2-cache.c
 create mode 100644 elf/tst-glibc-hwcaps-2-cache.root/etc/ld.so.conf
 create mode 100644 elf/tst-glibc-hwcaps-2-cache.root/postclean.req
 create mode 100644 elf/tst-glibc-hwcaps-2-cache.script
 create mode 100644 sysdeps/generic/read-prop.h
 create mode 100644 sysdeps/unix/sysv/linux/x86/read-prop.h
 create mode 100644 sysdeps/unix/sysv/linux/x86_64/tst-glibc-hwcaps-2.c
 create mode 100644 sysdeps/unix/sysv/linux/x86_64/x86-64-isa-level-VALUE.c
  

Comments

Florian Weimer Dec. 7, 2020, 10 a.m. UTC | #1
* H. J. Lu via Libc-alpha:

> Add ISA level check to detect misplaced shared objects with incompatible
> ISA level requirement in glibc-hwcaps subdirectories:
>
> /sbin/ldconfig: /usr/lib64/glibc-hwcaps/x86-64-v2/libx86-64-isa-level.so: skipped, ISA level mismatch (x86-64-v4 > x86-64-v2)

I think this is conceptually the wrong approach.  ldconfig should copy
the notes into the cache (and aux cache), and ld.so should skip cache
contents that doesn't match the run-time requirements during load.  Then
directory place does not matter that much anymore.

Thanks,
Florian
  
H.J. Lu Dec. 8, 2020, 1:25 p.m. UTC | #2
On Mon, Dec 7, 2020 at 2:00 AM Florian Weimer <fweimer@redhat.com> wrote:
>
> * H. J. Lu via Libc-alpha:
>
> > Add ISA level check to detect misplaced shared objects with incompatible
> > ISA level requirement in glibc-hwcaps subdirectories:
> >
> > /sbin/ldconfig: /usr/lib64/glibc-hwcaps/x86-64-v2/libx86-64-isa-level.so: skipped, ISA level mismatch (x86-64-v4 > x86-64-v2)
>
> I think this is conceptually the wrong approach.  ldconfig should copy
> the notes into the cache (and aux cache), and ld.so should skip cache
> contents that doesn't match the run-time requirements during load.  Then
> directory place does not matter that much anymore.
>

Do we have space in cache for it?
  
Florian Weimer Dec. 8, 2020, 1:31 p.m. UTC | #3
* H. J. Lu:

> On Mon, Dec 7, 2020 at 2:00 AM Florian Weimer <fweimer@redhat.com> wrote:
>>
>> * H. J. Lu via Libc-alpha:
>>
>> > Add ISA level check to detect misplaced shared objects with incompatible
>> > ISA level requirement in glibc-hwcaps subdirectories:
>> >
>> > /sbin/ldconfig: /usr/lib64/glibc-hwcaps/x86-64-v2/libx86-64-isa-level.so: skipped, ISA level mismatch (x86-64-v4 > x86-64-v2)
>>
>> I think this is conceptually the wrong approach.  ldconfig should copy
>> the notes into the cache (and aux cache), and ld.so should skip cache
>> contents that doesn't match the run-time requirements during load.  Then
>> directory place does not matter that much anymore.
>
> Do we have space in cache for it?

We have an extension mechanism.  The relevant struct looks like this:

struct file_entry_new
{
  int32_t flags;		/* This is 1 for an ELF library.  */
  uint32_t key, value;	/* String table indices.  */
  uint32_t osversion;		/* Required OS version.	 */
  uint64_t hwcap;		/* Hwcap entry.	 */
};

We can set a previously-unused bit hwcap to hide entries from current
ld.so.  This way, we can repurpose other hwcap bits, osversion, even
flags.  We could put an index there into a array of (property) notes,
and use deduplication to share identical notes from different shared
objects.

In essence, this is how the glibc-hwcaps subdirectory mechanism achieves
backwards-compatibility.

Thanks,
Florian
  
H.J. Lu Dec. 8, 2020, 2:10 p.m. UTC | #4
On Tue, Dec 8, 2020 at 5:32 AM Florian Weimer <fweimer@redhat.com> wrote:
>
> * H. J. Lu:
>
> > On Mon, Dec 7, 2020 at 2:00 AM Florian Weimer <fweimer@redhat.com> wrote:
> >>
> >> * H. J. Lu via Libc-alpha:
> >>
> >> > Add ISA level check to detect misplaced shared objects with incompatible
> >> > ISA level requirement in glibc-hwcaps subdirectories:
> >> >
> >> > /sbin/ldconfig: /usr/lib64/glibc-hwcaps/x86-64-v2/libx86-64-isa-level.so: skipped, ISA level mismatch (x86-64-v4 > x86-64-v2)
> >>
> >> I think this is conceptually the wrong approach.  ldconfig should copy
> >> the notes into the cache (and aux cache), and ld.so should skip cache
> >> contents that doesn't match the run-time requirements during load.  Then
> >> directory place does not matter that much anymore.
> >
> > Do we have space in cache for it?
>
> We have an extension mechanism.  The relevant struct looks like this:
>
> struct file_entry_new
> {
>   int32_t flags;                /* This is 1 for an ELF library.  */
>   uint32_t key, value;  /* String table indices.  */
>   uint32_t osversion;           /* Required OS version.  */
>   uint64_t hwcap;               /* Hwcap entry.  */
> };
>
> We can set a previously-unused bit hwcap to hide entries from current
> ld.so.  This way, we can repurpose other hwcap bits, osversion, even
> flags.  We could put an index there into a array of (property) notes,
> and use deduplication to share identical notes from different shared
> objects.
>
> In essence, this is how the glibc-hwcaps subdirectory mechanism achieves
> backwards-compatibility.
>

For each file entry for a shared object, the hwcap field has been used by
DL_CACHE_HWCAP_EXTENSION for glibc-hwcaps.  Are you suggesting
to add another file entry for the same shared object to store ISA level
requirement?
  
Florian Weimer Dec. 8, 2020, 2:18 p.m. UTC | #5
* H. J. Lu:

> On Tue, Dec 8, 2020 at 5:32 AM Florian Weimer <fweimer@redhat.com> wrote:
>>
>> * H. J. Lu:
>>
>> > On Mon, Dec 7, 2020 at 2:00 AM Florian Weimer <fweimer@redhat.com> wrote:
>> >>
>> >> * H. J. Lu via Libc-alpha:
>> >>
>> >> > Add ISA level check to detect misplaced shared objects with incompatible
>> >> > ISA level requirement in glibc-hwcaps subdirectories:
>> >> >
>> >> > /sbin/ldconfig: /usr/lib64/glibc-hwcaps/x86-64-v2/libx86-64-isa-level.so: skipped, ISA level mismatch (x86-64-v4 > x86-64-v2)
>> >>
>> >> I think this is conceptually the wrong approach.  ldconfig should copy
>> >> the notes into the cache (and aux cache), and ld.so should skip cache
>> >> contents that doesn't match the run-time requirements during load.  Then
>> >> directory place does not matter that much anymore.
>> >
>> > Do we have space in cache for it?
>>
>> We have an extension mechanism.  The relevant struct looks like this:
>>
>> struct file_entry_new
>> {
>>   int32_t flags;                /* This is 1 for an ELF library.  */
>>   uint32_t key, value;  /* String table indices.  */
>>   uint32_t osversion;           /* Required OS version.  */
>>   uint64_t hwcap;               /* Hwcap entry.  */
>> };
>>
>> We can set a previously-unused bit hwcap to hide entries from current
>> ld.so.  This way, we can repurpose other hwcap bits, osversion, even
>> flags.  We could put an index there into a array of (property) notes,
>> and use deduplication to share identical notes from different shared
>> objects.
>>
>> In essence, this is how the glibc-hwcaps subdirectory mechanism achieves
>> backwards-compatibility.
>>
>
> For each file entry for a shared object, the hwcap field has been used by
> DL_CACHE_HWCAP_EXTENSION for glibc-hwcaps.  Are you suggesting
> to add another file entry for the same shared object to store ISA level
> requirement?

It's a 64-bit field.  We do this:

/* This bit in the hwcap field of struct file_entry_new indicates that
   the lower 32 bits contain an index into the
   cache_extension_tag_glibc_hwcaps section.  Older glibc versions do
   not know about this HWCAP bit, so they will ignore these
   entries.  */
#define DL_CACHE_HWCAP_EXTENSION (1ULL << 62)

/* Return true if the ENTRY->hwcap value indicates that
   DL_CACHE_HWCAP_EXTENSION is used.  */
static inline bool
dl_cache_hwcap_extension (struct file_entry_new *entry)
{
  /* If DL_CACHE_HWCAP_EXTENSION is set, but other bits as well, this
     is a different kind of extension.  */
  return (entry->hwcap >> 32) == (DL_CACHE_HWCAP_EXTENSION >> 32);
}

So we can use a different bit pattern involving DL_CACHE_HWCAP_EXTENSION
to express something else.  In particular, only the lower 32 bits are
currently used as an index.

Thanks,
Florian
  
H.J. Lu Dec. 8, 2020, 2:23 p.m. UTC | #6
On Tue, Dec 8, 2020 at 6:18 AM Florian Weimer <fweimer@redhat.com> wrote:
>
> * H. J. Lu:
>
> > On Tue, Dec 8, 2020 at 5:32 AM Florian Weimer <fweimer@redhat.com> wrote:
> >>
> >> * H. J. Lu:
> >>
> >> > On Mon, Dec 7, 2020 at 2:00 AM Florian Weimer <fweimer@redhat.com> wrote:
> >> >>
> >> >> * H. J. Lu via Libc-alpha:
> >> >>
> >> >> > Add ISA level check to detect misplaced shared objects with incompatible
> >> >> > ISA level requirement in glibc-hwcaps subdirectories:
> >> >> >
> >> >> > /sbin/ldconfig: /usr/lib64/glibc-hwcaps/x86-64-v2/libx86-64-isa-level.so: skipped, ISA level mismatch (x86-64-v4 > x86-64-v2)
> >> >>
> >> >> I think this is conceptually the wrong approach.  ldconfig should copy
> >> >> the notes into the cache (and aux cache), and ld.so should skip cache
> >> >> contents that doesn't match the run-time requirements during load.  Then
> >> >> directory place does not matter that much anymore.
> >> >
> >> > Do we have space in cache for it?
> >>
> >> We have an extension mechanism.  The relevant struct looks like this:
> >>
> >> struct file_entry_new
> >> {
> >>   int32_t flags;                /* This is 1 for an ELF library.  */
> >>   uint32_t key, value;  /* String table indices.  */
> >>   uint32_t osversion;           /* Required OS version.  */
> >>   uint64_t hwcap;               /* Hwcap entry.  */
> >> };
> >>
> >> We can set a previously-unused bit hwcap to hide entries from current
> >> ld.so.  This way, we can repurpose other hwcap bits, osversion, even
> >> flags.  We could put an index there into a array of (property) notes,
> >> and use deduplication to share identical notes from different shared
> >> objects.
> >>
> >> In essence, this is how the glibc-hwcaps subdirectory mechanism achieves
> >> backwards-compatibility.
> >>
> >
> > For each file entry for a shared object, the hwcap field has been used by
> > DL_CACHE_HWCAP_EXTENSION for glibc-hwcaps.  Are you suggesting
> > to add another file entry for the same shared object to store ISA level
> > requirement?
>
> It's a 64-bit field.  We do this:
>
> /* This bit in the hwcap field of struct file_entry_new indicates that
>    the lower 32 bits contain an index into the
>    cache_extension_tag_glibc_hwcaps section.  Older glibc versions do
>    not know about this HWCAP bit, so they will ignore these
>    entries.  */
> #define DL_CACHE_HWCAP_EXTENSION (1ULL << 62)
>
> /* Return true if the ENTRY->hwcap value indicates that
>    DL_CACHE_HWCAP_EXTENSION is used.  */
> static inline bool
> dl_cache_hwcap_extension (struct file_entry_new *entry)
> {
>   /* If DL_CACHE_HWCAP_EXTENSION is set, but other bits as well, this
>      is a different kind of extension.  */
>   return (entry->hwcap >> 32) == (DL_CACHE_HWCAP_EXTENSION >> 32);
> }
>
> So we can use a different bit pattern involving DL_CACHE_HWCAP_EXTENSION
> to express something else.  In particular, only the lower 32 bits are
> currently used as an index.

So bits 33-47 can be used for ISA level requirement.  This should support
65536 ISA levels.
  
Florian Weimer Dec. 8, 2020, 3:15 p.m. UTC | #7
* H. J. Lu:

>> > For each file entry for a shared object, the hwcap field has been used by
>> > DL_CACHE_HWCAP_EXTENSION for glibc-hwcaps.  Are you suggesting
>> > to add another file entry for the same shared object to store ISA level
>> > requirement?
>>
>> It's a 64-bit field.  We do this:
>>
>> /* This bit in the hwcap field of struct file_entry_new indicates that
>>    the lower 32 bits contain an index into the
>>    cache_extension_tag_glibc_hwcaps section.  Older glibc versions do
>>    not know about this HWCAP bit, so they will ignore these
>>    entries.  */
>> #define DL_CACHE_HWCAP_EXTENSION (1ULL << 62)
>>
>> /* Return true if the ENTRY->hwcap value indicates that
>>    DL_CACHE_HWCAP_EXTENSION is used.  */
>> static inline bool
>> dl_cache_hwcap_extension (struct file_entry_new *entry)
>> {
>>   /* If DL_CACHE_HWCAP_EXTENSION is set, but other bits as well, this
>>      is a different kind of extension.  */
>>   return (entry->hwcap >> 32) == (DL_CACHE_HWCAP_EXTENSION >> 32);
>> }
>>
>> So we can use a different bit pattern involving DL_CACHE_HWCAP_EXTENSION
>> to express something else.  In particular, only the lower 32 bits are
>> currently used as an index.
>
> So bits 33-47 can be used for ISA level requirement.  This should support
> 65536 ISA levels.

The question is whether the ISA levels and the glibc-hwcaps name are
used in parallel.  If not, we can use  (1ULL << 62) | (1ULL << 32) as
the marker bits and a 32-bit index in the lower half.

(I do not think this is necessarily relevant for levels, but it could be
used for locating objects according to other criteria.)

Thanks,
Florian
  
H.J. Lu Dec. 8, 2020, 3:31 p.m. UTC | #8
On Tue, Dec 8, 2020 at 7:16 AM Florian Weimer <fweimer@redhat.com> wrote:
>
> * H. J. Lu:
>
> >> > For each file entry for a shared object, the hwcap field has been used by
> >> > DL_CACHE_HWCAP_EXTENSION for glibc-hwcaps.  Are you suggesting
> >> > to add another file entry for the same shared object to store ISA level
> >> > requirement?
> >>
> >> It's a 64-bit field.  We do this:
> >>
> >> /* This bit in the hwcap field of struct file_entry_new indicates that
> >>    the lower 32 bits contain an index into the
> >>    cache_extension_tag_glibc_hwcaps section.  Older glibc versions do
> >>    not know about this HWCAP bit, so they will ignore these
> >>    entries.  */
> >> #define DL_CACHE_HWCAP_EXTENSION (1ULL << 62)
> >>
> >> /* Return true if the ENTRY->hwcap value indicates that
> >>    DL_CACHE_HWCAP_EXTENSION is used.  */
> >> static inline bool
> >> dl_cache_hwcap_extension (struct file_entry_new *entry)
> >> {
> >>   /* If DL_CACHE_HWCAP_EXTENSION is set, but other bits as well, this
> >>      is a different kind of extension.  */
> >>   return (entry->hwcap >> 32) == (DL_CACHE_HWCAP_EXTENSION >> 32);
> >> }
> >>
> >> So we can use a different bit pattern involving DL_CACHE_HWCAP_EXTENSION
> >> to express something else.  In particular, only the lower 32 bits are
> >> currently used as an index.
> >
> > So bits 33-47 can be used for ISA level requirement.  This should support
> > 65536 ISA levels.
>
> The question is whether the ISA levels and the glibc-hwcaps name are
> used in parallel.  If not, we can use  (1ULL << 62) | (1ULL << 32) as
> the marker bits and a 32-bit index in the lower half.
>
> (I do not think this is necessarily relevant for levels, but it could be
> used for locating objects according to other criteria.)
>

I think ISA level and glibc-hwcaps should go together.
  

Patch

diff --git a/elf/ldconfig.c b/elf/ldconfig.c
index 10927a8c7f..f306d3df26 100644
--- a/elf/ldconfig.c
+++ b/elf/ldconfig.c
@@ -720,8 +720,8 @@  manual_link (char *library)
       return;
     }
 
-  if (process_file (real_library, library, libname, &flag, &osversion,
-		    &soname, 0, &stat_buf))
+  if (process_file (real_library, library, libname, NULL, &flag,
+		    &osversion, &soname, 0, &stat_buf))
     {
       error (0, 0, _("No link created since soname could not be found for %s"),
 	     library);
@@ -982,8 +982,9 @@  search_dir (const struct dir_entry *entry)
       unsigned int osversion;
       if (!search_aux_cache (&lstat_buf, &flag, &osversion, &soname))
 	{
-	  if (process_file (real_name, file_name, direntry->d_name, &flag,
-			    &osversion, &soname, is_link, &lstat_buf))
+	  if (process_file (real_name, file_name, direntry->d_name,
+			    entry->hwcaps, &flag, &osversion,
+			    &soname, is_link, &lstat_buf))
 	    {
 	      if (real_name != real_file_name)
 		free (real_name);
diff --git a/elf/readelflib.c b/elf/readelflib.c
index 5905f6d344..0784bbcc1f 100644
--- a/elf/readelflib.c
+++ b/elf/readelflib.c
@@ -17,6 +17,8 @@ 
    License along with the GNU C Library; if not, see
    <https://www.gnu.org/licenses/>.  */
 
+#include <read-prop.h>
+
 /* This code is a heavily simplified version of the readelf program
    that's part of the current binutils development version.  For architectures
    which need to handle both 32bit and 64bit ELF libraries,  this file is
@@ -39,7 +41,8 @@  do								\
 
 /* Returns 0 if everything is ok, != 0 in case of error.  */
 int
-process_elf_file (const char *file_name, const char *lib, int *flag,
+process_elf_file (const char *file_name, const char *lib,
+		  struct glibc_hwcaps_subdirectory *hwcaps, int *flag,
 		  unsigned int *osversion, char **soname, void *file_contents,
 		  size_t file_length)
 {
@@ -164,6 +167,84 @@  process_elf_file (const char *file_name, const char *lib, int *flag,
 	    }
 	  break;
 
+	case PT_GNU_PROPERTY:
+	  /* The NT_GNU_PROPERTY_TYPE_0 note must be aligned to 4 bytes
+	     in 32-bit objects and to 8 bytes in 64-bit objects.  Skip
+	     notes with incorrect alignment.  */
+	  if (segment->p_align == (__ELF_NATIVE_CLASS / 8))
+	    {
+	      const ElfW(Nhdr) *note = (const void *) (file_contents
+						       + segment->p_offset);
+	      const ElfW(Addr) size = segment->p_filesz;
+	      const ElfW(Addr) align = segment->p_align;
+
+	      const ElfW(Addr) start = (ElfW(Addr)) (uintptr_t) note;
+	      unsigned int last_type = 0;
+
+	      while ((ElfW(Addr)) (uintptr_t) (note + 1) - start < size)
+		{
+		  /* Find the NT_GNU_PROPERTY_TYPE_0 note.  */
+		  if (note->n_namesz == 4
+		      && note->n_type == NT_GNU_PROPERTY_TYPE_0
+		      && memcmp (note + 1, "GNU", 4) == 0)
+		    {
+		      /* Check for invalid property.  */
+		      if (note->n_descsz < 8
+			  || (note->n_descsz % sizeof (ElfW(Addr))) != 0)
+			goto done;
+
+		      /* Start and end of property array.  */
+		      unsigned char *ptr = (unsigned char *) (note + 1) + 4;
+		      unsigned char *ptr_end = ptr + note->n_descsz;
+
+		      do
+			{
+			  unsigned int type = *(unsigned int *) ptr;
+			  unsigned int datasz = *(unsigned int *) (ptr + 4);
+
+			  /* Property type must be in ascending order.  */
+			  if (type < last_type)
+			    goto done;
+
+			  ptr += 8;
+			  if ((ptr + datasz) > ptr_end)
+			    goto done;
+
+			  last_type = type;
+
+			  /* Target specific property processing.
+			     Return value:
+				0: Stop processing the properties.
+			      > 0: Continue processing the properties.
+			      < 0: Stop and skip the file.
+			   */
+			  int read_prop
+			    = ElfW(read_gnu_property) (hwcaps, file_name,
+						       segment, type,
+						       datasz, ptr);
+			  if (read_prop == 0)
+			    goto done;
+			  else if (read_prop < 0)
+			    return 1;
+
+			  /* Check the next property item.  */
+			  ptr += ALIGN_UP (datasz, sizeof (ElfW(Addr)));
+			}
+		      while ((ptr_end - ptr) >= 8);
+
+		      /* Only handle one NT_GNU_PROPERTY_TYPE_0.  */
+		      goto done;
+		    }
+
+		  note = ((const void *) note
+			  + ELF_NOTE_NEXT_OFFSET (note->n_namesz,
+						  note->n_descsz,
+						  align));
+		}
+	    }
+done:
+	  break;
+
 	default:
 	  break;
 	}
diff --git a/elf/readlib.c b/elf/readlib.c
index 994a4426a1..400f64bafa 100644
--- a/elf/readlib.c
+++ b/elf/readlib.c
@@ -74,7 +74,8 @@  is_gdb_python_file (const char *name)
 /* Returns 0 if everything is ok, != 0 in case of error.  */
 int
 process_file (const char *real_file_name, const char *file_name,
-	      const char *lib, int *flag, unsigned int *osversion,
+	      const char *lib, struct glibc_hwcaps_subdirectory *hwcaps,
+	      int *flag, unsigned int *osversion,
 	      char **soname, int is_link, struct stat64 *stat_buf)
 {
   FILE *file;
@@ -173,8 +174,8 @@  process_file (const char *real_file_name, const char *file_name,
   /* Libraries have to be shared object files.  */
   else if (elf_header->e_type != ET_DYN)
     ret = 1;
-  else if (process_elf_file (file_name, lib, flag, osversion, soname,
-			     file_contents, statbuf.st_size))
+  else if (process_elf_file (file_name, lib, hwcaps, flag, osversion,
+			     soname, file_contents, statbuf.st_size))
     ret = 1;
 
  done:
diff --git a/elf/tst-glibc-hwcaps-2-cache.c b/elf/tst-glibc-hwcaps-2-cache.c
new file mode 100644
index 0000000000..2c30a2c911
--- /dev/null
+++ b/elf/tst-glibc-hwcaps-2-cache.c
@@ -0,0 +1,45 @@ 
+/* Wrapper to invoke tst-glibc-hwcaps-2 in a container to test ldconfig.
+   Copyright (C) 2020 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
+   <https://www.gnu.org/licenses/>.  */
+
+/* This program is just a wrapper that runs ldconfig followed by
+   tst-glibc-hwcaps-2.  The actual test is provided via an
+   implementation in a sysdeps subdirectory.  */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <support/support.h>
+#include <unistd.h>
+
+int
+main (int argc, char **argv)
+{
+  /* Run ldconfig to populate the cache.  */
+  {
+    char *command = xasprintf ("%s/ldconfig", support_install_rootsbindir);
+    if (system (command) != 0)
+      return 1;
+    free (command);
+  }
+
+  /* Reuse tst-glibc-hwcaps.  Since this code is running in a
+     container, we can launch it directly.  */
+  char *path = xasprintf ("%s/elf/tst-glibc-hwcaps-2", support_objdir_root);
+  execv (path, argv);
+  printf ("error: execv of %s failed: %m\n", path);
+  return 1;
+}
diff --git a/elf/tst-glibc-hwcaps-2-cache.root/etc/ld.so.conf b/elf/tst-glibc-hwcaps-2-cache.root/etc/ld.so.conf
new file mode 100644
index 0000000000..e1e74dbda2
--- /dev/null
+++ b/elf/tst-glibc-hwcaps-2-cache.root/etc/ld.so.conf
@@ -0,0 +1,2 @@ 
+# This file was created to suppress a warning from ldconfig:
+# /sbin/ldconfig: Warning: ignoring configuration file that cannot be opened: /etc/ld.so.conf: No such file or directory
diff --git a/elf/tst-glibc-hwcaps-2-cache.root/postclean.req b/elf/tst-glibc-hwcaps-2-cache.root/postclean.req
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/elf/tst-glibc-hwcaps-2-cache.script b/elf/tst-glibc-hwcaps-2-cache.script
new file mode 100644
index 0000000000..29ccfc3b49
--- /dev/null
+++ b/elf/tst-glibc-hwcaps-2-cache.script
@@ -0,0 +1,6 @@ 
+# test-container does not support scripts in sysdeps directories, so
+# collect everything in one file.
+
+mkdirp 0770 $L/glibc-hwcaps/x86-64-v2
+cp $B/elf/libx86-64-isa-level-1.so $L/libx86-64-isa-level.so
+cp $B/elf/libx86-64-isa-level-4.so $L/glibc-hwcaps/x86-64-v2/libx86-64-isa-level.so
diff --git a/sysdeps/generic/ldconfig.h b/sysdeps/generic/ldconfig.h
index 1ad1528890..f85eaa48cb 100644
--- a/sysdeps/generic/ldconfig.h
+++ b/sysdeps/generic/ldconfig.h
@@ -88,13 +88,16 @@  extern void save_aux_cache (const char *aux_cache_name);
 
 /* Declared in readlib.c.  */
 extern int process_file (const char *real_file_name, const char *file_name,
-			 const char *lib, int *flag, unsigned int *osversion,
-			 char **soname, int is_link, struct stat64 *stat_buf);
+			 const char *lib, struct glibc_hwcaps_subdirectory *,
+			 int *flag, unsigned int *osversion, char **soname,
+			 int is_link, struct stat64 *stat_buf);
 
 extern char *implicit_soname (const char *lib, int flag);
 
 /* Declared in readelflib.c.  */
-extern int process_elf_file (const char *file_name, const char *lib, int *flag,
+extern int process_elf_file (const char *file_name, const char *lib,
+			     struct glibc_hwcaps_subdirectory *hwcaps,
+			     int *flag,
 			     unsigned int *osversion, char **soname,
 			     void *file_contents, size_t file_length);
 
diff --git a/sysdeps/generic/read-prop.h b/sysdeps/generic/read-prop.h
new file mode 100644
index 0000000000..49092f83f1
--- /dev/null
+++ b/sysdeps/generic/read-prop.h
@@ -0,0 +1,32 @@ 
+/* Support for GNU properties in ldconfig.  Generic version.
+   Copyright (C) 2020 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
+   <https://www.gnu.org/licenses/>.  */
+
+/* Called for each property in the NT_GNU_PROPERTY_TYPE_0 note of SEGMENT.
+   Return value:
+      0: Stop processing the properties.
+    > 0: Continue processing the properties.
+    < 0: Stop and skip the file.
+ */
+
+static inline int __attribute__ ((always_inline))
+ElfW(read_gnu_property) (struct glibc_hwcaps_subdirectory *hwcaps,
+			 const char *file_name, ElfW(Phdr) *segment,
+			 uint32_t type, uint32_t datasz, void *data)
+{
+  return 0;
+}
diff --git a/sysdeps/unix/sysv/linux/x86/read-prop.h b/sysdeps/unix/sysv/linux/x86/read-prop.h
new file mode 100644
index 0000000000..c22395f851
--- /dev/null
+++ b/sysdeps/unix/sysv/linux/x86/read-prop.h
@@ -0,0 +1,57 @@ 
+/* Support for GNU properties in ldconfig.  x86 version.
+   Copyright (C) 2020 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
+   <https://www.gnu.org/licenses/>.  */
+
+/* Called for each property in the NT_GNU_PROPERTY_TYPE_0 note of SEGMENT.
+   Return value:
+      0: Stop processing the properties.
+    > 0: Continue processing the properties.
+    < 0: Stop and skip the file for ISA level mismatch.
+ */
+
+static inline int __attribute__ ((always_inline))
+ElfW(read_gnu_property) (struct glibc_hwcaps_subdirectory *hwcaps,
+			 const char *file_name, ElfW(Phdr) *segment,
+			 uint32_t type, uint32_t datasz, void *data)
+{
+  if (type == GNU_PROPERTY_X86_ISA_1_NEEDED)
+    {
+      /* The size of GNU_PROPERTY_X86_ISA_1_NEEDED must be 4 bytes.  There
+	 is no point to continue if this type is ill-formed.  */
+      if (hwcaps != NULL && datasz == 4)
+	{
+	  const char *isa_string = glibc_hwcaps_subdirectory_name (hwcaps);
+	  if (isa_string != NULL)
+	    {
+	      unsigned int isa_1_needed = *(unsigned int *) data;
+	      unsigned int isa_level;
+	      if (get_isa_level_from_string (isa_string, &isa_level) == 0
+		  && isa_1_needed > isa_level)
+		{
+		  error (0, 0,
+			 _("%s: skipped, ISA level mismatch (%s > %s)"),
+			 file_name,
+			 get_isa_string_from_level (isa_1_needed),
+			 isa_string);
+		  return -1;
+		}
+	    }
+	}
+      return 0;
+    }
+  return 1;
+}
diff --git a/sysdeps/unix/sysv/linux/x86/readelflib.c b/sysdeps/unix/sysv/linux/x86/readelflib.c
index 3e83419f5b..336ba57ae7 100644
--- a/sysdeps/unix/sysv/linux/x86/readelflib.c
+++ b/sysdeps/unix/sysv/linux/x86/readelflib.c
@@ -17,17 +17,70 @@ 
    License along with the GNU C Library; if not, see
    <https://www.gnu.org/licenses/>.  */
 
+/* Get ISA level string from GNU_PROPERTY_X86_ISA_1_V[234] ISA level.  */
 
-int process_elf32_file (const char *file_name, const char *lib, int *flag,
+static const char *
+get_isa_string_from_level (unsigned int isa_level)
+{
+  if ((isa_level & GNU_PROPERTY_X86_ISA_1_V4))
+    return "x86-64-v4";
+  else if ((isa_level & GNU_PROPERTY_X86_ISA_1_V3))
+    return "x86-64-v3";
+  else if ((isa_level & GNU_PROPERTY_X86_ISA_1_V2))
+    return "x86-64-v2";
+  abort ();
+  return NULL;
+}
+
+/* Get GNU_PROPERTY_X86_ISA_1_V[234] ISA level from ISA level string.  */
+
+static int
+get_isa_level_from_string (const char *level, unsigned int *isa_level_p)
+{
+  if (memcmp (level, "x86-64-v", 8) == 0)
+    {
+      unsigned int isa_level = 0;
+      if (level[9] == '\0')
+	switch (level[8])
+	  {
+	  case '2':
+	    isa_level = (GNU_PROPERTY_X86_ISA_1_BASELINE
+			 | GNU_PROPERTY_X86_ISA_1_V2);
+	    break;
+	  case '3':
+	    isa_level = (GNU_PROPERTY_X86_ISA_1_BASELINE
+			 | GNU_PROPERTY_X86_ISA_1_V2
+			 | GNU_PROPERTY_X86_ISA_1_V3);
+	    break;
+	  case '4':
+	    isa_level = (GNU_PROPERTY_X86_ISA_1_BASELINE
+			 | GNU_PROPERTY_X86_ISA_1_V2
+			 | GNU_PROPERTY_X86_ISA_1_V3
+			 | GNU_PROPERTY_X86_ISA_1_V4);
+	  break;
+	}
+      if (isa_level)
+	{
+	  *isa_level_p = isa_level;
+	  return 0;
+	}
+    }
+  return -1;
+}
+
+int process_elf32_file (const char *file_name, const char *lib,
+			struct glibc_hwcaps_subdirectory *hwcaps, int *flag,
 			unsigned int *osversion, char **soname,
 			void *file_contents, size_t file_length);
-int process_elf64_file (const char *file_name, const char *lib, int *flag,
+int process_elf64_file (const char *file_name, const char *lib,
+			struct glibc_hwcaps_subdirectory *hwcaps, int *flag,
 			unsigned int *osversion, char **soname,
 			void *file_contents, size_t file_length);
 
 /* Returns 0 if everything is ok, != 0 in case of error.  */
 int
-process_elf_file (const char *file_name, const char *lib, int *flag,
+process_elf_file (const char *file_name, const char *lib,
+		  struct glibc_hwcaps_subdirectory *hwcaps, int *flag,
 		  unsigned int *osversion, char **soname, void *file_contents,
 		  size_t file_length)
 {
@@ -68,11 +121,11 @@  failed:
     }
 
   if (elf_header->e_ident[EI_CLASS] == ELFCLASS32)
-    ret = process_elf32_file (file_name, lib, flag, osversion, soname,
-			      file_contents, file_length);
+    ret = process_elf32_file (file_name, lib, hwcaps, flag, osversion,
+			      soname, file_contents, file_length);
   else
-    ret = process_elf64_file (file_name, lib, flag, osversion, soname,
-			      file_contents, file_length);
+    ret = process_elf64_file (file_name, lib, hwcaps, flag, osversion,
+			      soname, file_contents, file_length);
 
   if (!ret && file_flag)
     *flag = file_flag;
diff --git a/sysdeps/unix/sysv/linux/x86_64/Makefile b/sysdeps/unix/sysv/linux/x86_64/Makefile
index 9b82155393..a22bb6321f 100644
--- a/sysdeps/unix/sysv/linux/x86_64/Makefile
+++ b/sysdeps/unix/sysv/linux/x86_64/Makefile
@@ -13,3 +13,29 @@  endif
 ifeq ($(subdir),misc)
 gen-as-const-headers += sigaltstack-offsets.sym
 endif
+
+ifeq ($(subdir),elf)
+ifeq (yesyes,$(enable-x86-isa-level)$(config-cflags-skylake-avx512))
+tests += tst-glibc-hwcaps-2
+modules-names += libx86-64-isa-level-1 libx86-64-isa-level-4
+
+ifeq (no,$(build-hardcoded-path-in-tests))
+# This is an ld.so.cache test, and RPATH/RUNPATH in the executable
+# interferes with its test objectives.
+tests-container += tst-glibc-hwcaps-2-cache
+endif
+
+$(objpfx)tst-glibc-hwcaps-2: $(objpfx)libx86-64-isa-level.so
+
+CFLAGS-libx86-64-isa-level-1.os += -march=x86-64
+CFLAGS-libx86-64-isa-level-4.os += -march=skylake-avx512
+
+# The test modules are parameterized by preprocessor macros.
+LDFLAGS-libx86-64-isa-level-1.so += -Wl,-soname,libx86-64-isa-level.so
+LDFLAGS-libx86-64-isa-level-4.so += -Wl,-soname,libx86-64-isa-level.so
+$(objpfx)libx86-64-isa-level%.os: $(..)/sysdeps/unix/sysv/linux/x86_64/x86-64-isa-level-VALUE.c
+	$(compile-command.c) -DVALUE=$(lastword $(subst -, ,$*))
+$(objpfx)libx86-64-isa-level.so: $(objpfx)libx86-64-isa-level-1.so
+	cp $< $@
+endif
+endif # $(subdir) == elf
diff --git a/sysdeps/unix/sysv/linux/x86_64/tst-glibc-hwcaps-2.c b/sysdeps/unix/sysv/linux/x86_64/tst-glibc-hwcaps-2.c
new file mode 100644
index 0000000000..ce6b189415
--- /dev/null
+++ b/sysdeps/unix/sysv/linux/x86_64/tst-glibc-hwcaps-2.c
@@ -0,0 +1,50 @@ 
+/* Check ISA level on shared object in glibc-hwcaps subdirectories.
+   Copyright (C) 2020 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
+   <https://www.gnu.org/licenses/>.  */
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <support/check.h>
+#include <support/test-driver.h>
+
+extern int isa_level (void);
+
+static int
+do_test (void)
+{
+  int level = isa_level ();
+  int ret;
+  switch (level)
+    {
+    case 1:
+      /* The default libx86-64-isa-level.so should be used.  */
+      printf ("The default shared library is used.\n");
+      ret = EXIT_SUCCESS;
+      break;
+    case 4:
+      /* libx86-64-isa-level.so marked as x86-64 ISA level 4 needed in
+	 x86-64-v2 should be ignored.  */
+      printf ("x86-64 ISA level 4 shared library in x86-64-v2 is used.\n");
+      ret = EXIT_FAILURE;
+      break;
+    default:
+      abort ();
+    }
+  return ret;
+}
+
+#include <support/test-driver.c>
diff --git a/sysdeps/unix/sysv/linux/x86_64/x86-64-isa-level-VALUE.c b/sysdeps/unix/sysv/linux/x86_64/x86-64-isa-level-VALUE.c
new file mode 100644
index 0000000000..892e4c464b
--- /dev/null
+++ b/sysdeps/unix/sysv/linux/x86_64/x86-64-isa-level-VALUE.c
@@ -0,0 +1,4 @@ 
+#define INCLUDE_X86_ISA_LEVEL
+#define MARKER isa_level
+#include <isa-level.c>
+#include <markermodMARKER-VALUE.c>