kernel: add VFS spinlock (g_vfs_lock) to fix SMP mount-table races
The VFS mount table (g_mounts[], g_mount_count, fs_root) had zero
locking protection. On SMP (4 CPUs), concurrent access during
pivot_root/mount/umount2 corrupted the mount table, causing:
- Mount points disappearing: vfs_umount shifts array entries while
another CPU's vfs_lookup iterates, skipping/duplicating entries
- Sporadic QEMU reboot: corrupted fs_node_t* pointer from torn
reads causes kernel page fault -> triple fault -> reboot
Fix:
- Add spinlock_t g_vfs_lock in fs.c protecting fs_root, g_mounts[],
and g_mount_count
- Create _nolock variants (vfs_mount_nolock, vfs_umount_nolock) for
compound operations that need atomicity across multiple mutations
- Public vfs_mount/vfs_umount acquire the lock automatically
- vfs_lookup_depth snapshots mount table under lock before traversal
- SYSCALL_PIVOT_ROOT holds g_vfs_lock across the entire compound
mutation (fs_root update + two vfs_mount_nolock calls)
- Export g_vfs_lock and _nolock variants in fs.h
Also:
- fulltest pivot_root test: undo pivot_root in child before exit,
verify /dev/null accessible after undo
- ulibc strrchr: add null-pointer guard (cppcheck ctunullpointer)
- fulltest: struct init {0} instead of {{0}} (cppcheck uninitvar)
TESTING_PLAN.md:
- All test counts updated
- Pure function list expanded with tar_parse_octal, mount prefix/normalize,
vfs_check_permission, elf32_validate, signal mask logic, parse_symbolic
- Makefile targets updated
SYSCALL_TEST_COVERAGE.md:
- Date updated to 2026-04-27
- Battery: 33 → 152, Host: 69 → 212
POSIX_ROADMAP.md:
- Added host unit tests (212), test battery (152), GDB checks (10) rows
- Host utility test harness: 68 → 111
- overlayfs: remove dead 'written' assignment before early return
- scheduler: merge two consecutive if(current_process) blocks
- syscall: simplify addr_space assignment (condition was always true)
- test_utils: suppress unusedStructMember for ELF test struct fields
Tulio A M Mendes [Mon, 27 Apr 2026 16:40:36 +0000 (13:40 -0300)]
tests: update host utility tests for new features; fix chmod symbolic modes and grep -l
Host utility tests (tests/test_host_utils.sh):
- Add getdents shim for host builds (glibc only exposes getdents64)
- Add _DEFAULT_SOURCE to CFLAGS for BSD extensions (DT_DIR, etc.)
- Enhanced grep tests: -i (case-insensitive), -l (list files), -q (quiet), -E (extended regex)
- Enhanced sed tests: -n/p (suppress auto-print), d (delete), y (transliterate), line addressing
- Enhanced awk tests: BEGIN/END blocks, -v var=val, NR, NF
- Enhanced find tests: -name single file, -type f, -maxdepth, ! negation
- Enhanced dd tests: conv=ucase, count=1
- Enhanced rm test: -f flag
- Enhanced cp/mv tests: permission preservation
- New chmod tests: symbolic modes (u+x, go-w, a+x, a=rw)
- New stat tests: filename, type, mtime formatting
- New kill tests: -l signal listing, bad PID
- New ls tests: compilation and flag parsing (-l, -a, -n)
- New date tests: output format
- New du tests: single file
- New env tests: environment variable display
- New hostname tests: output
- New sleep tests: 1s delay
- New uptime tests: output
chmod (user/cmds/chmod/chmod.c):
- Fix symbolic mode parsing: who mask was incorrectly ANDed with perm bits,
causing u+x to only add setuid+user-x overlap (0100) instead of user-x (0100).
Now permissions are mapped per-who: u+x→0100, g+x→0010, o+x→0001, etc.
grep (user/cmds/grep/grep.c):
- Fix grep -l: was returning 1 (no match) immediately on first match without
printing the filename. Now prints filename and returns 0 on match.
Tulio A M Mendes [Mon, 27 Apr 2026 01:59:00 +0000 (22:59 -0300)]
cppcheck: fix unreadVariable in elf.c and sync.c
- elf.c: remove dead seg_vmm_flags computation from elf32_load_segments
(re-protection is done by elf32_reprotect_segments after relocations)
- sync.c: eliminate unused deadline variable, inline into wake_at_tick
Tulio A M Mendes [Mon, 27 Apr 2026 01:41:15 +0000 (22:41 -0300)]
kernel: security audit fixes — C1-C6, H3-H4, M5
Critical fixes:
- C1: procfs UAF — add g_pid_cmdline pool, stop overwriting g_pid_status
- C2: heap corruption — kill process + schedule() instead of infinite loop
- C3: ext2 consistency — write inode after each i_blocks increment
- C4: shm UAF — reject shm_at on IPC_RMID'd segments (-EIDRM),
skip marked_rm segments in shm_get lookup
- C6: ELF W^X — parse p_flags for segment permissions, re-protect
after relocations, only re-protect full pages within non-writable
segments (partial pages may be shared with .data)
High fixes:
- H3: execve_copy_user_str — add upfront user_range_ok check
- H4: rq_remove_if_queued — scan all priority queues in both
active and expired runqueues (not just current priority)
Tulio A M Mendes [Mon, 27 Apr 2026 00:28:10 +0000 (21:28 -0300)]
kernel: fix execve after pivot_root via vfs_lookup_initrd(); fork execve test
pivot_root changes the global fs_root and '/' mount entry to a tmpfs,
which breaks subsequent execve calls because vfs_lookup('/bin/echo')
fails in the new root. This caused the 'echo execve' flaky test
failure (it always failed when run after the pivot_root test).
Fix: save the original initrd overlayfs root at boot and add
vfs_lookup_initrd() which does a direct finddir traversal from that
saved root, bypassing the VFS mount table. Use it in:
- elf32_load_user_from_initrd (binary lookup)
- elf32_load_interp (ld.so lookup)
- elf32_load_shared_lib_at (libc.so lookup)
- syscall_execve_impl (early ENOENT check)
Also change the execve test to fork a child (like execveat), so PID 1
is not replaced and the test harness can verify the output.
Tulio A M Mendes [Sun, 26 Apr 2026 23:51:23 +0000 (20:51 -0300)]
kernel: fix COW page write + signal delivery; add 6 new tests
Kernel fixes:
- uaccess: x86_user_page_writable_user() now recognizes COW pages as
logically writable (checks X86_PTE_COW flag bit 9). Previously,
copy_to_user() rejected writes to forked COW pages, preventing
signal frame delivery after fork().
- idt: handle COW faults in kernel mode before uaccess_try_recover().
A write from copy_to_user() to a COW page now triggers page fault
resolution (private copy) instead of returning -EFAULT.
- scheduler: fork inherits sigactions and sig_blocked_mask from parent
(POSIX requirement). Pending signals stay 0 per POSIX spec.
- syscall: sigprocmask how values now match POSIX (0=BLOCK, 1=UNBLOCK,
2=SETMASK). sigsuspend no longer restores old mask before signal
delivery, allowing the handler to run.
New tests (fulltest.c):
- I12: clone — CLONE_VM|CLONE_FS|CLONE_FILES|CLONE_SIGHAND thread
creation with shared memory verification
- I13: sigqueue — send SIGUSR1 with value via sigsuspend
- I14: inotify_init1 — basic inotify with flags=0
- I15: dlopen/dlsym/dlclose — dynamic linker via libpietest.so
- I16: execveat — execute /bin/echo with AT_FDCWD
- I17: pivot_root — mount tmpfs, pivot, verify in isolated fork
Tulio A M Mendes [Sun, 19 Apr 2026 21:30:12 +0000 (18:30 -0300)]
fix select() timeout conversion — was passing raw pointer as kernel timeout
The select() wrapper in both newlib/posix_stubs.c and ulibc/unistd.c
was casting the struct timeval* pointer directly to int and passing it
as the 5th syscall argument. The kernel expects an int32_t timeout:
-1 = infinite wait, 0 = poll (return immediately), >0 = ticks.
When bash calls select() with timeout=NULL (infinite wait), the raw
pointer value was interpreted as a large positive number (poll with
timeout), not as -1 (infinite). Worse, on some code paths the pointer
could be 0 (NULL), which the kernel treats as timeout=0 (poll only),
causing select() to return 0 immediately with no fds ready — which
bash interprets as EOF on stdin, causing it to exit immediately.
Fix: convert struct timeval to int32_t ticks before passing to the
kernel. NULL timeout → -1 (infinite). tv_sec=0, tv_usec=0 → 0 (poll).
Otherwise convert ms → ticks (TIMER_HZ=100, 10ms/tick).
Tulio A M Mendes [Sun, 19 Apr 2026 21:05:46 +0000 (18:05 -0300)]
libgloss: fix isatty() and ttyname() — use TCGETS instead of TIOCGPGRP with NULL
isatty() in syscalls.c used ioctl(TIOCGPGRP, 0) which always failed
because the kernel rejects NULL user_arg with -EFAULT. This caused
bash to think stdin was not a terminal, running in non-interactive
mode (no prompt, no line editing).
Fix: use TCGETS (0x5401) with a stack-allocated termios struct instead.
TCGETS succeeds on any tty regardless of process group state — this
is the standard POSIX way to test for a terminal.
Also fix ttyname() in posix_stubs.c which had the same bug.
Tulio A M Mendes [Sun, 19 Apr 2026 20:41:52 +0000 (17:41 -0300)]
toolchain: fix libgloss/adros build errors
- Add sys/uio.h compat header (struct iovec + readv/writev declarations)
needed by posix_stubs.c which was missing from sysroot_headers
- Remove duplicate fcntl() definition in posix_stubs.c (line 541 was
identical to line 256, causing redefinition error with newlib headers)
- Move libgloss/adros source sync outside patch_newlib() marker guard
so edits to stubs are always picked up on rebuild (cp -f instead of
cp -u); keep mkdir -p inside patch_newlib() for autoconf file creation
Tulio A M Mendes [Sun, 19 Apr 2026 19:16:01 +0000 (16:16 -0300)]
kernel: remove dead-code duplicate SETITIMER/GETITIMER from socket_syscall_dispatch
The SETITIMER and GETITIMER syscall handlers were implemented twice:
1. In syscall_handler (lines 3941/4005) — using proper POSIX struct
k_itimerval with tv_sec/tv_usec fields and timeval_to_ticks/
ticks_to_timeval conversion helpers.
2. In socket_syscall_dispatch (lines 4673/4729) — using raw uint32_t
tick pairs, which would be incorrect if ever reached.
The socket_syscall_dispatch versions were dead code because the main
handler returns before falling through to socket dispatch. Removing
them eliminates confusion and prevents accidental use of the incorrect
tick-based format.
Part of POSIX compliance audit: all 141 SYSCALL_ enum values now
have exactly one handler each.
Tests: 103/103 QEMU, 16/16 battery, 69/69 host — zero regressions.
Tulio A M Mendes [Sun, 19 Apr 2026 18:38:13 +0000 (15:38 -0300)]
POSIX compliance audit: fix syscall wrappers, headers, and commands
ulibc fixes:
- mkdir(): change from variadic to (path, mode_t) per POSIX
- stat/fstat/fstatat: use struct stat* instead of void*
- wait4: use pid_t return and struct rusage* parameter
- waitid: use siginfo_t* instead of void*
- lseek: return off_t instead of int
- pread/pwrite: return ssize_t, use off_t offset
- truncate/ftruncate: use off_t length
- chmod: use mode_t instead of int
- open(): extract mode from va_args (was silently ignored)
- openat(): extract mode and pass as 4th syscall arg
- Add pivot_root() wrapper (syscall 120 existed but had no wrapper)
- Add lstat() (delegates to stat, no symlinks in AdrOS)
- Add fchmod() stub (ENOSYS, no kernel syscall yet)
Tulio A M Mendes [Sun, 19 Apr 2026 18:23:01 +0000 (15:23 -0300)]
Fix mount/umount commands: use proper syscall wrappers and error messages
mount.c: Replace direct _syscall3 call with mount() wrapper from
sys/mount.h. Print strerror(errno) instead of raw -1 return value
from __syscall_ret, which was always -1 and unhelpful for debugging.
umount.c: Replace stub that always printed 'operation not supported'
with a working implementation using the umount() wrapper from
sys/mount.h. The SYS_UMOUNT2 syscall existed in the kernel but the
umount command never used it.
Tulio A M Mendes [Sun, 19 Apr 2026 18:12:28 +0000 (15:12 -0300)]
Complete syscall coverage in ulibc and newlib wrappers
Audit of all 141 kernel syscalls vs ulibc/newlib wrappers revealed
many missing implementations. Both libraries had syscall numbers
defined but no corresponding POSIX wrapper functions.
ulibc header updates:
- unistd.h: declarations for all new functions
- signal.h: union sigval, sigqueue, sigreturn declarations
- sys/mount.h: new header with mount/umount2/umount + flags
- Removed old rename() stub from stdio.c
Tulio A M Mendes [Sun, 19 Apr 2026 17:55:59 +0000 (14:55 -0300)]
Fix getcwd(NULL, 0): allocate buffer when buf is NULL (glibc extension)
Bash calls getcwd(NULL, 0) — a glibc extension where NULL means
"allocate a buffer for me". Both ulibc and newlib wrappers passed
NULL straight to the kernel syscall, which returned -EFAULT because
the kernel rejects user_buf == NULL.
This caused:
shell-init: error retrieving current directory: getcwd:
cannot access parent directories: Bad address
Now both wrappers handle NULL buf by malloc'ing a buffer (4096 bytes
if size is 0), passing it to the kernel, and returning the allocated
pointer on success (or freeing it on error). This matches glibc
behavior and unblocks bash and other ported software.
Tulio A M Mendes [Sun, 19 Apr 2026 17:38:54 +0000 (14:38 -0300)]
Fix getcwd: return char* (POSIX) instead of int
ulibc getcwd() returned int (0 on success, -1 on error) but POSIX
specifies char* (buf on success, NULL on error). Bash and other
ported software call getcwd expecting a pointer return; getting 0
(NULL) on success caused:
shell-init: error retrieving current directory: getcwd:
cannot access parent directories: Bad address
The kernel syscall already returns 0 on success, so the fix is in
the ulibc wrapper: return buf on success, NULL on error (setting
errno). This matches the newlib wrapper in posix_stubs.c which had
the correct signature all along.
Updated all internal callers (sh, pwd, realpath) from int-style
checks (>= 0 / < 0) to pointer-style checks (truthy / !ptr).
Tulio A M Mendes [Sun, 19 Apr 2026 01:25:23 +0000 (22:25 -0300)]
Fix initrd LZ4 OOM: use page-level allocation instead of kmalloc
initrd_init used kmalloc() to allocate the decompression buffer for
LZ4-compressed initrd images. The kernel buddy heap is only 8 MB, so
a 4 MB allocation (the default content size) can easily fail due to
fragmentation from earlier allocations, producing:
This was especially likely with custom initrd images compressed with
lz4 -1 or -9, which can declare larger content sizes.
Replace kmalloc(orig_sz) with initrd_alloc_pages(), which allocates
individual physical pages via pmm_alloc_page() and maps them into a
dedicated virtual region (0xD0800000, above the 8MB heap). This
bypasses the buddy allocator entirely, using the physical memory
manager directly. On OOM, already-mapped pages are rolled back.
Tulio A M Mendes [Sun, 19 Apr 2026 00:17:24 +0000 (21:17 -0300)]
Fix FAT/ext2 heap corruption: skip kfree on static root nodes
fat_close_impl and ext2_close_impl unconditionally called kfree() on
the node passed to vfs_close(). When a directory fd pointed to a
filesystem mount root (a static variable like g_fat_root or
g_ext2_root), this kfree corrupted the heap (bad magic 0x0).
diskfs_close_impl already had the guard pattern (dn == &g_root).
Apply the same guard to fat and ext2 close handlers.
Tulio A M Mendes [Fri, 17 Apr 2026 21:46:32 +0000 (18:46 -0300)]
Fix job control TIOCSPGRP: allow session leader to claim TTY/PTY
When fulltest runs from the shell, the TTY's tty_session_id is already
set to the shell's session. The test's leader child calls setsid() then
TIOCSPGRP, which fails with EPERM because the new session doesn't match
tty_session_id. The kernel never allowed a new session leader to claim
the TTY as its controlling terminal.
Fix: in TIOCSPGRP handler for both tty.c and pty.c, allow a session
leader (session_id == pid) to claim the terminal by updating the
session when it explicitly sets its own pgrp as foreground. This
matches POSIX semantics where a session leader may establish a new
controlling terminal.
Test results: 103/103 smoke test PASS, 16/16 battery PASS.
Tulio A M Mendes [Fri, 17 Apr 2026 21:08:02 +0000 (18:08 -0300)]
diskfs: fix LBA space leak on unlink/rmdir + add spinlock protection + ATA DMA irqsave
Root cause: diskfs_unlink and diskfs_rmdir zeroed freed inodes but
never reclaimed LBA space -- next_free_lba only grew, never shrank.
After many test runs, next_free_lba exceeded the 8192-sector disk,
causing ata_pio_read28 to fail with -EIO on out-of-range LBAs,
making writes to newly created files fail silently.
Fixes:
- Add diskfs_reclaim_space() that recalculates next_free_lba by
scanning all inodes for the highest used LBA extent. Called from
both diskfs_unlink and diskfs_rmdir after freeing an inode.
- Add g_diskfs_lock spinlock with irqsave/irqrestore variants
protecting all superblock load/modify/store operations across all
diskfs functions. All early returns properly unlock.
- Restructure diskfs_read_impl and diskfs_write_impl to cache inode
metadata under lock, then release before sector I/O, avoiding long
interrupt-disabled periods that could cause deadlocks/timeouts.
- Replace spin_lock/spin_unlock with spin_lock_irqsave/spin_unlock_
irqrestore in all 4 ATA DMA channel functions to prevent deadlocks
from timer interrupts during lock hold.
Tulio A M Mendes [Fri, 17 Apr 2026 17:01:34 +0000 (14:01 -0300)]
Fix fulltest TIOCGPGRP failure when run from shell
When fulltest runs from /bin/sh (rather than as init directly), the
shell has already claimed the controlling TTY via setsid(), setting
tty_fg_pgrp to a non-zero value. The test assumed fg == 0, which
only holds when running as PID 1 (session_id=0, pgrp_id=0).
Fix: compare TIOCGPGRP result against sys_getpgrp() instead of
hardcoded 0, use our own pgrp for TIOCSPGRP success test, and use
fg=-1 (EINVAL) instead of fg=1 for the expected-failure case.
Also rename 'hello from init.elf' to 'hello from fulltest.elf' in
both the test binary and the smoke test expectations.
Tulio A M Mendes [Fri, 17 Apr 2026 04:09:18 +0000 (01:09 -0300)]
fix(mm): munmap/brk page leaks — free physical frames on unmap, rollback on partial failure
1. munmap: vmm_unmap_page only clears PTEs without freeing physical
frames. For anonymous mmaps (shmid == -1), call vmm_virt_to_phys +
pmm_free_page before vmm_unmap_page. Device-backed/shared mappings
keep their frames (managed by their own subsystems).
2. brk shrink: same leak — free physical frames before unmapping when
the heap shrinks.
3. brk grow partial failure: if pmm_alloc_page fails mid-expansion,
rollback all pages already mapped in this call (unmap + free), then
return old heap_break. Previously these pages were leaked permanently.
4. mmap anonymous partial failure: same rollback pattern applied to
syscall_mmap_impl — if pmm_alloc_page fails mid-allocation, unmap
and free all pages already mapped before returning -ENOMEM.
All tests pass: 69/69 host + 103/103 QEMU, zero regressions.
Tulio A M Mendes [Fri, 17 Apr 2026 03:54:10 +0000 (00:54 -0300)]
fix: 3 residual bugs from round-3 audit
5. dlopen page leak: if pmm_alloc_page() fails mid-segment-load,
rollback all previously mapped pages (unmap + pmm_free_page)
instead of leaking them. Added vmm_virt_to_phys() API to
recover physical frames before unmapping.
6. CLONE_THREAD without CLONE_VM: Linux requires CLONE_THREAD to
imply CLONE_VM (threads share address space). Now returns
-EINVAL if CLONE_THREAD is set without CLONE_VM, preventing
unexpected behavior where a "thread" gets its own AS copy.
7. pipe_close SMP race: readers/writers decrement and conditional
kfree(ps) were unprotected by a lock. Added spinlock_t to
pipe_state and wrapped the critical section in
spin_lock_irqsave, preventing underflow/double-free when both
pipe ends are closed concurrently on different CPUs.
Tulio A M Mendes [Fri, 17 Apr 2026 03:16:43 +0000 (00:16 -0300)]
fix: 4 security/robustness bugs from audit
1. epoll: remove fd from epoll instances on close() — prevents
use-after-close where a reused fd number would monitor the
wrong file. Also auto-remove stale entries in epoll_wait.
2. clone: validate flags against CLONE_SUPPORTED_MASK — unknown
flags (CLONE_NEWPID, CLONE_NEWUSER, etc.) now return EINVAL
instead of being silently ignored.
3. futex: cleanup waiters on process exit — futex_waiters table
moved to file scope; futex_cleanup_process() called from
SYSCALL_EXIT prevents UAF when FUTEX_WAKE dereferences a
freed process pointer.
4. ksem: fix infinite spin when waiters array full — replace
schedule() spin-yield with process_sleep(1), and increase
KSEM_MAX_WAITERS/KCOND_MAX_WAITERS from 16 to 64.
Tulio A M Mendes [Thu, 16 Apr 2026 05:42:54 +0000 (02:42 -0300)]
fix(test): skip /persist/counter test when no disk is available
Instead of failing with sys_exit(1) when /persist/counter can't
be opened, skip the test with goto. This allows the fulltest to
continue running other tests in environments without persistent
storage.
Tulio A M Mendes [Thu, 16 Apr 2026 05:36:59 +0000 (02:36 -0300)]
cleanup(idt): remove debug page fault traces for PID 1
Removed debug kprintf statements that were added during the
execve/SIGSEGV debugging session: PDE/PTE dumps, CR3 comparison,
GOT dumps, and LAPIC ID traces. These are no longer needed now
that the root cause has been fixed.
Tulio A M Mendes [Thu, 16 Apr 2026 05:36:47 +0000 (02:36 -0300)]
fix(ulibc): add null-pointer guards in __libc_init_array/fini_array
In a PIC shared library build, linker-generated symbols like
__init_array_start, __fini_array_end, and _init may remain
SHN_UNDEF (value 0) when the library has no .init_array/.fini_array
sections. The GOT entries for these resolve to 0.
Added null-pointer checks using volatile pointers (to prevent the
compiler from assuming the address is always non-null) for all
init/fini array pointers and for _init/_fini function pointers.
This prevents crashes when these symbols are unresolved.
Tulio A M Mendes [Thu, 16 Apr 2026 05:36:15 +0000 (02:36 -0300)]
fix(elf): skip SHN_UNDEF symbols in GLOB_DAT/32 relocations
The kernel's elf32_process_relocations incorrectly resolved R_386_GLOB_DAT
relocations for SHN_UNDEF symbols (st_shndx == 0) by setting the GOT
entry to base_offset. For libc.so loaded at 0x11000000, this meant
symbols like __init_array_start, __fini_array_end, and _init were
resolved to 0x11000000 (the ELF header), causing __libc_init_array
to execute garbage code at the ELF header and crash with SIGSEGV
at eip=0x11000012.
Now, SHN_UNDEF symbols are set to 0 in the GOT, leaving them for
the dynamic linker to resolve. Defined symbols (st_shndx != 0)
continue to be resolved as sym->st_value + base_offset.
Also removed debug kprintf from elf32_load_needed_libs.
Tulio A M Mendes [Thu, 16 Apr 2026 05:35:55 +0000 (02:35 -0300)]
fix(ld.so): use correct AdrOS syscall numbers instead of Linux numbers
dbg_write() was using Linux syscall number 4 (write) in EAX, but
AdrOS's SYSCALL_WRITE is 1. This caused dbg_write to call open()
instead of write(), so ld.so debug output never appeared.
Also fixed the inline asm to properly declare EAX as an output
operand (int $0x80 returns the result in EAX), preventing potential
compiler misoptimization.
Removed all debug prints from _start_c and dl_fixup that were
added during debugging.
Tulio A M Mendes [Thu, 16 Apr 2026 05:35:36 +0000 (02:35 -0300)]
fix(execve): reset signal handlers and clear pending signals on execve
POSIX requires that execve resets all signal handlers to SIG_DFL
(except SIG_IGN) and clears pending signals. Without this, the
kernel could attempt to deliver a signal to a handler address in
the destroyed old address space, causing a crash.
Also removed debug trace code (ldso trace, write trace, exit trace,
PTE dumps, GOT dumps) that was added during debugging and was
interfering with test output.
- Replace plain integer 0 with NULL for pointer assignments, returns,
and comparisons across 24 source files (kernel, drivers, HAL, arch, net)
- Add missing <stddef.h> includes for NULL in keyboard.c and uart.c
- Make file-local symbols static in idt.c (idt, idtp, interrupt_handlers,
idt_set_gate, pic_remap, print_reg) and scheduler.c (ready_queue_tail)
- Add missing extern/prototype declarations to headers:
idt.h (isr_handler), syscall.h (syscall_handler),
process.h (ready_queue_head, sched_lock, sched_set_init_pid,
sched_assign_pid1, sched_ap_init), net.h (net_dhcp_start),
percpu.h (_percpu_gs_lut, percpu_get_ptr), smp.h (ap_sched_go,
ap_entry), arch_syscall.h (x86_sysenter_set_kernel_stack,
sysenter_init_ap), hal/usermode.h (x86_usermode_test_start),
hal/cpu.h (g_smap_enabled), ata_pio.h (ata_register_devfs),
boot_info.h (kernel_main)
- Add forward declarations for compiler ABI symbols in utils.c
- Include own headers in source files so sparse sees declarations
- Add lwIP/net include paths to SPARSE_FLAGS in Makefile
Verified: make check (0 warnings), make test-host (116/116 pass),
make test (102/103 pass, 1 pre-existing timeout)
fix(security): mq_receive_impl TOCTOU race — copy msg data under lock
Mirror the mq_send_impl fix: copy message data into a kernel buffer
while holding mq_lock, then copy_to_user after release. Without this,
another thread could overwrite the dequeued slot via mq_send before
the copy_to_user completes.
Found during deep re-scan. All tests pass (102/103, 1 pre-existing timeout).
fix(security): Red Team bug fixes + deep analysis hardening
Red Team report fixes (8 bugs):
- pmm_free_page: add rc==0 double-free guard with BUG log
- uaccess: move global state to per-CPU via GS segment (SMP safety)
- elf.c: add overflow checks for p_vaddr+base_offset and vaddr+p_memsz
- ksem_wait: retry loop on waiter-array-full instead of silent discard
- kstack: add free-slot recycling stack (256 entries) to prevent leak
- schedule(): fix fallback dequeue from active (not expired) after swap
- buddy_of(): add bounds check, kfree handles NULL return
- e1000: add compiler memory barriers to MMIO read/write helpers
Deep analysis fixes (4 additional bugs):
- readdir: fix strcpy buffer overflow d_name[24] from name[128] in
tmpfs, devfs, initrd (use strncpy + null termination)
- mq_send_impl: fix TOCTOU race — copy user data to kbuf before lock
- dlopen: add 32-bit overflow checks for p_vaddr+base and vaddr+p_memsz
- tmpfs/overlayfs: replace unbounded strcpy into vfs.name[128] and
symlink_target[128] with strncpy
Regression fix:
- percpu: add self-pointer as first field of percpu_data so that
percpu_get() returns a valid pointer (was returning cpu_index=0 as
NULL pointer on BSP, causing triple fault)
- Add new section 2 'Getting the Source' with git clone --recursive
and fallback 'git submodule update --init --recursive' instructions.
- Note that the build system auto-initializes submodules and applies
patches if not done manually.
- Fix DOOM section: reference submodule init instead of manual git
clone; simplify to 'make doom'.
- Renumber sections 3-8 to accommodate the new section.
feat(toolchain): dual-libc support — ulibc default, Newlib optional
Redesign the toolchain build to safely support both ulibc (dynamic
linking) and Newlib (static linking) with a safe-by-default approach:
Architecture:
- adros.h now has Newlib-safe defaults (LIB_SPEC with -ladros, no
dynamic-linker in LINK_SPEC) so a fresh 'git clone' + 'build.sh'
always produces a working toolchain even without prior 'make iso'.
- Step 4b: generates specs from dumpspecs (Newlib-compatible).
- Step 4c: if ulibc build artifacts exist, re-patches specs for ulibc
(removes -ladros, adds -dynamic-linker /lib/ld.so).
- Step 4d: creates newlib.specs + i686-adros-gcc-newlib wrapper for
optional Newlib builds.
Bug fixes for fresh-clone reproducibility:
- Newlib build skip check changed from libc.a (overwritten by ulibc)
to a .built marker file.
- Newlib headers/libs backed up unconditionally (before ulibc check)
so newlib.specs always has valid include/newlib/ directory.
- Bash cross-compilation now uses newlib.specs explicitly (Bash needs
Newlib's full POSIX libc: regex, locale, wchar).
- Wrapper script uses expanded paths instead of hardcoded GCC version.
Usage:
i686-adros-gcc -o out in.c # ulibc dynamic (default)
i686-adros-gcc -static -o out in.c # ulibc static
i686-adros-gcc-newlib -o out in.c # Newlib static
feat(ulibc): add missing fdopen() and fileno() to stdio
Add POSIX fdopen() and fileno() implementations required by
popen/pclose and other stdio operations:
- fdopen(): wraps an existing file descriptor into a FILE*
- fileno(): returns the file descriptor from a FILE*
feat(ulibc): add __libc_init_array/__libc_fini_array and update crt0.S
Add constructor/destructor support to ulibc:
- New file init_fini.c: implements __libc_init_array() to run .preinit_array
and .init_array global constructors, and __libc_fini_array() to run
.fini_array global destructors.
- Updated crt0.S: calls __libc_init_array before main and __libc_fini_array
after main returns (matching Newlib crt0 behavior). Also uses 'environ'
symbol (POSIX) and adds .note.GNU-stack for NX compatibility.
fix(ulibc): rename __environ to environ for POSIX compliance
Replace __environ with environ as the primary environment pointer
symbol throughout ulibc and userland commands. POSIX specifies
'environ' as the standard name (IEEE Std 1003.1).
Files changed:
- user/ulibc/src/stdlib.c: environ as primary symbol, update getenv()
- user/ulibc/src/environ.c: all references updated
- user/ulibc/src/execvp.c: pass environ to execve()
- user/ulibc/include/unistd.h: declare extern environ
- user/cmds/env/env.c: use environ
- user/cmds/printenv/printenv.c: use environ
- Move AdrOS sysroot headers installation to AFTER Newlib install so
Newlib's make install doesn't overwrite our custom sys/dirent.h
(fixes DIR unknown type error on first build)
- Move Newlib header patching (sys/stat.h, sys/signal.h, sys/wait.h,
glob.h) to after Newlib install (fixes sed on non-existent files)
- Keep libcody in GCC host_libs — required by cc1plus (C++ frontend)
- Add all-libcody to step 4 make targets
- Move specs file creation to after step 4 (GCC full install overwrites it)
- Add .note.GNU-stack to crti-adros.S (fixes executable stack warning)
- Fix *.h glob in libgloss/adros cp (no .h files exist)
- Add toolchain/build/ and toolchain/logs/ to .gitignore
- Update README.md: clarify patches/ are reference diffs, add native-toolchain.patch
Tulio A M Mendes [Sat, 14 Mar 2026 16:14:55 +0000 (13:14 -0300)]
fix: doom build + move userspace build output to build/$ARCH/user/
- Fix doom.elf build: force i686-elf toolchain (CC/LD := instead of ?=),
add --unresolved-symbols=ignore-in-shared-libs for libc.so undefined refs
- Reorganize userspace build output from build/user/ to build/$ARCH/user/
(e.g. build/x86/user/) so all arch artifacts live under one directory
- Sub-Makefiles (common.mk, fulltest, ldso, pie_test) now accept BUILDDIR
override from root Makefile via ?= operator
- Root Makefile passes BUILDDIR=$(CURDIR)/$(USER_BUILD)/cmds/<name> to
all sub-Make invocations
Tulio A M Mendes [Sat, 14 Mar 2026 15:40:11 +0000 (12:40 -0300)]
refactor: reorganize userland into user/cmds/<name>/ with per-program Makefiles
- Move 53 user commands from user/<name>.c to user/cmds/<name>/<name>.c
- Add user/cmds/common.mk shared build rules for dynamically-linked commands
- Add per-program Makefiles for all commands (including fulltest, ldso, pie_test)
- Build all .o/.elf into build/user/cmds/<name>/ (out-of-tree)
- Replace [init] test prefix with [test] in fulltest.c, pie_main.c, test scripts
- Fix find.c and which.c: use opendir/readdir/closedir instead of raw getdents
- Fix ulibc glob.c missing stdio.h include
- Fix ulibc -Wno-incompatible-pointer-types for GCC 14+
- Fix test_host_utils.sh which test set -e issue
- Doom rootfs path changed to /usr/games/doom
- make clean now also cleans ulibc and doom in-tree artifacts
Tulio A M Mendes [Sat, 14 Mar 2026 13:23:54 +0000 (10:23 -0300)]
feat: full SMP scheduling — AP tick accounting, IPI wakeups, load balancing
Scheduler changes (src/kernel/scheduler.c):
- sched_ap_tick(): per-CPU tick accounting for APs (utime, ITIMER_VIRTUAL,
ITIMER_PROF) — previously only BSP tracked CPU time
- sched_load_balance(): periodic work stealing — migrates one process from
busiest to idlest CPU when imbalance >= 2 (avoids ping-pong)
- IPI resched after sleep wakeups: process_wake_check() now sends IPI to
remote CPUs that received newly-ready processes from the sleep queue
- IPI resched after parent wakeup: process_exit_notify() sends IPI when
waking a parent blocked in waitpid on a different CPU
- Load counter (sched_pcpu_inc_load) added to wakeup paths that were
missing it (exit_notify parent wake, sleep queue wake)
Timer changes:
- src/drivers/timer.c: hal_tick_bridge() now calls sched_ap_tick() on APs
and sched_load_balance() on BSP every 10 ticks (~200ms at 50Hz)
- src/hal/x86/timer.c: APs now go through hal_tick_bridge instead of
calling bare schedule(), enabling proper AP tick accounting
- Uses percpu_cpu_index() (GS segment read) instead of smp_current_cpu()
(LAPIC ID linear scan) for faster CPU identification
Tests:
- New SMP parallel fork smoke test: forks 8 children with busy loops,
verifies all complete with correct exit status (exercises multi-CPU
scheduling, IPI wakeups, and load balancing)
- 102/102 smoke tests pass, cppcheck clean, 64/64 host tests pass
Tulio A M Mendes [Sat, 14 Mar 2026 09:11:24 +0000 (06:11 -0300)]
refactor: move sysroot compat headers into project + update build.sh
All 71 Linux/POSIX compatibility headers that were previously created
directly in /opt/adros-toolchain/i686-adros/include/ are now stored
in newlib/sysroot_headers/ and installed by build.sh via cp -r.
Tulio A M Mendes [Sat, 14 Mar 2026 03:26:36 +0000 (00:26 -0300)]
fix: newlib crt0.o build error and gcc-full c++tools install failure
Two fixes to toolchain/build.sh:
1. Newlib: added have_crt0="no" to the adros entry in configure.host.
Newlib's build system expected crt0.o from libc/sys/adros/, but our
crt0 is provided by libgloss/adros. Setting have_crt0=no tells newlib
to skip the crt0.o copy. Also replaced the old Makefile.am/configure.in
(recursive autotools, wrong for newlib 4.4.0) with a minimal Makefile.inc
(non-recursive build system).
2. GCC full rebuild: use explicit targets (all-gcc all-target-libgcc,
install-gcc install-target-libgcc) instead of bare 'make install'.
The bare install fails on c++tools/g++-mapper-server which is not
needed for cross-compilation and doesn't build for freestanding
targets.
Verified:
- i686-adros-gcc 13.2.0 and i686-adros-g++ 13.2.0 both work
- C and C++ cross-compilation succeeds
- newlib libc.a and libm.a installed
- 100/100 smoke tests pass (10 sec)
Tulio A M Mendes [Sat, 14 Mar 2026 02:00:57 +0000 (23:00 -0300)]
fix: rewrite toolchain patching to use sed instead of fragile unified diffs
The original patch files (binutils-adros.patch, gcc-adros.patch,
newlib-adros.patch) used hardcoded line numbers that didn't match the
actual source tarballs (binutils 2.42, gcc 13.2.0, newlib 4.4.0),
causing config.sub and ld/configure.tgt hunks to fail.
Replaced the entire patching approach with pattern-based sed commands
that match unique context strings instead of line numbers. This is
robust against upstream line shifts across versions.
Changes to toolchain/build.sh:
- patch_config_sub(): inserts adros case before pikeos*) and adds
adros* to the OS validation list next to dicos*
- patch_binutils(): sed-patches bfd/config.bfd, gas/configure.tgt,
ld/configure.tgt using pattern anchors
- patch_gcc(): sed-patches gcc/config.gcc, libgcc/config.host;
creates adros.h, crti-adros.S, crtn-adros.S inline
- patch_newlib(): sed-patches newlib/configure.host, sys/config.h,
libgloss/configure.ac; creates sys/adros/ and libgloss/adros/
files inline
Phase 5 — Proper malloc with free():
- Replaced bump allocator with address-ordered free-list allocator
- 8-byte aligned blocks with 8-byte header (size | used_bit, next_free)
- First-fit allocation with block splitting
- free() with address-ordered insertion and bidirectional coalescing
- realloc() now preserves old data size from block header
Tulio A M Mendes [Sat, 14 Mar 2026 00:18:08 +0000 (21:18 -0300)]
feat: gettimeofday + mprotect syscalls + Newlib libgloss port
Kernel:
- Add SYSCALL_GETTIMEOFDAY (127): returns RTC epoch seconds + TSC-derived
microseconds via struct timeval. Timezone arg ignored per POSIX.
- Add SYSCALL_MPROTECT (128): changes page protection on heap, mmap, and
stack regions. Converts POSIX PROT_READ/WRITE/EXEC to VMM flags and
calls vmm_protect_range(). Validates ownership before modifying PTEs.
ulibc:
- Add gettimeofday() wrapper in sys/time.h + time.c
- Add mprotect() wrapper in sys/mman.h + mman.c
- Add SYS_GETTIMEOFDAY/SYS_MPROTECT to ulibc syscall.h
Newlib port (newlib/):
- newlib/libgloss/adros/crt0.S: C runtime startup for AdrOS, calls
__libc_init_array/__libc_fini_array for Newlib/C++ ctor/dtor support
- newlib/libgloss/adros/syscalls.c: all 21 Newlib-required OS stubs
(_exit, _read, _write, _open, _close, _lseek, _fstat, _stat, _isatty,
_kill, _getpid, _sbrk, _link, _unlink, _fork, _execve, _wait, _times,
_gettimeofday, _rename, _mkdir) implemented via INT 0x80
- newlib/libgloss/adros/Makefile: builds crt0.o + libadros.a
- newlib/README.md: build instructions for full Newlib cross-compilation
- newlib/patches/README.md: documents config.sub, configure.host, and
libgloss/configure.in changes needed in Newlib source tree
All 21 Newlib libgloss stubs are now implemented. To build Newlib:
1. Copy libgloss/adros/ into Newlib source tree
2. Add i686-*-adros* target to configure files
3. Build with: ../configure --target=i686-adros && make
Tulio A M Mendes [Sat, 14 Mar 2026 00:02:44 +0000 (21:02 -0300)]
docs: full POSIX/Unix audit + fix git-clone breakage with submodules
- Add docs/FULL_POSIX_AUDIT.md: comprehensive analysis of POSIX gaps,
build system issues, and porting requirements for Newlib/GCC/Binutils/
Bash/Busybox
- Convert lwIP and doomgeneric from untracked nested repos to proper
git submodules (.gitmodules) so git clone --recursive works
- Update .gitignore: remove third_party/ and user/doom/doomgeneric
exclusions now that they are tracked as submodules
Build system was broken after git clone: lwIP (required) and doomgeneric
(optional) directories were empty because they were gitignored nested
repos with no .gitmodules. Now users can:
git clone --recursive <url>
make CROSS=1 && make iso
Tulio A M Mendes [Fri, 13 Mar 2026 23:45:00 +0000 (20:45 -0300)]
fix: build system cleanup — replace non-freestanding <string.h> with utils.h, parameterize toolchain, fix header conflicts
- Kernel: replace #include <string.h> (not freestanding) with #include "utils.h"
in src/arch/x86/fpu.c, src/kernel/fpu.c, src/rump/rumpuser_adros.c
- Makefile: define USER_CC/USER_LD/USER_AR variables for userspace cross-compilation,
replacing all hardcoded i686-elf-gcc/ld references in FULLTEST, DYN_CC, DYN_LD,
LDSO, PIE_SO, PIE_ELF rules
- Makefile: pass USER_CC/USER_LD/USER_AR to ulibc sub-make for consistent toolchain
- Makefile: add missing .PHONY targets (test-battery, run-arm, run-riscv, run-mips)
- ulibc Makefile: use ?= for CC/AS/AR/LD so parent can override
- doom Makefile: use ?= for CC/LD so parent can override
- ulibc: remove duplicate stat/fstat declarations from unistd.h (conflicted with
sys/stat.h when both included); proper declarations remain in sys/stat.h per POSIX
Tulio A M Mendes [Tue, 17 Feb 2026 08:01:55 +0000 (05:01 -0300)]
feat: /dev/hdX block device nodes and /proc/dmesg
1. ATA block devices in devfs:
Detected ATA drives (hda, hdb, hdc, hdd) are now registered as
block device nodes in /dev via ata_register_devfs(). Each node
supports read/write at byte offsets (with sector-aligned I/O
internally). Previously, drives were detected but invisible
in /dev, so 'ls /dev' showed no disk devices and mount had to
use ata_name_to_drive() internally.
2. /proc/dmesg:
Added /proc/dmesg to procfs that reads the kprintf ring buffer
via klog_read(). Uses heap allocation (16KB) to avoid kernel
stack overflow. The 'dmesg' command now works correctly.
Tulio A M Mendes [Tue, 17 Feb 2026 07:51:33 +0000 (04:51 -0300)]
feat: ALT key support in PS/2 keyboard driver
Added alt_held state tracking for Left ALT (scancode 0x38/0xB8)
and Right ALT (E0 0x38/0xB8). When ALT is held, the driver
emits ESC (0x1B) prefix before the character, which is the
standard terminal encoding for Alt+key combinations.
Also handles Right CTRL (E0 0x1D/0x9D) which was previously
ignored since it uses E0-prefixed scancodes.
Summary of modifier keys now supported:
- Shift (L/R): uppercase and symbol characters
- CTRL (L/R): control characters (c & 0x1F)
- ALT (L/R): ESC prefix + character
Tulio A M Mendes [Tue, 17 Feb 2026 07:47:48 +0000 (04:47 -0300)]
fix: mount command -t option argument parsing
The mount command parsed device=argv[1] and mountpoint=argv[2]
BEFORE scanning for -t, so 'mount -t fat /dev/hdb /test' set
device='-t' and mountpoint='fat' instead of the correct values.
Rewrote argument parsing to scan for -t first, then collect
remaining positional arguments as device and mountpoint.
Added usage message for missing arguments.
Tulio A M Mendes [Tue, 17 Feb 2026 07:45:06 +0000 (04:45 -0300)]
fix: keyboard CTRL key support for CTRL+C/CTRL+Z in shell
Root cause: The PS/2 keyboard driver had no CTRL key tracking.
When CTRL was held and a letter pressed, the driver sent the
plain letter character (e.g., 'c') instead of the control
character (e.g., 0x03 for CTRL+C).
Fix: Added ctrl_held state tracking in hal/x86/keyboard.c for
Left CTRL (scancode 0x1D press / 0x9D release). When CTRL is
held and a letter key is pressed, emit (c & 0x1F) — the
standard control character encoding:
CTRL+C = 0x03, CTRL+Z = 0x1A, CTRL+D = 0x04, etc.
Shell read_line_edit() already handled these bytes correctly:
- 0x03 (CTRL+C): prints ^C, newline, cancels current line
- 0x1A (CTRL+Z): ignored at prompt (no foreground job)
- 0x04 (CTRL+D): EOF on empty line
During command execution, tty_restore() re-enables ISIG, so
the TTY kernel driver intercepts CTRL+C/CTRL+Z and sends
SIGINT/SIGTSTP to the foreground process group via TIOCSPGRP.
Tulio A M Mendes [Tue, 17 Feb 2026 07:30:53 +0000 (04:30 -0300)]
fix: CTRL+C/CTRL+Z job control and doom build errors
1. CTRL+C/CTRL+Z: Shell now calls setsid() instead of setpgid(0,0)
to create a proper session. This initializes tty_session_id so
TIOCSPGRP can actually set child processes as the foreground
group. Previously, TIOCSPGRP silently returned -EPERM because
tty_session_id was 0.
2. Doom mkdir: Added mkdir/stat/fstat/chmod declarations to
user/ulibc/include/sys/stat.h where POSIX expects them.
Doom's m_misc.c includes sys/stat.h for mkdir().
3. Doom __divdi3: Added libgcc.a to doom link step to provide
compiler runtime helpers for 64-bit arithmetic on i386.
Tulio A M Mendes [Tue, 17 Feb 2026 07:15:18 +0000 (04:15 -0300)]
feat: shell job control (&, &&, ||) and CTRL+C/CTRL+Z support
1. Background processes (&): trailing & forks without waiting, prints
[bg] PID. Works for simple commands and pipelines.
2. Command chaining (&&): executes next command only if previous
succeeded (exit status 0). Skips remaining && chain on failure
until a || or ; is found.
3. OR chaining (||): executes next command only if previous failed
(exit status != 0). Skips remaining || chain on success until
a && or ; is found.
4. CTRL+C / CTRL+Z: shell ignores SIGINT/SIGTSTP/SIGQUIT. Child
processes get their own process group (setpgid) and are set as
the foreground group (TIOCSPGRP). CTRL+C sends SIGINT only to
the child, not the shell. After child exits, shell restores
itself as foreground group.
New files:
- user/ulibc/include/sys/wait.h: WIFEXITED/WIFSIGNALED/etc macros
Modified:
- user/sh.c: process_line rewritten for ;/&&/||/& operators,
run_simple and run_pipeline use setpgid+TIOCSPGRP job control
- user/ulibc/include/termios.h: added TIOCSPGRP/TIOCGPGRP
Tulio A M Mendes [Tue, 17 Feb 2026 07:01:05 +0000 (04:01 -0300)]
fix: init PID 1, ls -l permissions/size, doom dynamic linking
1. Init process now gets PID 1 (like Linux): next_pid starts at 2,
sched_assign_pid1() explicitly assigns PID 1 to the init process
after it loads. Kernel threads and AP idles get PIDs 2+.
2. ls -l now shows permissions, nlink, size via stat() on each entry
instead of just the type character.
3. doom.elf Makefile switched from static linking (libulibc.a) to
dynamic linking (libc.so via ld.so) like all other user commands.
Tulio A M Mendes [Tue, 17 Feb 2026 06:50:42 +0000 (03:50 -0300)]
fix: diskfs kfree-on-static-root, mount syscall, user addr space 8MiB->1GiB
Bug 1: ls /disk heap corruption — diskfs_close_impl called kfree on
static g_root BSS variable. Added guard: skip kfree when node == g_root.
Bug 2: mount command only displayed mounts. Added SYSCALL_MOUNT (126)
with support for tmpfs and disk-based filesystems (diskfs/fat/ext2/persistfs).
Updated userspace mount to call the syscall with device, mountpoint, and
-t fstype args.
Bug 3: doom 'Unable to allocate 5 MiB' — user address space was capped
at 8 MiB (USER_STACK_BASE=0x00800000). Raised to 1 GiB (0x40000000) in
elf.c, usermode.c, and syscall_brk_impl.
- Fix echo: leading space when flags shift arg index
- Fix tail: off-by-one with trailing newline
- Fix tee/touch/cp/mv/dd: missing mode arg on open(O_CREAT)
- Fix ulibc open(): make variadic to accept optional mode
- Update smoke_test.exp with 8 new patterns (97 total)
- Add host utility tests to Makefile test-host target
Tulio A M Mendes [Tue, 17 Feb 2026 04:31:30 +0000 (01:31 -0300)]
fix: shell/command bugs, new utilities, procfs race condition
Shell fixes:
- Fix DELETE key showing ~ (handle \x1b[3~ escape sequence + Home/End)
- Fix builtin redirections (echo > file now works via saved fd restore)
- Fix initrd readdir (root cause of ls /bin empty + tab completion broken)
Command fixes:
- Fix cut -dX/-fN combined argument parsing (POSIX style)
- Fix ps showing ? for PIDs: add cmdline[128] to process struct, populate in execve + init
- Fix procfs race condition: use sched_lock for process list traversal
- Make sched_lock non-static for procfs access
New commands (22 total):
- Previous session: mount, umount, env, kill, sleep, clear, ps, df, free, tee, basename, dirname, rmdir
- This session: grep, id, uname, dmesg, printenv, tr, dd, pwd, stat
Arch contamination note: vdso.c includes arch/x86/kernel_va_map.h directly (acceptable for now, only x86 target)
Tulio A M Mendes [Tue, 17 Feb 2026 00:45:26 +0000 (21:45 -0300)]
fix: KVA_IOAPIC VA collision with BSS — move from 0xC0201000 to 0xC0401000
Root cause: multiboot_copy (64KB static buffer) starts at VA 0xC0200FE0,
spanning pages 0xC0200000-0xC0210000. KVA_IOAPIC at 0xC0201000 mapped
IOAPIC MMIO over the BSS page containing the multiboot2 cmdline tag data.
After arch_platform_setup, reading bi->cmdline returned IOAPIC register
data (zeros) instead of the original cmdline string.
Symptom: [CMDLINE] "" regardless of GRUB menu entry selected.
Classic Heisenbug — adding a debug kprintf before IOAPIC init read the
correct data, masking the corruption.
Fix: move KVA_IOAPIC to 0xC0401000 (next to LAPIC at 0xC0400000),
well past _end at 0xC0265728. Updated VA map comment to reflect
current BSS extent (~0xC0266000).
4 fixes for VirtualBox compatibility + 1 cosmetic:
1. UART hardware detection (fixes boot freeze with serial disabled)
- hal_uart_init() now probes the scratch register before configuring
- All UART operations (putc, drain_rx, poll_rx, try_getc) guarded
behind uart_present flag — prevents infinite loop on floating bus
- console_init() auto-enables VGA when no UART detected so boot
messages are visible
- Added hal_uart_is_present() API + stubs for ARM/MIPS/RISC-V
2. alarm/SIGALRM test: replace 20M-iteration busy-loop with nanosleep
polling (50ms × 40 = 2s max wait). Fast VirtualBox CPUs completed
the busy-loop before the 1-second alarm fired.
3. x86_enter_usermode: load DS/ES/FS/GS=0x23 before iret to ring 3.
Without this, iret nulls segment registers (kernel DPL=0 < new CPL=3
per Intel SDM §6.12.1). On QEMU this was masked by early context
switches that fixed DS via x86_enter_usermode_regs, but VirtualBox
with Hyper-V acceleration may expose the race window.
4. User-mode exception handling: deliver SIGSEGV for any ring-3
exception (#GP, #UD, etc.) instead of kernel panic. Previously only
#PF (14) had this handling. A user-mode #GP now kills the process
cleanly instead of halting the entire system.
5. LAPIC timer ticks printed in decimal instead of hex.
Kernel fix — SMP orphan reparenting:
- process_exit_notify() hardcoded parent_pid=1 for reparenting, but with
SMP the AP idle processes consume PIDs 1-3 before the init userspace
process is created (PID 4+).
- Added sched_set_init_pid() to register the actual init process PID.
- arch_platform.c calls sched_set_init_pid(current_process->pid) before
entering userspace, so orphan reparenting targets the correct process.
Bug 1 — Fork FD race (HIGH severity):
process_fork_create() enqueued the child to the runqueue under
sched_lock, but syscall_fork_impl() copied file descriptors AFTER
the function returned — with sched_lock released. On SMP, the child
could be scheduled on another CPU and reach userspace before FDs
were populated, seeing NULL file descriptors.
Fix: move FD copying (with refcount bumps) into process_fork_create()
itself, under sched_lock, before the child is enqueued. Added proper
rollback of refcount bumps if kstack_alloc fails.
Bug 2 — Orphaned zombie leak (MEDIUM severity):
When a process exited, its children were not reparented to PID 1
(init). Zombie children of exited parents could never be reaped via
waitpid, leaking process structs and kernel stacks forever.
Fix: in process_exit_notify(), iterate the process list and reparent
all children to PID 1. If any reparented child is already a zombie
and init is blocked in waitpid(-1), wake init immediately.
Also verified (no bugs found):
- EOI handling correct (sent before handlers, spurious skips EOI)
- Lock ordering safe (all locks use irqsave, no cross-CPU ABBA)
- Heap has double-free and corruption detection
- User stack has guard pages
Tulio A M Mendes [Mon, 16 Feb 2026 22:24:18 +0000 (19:24 -0300)]
feat: LZ4 official Frame format for initrd compression/decompression
Replace custom 'LZ4B' block wrapper with the official LZ4 Frame format
(spec: https://github.com/lz4/lz4/blob/dev/doc/lz4_Frame_format.md).
Compressor (tools/mkinitrd.c):
- Write official frame: magic 0x184D2204, FLG/BD descriptor with
content size and content checksum flags, xxHash-32 header checksum,
data block, EndMark, xxHash-32 content checksum
- Fix block compressor MFLIMIT: last match must start >= 12 bytes
before end of block (was 5, violating spec)
Tulio A M Mendes [Mon, 16 Feb 2026 22:08:36 +0000 (19:08 -0300)]
fix: PMM total_memory overflow — MMAP reserved regions near 4GB inflated highest_addr
Root cause: Multiboot2 MMAP includes a BIOS reserved region at
0xFFFC0000-0x100000000. The end address (0x100000000) overflows
uint32_t when stored in a uint64_t local variable, and (unsigned)
truncation yields 0 — hence '[PMM] total_memory bytes: 0x0'.
Fixes:
- Use uint32_t locals (32-bit x86 caps RAM at 512 MB anyway)
- Clamp MMAP end addresses to 0xFFFFFFFF before comparison
- Only track highest_avail from AVAILABLE regions, not reserved
- Use 'if' instead of 'else if' so both BASIC_MEMINFO and MMAP
are processed in the same pass
- Print total_memory and freed_frames in decimal with MB suffix
Tulio A M Mendes [Mon, 16 Feb 2026 21:08:55 +0000 (18:08 -0300)]
feat: SMP load balancing for fork/clone + IPI resched
Enable load balancing in process_fork_create and process_clone_create:
both now dispatch to the least-loaded CPU via sched_pcpu_least_loaded().
All three process creation functions (create_kernel, fork, clone) now
send IPI_RESCHED to the target CPU after releasing sched_lock, waking
idle APs immediately when work is enqueued to their runqueue.