From 0b2ad01a73cec20234687436fecdcb55ae256e47 Mon Sep 17 00:00:00 2001 From: Simon McVittie <smcv@collabora.com> Date: Mon, 1 Jun 2020 17:56:31 +0100 Subject: [PATCH] elf-utils: Add ELF SONAME resolution This will be used to delete libraries from the runtime if their SONAME is the same as a library that we brought in from outside, to avoid conflicts when precedence gets mixed up. A manual test is included. Signed-off-by: Simon McVittie <smcv@collabora.com> --- debian/control | 1 + meson.build | 5 ++ src/elf-utils.c | 158 +++++++++++++++++++++++++++++++++++++++++ src/elf-utils.h | 36 ++++++++++ src/meson.build | 3 + tests/elf-get-soname.c | 114 +++++++++++++++++++++++++++++ tests/meson.build | 38 ++++++---- 7 files changed, 340 insertions(+), 15 deletions(-) create mode 100644 src/elf-utils.c create mode 100644 src/elf-utils.h create mode 100644 tests/elf-get-soname.c diff --git a/debian/control b/debian/control index 4bba83d20..8ec09e31f 100644 --- a/debian/control +++ b/debian/control @@ -6,6 +6,7 @@ Standards-Version: 4.4.0 Build-Depends: debhelper (>= 9), g++ (>= 4:4.8) | g++-4.8, + libelf-dev, libglib2.0-dev, libsteam-runtime-tools-0-dev (>= 0.20200331.1~), libxau-dev, diff --git a/meson.build b/meson.build index e29eb930e..78edcb1e5 100644 --- a/meson.build +++ b/meson.build @@ -150,6 +150,11 @@ glib_tap_support = dependency( required : false, ) xau = dependency('xau', required : true) +libelf = dependency('libelf', required : false) + +if not libelf.found() + libelf = declare_dependency(link_args : ['-lelf']) +endif scripts = [ 'pressure-vessel-locale-gen', diff --git a/src/elf-utils.c b/src/elf-utils.c new file mode 100644 index 000000000..5fcb790bc --- /dev/null +++ b/src/elf-utils.c @@ -0,0 +1,158 @@ +/* + * Copyright © 2017-2020 Collabora Ltd. + * + * SPDX-License-Identifier: LGPL-2.1-or-later + * + * This program 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. + * + * This 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 this library. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "elf-utils.h" + +#include <glib.h> +#include <glib/gstdio.h> +#include <gio/gio.h> +#include <libelf.h> + +#include "libglnx/libglnx.h" + +#include "glib-backports.h" + +#define throw_elf_error(error, format, ...) \ + glnx_null_throw (error, format ": %s", ##__VA_ARGS__, elf_errmsg (elf_errno ())) + +/* + * pv_elf_open_fd: + * @fd: An open file descriptor + * + * Returns: (transfer full): A libelf object representing the library + */ +Elf * +pv_elf_open_fd (int fd, + GError **error) +{ + g_autoptr(Elf) elf = NULL; + + g_return_val_if_fail (fd >= 0, NULL); + g_return_val_if_fail (error == NULL || *error == NULL, NULL); + + if (elf_version (EV_CURRENT) == EV_NONE) + return throw_elf_error (error, "elf_version(EV_CURRENT)"); + + elf = elf_begin (fd, ELF_C_READ, NULL); + + if (elf == NULL) + return throw_elf_error (error, "elf_begin"); + + return g_steal_pointer (&elf); +} + +/* + * pv_elf_get_soname: + * @elf: A libelf object + * + * Return the `DT_SONAME` header, or %NULL on error + */ +gchar * +pv_elf_get_soname (Elf *elf, + GError **error) +{ + GElf_Ehdr ehdr; + size_t phdr_count = 0; + size_t i; + GElf_Phdr phdr_mem; + GElf_Phdr *phdr = NULL; + GElf_Dyn *dynamic_header = NULL; + GElf_Dyn dynamic_header_mem; + Elf_Scn *dynamic_section = NULL; + GElf_Shdr dynamic_section_header_mem; + GElf_Shdr *dynamic_section_header = NULL; + Elf_Scn *string_table_section = NULL; + GElf_Shdr string_table_header_mem; + GElf_Shdr *string_table_header = NULL; + Elf_Data *data = NULL; + size_t size_per_dyn; + const char *soname = NULL; + + g_return_val_if_fail (elf != NULL, NULL); + g_return_val_if_fail (error == NULL || *error == NULL, NULL); + + if (elf_kind (elf) != ELF_K_ELF) + return glnx_null_throw (error, "elf_kind %d, expected ELF_K_ELF=%d", + elf_kind (elf), ELF_K_ELF); + + if (gelf_getehdr (elf, &ehdr) == NULL) + return throw_elf_error (error, "elf_getehdr"); + + if (ehdr.e_type != ET_DYN) + return glnx_null_throw (error, "ehdr.e_type %d, expected ET_DYN=%d", + ehdr.e_type, ET_DYN); + + if (elf_getphdrnum (elf, &phdr_count) != 0) + return throw_elf_error (error, "elf_getphdrnum"); + + for (i = 0; i < phdr_count; i++) + { + phdr = gelf_getphdr (elf, i, &phdr_mem); + + if (phdr != NULL && phdr->p_type == PT_DYNAMIC) + { + dynamic_section = gelf_offscn (elf, phdr->p_offset); + dynamic_section_header = gelf_getshdr (dynamic_section, + &dynamic_section_header_mem); + break; + } + } + + if (dynamic_section == NULL || dynamic_section_header == NULL) + return glnx_null_throw (error, "Unable to find dynamic section header"); + + string_table_section = elf_getscn (elf, dynamic_section_header->sh_link); + string_table_header = gelf_getshdr (string_table_section, + &string_table_header_mem); + + if (string_table_section == NULL || string_table_header == NULL) + return glnx_null_throw (error, "Unable to find linked string table"); + + data = elf_getdata (dynamic_section, NULL); + + if (data == NULL) + return throw_elf_error (error, "elf_getdata(dynamic_section)"); + + size_per_dyn = gelf_fsize (elf, ELF_T_DYN, 1, EV_CURRENT); + + for (i = 0; i < dynamic_section_header->sh_size / size_per_dyn; i++) + { + dynamic_header = gelf_getdyn (data, i, &dynamic_header_mem); + + if (dynamic_header == NULL) + break; + + if (dynamic_header->d_tag == DT_SONAME) + { + soname = elf_strptr (elf, dynamic_section_header->sh_link, + dynamic_header->d_un.d_val); + + if (soname == NULL) + return glnx_null_throw (error, "Unable to read DT_SONAME"); + + + } + } + + if (soname == NULL) + return glnx_null_throw (error, "Unable to find DT_SONAME"); + + return g_strdup (soname); +} + diff --git a/src/elf-utils.h b/src/elf-utils.h new file mode 100644 index 000000000..fa89652e7 --- /dev/null +++ b/src/elf-utils.h @@ -0,0 +1,36 @@ +/* + * Copyright © 2020 Collabora Ltd. + * + * SPDX-License-Identifier: LGPL-2.1-or-later + * + * This 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. + * + * This 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 this library; if not, see <http://www.gnu.org/licenses/>. + */ + +#pragma once + +#include <gelf.h> +#include <glib.h> +#include <libelf.h> + +#include "libglnx/libglnx.h" + +#include "glib-backports.h" + +G_DEFINE_AUTOPTR_CLEANUP_FUNC (Elf, elf_end); + +Elf *pv_elf_open_fd (int fd, + GError **error); + +gchar *pv_elf_get_soname (Elf *elf, + GError **error); diff --git a/src/meson.build b/src/meson.build index 789e181cf..220d1d30b 100644 --- a/src/meson.build +++ b/src/meson.build @@ -37,6 +37,8 @@ pressure_vessel_utils = static_library( sources : [ 'bwrap-lock.c', 'bwrap-lock.h', + 'elf-utils.c', + 'elf-utils.h', 'flatpak-utils-base.c', 'flatpak-utils-base-private.h', 'flatpak-utils.c', @@ -50,6 +52,7 @@ pressure_vessel_utils = static_library( ], dependencies : [ gio_unix, + libelf, libglnx.get_variable('libglnx_dep'), ], include_directories : project_include_dirs, diff --git a/tests/elf-get-soname.c b/tests/elf-get-soname.c new file mode 100644 index 000000000..218623ed8 --- /dev/null +++ b/tests/elf-get-soname.c @@ -0,0 +1,114 @@ +/* + * Copyright © 2020 Collabora Ltd. + * + * SPDX-License-Identifier: LGPL-2.1-or-later + * + * This program 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. + * + * This 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 this library. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "config.h" +#include "subprojects/libglnx/config.h" + +#include <locale.h> +#include <sysexits.h> + +#include "libglnx/libglnx.h" + +#include "glib-backports.h" + +#include "elf-utils.h" +#include "utils.h" + +static GOptionEntry options[] = +{ + { NULL } +}; + +int +main (int argc, + char *argv[]) +{ + g_autoptr(GOptionContext) context = NULL; + g_autoptr(GError) local_error = NULL; + GError **error = &local_error; + int ret = EX_USAGE; + int i; + + setlocale (LC_ALL, ""); + pv_avoid_gvfs (); + + context = g_option_context_new ("LIBRARY..."); + g_option_context_add_main_entries (context, options, NULL); + + if (!g_option_context_parse (context, &argc, &argv, error)) + goto out; + + if (argc >= 2 && strcmp (argv[1], "--") == 0) + { + argv++; + argc--; + } + + if (argc < 2) + { + g_printerr ("A library to open is required\n"); + goto out; + } + + ret = 0; + + for (i = 1; i < argc; i++) + { + int fd = open (argv[i], O_RDONLY | O_CLOEXEC); + g_autoptr(Elf) elf = NULL; + g_autofree gchar *soname = NULL; + + if (fd < 0) + { + g_printerr ("Cannot open %s: %s\n", argv[i], g_strerror (errno)); + ret = 1; + continue; + } + + elf = pv_elf_open_fd (fd, error); + + if (elf == NULL) + { + g_printerr ("Unable to open %s: %s\n", + argv[i], local_error->message); + g_clear_error (error); + ret = 1; + continue; + } + + soname = pv_elf_get_soname (elf, error); + + if (soname == NULL) + { + g_printerr ("Unable to get SONAME of %s: %s\n", + argv[i], local_error->message); + g_clear_error (error); + ret = 1; + continue; + } + + g_print ("%s DT_SONAME: %s\n", argv[i], soname); + } + +out: + if (local_error != NULL) + g_warning ("%s", local_error->message); + + return ret; +} diff --git a/tests/meson.build b/tests/meson.build index 64bceaefd..b122e5e48 100644 --- a/tests/meson.build +++ b/tests/meson.build @@ -102,20 +102,28 @@ foreach test_name : tests endif endforeach -executable( - 'test-cheap-copy', - sources : [ - 'cheap-copy.c', - ], - dependencies : [ - gio_unix, - libglnx.get_variable('libglnx_dep'), - ], - link_with : [ - pressure_vessel_utils, - ], - include_directories : project_include_dirs, - install : false, -) +# Helper programs and manual tests +helpers = [ + 'cheap-copy', + 'elf-get-soname', +] + +foreach helper : helpers + executable( + 'test-' + helper, + sources : [ + helper + '.c', + ], + dependencies : [ + gio_unix, + libglnx.get_variable('libglnx_dep'), + ], + link_with : [ + pressure_vessel_utils, + ], + include_directories : project_include_dirs, + install : false, + ) +endforeach # vim:set sw=2 sts=2 et: -- GitLab