diff --git a/debian/control b/debian/control index 5cffd18fb2673b76ff9c89915a8f11dc6e9c5ee6..1ef16b1ee0905909929f1d950f8b2a2df9eb9ab3 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 db0dd4923600d91b502fc12ba611477426b4e027..cfee92ab4cd9eee20e37107b95f18003ee3ff975 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 5467eee539825b8ad0135f5d4c528a2d508f85c8..23779009a0713b19ba373125e3e9855ad52622e8 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 f8f64359628648f2e27baae8f950837e7491d25b..ebc11dea8fd812784808a680b703711a3ad62762 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 3e4a6ad19f2f84df6ed7f97e4efa242bb82acf95..21d6d0d7be2e846167f56567d0fe66cb3578bb9c 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 7619c3de666012b782092dfcbf54a98bcc970f1e..96726f4b7d377855eff1c33ea9a46aa98d657e09 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 0000000000000000000000000000000000000000..a46156c326b8c481e330a8614b14b761ff1ad0a0 --- /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 0000000000000000000000000000000000000000..a85385ce7a080ae40d5fd4c170ac671c86b1fa76 --- /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 0000000000000000000000000000000000000000..0d054e654714036dff10f99462cb6b6a9cae507f --- /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 0000000000000000000000000000000000000000..a46156c326b8c481e330a8614b14b761ff1ad0a0 --- /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 0000000000000000000000000000000000000000..0d054e654714036dff10f99462cb6b6a9cae507f --- /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 0000000000000000000000000000000000000000..a85385ce7a080ae40d5fd4c170ac671c86b1fa76 --- /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 bff4ea2acb8c452e8d6decab7930831995aa468d..cb330f5152d4bc126bc215cfb1183629acc87bbb 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;