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