exynos-linux-stable/drivers/base/superuser.c

143 lines
4.8 KiB
C
Raw Permalink Normal View History

// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) 2015-2018 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
*/
/* Hello. If this is enabled in your kernel for some reason, whoever is
* distributing your kernel to you is a complete moron, and you shouldn't
* use their kernel anymore. But it's not my fault! People: don't enable
* this driver! (Note that the existence of this file does not imply the
* driver is actually in use. Look in your .config to see whether this is
* enabled.) -Jason
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/uaccess.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/mman.h>
#include <linux/ptrace.h>
#include <linux/syscalls.h>
static bool is_su(const char __user *filename)
{
static const char su_path[] = "/system/bin/su";
char ufn[sizeof(su_path)];
return likely(!copy_from_user(ufn, filename, sizeof(ufn))) &&
unlikely(!memcmp(ufn, su_path, sizeof(ufn)));
}
static void __user *userspace_stack_buffer(const void *d, size_t len)
{
/* To avoid having to mmap a page in userspace, just write below the stack pointer. */
char __user *p = (void __user *)current_user_stack_pointer() - len;
return copy_to_user(p, d, len) ? NULL : p;
}
static char __user *sh_user_path(void)
{
static const char sh_path[] = "/system/bin/sh";
return userspace_stack_buffer(sh_path, sizeof(sh_path));
}
static long(*old_newfstatat)(int dfd, const char __user *filename,
struct stat *statbuf, int flag);
static long new_newfstatat(int dfd, const char __user *filename,
struct stat __user *statbuf, int flag)
{
if (!is_su(filename))
return old_newfstatat(dfd, filename, statbuf, flag);
return old_newfstatat(dfd, sh_user_path(), statbuf, flag);
}
static long(*old_faccessat)(int dfd, const char __user *filename, int mode);
static long new_faccessat(int dfd, const char __user *filename, int mode)
{
if (!is_su(filename))
return old_faccessat(dfd, filename, mode);
return old_faccessat(dfd, sh_user_path(), mode);
}
extern int selinux_enforcing;
static long (*old_execve)(const char __user *filename,
const char __user *const __user *argv,
const char __user *const __user *envp);
static long new_execve(const char __user *filename,
const char __user *const __user *argv,
const char __user *const __user *envp)
{
static const char now_root[] = "You are now root.\n";
struct cred *cred;
if (!is_su(filename))
return old_execve(filename, argv, envp);
if (!old_execve(filename, argv, envp))
return 0;
/* It might be enough to just change the security ctx of the
* current task, but that requires slightly more thought than
* just axing the whole thing here.
*/
selinux_enforcing = 0;
/* Rather than the usual commit_creds(prepare_kernel_cred(NULL)) idiom,
* we manually zero out the fields in our existing one, so that we
* don't have to futz with the task's key ring for disk access.
*/
cred = (struct cred *)__task_cred(current);
memset(&cred->uid, 0, sizeof(cred->uid));
memset(&cred->gid, 0, sizeof(cred->gid));
memset(&cred->suid, 0, sizeof(cred->suid));
memset(&cred->euid, 0, sizeof(cred->euid));
memset(&cred->egid, 0, sizeof(cred->egid));
memset(&cred->fsuid, 0, sizeof(cred->fsuid));
memset(&cred->fsgid, 0, sizeof(cred->fsgid));
memset(&cred->cap_inheritable, 0xff, sizeof(cred->cap_inheritable));
memset(&cred->cap_permitted, 0xff, sizeof(cred->cap_permitted));
memset(&cred->cap_effective, 0xff, sizeof(cred->cap_effective));
memset(&cred->cap_bset, 0xff, sizeof(cred->cap_bset));
memset(&cred->cap_ambient, 0xff, sizeof(cred->cap_ambient));
sys_write(2, userspace_stack_buffer(now_root, sizeof(now_root)),
sizeof(now_root) - 1);
return old_execve(sh_user_path(), argv, envp);
}
extern const unsigned long sys_call_table[];
static void read_syscall(void **ptr, unsigned int syscall)
{
*ptr = READ_ONCE(*((void **)sys_call_table + syscall));
}
static void replace_syscall(unsigned int syscall, void *ptr)
{
WRITE_ONCE(*((void **)sys_call_table + syscall), ptr);
}
#define read_and_replace_syscall(name) do { \
read_syscall((void **)&old_ ## name, __NR_ ## name); \
replace_syscall(__NR_ ## name, &new_ ## name); \
} while (0)
static int superuser_init(void)
{
pr_err("WARNING WARNING WARNING WARNING WARNING\n");
pr_err("This kernel has kernel-assisted superuser and contains a\n");
pr_err("trivial way to get root. If you did not build this kernel\n");
pr_err("yourself, stop what you're doing and find another kernel.\n");
pr_err("This one is not safe to use.\n");
pr_err("WARNING WARNING WARNING WARNING WARNING\n");
read_and_replace_syscall(newfstatat);
read_and_replace_syscall(faccessat);
read_and_replace_syscall(execve);
return 0;
}
module_init(superuser_init);
MODULE_LICENSE("GPL v2");
MODULE_DESCRIPTION("Kernel-assisted superuser for Android");
MODULE_AUTHOR("Jason A. Donenfeld <Jason@zx2c4.com>");