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