strace-ing an AppImage

4 minutes read


A little while ago I was asked to run an experiment involving a drone’s flight controller and its serial telemetry capabilities over various ports. I was specifically looking to see what the maximum stable baudrate was for the USB serial connection. Since this task required sending and receiving MAVLink messages, which is a binary protocol, the simplest way was to hook it up to QGroundControl and see what breaks.

QGroundControl usually automatically detects flight controllers when they are plugged in, but it’s also possible to disable this behaviour and specify the serial port and baudrate to use. Though I wasn’t one hundred percent sure it wasn’t trying to be too helpful and automatically lowering the baudrate in case of a garbled connection.

So I wanted to strace the application to see what sort of calls it makes to the kernel to configure the TTY. Should be simple, right?

[karcsesz@ubuntu tmp]> strace -f -o /tmp/trace_output ./QGroundControl-x86_64.AppImage
/usr/bin/fusermount: mount failed: Operation not permitted

Cannot mount AppImage, please check your FUSE setup.
You might still be able to extract the contents of this AppImage
if you run it with the --appimage-extract option.
See https://github.com/AppImage/AppImageKit/wiki/FUSE
for more information
open dir error: No such file or directory

Simple is dead, and we killed it.

AppImages and you

The official way to run QGroundControl on Linux is via the officially distributed AppImage. AppImages work by bundling (almost) all userspace dependencies that the application is built against, which then get transparently mounted to the system. And it’s this last part that’s the issue here.

AppImages mount their contents via FUSE, specifically by calling fusermount. And because mounting filesystems usually requires superuser privileges, fusermount has a very special bit set on it.

[karcsesz@ubuntu tmp]> ls -la (which fusermount)
-rwsr-xr-x 1 root root 39144 Mar 23  2022 /usr/bin/fusermount

That s in the fourth column means this binary makes use of the kernel’s setuid feature. We can also verify this by calling file on the executable.

[karcsesz@ubuntu tmp]> file (which fusermount)
/usr/bin/fusermount: setuid ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV),
dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2,
BuildID[sha1]=c7b49f5f258c2c7743a46d71ff72777d6b3defa8, for GNU/Linux 3.2.0, stripped

This makes the application very scary.

Setuid and security

The expression setuid is relatively easy to make sense of, it sets a userid somehow. But how and what does it set?

It’s how sudo works. The setuid bit tells the kernel to run the executable file as its owner. And since the owner of fusermount is root, it means this application (like sudo) runs with superuser permissions, no matter what user launches it.

There have been countless privilege escalation vulnerabilities thanks to this kernel capability. A vulnerability in anything that’s been blessed with setuid root could spell doom for a system. And this is why we fail to trace our target. Imagine what an attacker could surmise from a system trace of something launched as root. So strace (and ptrace for that matter) completely disables processing setuid.

Let’s say we wanted to strace sudo. It would fail to escalate to root and then collapse.

[karcsesz@ubuntu tmp]> strace -f -o /dev/null sudo
sudo: effective uid is not 0, is /usr/bin/sudo on a file system
with the 'nosuid' option set or an NFS file system without root privileges?

So we need to escalate permissions manually.

The setuid we have at home

If we run the trace under sudo, we run into an old classic.

Screenshot of an error window. 'You are running QGroundControl as root. You should not do this since it will cause other issues with QGroundControl. QGroundControl will now exit.'

Fair enough, thankfully if we read the manual of strace, there is a function to drop to a different user account for the traced program. And it doesn’t even disable setuid again!

[karcsesz@ubuntu tmp]> sudo strace -f -o /dev/null -u karcsesz ./QGroundControl-x86_64.AppImage
  [various QGroundControl messages]

We’re i-!

Wait, something’s not quite right yet.

Screenshot of a warning withing QGroundControl. 'The current user does not have the correct permissions to access serial devices. You should also remove modemmanager since it also interferes.' Followed by instructions for how to correct the issue in Ubuntu.

The flight controller connected fine behind the warning, so I’ll just dismiss it. But what’s this now?

Screenshot of a popup within QGroundControl asking me to configure the measurement units I want it to use.

I already did this setup. Why am I being prompted again? And where is my serial device configuration?

Oh no…

Changing Environment

When you switch users through strace -u the environment variables don’t get switched back to your user’s.

[karcsesz@ubuntu tmp]> sudo strace -o /dev/null -u karcsesz env
TERM=xterm-256color
  [...]
MAIL=/var/mail/root
LOGNAME=root
USER=root
HOME=/root
SHELL=/bin/bash
SUDO_COMMAND=/usr/bin/strace -o /dev/null -u karcsesz env
SUDO_USER=karcsesz
SUDO_UID=1000
SUDO_GID=1000

QGroundControl doesn’t complain because the UID isn’t 0, but the environment variables still tell it to read its configuration from /root instead of my home directory. There doesn’t seem to be an official way for strace to restore the environment, but we can ask sudo not to change it in the first place.

[karcsesz@ubuntu tmp]> sudo -E strace -o /dev/null -u karcsesz env
TERM=xterm-256color
  [...]
MAIL=/var/spool/mail/karcsesz
XDG_RUNTIME_DIR=/run/user/1000
GTK_RC_FILES=/etc/gtk/gtkrc:/home/karcsesz/.gtkrc:/home/karcsesz/.config/gtkrc
XDG_CONFIG_HOME=/home/karcsesz/.config
XDG_SESSION_TYPE=wayland
TERM_PROGRAM=terminology
USER=root
XDG_DATA_HOME=/home/karcsesz/.local/share
LOGNAME=root
SUDO_COMMAND=/usr/bin/strace -o /dev/null -u karcsesz env
SUDO_USER=karcsesz
SUDO_UID=1000
SUDO_GID=1000

But $USER is still root, because even with the -E flag, sudo still changes a few values. But we can disable these changes too, by adding --preserve-env multiple times. At this point I’d recommend that you manually diff the output of just bare env and env launched through this mess of utilities, but for me I still had to specify LOGNAME, USER, and PATH.

And with all this finished, we are finally victorious.

[karcsesz@ubuntu tmp]> sudo -E --preserve-env=LOGNAME,USER,PATH strace -f -o /dev/null -u karcsesz ./QGroundControl-x86_64.AppImage

For the record, I couldn’t tell if QGroundControl would autobaud on failure, because the flight controller could take the highest option of 4000000 baud without an issue.

Comments

You can comment on this blog post by publicly replying to this post using a Mastodon or other ActivityPub/Fediverse account. Known non-private replies are displayed below.

Open Post