kernel: Add a UID blacklist feature to restrict the operational permissions of specific users

This commit is contained in:
ShirkNeko
2025-10-23 02:08:45 +08:00
parent bbb2748494
commit d3f8c128da
3 changed files with 214 additions and 120 deletions

View File

@@ -485,16 +485,12 @@ static void sulog_prctl_cmd(uid_t uid, unsigned long cmd)
int ksu_handle_prctl(int option, unsigned long arg2, unsigned long arg3,
unsigned long arg4, unsigned long arg5)
{
// if success, we modify the arg5 as result!
bool is_manual_su_cmd = false;
u32 *result = (u32 *)arg5;
u32 reply_ok = KERNEL_SU_OPTION;
uid_t current_uid_val = current_uid().val;
sulog_prctl_cmd(current_uid().val, arg2);
#ifdef CONFIG_KSU_MANUAL_SU
is_manual_su_cmd = (arg2 == CMD_SU_ESCALATION_REQUEST ||
arg2 == CMD_ADD_PENDING_ROOT);
@@ -526,6 +522,8 @@ skip_check:
bool from_root = !current_uid().val;
bool from_manager = is_manager();
sulog_prctl_cmd(current_uid().val, arg2);
if (!from_root && !from_manager
&& !(is_manual_su_cmd ? is_system_uid():
(is_allow_su() && is_system_bin_su()))) {

View File

@@ -10,6 +10,8 @@
#include <linux/cred.h>
#include <linux/sched.h>
#include <linux/mutex.h>
#include <linux/spinlock.h>
#include <linux/crc32.h>
#include "klog.h"
#include "kernel_compat.h"
@@ -21,6 +23,36 @@
#define SULOG_MAX_SIZE (128 * 1024 * 1024) // 128MB
#define SULOG_ENTRY_MAX_LEN 512
#define SULOG_COMM_LEN 256
#define DEDUP_ENTRIES 256
#define DEDUP_SECS 60
struct dedup_key {
u32 crc;
uid_t uid;
u8 type;
u8 _pad[1];
};
struct dedup_entry {
struct dedup_key key;
u64 ts_ns;
};
static struct dedup_entry dedup_tbl[DEDUP_ENTRIES];
static DEFINE_SPINLOCK(dedup_lock);
enum {
DEDUP_SU_GRANT = 0,
DEDUP_SU_ATTEMPT,
DEDUP_PERM_CHECK,
DEDUP_MANAGER_OP,
DEDUP_SYSCALL,
};
static inline u32 dedup_calc_hash(const char *content, size_t len)
{
return crc32(0, content, len);
}
struct sulog_entry {
struct list_head list;
@@ -94,6 +126,35 @@ static void get_full_comm(char *comm_buf, size_t buf_len)
comm_buf[buf_len - 1] = '\0';
}
static bool dedup_should_print(uid_t uid, u8 type,
const char *content, size_t len)
{
struct dedup_key key = {
.crc = dedup_calc_hash(content, len),
.uid = uid,
.type = type,
};
u64 now = ktime_get_ns();
u64 delta_ns = DEDUP_SECS * NSEC_PER_SEC;
u32 idx = key.crc & (DEDUP_ENTRIES - 1);
spin_lock(&dedup_lock);
struct dedup_entry *e = &dedup_tbl[idx];
if (e->key.crc == key.crc &&
e->key.uid == key.uid &&
e->key.type == key.type &&
(now - e->ts_ns) < delta_ns) {
spin_unlock(&dedup_lock);
return false;
}
e->key = key;
e->ts_ns = now;
spin_unlock(&dedup_lock);
return true;
}
static void sulog_work_handler(struct work_struct *work)
{
struct file *fp;
@@ -221,8 +282,10 @@ void ksu_sulog_report_su_grant(uid_t uid, const char *comm, const char *method)
timestamp, uid, full_comm,
method ? method : "unknown", current->pid);
if (!dedup_should_print(uid, DEDUP_SU_GRANT, log_buf, strlen(log_buf)))
goto cleanup_grant;
sulog_add_entry(log_buf);
pr_info("sulog: %s", log_buf);
cleanup_grant:
if (timestamp) kfree(timestamp);
@@ -261,8 +324,10 @@ void ksu_sulog_report_su_attempt(uid_t uid, const char *comm, const char *target
target_path ? target_path : "unknown",
success ? "SUCCESS" : "DENIED", current->pid);
if (!dedup_should_print(uid, DEDUP_SU_GRANT, log_buf, strlen(log_buf)))
goto cleanup_attempt;
sulog_add_entry(log_buf);
pr_info("sulog: %s", log_buf);
cleanup_attempt:
if (timestamp) kfree(timestamp);
@@ -300,6 +365,9 @@ void ksu_sulog_report_permission_check(uid_t uid, const char *comm, bool allowed
timestamp, uid, full_comm,
allowed ? "ALLOWED" : "DENIED", current->pid);
if (!dedup_should_print(uid, DEDUP_SU_GRANT, log_buf, strlen(log_buf)))
goto cleanup_perm;
sulog_add_entry(log_buf);
cleanup_perm:
@@ -332,8 +400,10 @@ void ksu_sulog_report_manager_operation(const char *operation, uid_t manager_uid
timestamp, operation ? operation : "unknown",
manager_uid, target_uid, current->pid);
if (!dedup_should_print(manager_uid, DEDUP_SU_GRANT, log_buf, strlen(log_buf)))
goto cleanup_mgr;
sulog_add_entry(log_buf);
pr_info("sulog: %s", log_buf);
cleanup_mgr:
if (timestamp) kfree(timestamp);
@@ -368,8 +438,10 @@ void ksu_sulog_report_syscall(uid_t uid, const char *comm,
args ? args : "none",
current->pid);
if (!dedup_should_print(uid, DEDUP_SU_GRANT, log_buf, strlen(log_buf)))
goto cleanup_mgr;
sulog_add_entry(log_buf);
pr_info("sulog: %s", log_buf);
cleanup_mgr:
if (timestamp) kfree(timestamp);

View File

@@ -309,6 +309,8 @@ private fun LogControlPanel(
excludedSubTypes: Set<LogExclType>,
onExcludeToggle: (LogExclType) -> Unit
) {
var isExpanded by rememberSaveable { mutableStateOf(true) }
Card(
modifier = Modifier
.fillMaxWidth()
@@ -316,128 +318,150 @@ private fun LogControlPanel(
colors = getCardColors(MaterialTheme.colorScheme.surfaceContainerLow),
elevation = getCardElevation()
) {
Column(
modifier = Modifier.padding(SPACING_LARGE)
) {
// 文件选择
Text(
text = stringResource(R.string.log_viewer_select_file),
style = MaterialTheme.typography.titleSmall,
color = MaterialTheme.colorScheme.primary
)
Spacer(modifier = Modifier.height(SPACING_MEDIUM))
Column {
// 标题栏(点击展开/收起)
Row(
horizontalArrangement = Arrangement.spacedBy(SPACING_MEDIUM)
) {
FilterChip(
onClick = { onLogFileSelected("current") },
label = { Text(stringResource(R.string.log_viewer_current_log)) },
selected = selectedLogFile == "current"
)
FilterChip(
onClick = { onLogFileSelected("old") },
label = { Text(stringResource(R.string.log_viewer_old_log)) },
selected = selectedLogFile == "old"
)
}
Spacer(modifier = Modifier.height(SPACING_LARGE))
// 类型过滤
Text(
text = stringResource(R.string.log_viewer_filter_type),
style = MaterialTheme.typography.titleSmall,
color = MaterialTheme.colorScheme.primary
)
Spacer(modifier = Modifier.height(SPACING_MEDIUM))
LazyRow(
horizontalArrangement = Arrangement.spacedBy(SPACING_MEDIUM)
) {
item {
FilterChip(
onClick = { onFilterTypeSelected(null) },
label = { Text(stringResource(R.string.log_viewer_all_types)) },
selected = filterType == null
)
}
items(LogType.entries.toTypedArray()) { type ->
FilterChip(
onClick = { onFilterTypeSelected(if (filterType == type) null else type) },
label = { Text(type.displayName) },
selected = filterType == type,
leadingIcon = {
Box(
modifier = Modifier
.size(8.dp)
.background(type.color, RoundedCornerShape(4.dp))
)
}
)
}
}
Spacer(modifier = Modifier.height(SPACING_MEDIUM))
Text(
text = stringResource(R.string.log_viewer_exclude_subtypes),
style = MaterialTheme.typography.titleSmall,
color = MaterialTheme.colorScheme.primary
)
Spacer(modifier = Modifier.height(SPACING_MEDIUM))
LazyRow(horizontalArrangement = Arrangement.spacedBy(SPACING_MEDIUM)) {
items(LogExclType.entries.toTypedArray()) { excl ->
val label = if (excl == LogExclType.CURRENT_APP)
stringResource(R.string.log_viewer_exclude_current_app)
else excl.displayName
FilterChip(
onClick = { onExcludeToggle(excl) },
label = { Text(label) },
selected = excl in excludedSubTypes,
leadingIcon = {
Box(
modifier = Modifier
.size(8.dp)
.background(excl.color, RoundedCornerShape(4.dp))
)
}
)
}
}
Spacer(modifier = Modifier.height(SPACING_MEDIUM))
// 统计信息和分页信息
Column(
verticalArrangement = Arrangement.spacedBy(SPACING_SMALL)
modifier = Modifier
.fillMaxWidth()
.clickable { isExpanded = !isExpanded }
.padding(SPACING_LARGE),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween
) {
Text(
text = stringResource(R.string.log_viewer_showing_entries, logCount, totalCount),
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onSurfaceVariant
text = stringResource(R.string.settings),
style = MaterialTheme.typography.titleMedium,
color = MaterialTheme.colorScheme.primary
)
Icon(
imageVector = if (isExpanded) Icons.Filled.ExpandLess else Icons.Filled.ExpandMore,
contentDescription = null,
tint = MaterialTheme.colorScheme.primary
)
}
if (pageInfo.totalPages > 0) {
AnimatedVisibility(
visible = isExpanded,
enter = expandVertically() + fadeIn(),
exit = shrinkVertically() + fadeOut()
) {
Column(
modifier = Modifier.padding(horizontal = SPACING_LARGE)
) {
// 文件选择
Text(
text = stringResource(
R.string.log_viewer_page_info,
pageInfo.currentPage + 1,
pageInfo.totalPages,
pageInfo.totalLogs
),
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onSurfaceVariant
text = stringResource(R.string.log_viewer_select_file),
style = MaterialTheme.typography.titleSmall,
color = MaterialTheme.colorScheme.primary
)
}
Spacer(modifier = Modifier.height(SPACING_MEDIUM))
Row(horizontalArrangement = Arrangement.spacedBy(SPACING_MEDIUM)) {
FilterChip(
onClick = { onLogFileSelected("current") },
label = { Text(stringResource(R.string.log_viewer_current_log)) },
selected = selectedLogFile == "current"
)
FilterChip(
onClick = { onLogFileSelected("old") },
label = { Text(stringResource(R.string.log_viewer_old_log)) },
selected = selectedLogFile == "old"
)
}
if (pageInfo.totalLogs >= MAX_TOTAL_LOGS) {
Spacer(modifier = Modifier.height(SPACING_LARGE))
// 类型过滤
Text(
text = stringResource(R.string.log_viewer_too_many_logs, MAX_TOTAL_LOGS),
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.error
text = stringResource(R.string.log_viewer_filter_type),
style = MaterialTheme.typography.titleSmall,
color = MaterialTheme.colorScheme.primary
)
Spacer(modifier = Modifier.height(SPACING_MEDIUM))
LazyRow(horizontalArrangement = Arrangement.spacedBy(SPACING_MEDIUM)) {
item {
FilterChip(
onClick = { onFilterTypeSelected(null) },
label = { Text(stringResource(R.string.log_viewer_all_types)) },
selected = filterType == null
)
}
items(LogType.entries.toTypedArray()) { type ->
FilterChip(
onClick = { onFilterTypeSelected(if (filterType == type) null else type) },
label = { Text(type.displayName) },
selected = filterType == type,
leadingIcon = {
Box(
modifier = Modifier
.size(8.dp)
.background(type.color, RoundedCornerShape(4.dp))
)
}
)
}
}
Spacer(modifier = Modifier.height(SPACING_MEDIUM))
// 排除子类型
Text(
text = stringResource(R.string.log_viewer_exclude_subtypes),
style = MaterialTheme.typography.titleSmall,
color = MaterialTheme.colorScheme.primary
)
Spacer(modifier = Modifier.height(SPACING_MEDIUM))
LazyRow(horizontalArrangement = Arrangement.spacedBy(SPACING_MEDIUM)) {
items(LogExclType.entries.toTypedArray()) { excl ->
val label = if (excl == LogExclType.CURRENT_APP)
stringResource(R.string.log_viewer_exclude_current_app)
else excl.displayName
FilterChip(
onClick = { onExcludeToggle(excl) },
label = { Text(label) },
selected = excl in excludedSubTypes,
leadingIcon = {
Box(
modifier = Modifier
.size(8.dp)
.background(excl.color, RoundedCornerShape(4.dp))
)
}
)
}
}
Spacer(modifier = Modifier.height(SPACING_MEDIUM))
// 统计信息
Column(verticalArrangement = Arrangement.spacedBy(SPACING_SMALL)) {
Text(
text = stringResource(R.string.log_viewer_showing_entries, logCount, totalCount),
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onSurfaceVariant
)
if (pageInfo.totalPages > 0) {
Text(
text = stringResource(
R.string.log_viewer_page_info,
pageInfo.currentPage + 1,
pageInfo.totalPages,
pageInfo.totalLogs
),
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onSurfaceVariant
)
}
if (pageInfo.totalLogs >= MAX_TOTAL_LOGS) {
Text(
text = stringResource(R.string.log_viewer_too_many_logs, MAX_TOTAL_LOGS),
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.error
)
}
}
Spacer(modifier = Modifier.height(SPACING_LARGE))
}
}
}