From e58bf1fa30b13131d102d504200100e69b6d07df Mon Sep 17 00:00:00 2001 From: Ludovico de Nittis <ludovico.denittis@collabora.com> Date: Thu, 31 Oct 2019 11:02:08 +0100 Subject: [PATCH] Add support for library's hidden dependencies Some libraries like "libtheoraenc.so.1" or "libCgGL.so" assumes that you have already linked to other libraries. With this commit we add support for those situations with the concept of "hidden dependencies". Signed-off-by: Ludovico de Nittis <ludovico.denittis@collabora.com> --- debian/control | 2 + debian/gitlab-ci.yml | 1 + helpers/inspect-library.c | 43 +++++++ steam-runtime-tools/library-internal.h | 1 + steam-runtime-tools/library.c | 47 ++++--- steam-runtime-tools/system-info.c | 121 +++++++++++++++++- .../i386-linux-gnu/libtheora0.symbols | 4 + tests/expectations/steam-runtime-abi.json | 48 +++++++ .../x86_64-linux-gnu/libtheora0.symbols | 4 + .../i386-linux-gnu/libtheora0.symbols | 4 + .../x86_64-linux-gnu/libtheora0.symbols | 4 + .../usr/lib/steamrt/steam-runtime-abi.json | 48 +++++++ tests/system-info.c | 30 +++++ 13 files changed, 335 insertions(+), 22 deletions(-) create mode 100644 tests/expectations/i386-linux-gnu/libtheora0.symbols create mode 100644 tests/expectations/steam-runtime-abi.json create mode 100644 tests/expectations/x86_64-linux-gnu/libtheora0.symbols create mode 100644 tests/fake-steam-runtime/usr/lib/steamrt/expectations/i386-linux-gnu/libtheora0.symbols create mode 100644 tests/fake-steam-runtime/usr/lib/steamrt/expectations/x86_64-linux-gnu/libtheora0.symbols create mode 100644 tests/fake-steam-runtime/usr/lib/steamrt/steam-runtime-abi.json diff --git a/debian/control b/debian/control index 5cffd18fb..1ef16b1ee 100644 --- a/debian/control +++ b/debian/control @@ -9,6 +9,7 @@ Build-Depends: gtk-doc-tools <!nodoc>, libglib2.0-dev, libjson-glib-dev (>= 1.0), + libtheora0 <!nocheck>, libvulkan-dev, libxcb1-dev, locales <!nocheck> | locales-all <!nocheck>, @@ -88,6 +89,7 @@ Section: misc Depends: ${misc:Depends}, ${shlibs:Depends}, + libtheora0, steam-runtime-tools-bin, zlib1g, Description: diff --git a/debian/gitlab-ci.yml b/debian/gitlab-ci.yml index db0dd4923..cfee92ab4 100644 --- a/debian/gitlab-ci.yml +++ b/debian/gitlab-ci.yml @@ -16,6 +16,7 @@ variables: libgles2-mesa-dev libglib2.0-dev libjson-glib-dev + libtheora-dev libx11-dev libxcb1-dev libxcomposite-dev diff --git a/helpers/inspect-library.c b/helpers/inspect-library.c index 5467eee53..23779009a 100644 --- a/helpers/inspect-library.c +++ b/helpers/inspect-library.c @@ -76,10 +76,12 @@ enum { OPTION_HELP = 1, OPTION_DEB_SYMBOLS, + OPTION_HIDDEN_DEPENDENCY, }; struct option long_options[] = { + { "hidden-dependency", required_argument, NULL, OPTION_HIDDEN_DEPENDENCY }, { "deb-symbols", no_argument, NULL, OPTION_DEB_SYMBOLS }, { "help", no_argument, NULL, OPTION_HELP }, { NULL, 0, NULL, 0 } @@ -126,12 +128,17 @@ main (int argc, ssize_t chars; bool first; bool deb_symbols = false; + size_t dependency_counter = 0; int opt; while ((opt = getopt_long (argc, argv, "", long_options, NULL)) != -1) { switch (opt) { + case OPTION_HIDDEN_DEPENDENCY: + dependency_counter++; + break; + case OPTION_DEB_SYMBOLS: deb_symbols = true; break; @@ -152,11 +159,47 @@ main (int argc, usage (1); } + const char *hidden_deps[dependency_counter + 1]; + + if (dependency_counter > 0) + { + /* Reset getopt */ + optind = 1; + + size_t i = 0; + while ((opt = getopt_long (argc, argv, "", long_options, NULL)) != -1) + { + switch (opt) + { + case OPTION_HIDDEN_DEPENDENCY: + hidden_deps[i] = optarg; + i++; + break; + + default: + break; + } + } + } + soname = argv[optind]; printf ("{\n"); printf (" \""); print_json_string_content (soname); printf ("\": {"); + + for (size_t i = 0; i < dependency_counter; i++) + { + /* We don't call "dlclose" on global hidden dependencies, otherwise ubsan + * will report an indirect memory leak */ + if (dlopen (hidden_deps[i], RTLD_NOW|RTLD_GLOBAL) == NULL) + { + fprintf (stderr, "Unable to find the dependency library: %s\n", dlerror ()); + clean_exit (handle); + return 1; + } + } + handle = dlopen (soname, RTLD_NOW); if (handle == NULL) { diff --git a/steam-runtime-tools/library-internal.h b/steam-runtime-tools/library-internal.h index f8f643596..ebc11dea8 100644 --- a/steam-runtime-tools/library-internal.h +++ b/steam-runtime-tools/library-internal.h @@ -89,5 +89,6 @@ SrtLibraryIssues _srt_check_library_presence (const char *helpers_path, const char *soname, const char *multiarch, const char *symbols_path, + const char * const *hidden_deps, SrtLibrarySymbolsFormat symbols_format, SrtLibrary **more_details_out); diff --git a/steam-runtime-tools/library.c b/steam-runtime-tools/library.c index 3e4a6ad19..21d6d0d7b 100644 --- a/steam-runtime-tools/library.c +++ b/steam-runtime-tools/library.c @@ -474,8 +474,8 @@ srt_check_library_presence (const char *soname, SrtLibrary **more_details_out) { return _srt_check_library_presence (NULL, soname, multiarch, - symbols_path, symbols_format, - more_details_out); + symbols_path, NULL, + symbols_format, more_details_out); } SrtLibraryIssues @@ -483,17 +483,14 @@ _srt_check_library_presence (const char *helpers_path, const char *soname, const char *multiarch, const char *symbols_path, + const char * const *hidden_deps, SrtLibrarySymbolsFormat symbols_format, SrtLibrary **more_details_out) { - gchar *helper = NULL; + GPtrArray *argv = NULL; gchar *output = NULL; gchar *child_stderr = NULL; gchar *absolute_path = NULL; - const gchar *argv[] = - { - "inspect-library", "--deb-symbols", soname, symbols_path, NULL - }; int exit_status = -1; JsonParser *parser = NULL; JsonNode *node = NULL; @@ -509,6 +506,7 @@ _srt_check_library_presence (const char *helpers_path, GStrv my_environ = NULL; const gchar *ld_preload; gchar *filtered_preload = NULL; + argv = g_ptr_array_new_with_free_func (g_free); g_return_val_if_fail (soname != NULL, SRT_LIBRARY_ISSUES_INTERNAL_ERROR); g_return_val_if_fail (multiarch != NULL, SRT_LIBRARY_ISSUES_INTERNAL_ERROR); @@ -516,31 +514,40 @@ _srt_check_library_presence (const char *helpers_path, SRT_LIBRARY_ISSUES_INTERNAL_ERROR); g_return_val_if_fail (_srt_check_not_setuid (), SRT_LIBRARY_ISSUES_INTERNAL_ERROR); + if (helpers_path == NULL) + helpers_path = _srt_get_helpers_path (); + + g_ptr_array_add (argv, g_strdup_printf ("%s/%s-inspect-library", helpers_path, multiarch)); + g_debug ("Checking library %s integrity with %s", soname, (gchar *)g_ptr_array_index (argv, 0)); + switch (symbols_format) { case SRT_LIBRARY_SYMBOLS_FORMAT_PLAIN: - argv[1] = soname; - argv[2] = symbols_path; - argv[3] = NULL; + g_ptr_array_add (argv, g_strdup (soname)); + g_ptr_array_add (argv, g_strdup (symbols_path)); break; case SRT_LIBRARY_SYMBOLS_FORMAT_DEB_SYMBOLS: - /* do nothing, argv is already set up for this */ + g_ptr_array_add (argv, g_strdup ("--deb-symbols")); + g_ptr_array_add (argv, g_strdup (soname)); + g_ptr_array_add (argv, g_strdup (symbols_path)); break; default: g_return_val_if_reached (SRT_LIBRARY_ISSUES_CANNOT_LOAD); } - if (symbols_path == NULL) - issues |= SRT_LIBRARY_ISSUES_UNKNOWN_EXPECTATIONS; + for (gsize i = 0; hidden_deps != NULL && hidden_deps[i] != NULL; i++) + { + g_ptr_array_add (argv, g_strdup ("--hidden-dependency")); + g_ptr_array_add (argv, g_strdup (hidden_deps[i])); + } - if (helpers_path == NULL) - helpers_path = _srt_get_helpers_path (); + /* NULL terminate the array */ + g_ptr_array_add (argv, NULL); - helper = g_strdup_printf ("%s/%s-inspect-library", helpers_path, multiarch); - argv[0] = helper; - g_debug ("Checking library %s integrity with %s", soname, helper); + if (symbols_path == NULL) + issues |= SRT_LIBRARY_ISSUES_UNKNOWN_EXPECTATIONS; my_environ = g_get_environ (); ld_preload = g_environ_getenv (my_environ, "LD_PRELOAD"); @@ -551,7 +558,7 @@ _srt_check_library_presence (const char *helpers_path, } if (!g_spawn_sync (NULL, /* working directory */ - (gchar **) argv, + (gchar **) argv->pdata, my_environ, /* envp */ 0, /* flags */ NULL, /* child setup */ @@ -658,9 +665,9 @@ out: g_strfreev (missing_symbols); g_strfreev (misversioned_symbols); g_strfreev (dependencies); + g_ptr_array_free (argv, TRUE); g_free (absolute_path); g_free (child_stderr); - g_free (helper); g_free (output); g_free (filtered_preload); g_clear_error (&error); diff --git a/steam-runtime-tools/system-info.c b/steam-runtime-tools/system-info.c index 7619c3de6..96726f4b7 100644 --- a/steam-runtime-tools/system-info.c +++ b/steam-runtime-tools/system-info.c @@ -45,6 +45,7 @@ #include <sys/stat.h> #include <sys/types.h> #include <unistd.h> +#include <json-glib/json-glib.h> /** * SECTION:system-info @@ -92,6 +93,7 @@ struct _SrtSystemInfo /* Multiarch tuple to use for helper executables in cases where it * shouldn't matter, or %NULL to use the compiled-in default */ GQuark primary_multiarch_tuple; + GHashTable *cached_hidden_deps; struct { /* GQuark => MaybeLocale */ @@ -409,6 +411,8 @@ srt_system_info_finalize (GObject *object) g_free (self->expectations); g_free (self->helpers_path); g_strfreev (self->env); + if (self->cached_hidden_deps) + g_hash_table_unref (self->cached_hidden_deps); G_OBJECT_CLASS (srt_system_info_parent_class)->finalize (object); } @@ -607,6 +611,111 @@ ensure_expectations (SrtSystemInfo *self) return self->expectations[0] != '\0'; } +static void +ensure_hidden_deps (SrtSystemInfo *self) +{ + if (self->cached_hidden_deps == NULL) + { + JsonParser *parser = NULL; + JsonNode *node = NULL; + JsonArray *libraries_array = NULL; + JsonArray *hidden_libraries_array = NULL; + JsonObject *object; + gchar *soname = NULL; + gchar *path = NULL; + GPtrArray *arr = NULL; + GError *error = NULL; + + self->cached_hidden_deps = g_hash_table_new_full (g_str_hash, + g_str_equal, + g_free, + (GDestroyNotify) g_strfreev); + + if (!ensure_expectations (self)) + { + g_debug ("Hidden dependencies parsing skipped because of unknown expectations"); + goto out; + } + + path = g_build_filename (self->expectations, "steam-runtime-abi.json", NULL); + + /* Currently, in a standard Steam installation, we have the abi JSON one level up + * from the expectations folder */ + if (!g_file_test (path, G_FILE_TEST_EXISTS)) + { + g_free (path); + path = g_build_filename (self->expectations, "..", "steam-runtime-abi.json", NULL); + } + + parser = json_parser_new (); + if (!json_parser_load_from_file (parser, path, &error)) + { + g_debug ("Error parsing the expected JSON object in \"%s\": %s", path, error->message); + goto out; + } + + node = json_parser_get_root (parser); + object = json_node_get_object (node); + + if (!json_object_has_member (object, "shared_libraries")) + { + g_debug ("No \"shared_libraries\" in the JSON object \"%s\"", path); + goto out; + } + + libraries_array = json_object_get_array_member (object, "shared_libraries"); + /* If there are no libraries in the parsed JSON file we simply return */ + if (libraries_array == NULL || json_array_get_length (libraries_array) == 0) + goto out; + + for (guint i = 0; i < json_array_get_length (libraries_array); i++) + { + node = json_array_get_element (libraries_array, i); + if (!JSON_NODE_HOLDS_OBJECT (node)) + continue; + + object = json_node_get_object (node); + + GList *members = json_object_get_members (object); + if (members == NULL) + continue; + + soname = g_strdup (members->data); + g_list_free (members); + + object = json_object_get_object_member (object, soname); + if (!json_object_has_member (object, "hidden_dependencies")) + { + g_free (soname); + continue; + } + + hidden_libraries_array = json_object_get_array_member (object, "hidden_dependencies"); + if (hidden_libraries_array == NULL || json_array_get_length (hidden_libraries_array) == 0) + { + g_free (soname); + continue; + } + + arr = g_ptr_array_new_full (json_array_get_length (hidden_libraries_array) + 1, g_free); + + for (guint j = 0; j < json_array_get_length (hidden_libraries_array); j++) + g_ptr_array_add (arr, g_strdup (json_array_get_string_element (hidden_libraries_array, j))); + + g_ptr_array_add (arr, NULL); + + g_debug ("%s soname hidden dependencies have been parsed", soname); + g_hash_table_insert (self->cached_hidden_deps, g_steal_pointer (&soname), g_ptr_array_free (arr, FALSE)); + } + + out: + g_free (path); + g_clear_error (&error); + if (parser) + g_clear_object (&parser); + } +} + static void ensure_overrides_cached (SrtSystemInfo *self) { @@ -974,6 +1083,8 @@ srt_system_info_check_libraries (SrtSystemInfo *self, goto out; } + ensure_hidden_deps (self); + while ((filename = g_dir_read_name (dir))) { char *line = NULL; @@ -1007,10 +1118,12 @@ srt_system_info_check_libraries (SrtSystemInfo *self, * found it, as an argument. */ SrtLibrary *library = NULL; char *soname = g_strdup (strsep (&pointer_into_line, " \t")); + gchar **hidden_deps = g_hash_table_lookup (self->cached_hidden_deps, soname); abi->cached_combined_issues |= _srt_check_library_presence (self->helpers_path, soname, multiarch_tuple, symbols_file, + (const gchar * const *)hidden_deps, SRT_LIBRARY_SYMBOLS_FORMAT_DEB_SYMBOLS, &library); g_hash_table_insert (abi->cached_results, soname, library); @@ -1042,7 +1155,6 @@ srt_system_info_check_libraries (SrtSystemInfo *self, g_dir_close (dir); return ret; - } /** @@ -1110,6 +1222,8 @@ srt_system_info_check_library (SrtSystemInfo *self, } } + ensure_hidden_deps (self); + while (dir != NULL && (filename = g_dir_read_name (dir))) { if (!g_str_has_suffix (filename, ".symbols")) @@ -1141,10 +1255,12 @@ srt_system_info_check_library (SrtSystemInfo *self, char *soname_found = g_strdup (strsep (&pointer_into_line, " \t")); if (g_strcmp0 (soname_found, soname) == 0) { + gchar **hidden_deps = g_hash_table_lookup (self->cached_hidden_deps, soname); issues = _srt_check_library_presence (self->helpers_path, soname_found, multiarch_tuple, symbols_file, + (const gchar * const *)hidden_deps, SRT_LIBRARY_SYMBOLS_FORMAT_DEB_SYMBOLS, &library); g_hash_table_insert (abi->cached_results, soname_found, library); @@ -1169,6 +1285,7 @@ srt_system_info_check_library (SrtSystemInfo *self, soname, multiarch_tuple, NULL, + NULL, SRT_LIBRARY_SYMBOLS_FORMAT_DEB_SYMBOLS, &library); g_hash_table_insert (abi->cached_results, g_strdup (soname), library); @@ -1299,7 +1416,7 @@ srt_system_info_check_graphics (SrtSystemInfo *self, * * Returns: (transfer full) (type SrtGraphics): A list of #SrtGraphics objects * representing the items tested and results. - * Free with 'glist_free_full(list, g_object_unref)`. + * Free with 'g_list_free_full(list, g_object_unref)`. */ GList * srt_system_info_check_all_graphics (SrtSystemInfo *self, const char *multiarch_tuple) diff --git a/tests/expectations/i386-linux-gnu/libtheora0.symbols b/tests/expectations/i386-linux-gnu/libtheora0.symbols new file mode 100644 index 000000000..a46156c32 --- /dev/null +++ b/tests/expectations/i386-linux-gnu/libtheora0.symbols @@ -0,0 +1,4 @@ +# Cut-down version of libtheora0:i386.symbols, to illustrate what we expect +# to find here +libtheoraenc.so.1 libtheora0 #MINVER# +# No symbols listed here yet diff --git a/tests/expectations/steam-runtime-abi.json b/tests/expectations/steam-runtime-abi.json new file mode 100644 index 000000000..a85385ce7 --- /dev/null +++ b/tests/expectations/steam-runtime-abi.json @@ -0,0 +1,48 @@ +{ + "architectures": { + "i386-linux-gnu": { + "dpkg_name": "i386" + }, + "x86_64-linux-gnu": { + "dpkg_name": "amd64" + } + }, + "extra_debs": { + "libs": [ + "dconf-gsettings-backend", + "gtk2-engines" + ] + }, + "private_libraries": [ + { + "libVkLayer_*.so": { + "deb": "libvulkan1" + } + } + ], + "shared_libraries": [ + { + "libglut.so.3": { + "deb": "freeglut3" + } + }, + "libacl.so.1", + { + "libtheoraenc.so.1": { + "deb": "libtheora0", + "hidden_dependencies": [ + "libtheoradec.so.1" + ] + } + }, + { + "libtWithHiddens.so.1": { + "deb": "libtWithHiddens0", + "hidden_dependencies": [ + "firstHidden.so.0", + "secondHidden.so.3" + ] + } + } + ] +} diff --git a/tests/expectations/x86_64-linux-gnu/libtheora0.symbols b/tests/expectations/x86_64-linux-gnu/libtheora0.symbols new file mode 100644 index 000000000..0d054e654 --- /dev/null +++ b/tests/expectations/x86_64-linux-gnu/libtheora0.symbols @@ -0,0 +1,4 @@ +# Cut-down version of libtheora0:amd64.symbols, to illustrate what we expect +# to find here +libtheoraenc.so.1 libtheora0 #MINVER# +# No symbols listed here yet diff --git a/tests/fake-steam-runtime/usr/lib/steamrt/expectations/i386-linux-gnu/libtheora0.symbols b/tests/fake-steam-runtime/usr/lib/steamrt/expectations/i386-linux-gnu/libtheora0.symbols new file mode 100644 index 000000000..a46156c32 --- /dev/null +++ b/tests/fake-steam-runtime/usr/lib/steamrt/expectations/i386-linux-gnu/libtheora0.symbols @@ -0,0 +1,4 @@ +# Cut-down version of libtheora0:i386.symbols, to illustrate what we expect +# to find here +libtheoraenc.so.1 libtheora0 #MINVER# +# No symbols listed here yet diff --git a/tests/fake-steam-runtime/usr/lib/steamrt/expectations/x86_64-linux-gnu/libtheora0.symbols b/tests/fake-steam-runtime/usr/lib/steamrt/expectations/x86_64-linux-gnu/libtheora0.symbols new file mode 100644 index 000000000..0d054e654 --- /dev/null +++ b/tests/fake-steam-runtime/usr/lib/steamrt/expectations/x86_64-linux-gnu/libtheora0.symbols @@ -0,0 +1,4 @@ +# Cut-down version of libtheora0:amd64.symbols, to illustrate what we expect +# to find here +libtheoraenc.so.1 libtheora0 #MINVER# +# No symbols listed here yet diff --git a/tests/fake-steam-runtime/usr/lib/steamrt/steam-runtime-abi.json b/tests/fake-steam-runtime/usr/lib/steamrt/steam-runtime-abi.json new file mode 100644 index 000000000..a85385ce7 --- /dev/null +++ b/tests/fake-steam-runtime/usr/lib/steamrt/steam-runtime-abi.json @@ -0,0 +1,48 @@ +{ + "architectures": { + "i386-linux-gnu": { + "dpkg_name": "i386" + }, + "x86_64-linux-gnu": { + "dpkg_name": "amd64" + } + }, + "extra_debs": { + "libs": [ + "dconf-gsettings-backend", + "gtk2-engines" + ] + }, + "private_libraries": [ + { + "libVkLayer_*.so": { + "deb": "libvulkan1" + } + } + ], + "shared_libraries": [ + { + "libglut.so.3": { + "deb": "freeglut3" + } + }, + "libacl.so.1", + { + "libtheoraenc.so.1": { + "deb": "libtheora0", + "hidden_dependencies": [ + "libtheoradec.so.1" + ] + } + }, + { + "libtWithHiddens.so.1": { + "deb": "libtWithHiddens0", + "hidden_dependencies": [ + "firstHidden.so.0", + "secondHidden.so.3" + ] + } + } + ] +} diff --git a/tests/system-info.c b/tests/system-info.c index bff4ea2ac..cb330f515 100644 --- a/tests/system-info.c +++ b/tests/system-info.c @@ -225,6 +225,36 @@ check_libraries_result (GList *libraries) g_assert_true (seen_libc); + /* Test third library */ + l = g_list_next (l); + library = l->data; + g_assert_nonnull (library); + g_assert_cmpstr (srt_library_get_soname (library), ==, "libtheoraenc.so.1"); + missing_symbols = srt_library_get_missing_symbols (library); + g_assert_nonnull (missing_symbols); + g_assert_cmpstr (missing_symbols[0], ==, NULL); + + g_assert_cmpint (srt_library_get_issues (library), ==, SRT_LIBRARY_ISSUES_NONE); + + misversioned_symbols = srt_library_get_misversioned_symbols (library); + g_assert_nonnull (misversioned_symbols); + g_assert_cmpstr (misversioned_symbols[0], ==, NULL); + + dependencies = srt_library_get_dependencies (library); + g_assert_nonnull (dependencies); + g_assert_cmpstr (dependencies[0], !=, NULL); + seen_libc = FALSE; + + for (gsize i = 0; dependencies[i] != NULL; i++) + { + g_debug ("libtheoraenc.so.1 depends on %s", dependencies[i]); + + if (strstr (dependencies[i], "/libc.so.") != NULL) + seen_libc = TRUE; + } + + g_assert_true (seen_libc); + /* Test last library */ l = g_list_next (l); library = l->data; -- GitLab