Posted on 13 December 2024 by Dimitrios Glynos
The GTK project is a free and open-source cross-platform widget toolkit for creating graphical user interfaces.
Dimitrios Glynos of intWave found that applications based on GTK-3 or GTK-2 (aka GTK+3 / GTK+2) were vulnerable to library injection from the current working directory (CWD), whenever a GTK module was requested to be loaded but the module was missing from the standard paths. This issue is tracked as CVE-2024-6655 (assigned by RedHat).
The GTK project issued a security fix in version 3.24.43 of GTK-3. GTK-2 will not be receiving an official fix for this issue as it is no longer maintained.
Software distributions that maintain GTK-3 packages (and software vendors that bundle their software with GTK-3) should make sure that they are using a version of GTK-3 with the fix applied. Backporting the fix to a GTK-2 codebase is also an option.
Users are recommended to update GTK-2/GTK-3 libraries to the latest versions
available and use extreme caution when starting vulnerable GTK applications
from directories containing possibly untrusted content (e.g. ~/Downloads
).
While researching the exploitability of this issue, we found that both Debian and Ubuntu came with a GNOME configuration that could allow for remote (but victim-assisted) exploitation of the issue. A similar scenario was also identified on systems running GNOME under X11. For more information on these vectors please see the Exploitation section of this advisory.
Ubuntu published a fix for the GTK libraries on July 16 2024 covering
all supported LTS versions of Ubuntu, while Debian made the GTK fix available
in Debian 12.7 (stable) and Debian 11.11 (oldstable) on August 31st 2024.
Any mention of the term “GTK” in this section shall refer to GTK-2/GTK-3 codebases collectively.
A GTK-based application will try to load a GTK module (a shared library) when:
GTK2_MODULES
/GTK_MODULES
for GTK-2, and GTK3_MODULES
/GTK_MODULES
for GTK-3), or--gtk-module
parameter), orgtk-modules
.The actual loading of a GTK module in all of the abovementioned scenarios
occurs through load_module()
. This function uses a utility function
called find_module()
to locate and load the module. An excerpt of
find_module()
is provided below from gtk+-3.24.42/gtk/gtkmodules.c
:
210 static GModule *
211 find_module (const gchar *name)
212 {
213 GModule *module;
214 gchar *module_name;
215
216 module_name = _gtk_find_module (name, "modules");
217 if (!module_name)
218 {
219 /* As last resort, try loading without an absolute path (using system
220 * library path)
221 */
222 module_name = g_module_build_path (NULL, name);
223 }
224
225 module = g_module_open (module_name, G_MODULE_BIND_LOCAL | G_MODULE_BIND_LAZY);
226
227 if (_gtk_module_has_mixed_deps (module))
228 {
229 g_warning ("GTK+ module %s cannot be loaded.\n"
230 "GTK+ 2.x symbols detected. Using GTK+ 2.x and GTK+ 3 in the same process is not supported.", module_name);
231 g_module_close (module);
232 module = NULL;
In line 222 above, if the requested GTK module has not been found under
the standard GTK module directories, then the module is searched for in
the current working directory (notice the NULL argument to glib’s
g_module_build_path()
).
This opens the door to having malicious versions of missing modules be
loaded from a directory with attacker-controlled content.
This section presents two example exploitation scenarios that take advantage of the library injection issue.
Both scenarios share the theme where a victim user was tricked into receiving malicious files in a certain directory, and then at a later point in time the victim user executes a vulnerable GTK application using that directory as the current working directory (CWD). The vulnerable GTK application will automatically load the malicious files and the remote attacker will thus gain code execution on the victim’s host.
Receiving the malicious files could be performed in various ways, including:
~/Downloads
and ~/Downloads
later becomes the CWD.We are aware of the following methods to force a specific directory to be the CWD of a GTK application:
cd Downloads; eog .
).nautilus
through the terminal
(e.g. cd zip-files; nautilus .
). Any later (GUI) invocation of this
GTK-based file manager in the desktop session will continue to use the
same CWD and will further apply this CWD to utility programs used with
the files (like performing decompression with GNOME’s GTK-based
file-roller).If anyone has other ideas on how to force a specific directory to become the CWD of GTK applications or of a specific GTK application, we would be interested to know.
The rest of this section describes the two example exploitation scenarios.
Debian and Debian-derived distributions (including Ubuntu) push an environment
variable GTK_MODULES="gail:atk-bridge"
through the libatk-adaptor
package,
a core GNOME package. The variable mentions two modules that are meant for
GTK-2 applications and in fact, GTK-3 carries a blacklisting (but bypassable)
mechanism so that these modules would not be taken into consideration
for module loading purposes. The modules are there in their standard
directories for GTK-2 applications to use, but would normally appear as
non-existent to a GTK-3 application.
As previously mentioned, GTK-3 has a blacklisting mechanism for the gail
and atk-bridge
components. In load_module()
there is a call to
module_is_blacklisted()
. The blacklist is implemented by not invoking
the gtk_module_init()
function of the blacklisted modules.
However, just before checking the blacklist, each module is already loaded
into memory by find_module()
in gtk+-3.24.42/gtk/gtkmodule.c:292
.
263 static GSList *
264 load_module (GSList *module_list,
265 const gchar *name)
266 {
267 GtkModuleInitFunc modinit_func;
268 gpointer modinit_func_ptr;
269 GtkModuleInfo *info = NULL;
270 GModule *module = NULL;
271 GSList *l;
272 gboolean success = FALSE;
273
274 if (g_module_supported ())
275 {
276 for (l = gtk_modules; l; l = l->next)
...
290 if (!success)
291 {
292 module = find_module (name);
293
294 if (module)
295 {
296 /* Do the check this late so we only warn about existing modules,
297 * not old modules that are still in the modules path. */
298 if (module_is_blacklisted (name, TRUE))
299 {
300 modinit_func = NULL;
301 success = TRUE;
302 }
303 else if (g_module_symbol (module, "gtk_module_init", &modinit_func_ptr))
...
As we saw previously, find_module()
calls glib’s g_module_open()
to dlopen()
the shared library requested. Although gtk_module_init()
is not called on
a blacklisted module, the fact that the module has been loaded into memory
means that any (malicious) code found in library constructors will
be executed. Therefore this mechanism does not work as a true blacklist
for all types of modules in GTK-3, just for well behaving ones.
Now that we have a way to escape the blacklist, we will need to overcome
one more obstacle. In glibc’s dlopen(3)
implementation, simply providing the
filename of a library (without any path elements) does not by default load
the library from the current working directory (as other things
need also be tweaked like LD_LIBRARY_PATH
etc.). glibc would have been
happy to do that if we had provided ./libfoo.so
(i.e. a relative path
with a slash character).
This means that find_module()
’s call to glib’s g_module_open()
will always
fail for a libfoo.so
library. However, if we take a look at the
implementation of g_module_open()
in glib-2.80.3/gmodule/gmodule.c
we have:
704 GModule *
705 g_module_open (const gchar *file_name,
706 GModuleFlags flags)
707 {
708 return g_module_open_full (file_name, flags, NULL);
709 }
465 GModule*
466 g_module_open_full (const gchar *file_name,
467 GModuleFlags flags,
468 GError **error)
469 {
470 GModule *module;
471 gpointer handle = NULL;
472 gchar *name = NULL;
...
526 /* try completing file name with standard library suffix */
527 if (!name)
528 {
529 char *basename, *dirname;
530 size_t prefix_idx = 0, suffix_idx = 0;
531 const char *prefixes[2] = {0}, *suffixes[2] = {0};
532
533 basename = g_path_get_basename (file_name);
534 dirname = g_path_get_dirname (file_name);
...
562 if (!g_str_has_suffix (basename, ".so"))
563 suffixes[suffix_idx++] = ".so";
...
566 for (guint i = 0; i < prefix_idx; i++)
567 {
568 for (guint j = 0; j < suffix_idx; j++)
569 {
570 name = g_strconcat (dirname, G_DIR_SEPARATOR_S, prefixes[i],
571 basename, suffixes[j], NULL);
572 if (g_file_test (name, G_FILE_TEST_IS_REGULAR))
573 goto name_found;
574 g_free (name);
575 name = NULL;
576 }
577 }
578 name_found:
579 g_free (basename);
580 g_free (dirname);
581 }
582 /* try completing by appending libtool suffix */
583 if (!name)
584 {
585 name = g_strconcat (file_name, ".la", NULL);
586 if (!g_file_test (name, G_FILE_TEST_IS_REGULAR))
587 {
588 g_free (name);
589 name = NULL;
590 }
591 }
...
The branch in line 583 makes the application search for a similarly named
.la
libtool file when the library file requested is not found on disk.
Therefore, it is possible to craft this file (say libfoo.so.la
)
to point to a completely different library (say libx.so
) at so-called
libdir "."
. The parsing mechanism for .la
files will be happy to
synthesize the ./libx.so
path for us and this will eventually
be fed to dlopen(3)
to load the libx.so
module from the current
working directory.
As a proof of concept, we have created two files. The first one is a malicious libtool file (based on libtiff). You may rename this file to libatk-bridge.so.la
. The second file is the source code of
the malicious module. Its purpose is to print “HELLO!” to standard error.
You may rename this second file to foo.c
and compile based on the
instructions found in the file.
Place both libfoo.so
and libatk-bridge.so.la
in a directory.
Within that directory, try to start the GTK-3 based firefox application on an unpatched Debian (or Debian-derived)
system and you should see a “HELLO!” message printed to standard error.
$ cd directory
$ ls
libatk-bridge.so.la libfoo.so
$ firefox
HELLO!
Gtk-Message: 16:13:05.585: Not loading module "atk-bridge": The functionality
is provided by GTK natively. Please try to not load it.
Please note that we can substitute firefox with any other GTK-3 based
application and the effects will remain the same. Below you may find
a video recording demonstrating this exploitation path, using the
“Eye Of Gnome” (eog
) application shipped with the Ubuntu 24.04 live disc.
Let’s consider the case of an application that has been bundled with a
vulnerable version of GTK-3 and is missing a GTK module. The absense of a module
might not be due to an oversight in development, but rather due to an
environment setting. Some GTK applications when executed as part of a GNOME
desktop that runs under X11, search for the canberra-gtk-module
GTK module.
For our example we will use the official AppImage build of
Inkscape version 1.4 which reports a missing canberra-gtk-module
GTK module when
executed in GNOME under X11. Inkscape comes bundled with a version of GTK-3
that is vulnerable to CVE-2024-6655. Therefore, it is possible in this setting
to exploit the vulnerability to load a malicious version of the missing library.
To verify this claim, we can place a malicious libcanberra-gtk-module.so.la
libtool file and a libfoo.so
library in the Downloads
directory to simulate a
“drive-by download” attack, and then we can trigger the AppImage-bundled
Inkscape application from the GNOME file manager’s GUI.
We start by renaming this file to
libcanberra-gtk-module.so.la
and storing this to the Downloads
directory.
Then, we use this file to build a malicious GTK module that will spawn a GNOME calculator. We build the module based
on the instructions provided in the source code and we make sure the
output (libfoo.so
) is placed in the Downloads
directory.
Then, a browser is used to download the Inkscape AppImage bundle to the
Downloads
directory. With the GNOME file manager we make the AppImage
file executable. Once the AppImage file is double clicked, we should see
two GNOME calculators on the desktop (due to multiple requests for
canberra-gtk-module
).
Please note that this is a GUI-only attack scenario involving no use of the terminal by the victim user.
The video that follows demonstrates this attack vector on a Debian 12 system running a GNOME desktop under X11.
Many thanks to Red Hat, the GTK developers, the Ubuntu Security Team, the Debian Security Team and the GTK package mantainers for the work they put into tracking the issue and releasing the fix.
We remain available for any further information required on the issue.