Today, in coordination with Ilya Lipnitskiy (the maintainer of libcue) and the distros mailing list, the GitHub Security Lab is disclosing CVE-2023-43641, a memory corruption vulnerability in libcue. We have also sent a text-only version of this blog post to the oss-security list.

It’s quite likely that you have never heard of libcue before, and are wondering why it’s important. This situation is neatly illustrated by xkcd 2347:

libcue is a library used for parsing cue sheets—a metadata format for describing the layout of the tracks on a CD. Cue sheets are often used in combination with the FLAC audio file format, which means that libcue is a dependency of some audio players, such as Audacious. But the reason why I decided to audit libcue for security vulnerabilities is that it’s used by tracker-miners: an application that’s included with GNOME—the default graphical desktop environment of many open source operating systems. The purpose of tracker-miners is to index the files in your home directory to make them easily searchable. For example, the index is used by this search bar:

The index is automatically updated when you add or modify a file in certain subdirectories of your home directory, in particular including ~/Downloads. To make a long story short, that means that inadvertently clicking a malicious link is all it takes for an attacker to exploit CVE-2023-43641 and get code execution on your computer:

The video shows me clicking a link in a webpage, which causes a cue sheet to be downloaded. Because the file is saved to ~/Downloads, it is then automatically scanned by tracker-miners. And because it has a .cue filename extension, tracker-miners uses libcue to parse the file. The file exploits the vulnerability in libcue to gain code execution and pop a calculator. Cue sheets are just one of many file formats supported by tracker-miners. For example, it also includes scanners for HTML, JPEG, and PDF.

I am delaying publication of the proof of concept (PoC) used in the video, to give users time to install the patch. But if you’d like to test if your system is vulnerable, try downloading this file, which contains a much simpler version of the PoC that merely causes a (benign) crash.

The offsets in the full PoC need to be tuned for different distributions. I have only done this for Ubuntu 23.04 and Fedora 38, the most recent releases of Ubuntu and Fedora at this time. In my testing, I have found that the PoC works very reliably when run on the correct distribution (and will trigger a SIGSEGV when run on the wrong distribution). I have not created PoCs for any other distributions, but I believe that all distributions that run GNOME are potentially exploitable.

The bug in libcue

libcue is quite a small project. It’s primarily a bison grammar for cue sheets, with a few data structures for storing the parsed data. A simple example of a cue sheet looks like this:

REM GENRE "Pop, dance pop"
REM DATE 1987
PERFORMER "Rick Astley"
TITLE "Whenever You Need Somebody"
FILE "Whenever You Need Somebody.mp3" MP3 TRACK 01 AUDIO TITLE "Never Gonna Give You Up" PERFORMER "Rick Astley" SONGWRITER "Mike Stock, Matt Aitken, Pete Waterman" INDEX 01 00:00:00 TRACK 02 AUDIO TITLE "Whenever You Need Somebody" PERFORMER "Rick Astley" SONGWRITER "Mike Stock, Matt Aitken, Pete Waterman" INDEX 01 03:35:00

The vulnerability is in the handling of the INDEX syntax. Replacing one of those INDEX statements with this will trigger the bug:

INDEX 4294567296 0

There are two parts to the problem. The first is that the scanner (cue_scanner.l, line 132) uses atoi to scan the integers:

[[:digit:]]+ { yylval.ival = atoi(yytext); return NUMBER; }

atoi does not check for integer overflow, so it is easy to construct a negative index. For example, 4294567296 is converted to -400000 by atoi.

The second part of the problem (and this is the actual vulnerability) is that track_set_index does not check that i ≥ 0:

void track_set_index(Track *track, int i, long ind)
{ if (i > MAXINDEX) { fprintf(stderr, "too many indexes\n"); return; } track->index[i] = ind;
}

If i is negative, then this code can write to an address outside the bounds of the array. Since the value of ind is also attacker-controlled, this is a very powerful vulnerability.

The bug is simple to fix by adding an extra condition to the if-statement in track_set_index. This is the proposed patch:

diff --git a/cd.c b/cd.c
index cf77a18..4bbea19 100644
--- a/cd.c
+++ b/cd.c
@@ -339,7 +339,7 @@ track_get_rem(const Track* track) void track_set_index(Track *track, int i, long ind) {
- if (i > MAXINDEX) {
+ if (i < 0 || i > MAXINDEX) { fprintf(stderr, "too many indexes\n"); return; }

More about tracker-miners

I want to be clear that this bug is not a vulnerability in tracker-miners. But I have focused on tracker-miners because it magnifies the impact of this bug due to the way that it automatically scans the files in your ~/Downloads directory.

tracker-miners consists of two processes:

  1. tracker-miner-fs
  2. tracker-extract

The first, tracker-miner-fs, is a background process which is always running, whereas the second, tracker-extract, is only started on demand to scan new files. tracker-miner-fs uses inotify to monitor specific directories, such as ~/Downloads, ~/Music, and ~/Videos. When a new file is created, it launches tracker-extract to scan the file. tracker-extract sends the results back to tracker-miner-fs (which maintains the index) and then usually shuts down again after a few seconds. The vulnerability only affects tracker-extract, because that’s where libcue is used. Both processes run as the current user, so this vulnerability would need to be chained with a separate privilege escalation vulnerability for an attacker to gain admin privileges.

The vulnerability will not trigger if tracker-miners is not running. To check if it is, I use the command ps aux | grep track. It usually shows that tracker-miner-fs is running and that tracker-extract isn’t. If neither is running (which I think is rare), then using the search bar (press the “super” key and type something) should automatically restart tracker-miner-fs. As far as I know, tracker-miners is quite tightly integrated into GNOME, so there’s no easy way to switch it off. There’s certainly nothing like a simple checkbox in the settings dialog. There’s some discussion here about how to switch it off by modifying your systemd configuration.

The two-process architecture of tracker-miners is helpful for exploitation. Firstly, it’s much easier to predict the memory layout in a freshly started process than in one that’s already been running for hours, so the fact that tracker-extract is only started on-demand is very convenient. Even better, tracker-extract always creates a fresh thread to scan the downloaded file, and I’ve found that the heap layout in the thread’s malloc arena is very consistent: it varies between distributions, so, for example, Ubuntu 23.04 has a slightly different layout than Fedora 38, but on the same distribution the layout is identical every single time. Secondly, because tracker-extract is restarted on demand, an attacker could potentially crash it many times until their exploit succeeds. Due to the consistency of the heap layout, I’ve found that my exploit works very reliably without needing to use this, but I could imagine an attacker loading a zip file with thousands of copies of their exploit to increase their chance of success when the victim unzips the download.

tracker-miners seccomp sandbox escape

The difficult part of exploiting this vulnerability was finding a way to bypass ASLR. But what I didn’t realize when I started writing the PoC, is that tracker-extract also has a seccomp sandbox which is intended to prevent this kind of exploit from working. It was a nasty surprise when I thought I had all the pieces in place for a working PoC and it failed with the error message: Disallowed syscall "close_range" caught in sandbox. But I still failed to understand that I was attempting a sandbox escape here. I just thought I needed to take a different code path that didn’t use the close_range function. So I tried a different route, it worked, and I didn’t give it any more thought until the GNOME developers asked how I’d managed to escape the sandbox. It turned out that I’d discovered the escape entirely by accident: while I was working on the new route, I unwittingly made a change to the PoC that solved it. I have since discovered that I could have got the original PoC working with a one-line change. I’ll go into more detail on this in a follow-up blog post when I publish the PoC, but for now I’ll just mention that, in response to this, Carlos Garnacho has very quickly implemented some changes to strengthen the sandbox, which will prevent this exploitation path from working in the future.

Conclusion

Sometimes a vulnerability in a seemingly innocuous library can have a large impact. Due to the way that it’s used by tracker-miners, this vulnerability in libcue became a 1-click RCE. If you use GNOME, please update today!

I’m delaying the release of the full PoC to give users time to install the update, but planning to publish a follow-up blog post soon with details of how the full PoC works. Save an unpatched VM with Ubuntu 23.04 or Fedora 38 if you’d like to test the full PoC when I release it.

Notes