kernel: Add functionality to generate and validate authentication tokens for cmd_su
This commit is contained in:
@@ -454,6 +454,7 @@ static void sulog_prctl_cmd(uid_t uid, unsigned long cmd)
|
|||||||
#ifdef CONFIG_KSU_MANUAL_SU
|
#ifdef CONFIG_KSU_MANUAL_SU
|
||||||
case CMD_SU_ESCALATION_REQUEST: name = "prctl_su_escalation_request"; break;
|
case CMD_SU_ESCALATION_REQUEST: name = "prctl_su_escalation_request"; break;
|
||||||
case CMD_ADD_PENDING_ROOT: name = "prctl_add_pending_root"; break;
|
case CMD_ADD_PENDING_ROOT: name = "prctl_add_pending_root"; break;
|
||||||
|
case CMD_GENERATE_AUTH_TOKEN: name = "prctl_generate_auth_token"; break;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
default: name = "prctl_unknown"; break;
|
default: name = "prctl_unknown"; break;
|
||||||
@@ -806,23 +807,45 @@ skip_check:
|
|||||||
#ifdef CONFIG_KSU_MANUAL_SU
|
#ifdef CONFIG_KSU_MANUAL_SU
|
||||||
if (arg2 == CMD_SU_ESCALATION_REQUEST) {
|
if (arg2 == CMD_SU_ESCALATION_REQUEST) {
|
||||||
uid_t target_uid = (uid_t)arg3;
|
uid_t target_uid = (uid_t)arg3;
|
||||||
struct su_request_arg __user *user_req = (struct su_request_arg __user *)arg4;
|
pid_t target_pid = (pid_t)arg4;
|
||||||
|
|
||||||
pid_t target_pid;
|
int ret = ksu_manual_su_escalate(target_uid, target_pid);
|
||||||
const char __user *user_password;
|
|
||||||
|
|
||||||
if (copy_from_user(&target_pid, &user_req->target_pid, sizeof(target_pid)))
|
if (ret == 0 && copy_to_user(result, &reply_ok, sizeof(reply_ok)))
|
||||||
return -EFAULT;
|
|
||||||
if (copy_from_user(&user_password, &user_req->user_password, sizeof(user_password)))
|
|
||||||
return -EFAULT;
|
return -EFAULT;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
int ret = ksu_manual_su_escalate(target_uid, target_pid, user_password);
|
if (arg2 == CMD_GENERATE_AUTH_TOKEN) {
|
||||||
|
char __user *token_buffer = (char __user *)arg3;
|
||||||
|
size_t buffer_size = (size_t)arg4;
|
||||||
|
|
||||||
if (ret == 0) {
|
if (current_uid().val > 2000) {
|
||||||
if (copy_to_user(result, &reply_ok, sizeof(reply_ok))) {
|
pr_warn("CMD_GENERATE_AUTH_TOKEN: denied for app UID %d\n", current_uid().val);
|
||||||
pr_err("cmd_su_escalation: prctl reply error\n");
|
return 0;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (buffer_size < KSU_TOKEN_LENGTH + 1) {
|
||||||
|
pr_err("CMD_GENERATE_AUTH_TOKEN: buffer too small\n");
|
||||||
|
return -EINVAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
char *new_token = ksu_generate_auth_token();
|
||||||
|
if (!new_token) {
|
||||||
|
pr_err("CMD_GENERATE_AUTH_TOKEN: failed to generate token\n");
|
||||||
|
return -ENOMEM;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (copy_to_user(token_buffer, new_token, KSU_TOKEN_LENGTH + 1)) {
|
||||||
|
pr_err("CMD_GENERATE_AUTH_TOKEN: failed to copy token to user\n");
|
||||||
|
return -EFAULT;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (copy_to_user(result, &reply_ok, sizeof(reply_ok))) {
|
||||||
|
pr_err("CMD_GENERATE_AUTH_TOKEN: prctl reply error\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
pr_info("prctl: auth token generated successfully\n");
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -27,6 +27,7 @@
|
|||||||
#ifdef CONFIG_KSU_MANUAL_SU
|
#ifdef CONFIG_KSU_MANUAL_SU
|
||||||
#define CMD_SU_ESCALATION_REQUEST 50
|
#define CMD_SU_ESCALATION_REQUEST 50
|
||||||
#define CMD_ADD_PENDING_ROOT 51
|
#define CMD_ADD_PENDING_ROOT 51
|
||||||
|
#define CMD_GENERATE_AUTH_TOKEN 52
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#define CMD_GET_FULL_VERSION 0xC0FFEE1A
|
#define CMD_GET_FULL_VERSION 0xC0FFEE1A
|
||||||
|
|||||||
@@ -5,16 +5,18 @@
|
|||||||
#include <linux/fs.h>
|
#include <linux/fs.h>
|
||||||
#include <linux/uaccess.h>
|
#include <linux/uaccess.h>
|
||||||
#include <linux/file.h>
|
#include <linux/file.h>
|
||||||
|
#include <linux/mm.h>
|
||||||
|
#include <linux/binfmts.h>
|
||||||
#include "kernel_compat.h"
|
#include "kernel_compat.h"
|
||||||
#include "manual_su.h"
|
#include "manual_su.h"
|
||||||
#include "ksu.h"
|
#include "ksu.h"
|
||||||
#include "allowlist.h"
|
#include "allowlist.h"
|
||||||
#include "manager.h"
|
#include "manager.h"
|
||||||
|
|
||||||
static const char *ksu_su_password = KSU_SU_PASSWORD;
|
|
||||||
extern void escape_to_root_for_cmd_su(uid_t, pid_t);
|
extern void escape_to_root_for_cmd_su(uid_t, pid_t);
|
||||||
#define MAX_PENDING 16
|
#define MAX_PENDING 16
|
||||||
#define REMOVE_DELAY_CALLS 150
|
#define REMOVE_DELAY_CALLS 150
|
||||||
|
#define MAX_TOKENS 10
|
||||||
|
|
||||||
struct pending_uid {
|
struct pending_uid {
|
||||||
uid_t uid;
|
uid_t uid;
|
||||||
@@ -24,37 +26,191 @@ struct pending_uid {
|
|||||||
|
|
||||||
static struct pending_uid pending_uids[MAX_PENDING] = {0};
|
static struct pending_uid pending_uids[MAX_PENDING] = {0};
|
||||||
static int pending_cnt = 0;
|
static int pending_cnt = 0;
|
||||||
|
static struct ksu_token_entry auth_tokens[MAX_TOKENS] = {0};
|
||||||
|
static int token_count = 0;
|
||||||
|
static DEFINE_SPINLOCK(token_lock);
|
||||||
|
|
||||||
bool current_verified = false;
|
bool current_verified = false;
|
||||||
|
|
||||||
int ksu_manual_su_escalate(uid_t target_uid, pid_t target_pid,
|
static char* get_token_from_envp(void)
|
||||||
const char __user *user_password)
|
|
||||||
{
|
{
|
||||||
if (ksu_is_current_verified())
|
struct mm_struct *mm;
|
||||||
goto allowed;
|
char *envp_start, *envp_end;
|
||||||
|
char *env_ptr, *token = NULL;
|
||||||
|
unsigned long env_len;
|
||||||
|
char *env_copy = NULL;
|
||||||
|
|
||||||
|
if (!current->mm)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
mm = current->mm;
|
||||||
|
|
||||||
|
down_read(&mm->mmap_lock);
|
||||||
|
|
||||||
|
envp_start = (char *)mm->env_start;
|
||||||
|
envp_end = (char *)mm->env_end;
|
||||||
|
env_len = envp_end - envp_start;
|
||||||
|
|
||||||
|
if (env_len <= 0 || env_len > PAGE_SIZE * 32) {
|
||||||
|
up_read(&mm->mmap_lock);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
env_copy = kmalloc(env_len + 1, GFP_KERNEL);
|
||||||
|
if (!env_copy) {
|
||||||
|
up_read(&mm->mmap_lock);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (copy_from_user(env_copy, envp_start, env_len)) {
|
||||||
|
kfree(env_copy);
|
||||||
|
up_read(&mm->mmap_lock);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
up_read(&mm->mmap_lock);
|
||||||
|
|
||||||
|
env_copy[env_len] = '\0';
|
||||||
|
env_ptr = env_copy;
|
||||||
|
|
||||||
|
while (env_ptr < env_copy + env_len) {
|
||||||
|
if (strncmp(env_ptr, KSU_TOKEN_ENV_NAME "=", strlen(KSU_TOKEN_ENV_NAME) + 1) == 0) {
|
||||||
|
char *token_start = env_ptr + strlen(KSU_TOKEN_ENV_NAME) + 1;
|
||||||
|
char *token_end = strchr(token_start, '\0');
|
||||||
|
|
||||||
|
if (token_end && (token_end - token_start) == KSU_TOKEN_LENGTH) {
|
||||||
|
token = kmalloc(KSU_TOKEN_LENGTH + 1, GFP_KERNEL);
|
||||||
|
if (token) {
|
||||||
|
memcpy(token, token_start, KSU_TOKEN_LENGTH);
|
||||||
|
token[KSU_TOKEN_LENGTH] = '\0';
|
||||||
|
pr_info("manual_su: found auth token in environment\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
env_ptr += strlen(env_ptr) + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
kfree(env_copy);
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
|
||||||
|
char* ksu_generate_auth_token(void)
|
||||||
|
{
|
||||||
|
static char token_buffer[KSU_TOKEN_LENGTH + 1];
|
||||||
|
unsigned long flags;
|
||||||
|
int i;
|
||||||
|
|
||||||
|
ksu_cleanup_expired_tokens();
|
||||||
|
|
||||||
|
spin_lock_irqsave(&token_lock, flags);
|
||||||
|
|
||||||
|
if (token_count >= MAX_TOKENS) {
|
||||||
|
for (i = 0; i < MAX_TOKENS - 1; i++) {
|
||||||
|
auth_tokens[i] = auth_tokens[i + 1];
|
||||||
|
}
|
||||||
|
token_count = MAX_TOKENS - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (i = 0; i < KSU_TOKEN_LENGTH; i++) {
|
||||||
|
u8 rand_byte;
|
||||||
|
get_random_bytes(&rand_byte, 1);
|
||||||
|
int char_type = rand_byte % 3;
|
||||||
|
if (char_type == 0) {
|
||||||
|
token_buffer[i] = 'A' + (rand_byte % 26);
|
||||||
|
} else if (char_type == 1) {
|
||||||
|
token_buffer[i] = 'a' + (rand_byte % 26);
|
||||||
|
} else {
|
||||||
|
token_buffer[i] = '0' + (rand_byte % 10);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
token_buffer[KSU_TOKEN_LENGTH] = '\0';
|
||||||
|
|
||||||
|
strncpy(auth_tokens[token_count].token, token_buffer, KSU_TOKEN_LENGTH + 1);
|
||||||
|
auth_tokens[token_count].expire_time = jiffies + KSU_TOKEN_EXPIRE_TIME * HZ;
|
||||||
|
auth_tokens[token_count].used = false;
|
||||||
|
token_count++;
|
||||||
|
|
||||||
|
spin_unlock_irqrestore(&token_lock, flags);
|
||||||
|
|
||||||
|
pr_info("manual_su: generated new auth token (expires in %d seconds)\n", KSU_TOKEN_EXPIRE_TIME);
|
||||||
|
return token_buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ksu_verify_auth_token(const char *token)
|
||||||
|
{
|
||||||
|
unsigned long flags;
|
||||||
|
bool valid = false;
|
||||||
|
int i;
|
||||||
|
|
||||||
|
if (!token || strlen(token) != KSU_TOKEN_LENGTH) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
spin_lock_irqsave(&token_lock, flags);
|
||||||
|
|
||||||
|
for (i = 0; i < token_count; i++) {
|
||||||
|
if (!auth_tokens[i].used &&
|
||||||
|
time_before(jiffies, auth_tokens[i].expire_time) &&
|
||||||
|
strcmp(auth_tokens[i].token, token) == 0) {
|
||||||
|
|
||||||
|
auth_tokens[i].used = true;
|
||||||
|
valid = true;
|
||||||
|
pr_info("manual_su: auth token verified successfully\n");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
spin_unlock_irqrestore(&token_lock, flags);
|
||||||
|
|
||||||
|
if (!valid) {
|
||||||
|
pr_warn("manual_su: invalid or expired auth token\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
return valid;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ksu_cleanup_expired_tokens(void)
|
||||||
|
{
|
||||||
|
unsigned long flags;
|
||||||
|
int i, j;
|
||||||
|
|
||||||
|
spin_lock_irqsave(&token_lock, flags);
|
||||||
|
|
||||||
|
for (i = 0; i < token_count; ) {
|
||||||
|
if (time_after(jiffies, auth_tokens[i].expire_time) || auth_tokens[i].used) {
|
||||||
|
for (j = i; j < token_count - 1; j++) {
|
||||||
|
auth_tokens[j] = auth_tokens[j + 1];
|
||||||
|
}
|
||||||
|
token_count--;
|
||||||
|
pr_debug("manual_su: cleaned up expired/used token\n");
|
||||||
|
} else {
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
spin_unlock_irqrestore(&token_lock, flags);
|
||||||
|
}
|
||||||
|
|
||||||
|
int ksu_manual_su_escalate(uid_t target_uid, pid_t target_pid)
|
||||||
|
{
|
||||||
if (current_uid().val == 0 || is_manager() || ksu_is_allow_uid(current_uid().val))
|
if (current_uid().val == 0 || is_manager() || ksu_is_allow_uid(current_uid().val))
|
||||||
goto allowed;
|
goto allowed;
|
||||||
|
|
||||||
if (!user_password) {
|
char *env_token = get_token_from_envp();
|
||||||
pr_warn("manual_su: password required\n");
|
if (!env_token) {
|
||||||
return -EACCES;
|
pr_warn("manual_su: no auth token found in environment\n");
|
||||||
}
|
|
||||||
char buf[64];
|
|
||||||
long copied;
|
|
||||||
|
|
||||||
copied = ksu_copy_from_user_retry(buf, user_password, sizeof(buf) - 1);
|
|
||||||
if (copied < 0)
|
|
||||||
return -EFAULT;
|
|
||||||
|
|
||||||
buf[copied] = '\0';
|
|
||||||
|
|
||||||
if (strcmp(buf, ksu_su_password) != 0) {
|
|
||||||
pr_warn("manual_su: wrong password (input=%s, expect=%s)\n", buf, ksu_su_password);
|
|
||||||
return -EACCES;
|
return -EACCES;
|
||||||
}
|
}
|
||||||
|
|
||||||
ksu_mark_current_verified();
|
bool token_valid = ksu_verify_auth_token(env_token);
|
||||||
|
kfree(env_token);
|
||||||
|
|
||||||
|
if (!token_valid) {
|
||||||
|
pr_warn("manual_su: token verification failed\n");
|
||||||
|
return -EACCES;
|
||||||
|
}
|
||||||
|
|
||||||
allowed:
|
allowed:
|
||||||
current_verified = true;
|
current_verified = true;
|
||||||
|
|||||||
@@ -5,28 +5,24 @@
|
|||||||
#include <linux/sched.h>
|
#include <linux/sched.h>
|
||||||
|
|
||||||
#define KSU_SU_VERIFIED_BIT (1UL << 0)
|
#define KSU_SU_VERIFIED_BIT (1UL << 0)
|
||||||
|
#define KSU_TOKEN_LENGTH 32
|
||||||
|
#define KSU_TOKEN_ENV_NAME "KSU_AUTH_TOKEN"
|
||||||
|
#define KSU_TOKEN_EXPIRE_TIME 30
|
||||||
|
|
||||||
struct su_request_arg {
|
struct ksu_token_entry {
|
||||||
pid_t target_pid;
|
char token[KSU_TOKEN_LENGTH + 1];
|
||||||
const char __user *user_password;
|
unsigned long expire_time;
|
||||||
|
bool used;
|
||||||
};
|
};
|
||||||
|
|
||||||
static inline bool ksu_is_current_verified(void)
|
int ksu_manual_su_escalate(uid_t target_uid, pid_t target_pid);
|
||||||
{
|
|
||||||
return ((unsigned long)(current->security) & KSU_SU_VERIFIED_BIT) != 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline void ksu_mark_current_verified(void)
|
|
||||||
{
|
|
||||||
current->security = (void *)((unsigned long)(current->security) | KSU_SU_VERIFIED_BIT);
|
|
||||||
}
|
|
||||||
|
|
||||||
int ksu_manual_su_escalate(uid_t target_uid, pid_t target_pid,
|
|
||||||
const char __user *user_password);
|
|
||||||
|
|
||||||
bool is_pending_root(uid_t uid);
|
bool is_pending_root(uid_t uid);
|
||||||
void remove_pending_root(uid_t uid);
|
void remove_pending_root(uid_t uid);
|
||||||
void add_pending_root(uid_t uid);
|
void add_pending_root(uid_t uid);
|
||||||
bool is_current_verified(void);
|
bool is_current_verified(void);
|
||||||
|
char* ksu_generate_auth_token(void);
|
||||||
|
bool ksu_verify_auth_token(const char *token);
|
||||||
|
void ksu_cleanup_expired_tokens(void);
|
||||||
extern bool current_verified;
|
extern bool current_verified;
|
||||||
#endif
|
#endif
|
||||||
Reference in New Issue
Block a user