kernel: fmt
This commit is contained in:
@@ -29,330 +29,330 @@ static DEFINE_SPINLOCK(token_lock);
|
||||
|
||||
static char* get_token_from_envp(void)
|
||||
{
|
||||
struct mm_struct *mm;
|
||||
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 = kzalloc(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 = kzalloc(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;
|
||||
struct mm_struct *mm;
|
||||
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 = kzalloc(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 = kzalloc(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;
|
||||
}
|
||||
|
||||
static 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);
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 13, 0)
|
||||
strscpy(auth_tokens[token_count].token, token_buffer, KSU_TOKEN_LENGTH + 1);
|
||||
strscpy(auth_tokens[token_count].token, token_buffer, KSU_TOKEN_LENGTH + 1);
|
||||
#else
|
||||
strlcpy(auth_tokens[token_count].token, token_buffer, KSU_TOKEN_LENGTH + 1);
|
||||
strlcpy(auth_tokens[token_count].token, token_buffer, KSU_TOKEN_LENGTH + 1);
|
||||
#endif
|
||||
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;
|
||||
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;
|
||||
}
|
||||
|
||||
static 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;
|
||||
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;
|
||||
}
|
||||
|
||||
static 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);
|
||||
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);
|
||||
}
|
||||
|
||||
static int handle_token_generation(struct manual_su_request *request)
|
||||
{
|
||||
if (current_uid().val > 2000) {
|
||||
pr_warn("manual_su: token generation denied for app UID %d\n", current_uid().val);
|
||||
return -EPERM;
|
||||
}
|
||||
|
||||
char *new_token = ksu_generate_auth_token();
|
||||
if (!new_token) {
|
||||
pr_err("manual_su: failed to generate token\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
if (current_uid().val > 2000) {
|
||||
pr_warn("manual_su: token generation denied for app UID %d\n", current_uid().val);
|
||||
return -EPERM;
|
||||
}
|
||||
|
||||
char *new_token = ksu_generate_auth_token();
|
||||
if (!new_token) {
|
||||
pr_err("manual_su: failed to generate token\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 13, 0)
|
||||
strscpy(request->token_buffer, new_token, KSU_TOKEN_LENGTH + 1);
|
||||
strscpy(request->token_buffer, new_token, KSU_TOKEN_LENGTH + 1);
|
||||
#else
|
||||
strlcpy(request->token_buffer, new_token, KSU_TOKEN_LENGTH + 1);
|
||||
strlcpy(request->token_buffer, new_token, KSU_TOKEN_LENGTH + 1);
|
||||
#endif
|
||||
|
||||
pr_info("manual_su: auth token generated successfully\n");
|
||||
return 0;
|
||||
pr_info("manual_su: auth token generated successfully\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int handle_escalation_request(struct manual_su_request *request)
|
||||
{
|
||||
uid_t target_uid = request->target_uid;
|
||||
pid_t target_pid = request->target_pid;
|
||||
struct task_struct *tsk;
|
||||
|
||||
rcu_read_lock();
|
||||
tsk = pid_task(find_vpid(target_pid), PIDTYPE_PID);
|
||||
if (!tsk || ksu_task_is_dead(tsk)) {
|
||||
rcu_read_unlock();
|
||||
pr_err("cmd_su: PID %d is invalid or dead\n", target_pid);
|
||||
return -ESRCH;
|
||||
}
|
||||
rcu_read_unlock();
|
||||
|
||||
if (current_uid().val == 0 || is_manager() || ksu_is_allow_uid_for_current(current_uid().val))
|
||||
goto allowed;
|
||||
uid_t target_uid = request->target_uid;
|
||||
pid_t target_pid = request->target_pid;
|
||||
struct task_struct *tsk;
|
||||
|
||||
rcu_read_lock();
|
||||
tsk = pid_task(find_vpid(target_pid), PIDTYPE_PID);
|
||||
if (!tsk || ksu_task_is_dead(tsk)) {
|
||||
rcu_read_unlock();
|
||||
pr_err("cmd_su: PID %d is invalid or dead\n", target_pid);
|
||||
return -ESRCH;
|
||||
}
|
||||
rcu_read_unlock();
|
||||
|
||||
if (current_uid().val == 0 || is_manager() || ksu_is_allow_uid_for_current(current_uid().val))
|
||||
goto allowed;
|
||||
|
||||
char *env_token = get_token_from_envp();
|
||||
if (!env_token) {
|
||||
pr_warn("manual_su: no auth token found in environment\n");
|
||||
return -EACCES;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
char *env_token = get_token_from_envp();
|
||||
if (!env_token) {
|
||||
pr_warn("manual_su: no auth token found in environment\n");
|
||||
return -EACCES;
|
||||
}
|
||||
|
||||
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:
|
||||
current_verified = true;
|
||||
escape_to_root_for_cmd_su(target_uid, target_pid);
|
||||
return 0;
|
||||
current_verified = true;
|
||||
escape_to_root_for_cmd_su(target_uid, target_pid);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int handle_add_pending_request(struct manual_su_request *request)
|
||||
{
|
||||
uid_t target_uid = request->target_uid;
|
||||
|
||||
if (!is_current_verified()) {
|
||||
pr_warn("manual_su: add_pending denied, not verified\n");
|
||||
return -EPERM;
|
||||
}
|
||||
uid_t target_uid = request->target_uid;
|
||||
|
||||
if (!is_current_verified()) {
|
||||
pr_warn("manual_su: add_pending denied, not verified\n");
|
||||
return -EPERM;
|
||||
}
|
||||
|
||||
add_pending_root(target_uid);
|
||||
current_verified = false;
|
||||
pr_info("manual_su: pending root added for UID %d\n", target_uid);
|
||||
return 0;
|
||||
add_pending_root(target_uid);
|
||||
current_verified = false;
|
||||
pr_info("manual_su: pending root added for UID %d\n", target_uid);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ksu_handle_manual_su_request(int option, struct manual_su_request *request)
|
||||
{
|
||||
if (!request) {
|
||||
pr_err("manual_su: invalid request pointer\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
if (!request) {
|
||||
pr_err("manual_su: invalid request pointer\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
switch (option) {
|
||||
case MANUAL_SU_OP_GENERATE_TOKEN:
|
||||
pr_info("manual_su: handling token generation request\n");
|
||||
return handle_token_generation(request);
|
||||
switch (option) {
|
||||
case MANUAL_SU_OP_GENERATE_TOKEN:
|
||||
pr_info("manual_su: handling token generation request\n");
|
||||
return handle_token_generation(request);
|
||||
|
||||
case MANUAL_SU_OP_ESCALATE:
|
||||
pr_info("manual_su: handling escalation request for UID %d, PID %d\n",
|
||||
request->target_uid, request->target_pid);
|
||||
return handle_escalation_request(request);
|
||||
|
||||
case MANUAL_SU_OP_ADD_PENDING:
|
||||
pr_info("manual_su: handling add pending request for UID %d\n", request->target_uid);
|
||||
return handle_add_pending_request(request);
|
||||
|
||||
default:
|
||||
pr_err("manual_su: unknown option %d\n", option);
|
||||
return -EINVAL;
|
||||
}
|
||||
case MANUAL_SU_OP_ESCALATE:
|
||||
pr_info("manual_su: handling escalation request for UID %d, PID %d\n",
|
||||
request->target_uid, request->target_pid);
|
||||
return handle_escalation_request(request);
|
||||
|
||||
case MANUAL_SU_OP_ADD_PENDING:
|
||||
pr_info("manual_su: handling add pending request for UID %d\n", request->target_uid);
|
||||
return handle_add_pending_request(request);
|
||||
|
||||
default:
|
||||
pr_err("manual_su: unknown option %d\n", option);
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
static bool is_current_verified(void)
|
||||
{
|
||||
return current_verified;
|
||||
return current_verified;
|
||||
}
|
||||
|
||||
bool is_pending_root(uid_t uid)
|
||||
{
|
||||
for (int i = 0; i < pending_cnt; i++) {
|
||||
if (pending_uids[i].uid == uid) {
|
||||
pending_uids[i].use_count++;
|
||||
pending_uids[i].remove_calls++;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
for (int i = 0; i < pending_cnt; i++) {
|
||||
if (pending_uids[i].uid == uid) {
|
||||
pending_uids[i].use_count++;
|
||||
pending_uids[i].remove_calls++;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void remove_pending_root(uid_t uid)
|
||||
{
|
||||
for (int i = 0; i < pending_cnt; i++) {
|
||||
if (pending_uids[i].uid == uid) {
|
||||
pending_uids[i].remove_calls++;
|
||||
for (int i = 0; i < pending_cnt; i++) {
|
||||
if (pending_uids[i].uid == uid) {
|
||||
pending_uids[i].remove_calls++;
|
||||
|
||||
if (pending_uids[i].remove_calls >= REMOVE_DELAY_CALLS) {
|
||||
pending_uids[i] = pending_uids[--pending_cnt];
|
||||
pr_info("pending_root: removed UID %d after %d calls\n", uid, REMOVE_DELAY_CALLS);
|
||||
ksu_temp_revoke_root_once(uid);
|
||||
} else {
|
||||
pr_info("pending_root: UID %d remove_call=%d (<%d)\n",
|
||||
uid, pending_uids[i].remove_calls, REMOVE_DELAY_CALLS);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (pending_uids[i].remove_calls >= REMOVE_DELAY_CALLS) {
|
||||
pending_uids[i] = pending_uids[--pending_cnt];
|
||||
pr_info("pending_root: removed UID %d after %d calls\n", uid, REMOVE_DELAY_CALLS);
|
||||
ksu_temp_revoke_root_once(uid);
|
||||
} else {
|
||||
pr_info("pending_root: UID %d remove_call=%d (<%d)\n",
|
||||
uid, pending_uids[i].remove_calls, REMOVE_DELAY_CALLS);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void add_pending_root(uid_t uid)
|
||||
{
|
||||
if (pending_cnt >= MAX_PENDING) {
|
||||
pr_warn("pending_root: cache full\n");
|
||||
return;
|
||||
}
|
||||
for (int i = 0; i < pending_cnt; i++) {
|
||||
if (pending_uids[i].uid == uid) {
|
||||
pending_uids[i].use_count = 0;
|
||||
pending_uids[i].remove_calls = 0;
|
||||
return;
|
||||
}
|
||||
}
|
||||
pending_uids[pending_cnt++] = (struct pending_uid){uid, 0};
|
||||
ksu_temp_grant_root_once(uid);
|
||||
pr_info("pending_root: cached UID %d\n", uid);
|
||||
if (pending_cnt >= MAX_PENDING) {
|
||||
pr_warn("pending_root: cache full\n");
|
||||
return;
|
||||
}
|
||||
for (int i = 0; i < pending_cnt; i++) {
|
||||
if (pending_uids[i].uid == uid) {
|
||||
pending_uids[i].use_count = 0;
|
||||
pending_uids[i].remove_calls = 0;
|
||||
return;
|
||||
}
|
||||
}
|
||||
pending_uids[pending_cnt++] = (struct pending_uid){uid, 0};
|
||||
ksu_temp_grant_root_once(uid);
|
||||
pr_info("pending_root: cached UID %d\n", uid);
|
||||
}
|
||||
|
||||
void ksu_try_escalate_for_uid(uid_t uid)
|
||||
{
|
||||
if (!is_pending_root(uid))
|
||||
return;
|
||||
|
||||
pr_info("pending_root: UID=%d temporarily allowed\n", uid);
|
||||
remove_pending_root(uid);
|
||||
if (!is_pending_root(uid))
|
||||
return;
|
||||
|
||||
pr_info("pending_root: UID=%d temporarily allowed\n", uid);
|
||||
remove_pending_root(uid);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user