b1dca6
commit 84ba719b260551918965d0a433914de683087645
b1dca6
Author: Florian Weimer <fweimer@redhat.com>
b1dca6
Date:   Fri Dec 4 09:13:43 2020 +0100
b1dca6
b1dca6
    elf: Add endianness markup to ld.so.cache (bug 27008)
b1dca6
    
b1dca6
    Use a reserved byte in the new format cache header to indicate whether
b1dca6
    the file is in little endian or big endian format.  Eventually, this
b1dca6
    information could be used to provide a unified cache for qemu-user
b1dca6
    and similiar scenarios.
b1dca6
    
b1dca6
    Reviewed-by: Adhemerval Zanella  <adhemerval.zanella@linaro.org>
b1dca6
b1dca6
diff --git a/elf/cache.c b/elf/cache.c
b1dca6
index edcdd4b7cc1a6a0b..28e4889d006d2f0b 100644
b1dca6
--- a/elf/cache.c
b1dca6
+++ b/elf/cache.c
b1dca6
@@ -152,6 +152,14 @@ print_entry (const char *lib, int flag, unsigned int osversion,
b1dca6
   printf (") => %s\n", key);
b1dca6
 }
b1dca6
 
b1dca6
+/* Print an error and exit if the new-file cache is internally
b1dca6
+   inconsistent.  */
b1dca6
+static void
b1dca6
+check_new_cache (struct cache_file_new *cache)
b1dca6
+{
b1dca6
+  if (! cache_file_new_matches_endian (cache))
b1dca6
+    error (EXIT_FAILURE, 0, _("Cache file has wrong endianness.\n"));
b1dca6
+}
b1dca6
 
b1dca6
 /* Print the whole cache file, if a file contains the new cache format
b1dca6
    hidden in the old one, print the contents of the new format.  */
b1dca6
@@ -193,6 +201,7 @@ print_cache (const char *cache_name)
b1dca6
 	  || memcmp (cache_new->version, CACHE_VERSION,
b1dca6
 		      sizeof CACHE_VERSION - 1))
b1dca6
 	error (EXIT_FAILURE, 0, _("File is not a cache file.\n"));
b1dca6
+      check_new_cache (cache_new);
b1dca6
       format = 1;
b1dca6
       /* This is where the strings start.  */
b1dca6
       cache_data = (const char *) cache_new;
b1dca6
@@ -222,6 +231,7 @@ print_cache (const char *cache_name)
b1dca6
 	      && memcmp (cache_new->version, CACHE_VERSION,
b1dca6
 			 sizeof CACHE_VERSION - 1) == 0)
b1dca6
 	    {
b1dca6
+	      check_new_cache (cache_new);
b1dca6
 	      cache_data = (const char *) cache_new;
b1dca6
 	      format = 1;
b1dca6
 	    }
b1dca6
@@ -361,6 +371,7 @@ save_cache (const char *cache_name)
b1dca6
 
b1dca6
       file_entries_new->nlibs = cache_entry_count;
b1dca6
       file_entries_new->len_strings = total_strlen;
b1dca6
+      file_entries_new->flags = cache_file_new_flags_endian_current;
b1dca6
     }
b1dca6
 
b1dca6
   /* Pad for alignment of cache_file_new.  */
b1dca6
diff --git a/elf/dl-cache.c b/elf/dl-cache.c
b1dca6
index 366a051dfcd26132..de063faa8b2c88ae 100644
b1dca6
--- a/elf/dl-cache.c
b1dca6
+++ b/elf/dl-cache.c
b1dca6
@@ -242,6 +242,11 @@ _dl_load_cache_lookup (const char *name)
b1dca6
 	  && ((cachesize - sizeof *cache_new) / sizeof (struct file_entry_new)
b1dca6
 	      >= ((struct cache_file_new *) file)->nlibs))
b1dca6
 	{
b1dca6
+	  if (! cache_file_new_matches_endian (file))
b1dca6
+	    {
b1dca6
+	      __munmap (file, cachesize);
b1dca6
+	      file = (void *) -1;
b1dca6
+	    }
b1dca6
 	  cache_new = file;
b1dca6
 	  cache = file;
b1dca6
 	}
b1dca6
@@ -263,7 +268,20 @@ _dl_load_cache_lookup (const char *name)
b1dca6
 	  if (cachesize < (offset + sizeof (struct cache_file_new))
b1dca6
 	      || memcmp (cache_new->magic, CACHEMAGIC_VERSION_NEW,
b1dca6
 			 sizeof CACHEMAGIC_VERSION_NEW - 1) != 0)
b1dca6
-	    cache_new = (void *) -1;
b1dca6
+	      cache_new = (void *) -1;
b1dca6
+	  else
b1dca6
+	    {
b1dca6
+	      if (! cache_file_new_matches_endian (cache_new))
b1dca6
+		{
b1dca6
+		  /* The old-format part of the cache is bogus as well
b1dca6
+		     if the endianness does not match.  (But it is
b1dca6
+		     unclear how the new header can be located if the
b1dca6
+		     endianess does not match.)  */
b1dca6
+		  cache = (void *) -1;
b1dca6
+		  cache_new = (void *) -1;
b1dca6
+		  __munmap (file, cachesize);
b1dca6
+		}
b1dca6
+	    }
b1dca6
 	}
b1dca6
       else
b1dca6
 	{
b1dca6
diff --git a/sysdeps/generic/dl-cache.h b/sysdeps/generic/dl-cache.h
b1dca6
index 3c5730dfe42c7c88..6ecfd6da0e59329c 100644
b1dca6
--- a/sysdeps/generic/dl-cache.h
b1dca6
+++ b/sysdeps/generic/dl-cache.h
b1dca6
@@ -16,6 +16,11 @@
b1dca6
    License along with the GNU C Library; if not, see
b1dca6
    <http://www.gnu.org/licenses/>.  */
b1dca6
 
b1dca6
+#ifndef _DL_CACHE_H
b1dca6
+#define _DL_CACHE_H
b1dca6
+
b1dca6
+#include <endian.h>
b1dca6
+#include <stdbool.h>
b1dca6
 #include <stdint.h>
b1dca6
 
b1dca6
 #ifndef _DL_CACHE_DEFAULT_ID
b1dca6
@@ -92,17 +97,72 @@ struct file_entry_new
b1dca6
   uint64_t hwcap;		/* Hwcap entry.	 */
b1dca6
 };
b1dca6
 
b1dca6
+/* See flags member of struct cache_file_new below.  */
b1dca6
+enum
b1dca6
+  {
b1dca6
+    /* No endianness information available.  An old ldconfig version
b1dca6
+       without endianness support wrote the file.  */
b1dca6
+    cache_file_new_flags_endian_unset = 0,
b1dca6
+
b1dca6
+    /* Cache is invalid and should be ignored.  */
b1dca6
+    cache_file_new_flags_endian_invalid = 1,
b1dca6
+
b1dca6
+    /* Cache format is little endian.  */
b1dca6
+    cache_file_new_flags_endian_little = 2,
b1dca6
+
b1dca6
+    /* Cache format is big endian.  */
b1dca6
+    cache_file_new_flags_endian_big = 3,
b1dca6
+
b1dca6
+    /* Bit mask to extract the cache_file_new_flags_endian_*
b1dca6
+       values.  */
b1dca6
+    cache_file_new_flags_endian_mask = 3,
b1dca6
+
b1dca6
+    /* Expected value of the endian bits in the flags member for the
b1dca6
+       current architecture.  */
b1dca6
+    cache_file_new_flags_endian_current
b1dca6
+      = (__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
b1dca6
+	 ? cache_file_new_flags_endian_little
b1dca6
+	 : cache_file_new_flags_endian_big),
b1dca6
+  };
b1dca6
+
b1dca6
 struct cache_file_new
b1dca6
 {
b1dca6
   char magic[sizeof CACHEMAGIC_NEW - 1];
b1dca6
   char version[sizeof CACHE_VERSION - 1];
b1dca6
   uint32_t nlibs;		/* Number of entries.  */
b1dca6
   uint32_t len_strings;		/* Size of string table. */
b1dca6
-  uint32_t unused[5];		/* Leave space for future extensions
b1dca6
+
b1dca6
+  /* flags & cache_file_new_flags_endian_mask is one of the values
b1dca6
+     cache_file_new_flags_endian_unset, cache_file_new_flags_endian_invalid,
b1dca6
+     cache_file_new_flags_endian_little, cache_file_new_flags_endian_big.
b1dca6
+
b1dca6
+     The remaining bits are unused and should be generated as zero and
b1dca6
+     ignored by readers.  */
b1dca6
+  uint8_t flags;
b1dca6
+
b1dca6
+  uint8_t padding_unsed[3];	/* Not used, for future extensions.  */
b1dca6
+
b1dca6
+  uint32_t unused[4];		/* Leave space for future extensions
b1dca6
 				   and align to 8 byte boundary.  */
b1dca6
   struct file_entry_new libs[0]; /* Entries describing libraries.  */
b1dca6
   /* After this the string table of size len_strings is found.	*/
b1dca6
 };
b1dca6
+_Static_assert (sizeof (struct cache_file_new) == 48,
b1dca6
+		"size of struct cache_file_new");
b1dca6
+
b1dca6
+/* Returns false if *CACHE has the wrong endianness for this
b1dca6
+   architecture, and true if the endianness matches (or is
b1dca6
+   unknown).  */
b1dca6
+static inline bool
b1dca6
+cache_file_new_matches_endian (const struct cache_file_new *cache)
b1dca6
+{
b1dca6
+  /* A zero value for cache->flags means that no endianness
b1dca6
+     information is available.  */
b1dca6
+  return cache->flags == 0
b1dca6
+    || ((cache->flags & cache_file_new_flags_endian_big)
b1dca6
+	== cache_file_new_flags_endian_current);
b1dca6
+}
b1dca6
+
b1dca6
 
b1dca6
 /* Used to align cache_file_new.  */
b1dca6
 #define ALIGN_CACHE(addr)				\
b1dca6
@@ -110,3 +170,5 @@ struct cache_file_new
b1dca6
  & (~(__alignof__ (struct cache_file_new) - 1)))
b1dca6
 
b1dca6
 extern int _dl_cache_libcmp (const char *p1, const char *p2) attribute_hidden;
b1dca6
+
b1dca6
+#endif /* _DL_CACHE_H */