b1dca6
commit dfb3f101c5ef23adf60d389058a2b33e23303d04
b1dca6
Author: Florian Weimer <fweimer@redhat.com>
b1dca6
Date:   Fri Dec 4 09:13:43 2020 +0100
b1dca6
b1dca6
    elf: Add extension mechanism to ld.so.cache
b1dca6
    
b1dca6
    A previously unused new-format header field is used to record
b1dca6
    the address of an extension directory.
b1dca6
    
b1dca6
    This change adds a demo extension which records the version of
b1dca6
    ldconfig which builds a file.
b1dca6
    
b1dca6
    Reviewed-by: Adhemerval Zanella  <adhemerval.zanella@linaro.org>
b1dca6
b1dca6
diff --git a/elf/cache.c b/elf/cache.c
b1dca6
index 28e4889d006d2f0b..5a8f1ad70cc3fead 100644
b1dca6
--- a/elf/cache.c
b1dca6
+++ b/elf/cache.c
b1dca6
@@ -15,6 +15,7 @@
b1dca6
    You should have received a copy of the GNU General Public License
b1dca6
    along with this program; if not, see <http://www.gnu.org/licenses/>.  */
b1dca6
 
b1dca6
+#include <assert.h>
b1dca6
 #include <errno.h>
b1dca6
 #include <error.h>
b1dca6
 #include <dirent.h>
b1dca6
@@ -33,6 +34,7 @@
b1dca6
 
b1dca6
 #include <ldconfig.h>
b1dca6
 #include <dl-cache.h>
b1dca6
+#include <version.h>
b1dca6
 
b1dca6
 struct cache_entry
b1dca6
 {
b1dca6
@@ -161,6 +163,21 @@ check_new_cache (struct cache_file_new *cache)
b1dca6
     error (EXIT_FAILURE, 0, _("Cache file has wrong endianness.\n"));
b1dca6
 }
b1dca6
 
b1dca6
+/* Print the extension information at the cache at start address
b1dca6
+   FILE_BASE, of length FILE_SIZE bytes.  The new-format cache header
b1dca6
+   is at CACHE, and the file name for diagnostics is CACHE_NAME.  */
b1dca6
+static void
b1dca6
+print_extensions (struct cache_extension_all_loaded *ext)
b1dca6
+{
b1dca6
+  if (ext->sections[cache_extension_tag_generator].base != NULL)
b1dca6
+    {
b1dca6
+      fputs (_("Cache generated by: "), stdout);
b1dca6
+      fwrite (ext->sections[cache_extension_tag_generator].base, 1,
b1dca6
+	      ext->sections[cache_extension_tag_generator].size, stdout);
b1dca6
+      putchar ('\n');
b1dca6
+    }
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
 void
b1dca6
@@ -250,6 +267,11 @@ print_cache (const char *cache_name)
b1dca6
     }
b1dca6
   else if (format == 1)
b1dca6
     {
b1dca6
+      struct cache_extension_all_loaded ext;
b1dca6
+      if (!cache_extension_load (cache_new, cache, cache_size, &ext))
b1dca6
+	error (EXIT_FAILURE, 0,
b1dca6
+	       _("Malformed extension data in cache file %s\n"), cache_name);
b1dca6
+
b1dca6
       printf (_("%d libs found in cache `%s'\n"),
b1dca6
 	      cache_new->nlibs, cache_name);
b1dca6
 
b1dca6
@@ -260,6 +282,7 @@ print_cache (const char *cache_name)
b1dca6
 		     cache_new->libs[i].osversion,
b1dca6
 		     cache_new->libs[i].hwcap,
b1dca6
 		     cache_data + cache_new->libs[i].value);
b1dca6
+      print_extensions (&ext;;
b1dca6
     }
b1dca6
   /* Cleanup.  */
b1dca6
   munmap (cache, cache_size);
b1dca6
@@ -301,6 +324,45 @@ compare (const struct cache_entry *e1, const struct cache_entry *e2)
b1dca6
   return res;
b1dca6
 }
b1dca6
 
b1dca6
+/* Size of the cache extension directory.  All tags are assumed to be
b1dca6
+   present.  */
b1dca6
+enum
b1dca6
+  {
b1dca6
+   cache_extension_size = (offsetof (struct cache_extension, sections)
b1dca6
+			   + (cache_extension_count
b1dca6
+			      * sizeof (struct cache_extension_section)))
b1dca6
+  };
b1dca6
+
b1dca6
+/* Write the cache extensions to FD.  The extension directory is
b1dca6
+   assumed to be located at CACHE_EXTENSION_OFFSET.  */
b1dca6
+static void
b1dca6
+write_extensions (int fd, uint32_t cache_extension_offset)
b1dca6
+{
b1dca6
+  assert ((cache_extension_offset % 4) == 0);
b1dca6
+
b1dca6
+  struct cache_extension *ext = xmalloc (cache_extension_size);
b1dca6
+  ext->magic = cache_extension_magic;
b1dca6
+  ext->count = cache_extension_count;
b1dca6
+
b1dca6
+  for (int i = 0; i < cache_extension_count; ++i)
b1dca6
+    {
b1dca6
+      ext->sections[i].tag = i;
b1dca6
+      ext->sections[i].flags = 0;
b1dca6
+    }
b1dca6
+
b1dca6
+  const char *generator
b1dca6
+    = "ldconfig " PKGVERSION RELEASE " release version " VERSION;
b1dca6
+  ext->sections[cache_extension_tag_generator].offset
b1dca6
+    = cache_extension_offset + cache_extension_size;
b1dca6
+  ext->sections[cache_extension_tag_generator].size = strlen (generator);
b1dca6
+
b1dca6
+  if (write (fd, ext, cache_extension_size) != cache_extension_size
b1dca6
+      || write (fd, generator, strlen (generator)) != strlen (generator))
b1dca6
+    error (EXIT_FAILURE, errno, _("Writing of cache extension data failed"));
b1dca6
+
b1dca6
+  free (ext);
b1dca6
+}
b1dca6
+
b1dca6
 /* Save the contents of the cache.  */
b1dca6
 void
b1dca6
 save_cache (const char *cache_name)
b1dca6
@@ -435,6 +497,25 @@ save_cache (const char *cache_name)
b1dca6
       && idx_old < cache_entry_old_count)
b1dca6
     file_entries->libs[idx_old] = file_entries->libs[idx_old - 1];
b1dca6
 
b1dca6
+  /* Compute the location of the extension directory.  This
b1dca6
+     implementation puts the directory after the string table.  The
b1dca6
+     size computation matches the write calls below.  The extension
b1dca6
+     directory does not exist with format 0, so the value does not
b1dca6
+     matter.  */
b1dca6
+  uint32_t extension_offset = 0;
b1dca6
+  if (opt_format != opt_format_new)
b1dca6
+    extension_offset += file_entries_size;
b1dca6
+  if (opt_format != opt_format_old)
b1dca6
+    {
b1dca6
+      if (opt_format != opt_format_new)
b1dca6
+	extension_offset += pad;
b1dca6
+      extension_offset += file_entries_new_size;
b1dca6
+    }
b1dca6
+  extension_offset += total_strlen;
b1dca6
+  extension_offset = roundup (extension_offset, 4); /* Provide alignment.  */
b1dca6
+  if (opt_format != opt_format_old)
b1dca6
+    file_entries_new->extension_offset = extension_offset;
b1dca6
+
b1dca6
   /* Write out the cache.  */
b1dca6
 
b1dca6
   /* Write cache first to a temporary file and rename it later.  */
b1dca6
@@ -473,6 +554,14 @@ save_cache (const char *cache_name)
b1dca6
   if (write (fd, strings, total_strlen) != (ssize_t) total_strlen)
b1dca6
     error (EXIT_FAILURE, errno, _("Writing of cache data failed"));
b1dca6
 
b1dca6
+  if (opt_format != opt_format_old)
b1dca6
+    {
b1dca6
+      /* Align file position to 4.  */
b1dca6
+      off64_t old_offset = lseek64 (fd, extension_offset, SEEK_SET);
b1dca6
+      assert ((unsigned long long int) (extension_offset - old_offset) < 4);
b1dca6
+      write_extensions (fd, extension_offset);
b1dca6
+    }
b1dca6
+
b1dca6
   /* Make sure user can always read cache file */
b1dca6
   if (chmod (temp_name, S_IROTH|S_IRGRP|S_IRUSR|S_IWUSR))
b1dca6
     error (EXIT_FAILURE, errno,
b1dca6
diff --git a/sysdeps/generic/dl-cache.h b/sysdeps/generic/dl-cache.h
b1dca6
index 6ecfd6da0e59329c..259e843724531630 100644
b1dca6
--- a/sysdeps/generic/dl-cache.h
b1dca6
+++ b/sysdeps/generic/dl-cache.h
b1dca6
@@ -21,7 +21,9 @@
b1dca6
 
b1dca6
 #include <endian.h>
b1dca6
 #include <stdbool.h>
b1dca6
+#include <stddef.h>
b1dca6
 #include <stdint.h>
b1dca6
+#include <string.h>
b1dca6
 
b1dca6
 #ifndef _DL_CACHE_DEFAULT_ID
b1dca6
 # define _DL_CACHE_DEFAULT_ID	3
b1dca6
@@ -142,7 +144,11 @@ struct cache_file_new
b1dca6
 
b1dca6
   uint8_t padding_unsed[3];	/* Not used, for future extensions.  */
b1dca6
 
b1dca6
-  uint32_t unused[4];		/* Leave space for future extensions
b1dca6
+  /* File offset of the extension directory.  See struct
b1dca6
+     cache_extension below.  Must be a multiple of four.  */
b1dca6
+  uint32_t extension_offset;
b1dca6
+
b1dca6
+  uint32_t unused[3];		/* 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
@@ -164,6 +170,121 @@ cache_file_new_matches_endian (const struct cache_file_new *cache)
b1dca6
 }
b1dca6
 
b1dca6
 
b1dca6
+/* Randomly chosen magic value, which allows for additional
b1dca6
+   consistency verification.  */
b1dca6
+enum { cache_extension_magic = (uint32_t) -358342284 };
b1dca6
+
b1dca6
+/* Tag values for different kinds of extension sections.  Similar to
b1dca6
+   SHT_* constants.  */
b1dca6
+enum cache_extension_tag
b1dca6
+  {
b1dca6
+   /* Array of bytes containing the glibc version that generated this
b1dca6
+      cache file.  */
b1dca6
+   cache_extension_tag_generator,
b1dca6
+
b1dca6
+   /* Total number of known cache extension tags.  */
b1dca6
+   cache_extension_count
b1dca6
+  };
b1dca6
+
b1dca6
+/* Element in the array following struct cache_extension.  Similar to
b1dca6
+   an ELF section header.  */
b1dca6
+struct cache_extension_section
b1dca6
+{
b1dca6
+  /* Type of the extension section.  A enum cache_extension_tag value.  */
b1dca6
+  uint32_t tag;
b1dca6
+
b1dca6
+  /* Extension-specific flags.  Currently generated as zero.  */
b1dca6
+  uint32_t flags;
b1dca6
+
b1dca6
+  /* Offset from the start of the file for the data in this extension
b1dca6
+     section.  Specific extensions can have alignment constraints.  */
b1dca6
+  uint32_t offset;
b1dca6
+
b1dca6
+  /* Length in bytes of the extension data.  Specific extensions may
b1dca6
+     have size requirements.  */
b1dca6
+  uint32_t size;
b1dca6
+};
b1dca6
+
b1dca6
+/* The extension directory in the cache.  An array of struct
b1dca6
+   cache_extension_section entries.  */
b1dca6
+struct cache_extension
b1dca6
+{
b1dca6
+  uint32_t magic;		/* Always cache_extension_magic.  */
b1dca6
+  uint32_t count;		/* Number of following entries.  */
b1dca6
+
b1dca6
+  /* count section descriptors of type struct cache_extension_section
b1dca6
+     follow.  */
b1dca6
+  struct cache_extension_section sections[];
b1dca6
+};
b1dca6
+
b1dca6
+/* A relocated version of struct cache_extension_section.  */
b1dca6
+struct cache_extension_loaded
b1dca6
+{
b1dca6
+  /* Address and size of this extension section.  base is NULL if the
b1dca6
+     section is missing from the file.  */
b1dca6
+  const void *base;
b1dca6
+  size_t size;
b1dca6
+
b1dca6
+  /* Flags from struct cache_extension_section.  */
b1dca6
+  uint32_t flags;
b1dca6
+};
b1dca6
+
b1dca6
+/* All supported extension sections, relocated.  Filled in by
b1dca6
+   cache_extension_load below.  */
b1dca6
+struct cache_extension_all_loaded
b1dca6
+{
b1dca6
+  struct cache_extension_loaded sections[cache_extension_count];
b1dca6
+};
b1dca6
+
b1dca6
+static bool __attribute__ ((unused))
b1dca6
+cache_extension_load (const struct cache_file_new *cache,
b1dca6
+		      const void *file_base, size_t file_size,
b1dca6
+		      struct cache_extension_all_loaded *loaded)
b1dca6
+{
b1dca6
+  memset (loaded, 0, sizeof (*loaded));
b1dca6
+  if (cache->extension_offset == 0)
b1dca6
+    /* No extensions present.  This is not a format error.  */
b1dca6
+    return true;
b1dca6
+  if ((cache->extension_offset % 4) != 0)
b1dca6
+    /* Extension offset is misaligned.  */
b1dca6
+    return false;
b1dca6
+  size_t size_tmp;
b1dca6
+  if (__builtin_add_overflow (cache->extension_offset,
b1dca6
+			      sizeof (struct cache_extension), &size_tmp)
b1dca6
+      || size_tmp > file_size)
b1dca6
+    /* Extension extends beyond the end of the file.  */
b1dca6
+    return false;
b1dca6
+  const struct cache_extension *ext = file_base + cache->extension_offset;
b1dca6
+  if (ext->magic != cache_extension_magic)
b1dca6
+    return false;
b1dca6
+  if (__builtin_mul_overflow (ext->count,
b1dca6
+			      sizeof (struct cache_extension_section),
b1dca6
+			      &size_tmp)
b1dca6
+      || __builtin_add_overflow (cache->extension_offset
b1dca6
+				 + sizeof (struct cache_extension), size_tmp,
b1dca6
+				 &size_tmp)
b1dca6
+      || size_tmp > file_size)
b1dca6
+    /* Extension array extends beyond the end of the file.  */
b1dca6
+    return false;
b1dca6
+  for (uint32_t i = 0; i < ext->count; ++i)
b1dca6
+    {
b1dca6
+      if (__builtin_add_overflow (ext->sections[i].offset,
b1dca6
+				  ext->sections[i].size, &size_tmp)
b1dca6
+	  || size_tmp > file_size)
b1dca6
+	/* Extension data extends beyond the end of the file.  */
b1dca6
+	return false;
b1dca6
+
b1dca6
+      uint32_t tag = ext->sections[i].tag;
b1dca6
+      if (tag >= cache_extension_count)
b1dca6
+	/* Tag is out of range and unrecognized.  */
b1dca6
+	continue;
b1dca6
+      loaded->sections[tag].base = file_base + ext->sections[i].offset;
b1dca6
+      loaded->sections[tag].size = ext->sections[i].size;
b1dca6
+      loaded->sections[tag].flags = ext->sections[i].flags;
b1dca6
+    }
b1dca6
+  return true;
b1dca6
+}
b1dca6
+
b1dca6
 /* Used to align cache_file_new.  */
b1dca6
 #define ALIGN_CACHE(addr)				\
b1dca6
 (((addr) + __alignof__ (struct cache_file_new) -1)	\