From 1a3510d8d926c292c784f393573d5513e6cae017 Mon Sep 17 00:00:00 2001 From: Leonmmcoset Date: Sun, 19 Apr 2026 20:21:12 +0800 Subject: [PATCH] =?UTF-8?q?USC=E5=AE=89=E5=85=A8=E7=B3=BB=E7=BB=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CMakeLists.txt | 48 ++++ clks/kernel/runtime/syscall.c | 323 ++++++++++++++++++++++++++ configs/menuconfig/clks_features.json | 117 ++++++++++ scripts/menuconfig.py | 154 +++++++++++- 4 files changed, 639 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 16c6393..24b33db 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -181,6 +181,19 @@ cl_set_bool_cache(CLEONOS_CLKS_ENABLE_TTY_READY_LOG ON "Print TTY ready logs") cl_set_bool_cache(CLEONOS_CLKS_ENABLE_IDLE_DEBUG_LOG ON "Print idle loop debug log") cl_set_bool_cache(CLEONOS_CLKS_ENABLE_PROCFS ON "Enable virtual /proc procfs syscall layer") cl_set_bool_cache(CLEONOS_CLKS_ENABLE_EXEC_SERIAL_LOG ON "Enable EXEC info logs on serial output") +cl_set_bool_cache(CLEONOS_CLKS_ENABLE_USC ON "Enable UserSafeController dangerous syscall confirmations") +cl_set_bool_cache(CLEONOS_CLKS_ENABLE_USC_SC_FS_MKDIR ON "USC intercept policy: FS_MKDIR") +cl_set_bool_cache(CLEONOS_CLKS_ENABLE_USC_SC_FS_WRITE ON "USC intercept policy: FS_WRITE") +cl_set_bool_cache(CLEONOS_CLKS_ENABLE_USC_SC_FS_APPEND ON "USC intercept policy: FS_APPEND") +cl_set_bool_cache(CLEONOS_CLKS_ENABLE_USC_SC_FS_REMOVE ON "USC intercept policy: FS_REMOVE") +cl_set_bool_cache(CLEONOS_CLKS_ENABLE_USC_SC_EXEC_PATH ON "USC intercept policy: EXEC_PATH") +cl_set_bool_cache(CLEONOS_CLKS_ENABLE_USC_SC_EXEC_PATHV ON "USC intercept policy: EXEC_PATHV") +cl_set_bool_cache(CLEONOS_CLKS_ENABLE_USC_SC_EXEC_PATHV_IO ON "USC intercept policy: EXEC_PATHV_IO") +cl_set_bool_cache(CLEONOS_CLKS_ENABLE_USC_SC_SPAWN_PATH ON "USC intercept policy: SPAWN_PATH") +cl_set_bool_cache(CLEONOS_CLKS_ENABLE_USC_SC_SPAWN_PATHV ON "USC intercept policy: SPAWN_PATHV") +cl_set_bool_cache(CLEONOS_CLKS_ENABLE_USC_SC_PROC_KILL ON "USC intercept policy: PROC_KILL") +cl_set_bool_cache(CLEONOS_CLKS_ENABLE_USC_SC_SHUTDOWN ON "USC intercept policy: SHUTDOWN") +cl_set_bool_cache(CLEONOS_CLKS_ENABLE_USC_SC_RESTART ON "USC intercept policy: RESTART") cl_set_bool_cache(CLEONOS_CLKS_ENABLE_KBD_TTY_SWITCH_HOTKEY ON "Enable ALT+F1..F4 TTY switch hotkey") cl_set_bool_cache(CLEONOS_CLKS_ENABLE_KBD_CTRL_SHORTCUTS ON "Enable Ctrl+A/C/V keyboard shortcuts") cl_set_bool_cache(CLEONOS_CLKS_ENABLE_KBD_FORCE_STOP_HOTKEY ON "Enable Ctrl+Alt+C force-stop hotkey") @@ -214,6 +227,19 @@ cl_bool_to_int(CLEONOS_CLKS_ENABLE_TTY_READY_LOG CLKS_CFG_TTY_READY_LOG_INT) cl_bool_to_int(CLEONOS_CLKS_ENABLE_IDLE_DEBUG_LOG CLKS_CFG_IDLE_DEBUG_LOG_INT) cl_bool_to_int(CLEONOS_CLKS_ENABLE_PROCFS CLKS_CFG_PROCFS_INT) cl_bool_to_int(CLEONOS_CLKS_ENABLE_EXEC_SERIAL_LOG CLKS_CFG_EXEC_SERIAL_LOG_INT) +cl_bool_to_int(CLEONOS_CLKS_ENABLE_USC CLKS_CFG_USC_INT) +cl_bool_to_int(CLEONOS_CLKS_ENABLE_USC_SC_FS_MKDIR CLKS_CFG_USC_SC_FS_MKDIR_INT) +cl_bool_to_int(CLEONOS_CLKS_ENABLE_USC_SC_FS_WRITE CLKS_CFG_USC_SC_FS_WRITE_INT) +cl_bool_to_int(CLEONOS_CLKS_ENABLE_USC_SC_FS_APPEND CLKS_CFG_USC_SC_FS_APPEND_INT) +cl_bool_to_int(CLEONOS_CLKS_ENABLE_USC_SC_FS_REMOVE CLKS_CFG_USC_SC_FS_REMOVE_INT) +cl_bool_to_int(CLEONOS_CLKS_ENABLE_USC_SC_EXEC_PATH CLKS_CFG_USC_SC_EXEC_PATH_INT) +cl_bool_to_int(CLEONOS_CLKS_ENABLE_USC_SC_EXEC_PATHV CLKS_CFG_USC_SC_EXEC_PATHV_INT) +cl_bool_to_int(CLEONOS_CLKS_ENABLE_USC_SC_EXEC_PATHV_IO CLKS_CFG_USC_SC_EXEC_PATHV_IO_INT) +cl_bool_to_int(CLEONOS_CLKS_ENABLE_USC_SC_SPAWN_PATH CLKS_CFG_USC_SC_SPAWN_PATH_INT) +cl_bool_to_int(CLEONOS_CLKS_ENABLE_USC_SC_SPAWN_PATHV CLKS_CFG_USC_SC_SPAWN_PATHV_INT) +cl_bool_to_int(CLEONOS_CLKS_ENABLE_USC_SC_PROC_KILL CLKS_CFG_USC_SC_PROC_KILL_INT) +cl_bool_to_int(CLEONOS_CLKS_ENABLE_USC_SC_SHUTDOWN CLKS_CFG_USC_SC_SHUTDOWN_INT) +cl_bool_to_int(CLEONOS_CLKS_ENABLE_USC_SC_RESTART CLKS_CFG_USC_SC_RESTART_INT) cl_bool_to_int(CLEONOS_CLKS_ENABLE_KBD_TTY_SWITCH_HOTKEY CLKS_CFG_KBD_TTY_SWITCH_HOTKEY_INT) cl_bool_to_int(CLEONOS_CLKS_ENABLE_KBD_CTRL_SHORTCUTS CLKS_CFG_KBD_CTRL_SHORTCUTS_INT) cl_bool_to_int(CLEONOS_CLKS_ENABLE_KBD_FORCE_STOP_HOTKEY CLKS_CFG_KBD_FORCE_STOP_HOTKEY_INT) @@ -261,6 +287,19 @@ set(ARCH_CFLAGS "-DCLKS_CFG_IDLE_DEBUG_LOG=${CLKS_CFG_IDLE_DEBUG_LOG_INT}" "-DCLKS_CFG_PROCFS=${CLKS_CFG_PROCFS_INT}" "-DCLKS_CFG_EXEC_SERIAL_LOG=${CLKS_CFG_EXEC_SERIAL_LOG_INT}" + "-DCLKS_CFG_USC=${CLKS_CFG_USC_INT}" + "-DCLKS_CFG_USC_SC_FS_MKDIR=${CLKS_CFG_USC_SC_FS_MKDIR_INT}" + "-DCLKS_CFG_USC_SC_FS_WRITE=${CLKS_CFG_USC_SC_FS_WRITE_INT}" + "-DCLKS_CFG_USC_SC_FS_APPEND=${CLKS_CFG_USC_SC_FS_APPEND_INT}" + "-DCLKS_CFG_USC_SC_FS_REMOVE=${CLKS_CFG_USC_SC_FS_REMOVE_INT}" + "-DCLKS_CFG_USC_SC_EXEC_PATH=${CLKS_CFG_USC_SC_EXEC_PATH_INT}" + "-DCLKS_CFG_USC_SC_EXEC_PATHV=${CLKS_CFG_USC_SC_EXEC_PATHV_INT}" + "-DCLKS_CFG_USC_SC_EXEC_PATHV_IO=${CLKS_CFG_USC_SC_EXEC_PATHV_IO_INT}" + "-DCLKS_CFG_USC_SC_SPAWN_PATH=${CLKS_CFG_USC_SC_SPAWN_PATH_INT}" + "-DCLKS_CFG_USC_SC_SPAWN_PATHV=${CLKS_CFG_USC_SC_SPAWN_PATHV_INT}" + "-DCLKS_CFG_USC_SC_PROC_KILL=${CLKS_CFG_USC_SC_PROC_KILL_INT}" + "-DCLKS_CFG_USC_SC_SHUTDOWN=${CLKS_CFG_USC_SC_SHUTDOWN_INT}" + "-DCLKS_CFG_USC_SC_RESTART=${CLKS_CFG_USC_SC_RESTART_INT}" "-DCLKS_CFG_KBD_TTY_SWITCH_HOTKEY=${CLKS_CFG_KBD_TTY_SWITCH_HOTKEY_INT}" "-DCLKS_CFG_KBD_CTRL_SHORTCUTS=${CLKS_CFG_KBD_CTRL_SHORTCUTS_INT}" "-DCLKS_CFG_KBD_FORCE_STOP_HOTKEY=${CLKS_CFG_KBD_FORCE_STOP_HOTKEY_INT}" @@ -344,6 +383,15 @@ list(REMOVE_DUPLICATES ASM_SOURCES) list(SORT C_SOURCES) list(SORT ASM_SOURCES) +set(CLKS_BOOT_LIMINE_SOURCE "clks/kernel/boot/limine/limine_requests.c") +if(EXISTS "${CMAKE_SOURCE_DIR}/${CLKS_BOOT_LIMINE_SOURCE}") + list(APPEND C_SOURCES "${CLKS_BOOT_LIMINE_SOURCE}") + list(REMOVE_DUPLICATES C_SOURCES) + list(SORT C_SOURCES) +else() + cl_log_error("missing required boot source: ${CLKS_BOOT_LIMINE_SOURCE}") +endif() + file(GLOB_RECURSE KERNEL_INC_SOURCES_ABS CONFIGURE_DEPENDS "${CMAKE_SOURCE_DIR}/clks/**/*.inc" ) diff --git a/clks/kernel/runtime/syscall.c b/clks/kernel/runtime/syscall.c index d2072ca..61877c3 100644 --- a/clks/kernel/runtime/syscall.c +++ b/clks/kernel/runtime/syscall.c @@ -34,11 +34,64 @@ #define CLKS_SYSCALL_KERNEL_ADDR_BASE 0xFFFF800000000000ULL #define CLKS_SYSCALL_STATS_MAX_ID CLKS_SYSCALL_EXEC_PATHV_IO #define CLKS_SYSCALL_STATS_RING_SIZE 256U +#define CLKS_SYSCALL_USC_MAX_ALLOWED_APPS 64U #ifndef CLKS_CFG_PROCFS #define CLKS_CFG_PROCFS 1 #endif +#ifndef CLKS_CFG_USC +#define CLKS_CFG_USC 1 +#endif + +#ifndef CLKS_CFG_USC_SC_FS_MKDIR +#define CLKS_CFG_USC_SC_FS_MKDIR 1 +#endif + +#ifndef CLKS_CFG_USC_SC_FS_WRITE +#define CLKS_CFG_USC_SC_FS_WRITE 1 +#endif + +#ifndef CLKS_CFG_USC_SC_FS_APPEND +#define CLKS_CFG_USC_SC_FS_APPEND 1 +#endif + +#ifndef CLKS_CFG_USC_SC_FS_REMOVE +#define CLKS_CFG_USC_SC_FS_REMOVE 1 +#endif + +#ifndef CLKS_CFG_USC_SC_EXEC_PATH +#define CLKS_CFG_USC_SC_EXEC_PATH 1 +#endif + +#ifndef CLKS_CFG_USC_SC_EXEC_PATHV +#define CLKS_CFG_USC_SC_EXEC_PATHV 1 +#endif + +#ifndef CLKS_CFG_USC_SC_EXEC_PATHV_IO +#define CLKS_CFG_USC_SC_EXEC_PATHV_IO 1 +#endif + +#ifndef CLKS_CFG_USC_SC_SPAWN_PATH +#define CLKS_CFG_USC_SC_SPAWN_PATH 1 +#endif + +#ifndef CLKS_CFG_USC_SC_SPAWN_PATHV +#define CLKS_CFG_USC_SC_SPAWN_PATHV 1 +#endif + +#ifndef CLKS_CFG_USC_SC_PROC_KILL +#define CLKS_CFG_USC_SC_PROC_KILL 1 +#endif + +#ifndef CLKS_CFG_USC_SC_SHUTDOWN +#define CLKS_CFG_USC_SC_SHUTDOWN 1 +#endif + +#ifndef CLKS_CFG_USC_SC_RESTART +#define CLKS_CFG_USC_SC_RESTART 1 +#endif + struct clks_syscall_frame { u64 rax; u64 rbx; @@ -92,6 +145,10 @@ static u64 clks_syscall_stats_recent_id_count[CLKS_SYSCALL_STATS_MAX_ID + 1ULL]; static u16 clks_syscall_stats_recent_ring[CLKS_SYSCALL_STATS_RING_SIZE]; static u32 clks_syscall_stats_recent_head = 0U; static u32 clks_syscall_stats_recent_size = 0U; +#if CLKS_CFG_USC != 0 +static clks_bool clks_syscall_usc_allowed_used[CLKS_SYSCALL_USC_MAX_ALLOWED_APPS]; +static char clks_syscall_usc_allowed_path[CLKS_SYSCALL_USC_MAX_ALLOWED_APPS][CLKS_EXEC_PROC_PATH_MAX]; +#endif #if defined(CLKS_ARCH_X86_64) static inline void clks_syscall_outb(u16 port, u8 value) { @@ -1641,6 +1698,264 @@ static void clks_syscall_serial_write_hex64(u64 value) { } } +#if CLKS_CFG_USC != 0 +static void clks_syscall_usc_sleep_until_input(void) { +#if defined(CLKS_ARCH_X86_64) + u64 flags = 0ULL; + + __asm__ volatile("pushfq; popq %0" : "=r"(flags) : : "memory"); + + if ((flags & (1ULL << 9)) != 0ULL) { + __asm__ volatile("hlt" : : : "memory"); + } else { + __asm__ volatile("sti; hlt; cli" : : : "memory"); + } +#elif defined(CLKS_ARCH_AARCH64) + clks_cpu_pause(); +#endif +} + +static const char *clks_syscall_usc_syscall_name(u64 id) { + switch (id) { + case CLKS_SYSCALL_FS_MKDIR: + return "FS_MKDIR"; + case CLKS_SYSCALL_FS_WRITE: + return "FS_WRITE"; + case CLKS_SYSCALL_FS_APPEND: + return "FS_APPEND"; + case CLKS_SYSCALL_FS_REMOVE: + return "FS_REMOVE"; + case CLKS_SYSCALL_EXEC_PATH: + return "EXEC_PATH"; + case CLKS_SYSCALL_EXEC_PATHV: + return "EXEC_PATHV"; + case CLKS_SYSCALL_EXEC_PATHV_IO: + return "EXEC_PATHV_IO"; + case CLKS_SYSCALL_SPAWN_PATH: + return "SPAWN_PATH"; + case CLKS_SYSCALL_SPAWN_PATHV: + return "SPAWN_PATHV"; + case CLKS_SYSCALL_PROC_KILL: + return "PROC_KILL"; + case CLKS_SYSCALL_SHUTDOWN: + return "SHUTDOWN"; + case CLKS_SYSCALL_RESTART: + return "RESTART"; + default: + return "UNKNOWN"; + } +} + +static clks_bool clks_syscall_usc_is_dangerous(u64 id) { + switch (id) { + case CLKS_SYSCALL_FS_MKDIR: + return (CLKS_CFG_USC_SC_FS_MKDIR != 0) ? CLKS_TRUE : CLKS_FALSE; + case CLKS_SYSCALL_FS_WRITE: + return (CLKS_CFG_USC_SC_FS_WRITE != 0) ? CLKS_TRUE : CLKS_FALSE; + case CLKS_SYSCALL_FS_APPEND: + return (CLKS_CFG_USC_SC_FS_APPEND != 0) ? CLKS_TRUE : CLKS_FALSE; + case CLKS_SYSCALL_FS_REMOVE: + return (CLKS_CFG_USC_SC_FS_REMOVE != 0) ? CLKS_TRUE : CLKS_FALSE; + case CLKS_SYSCALL_EXEC_PATH: + return (CLKS_CFG_USC_SC_EXEC_PATH != 0) ? CLKS_TRUE : CLKS_FALSE; + case CLKS_SYSCALL_EXEC_PATHV: + return (CLKS_CFG_USC_SC_EXEC_PATHV != 0) ? CLKS_TRUE : CLKS_FALSE; + case CLKS_SYSCALL_EXEC_PATHV_IO: + return (CLKS_CFG_USC_SC_EXEC_PATHV_IO != 0) ? CLKS_TRUE : CLKS_FALSE; + case CLKS_SYSCALL_SPAWN_PATH: + return (CLKS_CFG_USC_SC_SPAWN_PATH != 0) ? CLKS_TRUE : CLKS_FALSE; + case CLKS_SYSCALL_SPAWN_PATHV: + return (CLKS_CFG_USC_SC_SPAWN_PATHV != 0) ? CLKS_TRUE : CLKS_FALSE; + case CLKS_SYSCALL_PROC_KILL: + return (CLKS_CFG_USC_SC_PROC_KILL != 0) ? CLKS_TRUE : CLKS_FALSE; + case CLKS_SYSCALL_SHUTDOWN: + return (CLKS_CFG_USC_SC_SHUTDOWN != 0) ? CLKS_TRUE : CLKS_FALSE; + case CLKS_SYSCALL_RESTART: + return (CLKS_CFG_USC_SC_RESTART != 0) ? CLKS_TRUE : CLKS_FALSE; + default: + return CLKS_FALSE; + } +} + +static void clks_syscall_usc_copy_path(char *dst, usize dst_size, const char *src) { + usize i = 0U; + + if (dst == CLKS_NULL || dst_size == 0U) { + return; + } + + if (src == CLKS_NULL) { + dst[0] = '\0'; + return; + } + + while (src[i] != '\0' && i + 1U < dst_size) { + dst[i] = src[i]; + i++; + } + + dst[i] = '\0'; +} + +static clks_bool clks_syscall_usc_current_app_path(char *out_path, usize out_size) { + u64 pid; + struct clks_exec_proc_snapshot snap; + + if (out_path == CLKS_NULL || out_size == 0U) { + return CLKS_FALSE; + } + + out_path[0] = '\0'; + pid = clks_exec_current_pid(); + + if (pid == 0ULL) { + return CLKS_FALSE; + } + + if (clks_exec_proc_snapshot(pid, &snap) == CLKS_FALSE || snap.path[0] == '\0') { + return CLKS_FALSE; + } + + clks_syscall_usc_copy_path(out_path, out_size, snap.path); + return CLKS_TRUE; +} + +static i32 clks_syscall_usc_find_allowed_path(const char *path) { + u32 i; + + if (path == CLKS_NULL || path[0] == '\0') { + return -1; + } + + for (i = 0U; i < CLKS_SYSCALL_USC_MAX_ALLOWED_APPS; i++) { + if (clks_syscall_usc_allowed_used[i] == CLKS_TRUE && + clks_strcmp(clks_syscall_usc_allowed_path[i], path) == 0) { + return (i32)i; + } + } + + return -1; +} + +static void clks_syscall_usc_remember_path(const char *path) { + u32 i; + + if (path == CLKS_NULL || path[0] == '\0') { + return; + } + + if (clks_syscall_usc_find_allowed_path(path) >= 0) { + return; + } + + for (i = 0U; i < CLKS_SYSCALL_USC_MAX_ALLOWED_APPS; i++) { + if (clks_syscall_usc_allowed_used[i] == CLKS_FALSE) { + clks_syscall_usc_allowed_used[i] = CLKS_TRUE; + clks_syscall_usc_copy_path(clks_syscall_usc_allowed_path[i], sizeof(clks_syscall_usc_allowed_path[i]), path); + return; + } + } +} + +static void clks_syscall_usc_emit_text_line(const char *label, const char *value) { + char message[320]; + usize pos = 0U; + + message[0] = '\0'; + pos = clks_syscall_procfs_append_text(message, sizeof(message), pos, label); + pos = clks_syscall_procfs_append_text(message, sizeof(message), pos, ": "); + pos = clks_syscall_procfs_append_text(message, sizeof(message), pos, value); + (void)pos; + clks_log(CLKS_LOG_WARN, "USC", message); +} + +static void clks_syscall_usc_emit_hex_line(const char *label, u64 value) { + clks_log_hex(CLKS_LOG_WARN, "USC", label, value); +} + +static clks_bool clks_syscall_usc_prompt_allow(const char *app_path, u64 id, u64 arg0, u64 arg1, u64 arg2) { + const char *name = clks_syscall_usc_syscall_name(id); + u32 tty_index = clks_exec_current_tty(); + +#if !defined(CLKS_CFG_KEYBOARD) || (CLKS_CFG_KEYBOARD == 0) + (void)tty_index; + clks_syscall_usc_emit_text_line("BLOCK", "keyboard disabled, cannot prompt"); + return CLKS_FALSE; +#else + clks_syscall_usc_emit_text_line("DANGEROUS_SYSCALL", "REQUEST DETECTED"); + clks_syscall_usc_emit_text_line("APP", app_path); + clks_syscall_usc_emit_hex_line("SYSCALL_ID", id); + clks_syscall_usc_emit_text_line("SYSCALL_NAME", name); + clks_syscall_usc_emit_hex_line("ARG0", arg0); + clks_syscall_usc_emit_hex_line("ARG1", arg1); + clks_syscall_usc_emit_hex_line("ARG2", arg2); + clks_log(CLKS_LOG_WARN, "USC", "CONFIRM: Allow this app permanently? [y/N]"); + clks_tty_write("[WARN][USC] Allow this app permanently? [y/N]: "); + clks_serial_write("[WARN][USC] Allow this app permanently? [y/N]: "); + + while (1) { + char ch = '\0'; + + if (clks_keyboard_pop_char_for_tty(tty_index, &ch) == CLKS_TRUE) { + if (ch == 'y' || ch == 'Y') { + clks_tty_write("y\n"); + clks_serial_write("y\n"); + return CLKS_TRUE; + } + + if (ch == 'n' || ch == 'N' || ch == '\n' || ch == '\r' || ch == 27) { + clks_tty_write("n\n"); + clks_serial_write("n\n"); + return CLKS_FALSE; + } + + continue; + } + + clks_syscall_usc_sleep_until_input(); + } +#endif +} +#endif + +static clks_bool clks_syscall_usc_check(u64 id, u64 arg0, u64 arg1, u64 arg2) { +#if CLKS_CFG_USC == 0 + (void)id; + (void)arg0; + (void)arg1; + (void)arg2; + return CLKS_TRUE; +#else + char app_path[CLKS_EXEC_PROC_PATH_MAX]; + + if (clks_syscall_usc_is_dangerous(id) == CLKS_FALSE) { + return CLKS_TRUE; + } + + if (clks_exec_is_running() == CLKS_FALSE || clks_exec_current_path_is_user() == CLKS_FALSE) { + return CLKS_TRUE; + } + + if (clks_syscall_usc_current_app_path(app_path, sizeof(app_path)) == CLKS_FALSE) { + clks_syscall_usc_emit_text_line("BLOCK", "cannot resolve current app path"); + return CLKS_FALSE; + } + + if (clks_syscall_usc_find_allowed_path(app_path) >= 0) { + return CLKS_TRUE; + } + + if (clks_syscall_usc_prompt_allow(app_path, id, arg0, arg1, arg2) == CLKS_TRUE) { + clks_syscall_usc_remember_path(app_path); + clks_syscall_usc_emit_text_line("ALLOW", app_path); + return CLKS_TRUE; + } + + clks_syscall_usc_emit_text_line("DENY", app_path); + return CLKS_FALSE; +#endif +} + static void clks_syscall_stats_reset(void) { clks_syscall_stats_total = 0ULL; clks_memset(clks_syscall_stats_id_count, 0, sizeof(clks_syscall_stats_id_count)); @@ -1754,6 +2069,10 @@ void clks_syscall_init(void) { clks_syscall_symbols_checked = CLKS_FALSE; clks_syscall_symbols_data = CLKS_NULL; clks_syscall_symbols_size = 0ULL; +#if CLKS_CFG_USC != 0 + clks_memset(clks_syscall_usc_allowed_used, 0, sizeof(clks_syscall_usc_allowed_used)); + clks_memset(clks_syscall_usc_allowed_path, 0, sizeof(clks_syscall_usc_allowed_path)); +#endif clks_syscall_stats_reset(); clks_log(CLKS_LOG_INFO, "SYSCALL", "INT80 FRAMEWORK ONLINE"); } @@ -1773,6 +2092,10 @@ u64 clks_syscall_dispatch(void *frame_ptr) { clks_syscall_stats_record(id); clks_syscall_trace_user_program(id); + if (clks_syscall_usc_check(id, frame->rbx, frame->rcx, frame->rdx) == CLKS_FALSE) { + return (u64)-1; + } + switch (id) { case CLKS_SYSCALL_LOG_WRITE: return clks_syscall_log_write(frame->rbx, frame->rcx); diff --git a/configs/menuconfig/clks_features.json b/configs/menuconfig/clks_features.json index 5715d31..560a793 100644 --- a/configs/menuconfig/clks_features.json +++ b/configs/menuconfig/clks_features.json @@ -190,6 +190,123 @@ "type": "tristate", "default": "y" }, + { + "key": "CLEONOS_CLKS_ENABLE_USC", + "title": "UserSafeController (USC)", + "description": "Prompt before dangerous user syscalls and remember per-app approval for current boot.", + "type": "bool", + "default": true, + "depends_on": "CLEONOS_CLKS_ENABLE_KEYBOARD", + "group": "USC Syscall Policy" + }, + { + "key": "CLEONOS_CLKS_ENABLE_USC_SC_FS_MKDIR", + "title": "Intercept FS_MKDIR", + "description": "USC prompt for syscall FS_MKDIR.", + "type": "bool", + "default": true, + "depends_on": "CLEONOS_CLKS_ENABLE_USC", + "group": "USC Syscall Policy" + }, + { + "key": "CLEONOS_CLKS_ENABLE_USC_SC_FS_WRITE", + "title": "Intercept FS_WRITE", + "description": "USC prompt for syscall FS_WRITE.", + "type": "bool", + "default": true, + "depends_on": "CLEONOS_CLKS_ENABLE_USC", + "group": "USC Syscall Policy" + }, + { + "key": "CLEONOS_CLKS_ENABLE_USC_SC_FS_APPEND", + "title": "Intercept FS_APPEND", + "description": "USC prompt for syscall FS_APPEND.", + "type": "bool", + "default": true, + "depends_on": "CLEONOS_CLKS_ENABLE_USC", + "group": "USC Syscall Policy" + }, + { + "key": "CLEONOS_CLKS_ENABLE_USC_SC_FS_REMOVE", + "title": "Intercept FS_REMOVE", + "description": "USC prompt for syscall FS_REMOVE.", + "type": "bool", + "default": true, + "depends_on": "CLEONOS_CLKS_ENABLE_USC", + "group": "USC Syscall Policy" + }, + { + "key": "CLEONOS_CLKS_ENABLE_USC_SC_EXEC_PATH", + "title": "Intercept EXEC_PATH", + "description": "USC prompt for syscall EXEC_PATH.", + "type": "bool", + "default": true, + "depends_on": "CLEONOS_CLKS_ENABLE_USC", + "group": "USC Syscall Policy" + }, + { + "key": "CLEONOS_CLKS_ENABLE_USC_SC_EXEC_PATHV", + "title": "Intercept EXEC_PATHV", + "description": "USC prompt for syscall EXEC_PATHV.", + "type": "bool", + "default": true, + "depends_on": "CLEONOS_CLKS_ENABLE_USC", + "group": "USC Syscall Policy" + }, + { + "key": "CLEONOS_CLKS_ENABLE_USC_SC_EXEC_PATHV_IO", + "title": "Intercept EXEC_PATHV_IO", + "description": "USC prompt for syscall EXEC_PATHV_IO.", + "type": "bool", + "default": true, + "depends_on": "CLEONOS_CLKS_ENABLE_USC", + "group": "USC Syscall Policy" + }, + { + "key": "CLEONOS_CLKS_ENABLE_USC_SC_SPAWN_PATH", + "title": "Intercept SPAWN_PATH", + "description": "USC prompt for syscall SPAWN_PATH.", + "type": "bool", + "default": true, + "depends_on": "CLEONOS_CLKS_ENABLE_USC", + "group": "USC Syscall Policy" + }, + { + "key": "CLEONOS_CLKS_ENABLE_USC_SC_SPAWN_PATHV", + "title": "Intercept SPAWN_PATHV", + "description": "USC prompt for syscall SPAWN_PATHV.", + "type": "bool", + "default": true, + "depends_on": "CLEONOS_CLKS_ENABLE_USC", + "group": "USC Syscall Policy" + }, + { + "key": "CLEONOS_CLKS_ENABLE_USC_SC_PROC_KILL", + "title": "Intercept PROC_KILL", + "description": "USC prompt for syscall PROC_KILL.", + "type": "bool", + "default": true, + "depends_on": "CLEONOS_CLKS_ENABLE_USC", + "group": "USC Syscall Policy" + }, + { + "key": "CLEONOS_CLKS_ENABLE_USC_SC_SHUTDOWN", + "title": "Intercept SHUTDOWN", + "description": "USC prompt for syscall SHUTDOWN.", + "type": "bool", + "default": true, + "depends_on": "CLEONOS_CLKS_ENABLE_USC", + "group": "USC Syscall Policy" + }, + { + "key": "CLEONOS_CLKS_ENABLE_USC_SC_RESTART", + "title": "Intercept RESTART", + "description": "USC prompt for syscall RESTART.", + "type": "bool", + "default": true, + "depends_on": "CLEONOS_CLKS_ENABLE_USC", + "group": "USC Syscall Policy" + }, { "key": "CLEONOS_CLKS_ENABLE_KBD_TTY_SWITCH_HOTKEY", "title": "Keyboard TTY Switch Hotkey", diff --git a/scripts/menuconfig.py b/scripts/menuconfig.py index 1e6baff..155f6ad 100644 --- a/scripts/menuconfig.py +++ b/scripts/menuconfig.py @@ -56,6 +56,7 @@ class OptionItem: depends_on: str selects: Tuple[str, ...] implies: Tuple[str, ...] + group: str @dataclass @@ -312,6 +313,7 @@ def load_clks_options() -> List[OptionItem]: depends_on = str(entry.get("depends_on", entry.get("depends", ""))).strip() selects = _normalize_key_list(entry.get("select", entry.get("selects", ()))) implies = _normalize_key_list(entry.get("imply", entry.get("implies", ()))) + group = str(entry.get("group", entry.get("menu", "General"))).strip() or "General" if not key: continue options.append( @@ -324,6 +326,7 @@ def load_clks_options() -> List[OptionItem]: depends_on=depends_on, selects=selects, implies=implies, + group=group, ) ) @@ -378,6 +381,7 @@ def discover_user_apps() -> List[OptionItem]: depends_on="", selects=(), implies=(), + group=section, ) ) @@ -417,6 +421,27 @@ def _build_index(options: Iterable[OptionItem]) -> Dict[str, OptionItem]: return {item.key: item for item in options} +def _grouped_options(options: List[OptionItem]) -> List[Tuple[str, List[OptionItem]]]: + groups: Dict[str, List[OptionItem]] = {} + ordered_names: List[str] = [] + + for item in options: + name = (item.group or "General").strip() or "General" + if name not in groups: + groups[name] = [] + ordered_names.append(name) + groups[name].append(item) + + out: List[Tuple[str, List[OptionItem]]] = [] + for name in ordered_names: + out.append((name, groups[name])) + return out + + +def _group_enabled_count(group_options: List[OptionItem], ev: EvalResult) -> int: + return sum(1 for item in group_options if ev.effective.get(item.key, item.default) > TRI_N) + + def _set_option_if_exists(values: Dict[str, int], option_index: Dict[str, OptionItem], key: str, level: int) -> None: if key in values: item = option_index.get(key) @@ -829,6 +854,47 @@ def section_loop(title: str, section_options: List[OptionItem], all_options: Lis print("unknown command") +def grouped_section_loop( + title: str, + section_options: List[OptionItem], + all_options: List[OptionItem], + values: Dict[str, int], +) -> None: + groups = _grouped_options(section_options) + + if len(groups) <= 1: + section_loop(title, section_options, all_options, values) + return + + while True: + ev = evaluate_config(all_options, values) + print() + print(f"== {title} / Groups ==") + print(f" 0. All ({_group_enabled_count(section_options, ev)}/{len(section_options)} enabled)") + for idx, (name, opts) in enumerate(groups, start=1): + print(f"{idx:3d}. {name} ({_group_enabled_count(opts, ev)}/{len(opts)} enabled)") + print("Commands: open, b back") + + raw = input(f"{title}/groups> ").strip().lower() + if not raw: + continue + if raw in {"b", "back", "q", "quit"}: + return + if not raw.isdigit(): + print("invalid selection") + continue + + idx = int(raw) + if idx == 0: + section_loop(title, section_options, all_options, values) + continue + if 1 <= idx <= len(groups): + group_name, group_items = groups[idx - 1] + section_loop(f"{title}/{group_name}", group_items, all_options, values) + continue + print("invalid selection") + + def _safe_addnstr(stdscr, y: int, x: int, text: str, attr: int = 0) -> None: h, w = stdscr.getmaxyx() if y < 0 or y >= h or x >= w: @@ -1213,6 +1279,75 @@ def _run_ncurses_section( continue +def _run_ncurses_grouped_section( + stdscr, + theme: Dict[str, int], + title: str, + section_options: List[OptionItem], + all_options: List[OptionItem], + values: Dict[str, int], +) -> None: + groups = _grouped_options(section_options) + if len(groups) <= 1: + _run_ncurses_section(stdscr, theme, title, section_options, all_options, values) + return + + selected = 0 + + while True: + ev = evaluate_config(all_options, values) + stdscr.erase() + h, w = stdscr.getmaxyx() + items: List[Tuple[str, List[OptionItem]]] = [("All", section_options)] + groups + + if h < 12 or w < 56: + _safe_addnstr(stdscr, 0, 0, "Terminal too small for grouped view (need >= 56x12).", theme["status_warn"]) + _safe_addnstr(stdscr, 2, 0, "Resize terminal then press any key, or ESC to go back.") + key = stdscr.getch() + if key in (27,): + return + continue + + _safe_addnstr(stdscr, 0, 0, f" CLeonOS menuconfig / {title} / Groups ", theme["header"]) + _safe_addnstr(stdscr, 1, 0, " Enter: open group ESC: back ", theme["subtitle"]) + + _draw_box(stdscr, 2, 0, h - 4, w, "CLKS Groups", theme["panel_border"], theme["panel_title"]) + + if selected < 0: + selected = 0 + if selected >= len(items): + selected = len(items) - 1 + + for i, (name, opts) in enumerate(items): + row = 4 + i + if row >= h - 2: + break + on_count = _group_enabled_count(opts, ev) + line = f"{'>' if i == selected else ' '} {i:02d} {name} ({on_count}/{len(opts)} enabled)" + attr = theme["selected"] if i == selected else theme["value_label"] + _safe_addnstr(stdscr, row, 2, line, attr) + + _safe_addnstr(stdscr, h - 1, 0, " Arrows/jk move Enter open ESC back ", theme["help"]) + stdscr.refresh() + key = stdscr.getch() + + if key in (27, ord("q"), ord("Q"), curses.KEY_LEFT): + return + if key in (curses.KEY_UP, ord("k"), ord("K")): + selected = (selected - 1) % len(items) + continue + if key in (curses.KEY_DOWN, ord("j"), ord("J")): + selected = (selected + 1) % len(items) + continue + if key in (curses.KEY_ENTER, 10, 13): + name, opts = items[selected] + if name == "All": + _run_ncurses_section(stdscr, theme, title, opts, all_options, values) + else: + _run_ncurses_section(stdscr, theme, f"{title}/{name}", opts, all_options, values) + continue + + def _run_ncurses_main(stdscr, clks_options: List[OptionItem], user_options: List[OptionItem], values: Dict[str, int]) -> bool: theme = _curses_theme() all_options = clks_options + user_options @@ -1288,7 +1423,7 @@ def _run_ncurses_main(stdscr, clks_options: List[OptionItem], user_options: List continue if key in (curses.KEY_ENTER, 10, 13): if selected == 0: - _run_ncurses_section(stdscr, theme, "CLKS", clks_options, all_options, values) + _run_ncurses_grouped_section(stdscr, theme, "CLKS", clks_options, all_options, values) elif selected == 1: _run_ncurses_section(stdscr, theme, "USER", user_options, all_options, values) elif selected == 2: @@ -1594,7 +1729,20 @@ def interactive_menu_gui(clks_options: List[OptionItem], user_options: List[Opti _set_option_value(values, item, TRI_N) self.refresh(keep_selection=False) - clks_panel = _SectionPanel("CLKS Features", clks_options) + clks_groups = _grouped_options(clks_options) + if len(clks_groups) <= 1: + clks_panel = _SectionPanel("CLKS Features", clks_options) + else: + clks_panel = QtWidgets.QWidget() + clks_layout = QtWidgets.QVBoxLayout(clks_panel) + clks_layout.setContentsMargins(0, 0, 0, 0) + clks_layout.setSpacing(6) + clks_tabs = QtWidgets.QTabWidget() + clks_layout.addWidget(clks_tabs, 1) + clks_tabs.addTab(_SectionPanel("CLKS Features / All", clks_options), "All") + for group_name, group_items in clks_groups: + clks_tabs.addTab(_SectionPanel(f"CLKS Features / {group_name}", group_items), group_name) + user_panel = _SectionPanel("User Apps", user_options) tabs.addTab(clks_panel, "CLKS") tabs.addTab(user_panel, "USER") @@ -1689,7 +1837,7 @@ def interactive_menu(clks_options: List[OptionItem], user_options: List[OptionIt show_summary(clks_options, user_options, values) choice = input("Select> ").strip().lower() if choice == "1": - section_loop("CLKS", clks_options, all_options, values) + grouped_section_loop("CLKS", clks_options, all_options, values) continue if choice == "2": section_loop("USER", user_options, all_options, values)