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