From c982d6b5382feec40d8f247f7688d1ee4193220c Mon Sep 17 00:00:00 2001 From: subhobhai943 Date: Tue, 25 Aug 2026 17:57:47 +0530 Subject: [PATCH] feat(auth): Boot-time login prompt; fix(configure): arch selection via dialog Login: - Add console_login() (security/auth.c): an interactive boot-time username / password gate rendered on the framebuffer console before any session starts. It blocks until valid credentials are entered, masks the password, scrubs it from the stack, and sets the current user. Default account is SUB / SUB, shown as a hint. Wired into the boot handoff in kernel/main.c ahead of both the GUI desktop and the emergency TTY shell. Configure: - scripts/kconfig/menuconfig.py: fix the architecture (and optimization) picker. The lxdialog front-end used --radiolist, where confirms the ORIGINAL choice and only moves the dot -- so highlighting a new architecture and pressing appeared to do nothing. Single-choice groups now use --menu (with --default-item), where selects the highlighted row directly. Also stop the unconditional save-on-exit from overriding an explicit "No" at the exit confirmation. Verified: SUB/SUB logs in (injected via QEMU monitor) -> "Login successful" -> desktop starts; the desktop stays blocked until login. dialog --menu selection of aarch64 persists to .config/autoconf.h and retargets `make`. Clean build on x86_64/aarch64/armv8i. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01Fh5z6V7nGPbHvqVG5p4CXX --- .gitignore | 4 ++ include/security/auth.h | 5 ++ kernel/main.c | 21 +++++---- scripts/kconfig/menuconfig.py | 86 ++++++++++++++++++++--------------- security/auth.c | 74 ++++++++++++++++++++++++++++++ 5 files changed, 146 insertions(+), 44 deletions(-) diff --git a/.gitignore b/.gitignore index 7d63841..0ae4b65 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,7 @@ build/ .DS_Store .system_generated/ .config + +# Python bytecode +__pycache__/ +*.pyc diff --git a/include/security/auth.h b/include/security/auth.h index 240f5d5..b228e6c 100644 --- a/include/security/auth.h +++ b/include/security/auth.h @@ -31,4 +31,9 @@ int auth_set_current_user(const char* username); size_t auth_get_user_count(void); const user_account_t* auth_get_user_by_index(size_t index); +/* Interactive boot-time login: prompts for a username and password on the + * console and blocks until a valid pair is entered, then sets the current + * user. Default account is SUB / SUB. */ +void console_login(void); + #endif // _SECURITY_AUTH_H diff --git a/kernel/main.c b/kernel/main.c index 44802b5..bdd363c 100644 --- a/kernel/main.c +++ b/kernel/main.c @@ -275,20 +275,25 @@ static void subos_modular_core_boot(void) { bool emergency_text = init_has_param("nogui") || init_has_param("text") || init_has_param("emergency") || init_has_param("single"); + // End the splash and return the screen to a text console so the login + // prompt is visible before any session (graphical or TTY) begins. + boot_logo_splash_end(); + fbcon_enable(true); + + // Gate the session behind an interactive login. This blocks until a valid + // username/password is entered; the default account is SUB / SUB. + console_login(); + if (!emergency_text) { - printk(KERN_INFO "INIT: Booting directly into SUB-OS Graphical Desktop Environment...\n"); - boot_logo_splash_end(); + printk(KERN_INFO "INIT: Starting SUB-OS Graphical Desktop Environment...\n"); gui_start_desktop(); - printk(KERN_INFO "INIT: GUI session closed. Entering Emergency Kernel TTY Mode.\n"); + printk(KERN_INFO "INIT: GUI session closed. Entering Kernel TTY Mode.\n"); + // The desktop owned the framebuffer; return it to the text console. + fbcon_enable(true); } else { printk(KERN_INFO "INIT: Emergency text/kernel TTY mode requested via boot parameters.\n"); - boot_logo_splash_end(); } - // The screen returns to the console before handing over to userland, whether - // the GUI session just closed or a text boot was requested. - fbcon_enable(true); - // Hand control to userland: /sbin/init is a separate static ELF that runs // in ring 3 with its own page tables and talks back only through INT 0x80. exec_run_builtin_init(); diff --git a/scripts/kconfig/menuconfig.py b/scripts/kconfig/menuconfig.py index b4e382b..b72b78b 100755 --- a/scripts/kconfig/menuconfig.py +++ b/scripts/kconfig/menuconfig.py @@ -407,56 +407,68 @@ def run_dialog_gui(): continue items = submenu["items"] - item_opts = [] + is_radio = any(i.get("type") == "radio" for i in items) - for item in items: - itype = item.get("type") - if itype == "bool": + item_opts = [] + if is_radio: + # Single-choice group (architecture, optimization). Use a --menu, + # not a --radiolist: in a radiolist the arrow keys only move the + # highlight and is required to move the (*) dot, so a user + # who highlights another architecture and presses confirms + # the ORIGINAL choice -- the selection appears to do nothing. With + # --menu, selects the highlighted row directly. + default_item = None + for item in items: + item_opts.extend([item["id"], item["label"]]) + group = item.get("group") + cur = cfg.get("CONFIG_ARCH") if group == "arch" else ( + cfg.get("CONFIG_OPTIMIZATION") if group == "opt" else None) + if cur is not None and cur == item["val"]: + default_item = item["id"] + + cmd = ["dialog", "--clear", "--backtitle", backtitle, + "--title", submenu["title"], "--help-button"] + if default_item: + cmd += ["--default-item", default_item] + cmd += ["--menu", + "Highlight a choice with the arrow keys and press to select it.", + "20", "78", "10"] + item_opts + else: + for item in items: status = "ON" if cfg.get(item["id"], False) else "OFF" item_opts.extend([item["id"], item["label"], status]) - elif itype == "radio": - group = item.get("group") - if group == "arch": - status = "ON" if cfg.get("CONFIG_ARCH") == item["val"] else "OFF" - elif group == "opt": - status = "ON" if cfg.get("CONFIG_OPTIMIZATION") == item["val"] else "OFF" - else: - status = "OFF" - item_opts.extend([item["id"], item["label"], status]) - is_radiolist = any(i.get("type") == "radio" for i in items) - dialog_type = "--radiolist" if is_radiolist else "--checklist" - - cmd = [ - "dialog", - "--clear", - "--backtitle", backtitle, - "--title", submenu["title"], - "--help-button", - "--help-status", - dialog_type, - "Use to toggle features [*], to confirm, for info.", - "20", "78", "10" - ] + item_opts + cmd = ["dialog", "--clear", "--backtitle", backtitle, + "--title", submenu["title"], "--help-button", "--help-status", + "--checklist", + "Use to toggle features [*], to confirm, for info.", + "20", "78", "10"] + item_opts res = subprocess.run(cmd, stderr=subprocess.PIPE, text=True) ret = res.returncode out = res.stderr.strip() if ret == 0: # OK / Confirm - _apply_selection(cfg, items, [t.strip('"') for t in out.split()], is_radiolist) + if is_radio: + # --menu returns just the highlighted tag. + _apply_selection(cfg, items, [out.strip('"')], True) + else: + _apply_selection(cfg, items, [t.strip('"') for t in out.split()], False) current_menu_id = "main" elif ret == 1 or ret == 255: # Cancel or Esc current_menu_id = "main" elif ret == 2: # Help button - # With --help-status the payload is "HELP ". - # Re-apply the on-tags first so the toggles made before pressing - # survive the round trip, then show the focused item's help. - tokens = _help_tokens(out) - focus = tokens[0] if tokens else None - _apply_selection(cfg, items, tokens[1:], is_radiolist) + if is_radio: + # --menu help payload is just "HELP ". + focus = _help_tag(out) + else: + # --help-status payload is "HELP "; + # re-apply the on-tags so toggles made before survive. + tokens = _help_tokens(out) + focus = tokens[0] if tokens else None + _apply_selection(cfg, items, tokens[1:], False) focused = next((i for i in items if i["id"] == focus), None) if focused: help_title = f"{focused['label']} - Help" @@ -467,8 +479,10 @@ def run_dialog_gui(): subprocess.run(["dialog", "--backtitle", backtitle, "--title", help_title, "--msgbox", help_text, "16", "72"]) - save_config(cfg) - print(f"*** Configuration written to {CONFIG_FILE} and {AUTOCONF_HEADER} ***") + # Saving is handled inside the loop: the button writes immediately, + # and asks first and writes only on "Yes". Saving again here would + # override an explicit "No", so just report the resulting state. + print(f"*** Kernel configuration ready: {CONFIG_FILE} (ARCH={cfg.get('CONFIG_ARCH', 'x86_64')}) ***") # ----------------------------------------------------------------------------- # Pure Python Curses GUI Renderer (Pixel-Perfect Linux lxdialog Replica) diff --git a/security/auth.c b/security/auth.c index d13ace9..c749d6e 100644 --- a/security/auth.c +++ b/security/auth.c @@ -2,6 +2,7 @@ #include #include #include +#include static user_account_t user_table[MAX_USERS]; static size_t user_count = 0; @@ -123,3 +124,76 @@ const user_account_t* auth_get_user_by_index(size_t index) { if (index >= MAX_USERS || !user_table[index].in_use) return NULL; return &user_table[index]; } + +/* Read a line from the PS/2 keyboard into buf. When echo is false the typed + * characters are masked with '*' (password entry). Blocks until Enter. */ +static void login_read_line(char* buf, size_t max, bool echo) { + size_t len = 0; + while (true) { + uint16_t key = keyboard_get_key(); + if (key & KEY_SPECIAL_FLAG) { + continue; // ignore arrows / function keys at the login prompt + } + char c = (char)(key & 0xFF); + + if (c == '\n' || c == '\r') { + printk("\n"); + break; + } + if (c == '\b' || (unsigned char)c == 0x7F || c == 0x08) { + if (len > 0) { + len--; + printk("\b \b"); // erase the last glyph on screen + } + continue; + } + if ((unsigned char)c >= 32 && (unsigned char)c <= 126 && len < max - 1) { + buf[len++] = c; + if (echo) { + printk("%c", c); + } else { + printk("*"); + } + } + } + buf[len] = '\0'; +} + +void console_login(void) { + char username[32]; + char password[64]; + + printk("\n" ANSI_BRIGHT_CYAN + "=================================================================\n" + " SUB-OS Secure Login -- authenticate to start your session\n" + "=================================================================\n" + ANSI_RESET); + printk(ANSI_YELLOW " Default credentials: username " ANSI_BRIGHT_GREEN "SUB" + ANSI_YELLOW ", password " ANSI_BRIGHT_GREEN "SUB" ANSI_RESET "\n\n"); + + while (true) { + printk(ANSI_BRIGHT_GREEN "sub-os " ANSI_RESET "login: "); + login_read_line(username, sizeof(username), true); + if (username[0] == '\0') { + continue; // empty username, re-prompt + } + + printk("Password: "); + login_read_line(password, sizeof(password), false); + + bool ok = auth_verify_password(username, password); + + // Scrub the password from the stack regardless of the outcome. + memset(password, 0, sizeof(password)); + + if (ok) { + auth_set_current_user(username); + printk(ANSI_BRIGHT_GREEN + "\nLogin successful. Welcome to SUB-OS, %s!\n\n" ANSI_RESET, + username); + return; + } + + printk(ANSI_BRIGHT_RED "\nLogin incorrect. Please try again.\n\n" ANSI_RESET); + } +}