From a92b0116721938d3bbdf2c99398402782da05f15 Mon Sep 17 00:00:00 2001 From: MichealFlick Date: Thu, 28 May 2026 08:54:57 +0200 Subject: [PATCH 01/12] Fix memory leak in readHandler - kernelBuff was never freed after read --- src/kernel/syscalls/io.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/kernel/syscalls/io.c b/src/kernel/syscalls/io.c index 4f5288d1..344e4540 100644 --- a/src/kernel/syscalls/io.c +++ b/src/kernel/syscalls/io.c @@ -30,6 +30,7 @@ size_t readHandler(OpenFile *fd, uint8_t *in, size_t limit) { // finalise uint32_t fr = currentTask->tmpRecV; memcpy(in, kernelBuff, fr); + free(kernelBuff); if (currentTask->term.c_lflag & ICANON && fr < limit) in[fr++] = '\n'; // only add newline if we can! From 06ced256eb5d61789ed4cd903955e853ff657882 Mon Sep 17 00:00:00 2001 From: MichealFlick Date: Thu, 28 May 2026 08:55:14 +0200 Subject: [PATCH 02/12] Fix buffer overflow in fsReadlink when size is negative --- src/kernel/filesystems/vfs/vfs_controller.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/kernel/filesystems/vfs/vfs_controller.c b/src/kernel/filesystems/vfs/vfs_controller.c index 950c084f..a51fbe8c 100644 --- a/src/kernel/filesystems/vfs/vfs_controller.c +++ b/src/kernel/filesystems/vfs/vfs_controller.c @@ -278,11 +278,13 @@ size_t fsUserSeek(void *task, uint32_t fd, int offset, int whence) { } size_t fsReadlink(void *task, char *path, char *buf, int size) { + if (size <= 0) + return ERR(EINVAL); if (strlength(path) == 14 && memcmp(path, "/proc/self/exe", 15) == 0 && currentTask->execname) { // todo: hack-y, needs to be done properly sometime! size_t total = strlength(currentTask->execname); - size_t toCopy = MIN(size, total); + size_t toCopy = MIN((size_t)size, total); memcpy(buf, currentTask->execname, toCopy); return toCopy; // memcpy(buf, "/usr/libexec/webkit2gtk-4.1/MiniBrowser", 40); From 32192a3c835efca92637ec5844c85266fc26b976 Mon Sep 17 00:00:00 2001 From: MichealFlick Date: Thu, 28 May 2026 08:55:53 +0200 Subject: [PATCH 03/12] Add double-free detection to PhysicalFree --- src/kernel/memory/pmm.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/kernel/memory/pmm.c b/src/kernel/memory/pmm.c index 7ea1d78a..153829e2 100644 --- a/src/kernel/memory/pmm.c +++ b/src/kernel/memory/pmm.c @@ -74,9 +74,14 @@ size_t PhysicalAllocate(int pages) { } void PhysicalFree(size_t ptr, int pages) { - // maybe verify no double-frees are occuring.. - spinlockAcquire(&LOCK_PMM); + for (int i = 0; i < pages; i++) { + size_t block = (ptr / BLOCK_SIZE) + i; + if (!BitmapGet(&physical, block)) { + debugf("[pmm] Double-free detected! ptr{%lx} block{%ld}\n", ptr, block); + panic(); + } + } MarkRegion(&physical, (void *)ptr, pages * BLOCK_SIZE, 0); spinlockRelease(&LOCK_PMM); } From 0e4fa4f01211b20e1dc6ddc79cc78441d506a072 Mon Sep 17 00:00:00 2001 From: MichealFlick Date: Thu, 28 May 2026 08:56:34 +0200 Subject: [PATCH 04/12] Fix sbrk: return (void*)-1 on failure instead of 0 --- src/kernel/memory/malloc_glue.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/kernel/memory/malloc_glue.c b/src/kernel/memory/malloc_glue.c index 09966a7a..9f25e997 100644 --- a/src/kernel/memory/malloc_glue.c +++ b/src/kernel/memory/malloc_glue.c @@ -5,13 +5,12 @@ #define DEBUG_DLMALLOC_GLUE 0 -void *last = 0; void *sbrk(long increment) { #if DEBUG_DLMALLOC_GLUE debugf("[dlmalloc::sbrk] size{%lx}\n", increment); #endif if (increment < 0) - return 0; // supposed to release, whatever. + return (void *)-1; // supposed to release, whatever. if (!increment) return last; // return 0; From 52a1cc88ffaa6a8f7ba8f5aac695ffe440b85c3e Mon Sep 17 00:00:00 2001 From: MichealFlick Date: Thu, 28 May 2026 08:57:15 +0200 Subject: [PATCH 05/12] Fix taskKill: yield to scheduler instead of busy-looping when current task is killed --- src/kernel/multitasking/task.c | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/kernel/multitasking/task.c b/src/kernel/multitasking/task.c index a1e14616..02c67fef 100644 --- a/src/kernel/multitasking/task.c +++ b/src/kernel/multitasking/task.c @@ -249,13 +249,9 @@ void taskKill(uint32_t id, uint16_t ret) { task->state = TASK_STATE_DEAD; if (currentTask == task) { - // we're most likely in a syscall context, so... - // taskKillCleanup(task); // left for sched asm volatile("sti"); - // wait until we're outta here - while (1) { - // debugf("GET ME OUT "); - } + while (1) + handControl(); } } From 17c6b1b04aad7d880eba2160148198877a0f53ef Mon Sep 17 00:00:00 2001 From: MichealFlick Date: Thu, 28 May 2026 08:57:20 +0200 Subject: [PATCH 06/12] Fix VirtualToPhysicalL: re-enable page table walk spinlocks to prevent TOCTOU races --- src/kernel/memory/paging.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/kernel/memory/paging.c b/src/kernel/memory/paging.c index 36f67b14..6ee1a481 100644 --- a/src/kernel/memory/paging.c +++ b/src/kernel/memory/paging.c @@ -249,7 +249,7 @@ size_t VirtualToPhysicalL(uint64_t *pagedir, size_t virt_addr) { uint32_t pd_index = PDE(virt_addr); uint32_t pt_index = PTE(virt_addr); - // spinlockCntReadAcquire(&WLOCK_PAGING); + spinlockCntReadAcquire(&WLOCK_PAGING); if (!(pagedir[pml4_index] & PF_PRESENT)) goto error; /*else if (pagedir[pml4_index] & PF_PRESENT && @@ -273,13 +273,13 @@ size_t VirtualToPhysicalL(uint64_t *pagedir, size_t virt_addr) { size_t *pt = (size_t *)(PTE_GET_ADDR(pd[pd_index]) + HHDMoffset); if (pt[pt_index] & PF_PRESENT) { - // spinlockCntReadRelease(&WLOCK_PAGING); + spinlockCntReadRelease(&WLOCK_PAGING); return (size_t)(PTE_GET_ADDR(pt[pt_index]) + ((size_t)virt_addr_init & 0xFFF)); } error: - // spinlockCntReadRelease(&WLOCK_PAGING); + spinlockCntReadRelease(&WLOCK_PAGING); return 0; } From d1948b0c75cad62953bc0fcf2852a431cd4cb31c Mon Sep 17 00:00:00 2001 From: MichealFlick Date: Thu, 28 May 2026 08:57:47 +0200 Subject: [PATCH 07/12] Fix PageDirectoryUserDuplicate TOCTOU race: hold write lock for entire operation and inline mappings instead of drop-and-reacquire --- src/kernel/memory/paging.c | 32 +++++++++++++++++++++++++++----- 1 file changed, 27 insertions(+), 5 deletions(-) diff --git a/src/kernel/memory/paging.c b/src/kernel/memory/paging.c index 6ee1a481..a441f6f7 100644 --- a/src/kernel/memory/paging.c +++ b/src/kernel/memory/paging.c @@ -348,7 +348,7 @@ void PageDirectoryFree(uint64_t *page_dir) { } void PageDirectoryUserDuplicate(uint64_t *source, uint64_t *target) { - spinlockCntReadAcquire(&WLOCK_PAGING); + spinlockCntWriteAcquire(&WLOCK_PAGING); for (int pml4_index = 0; pml4_index < 512; pml4_index++) { if (!(source[pml4_index] & PF_PRESENT) || source[pml4_index] & PF_PS) continue; @@ -384,13 +384,35 @@ void PageDirectoryUserDuplicate(uint64_t *source, uint64_t *target) { memcpy(ptrTarget, ptrSource, PAGE_SIZE); - spinlockCntReadRelease(&WLOCK_PAGING); - VirtualMapL(target, virt, physTarget, PF_RW | PF_USER); - spinlockCntReadAcquire(&WLOCK_PAGING); + // manually map into target (lock already held as write) + uint64_t tvirt = AMD64_MM_STRIPSX(virt); + uint32_t tpml4 = PML4E(tvirt); + uint32_t tpdp = PDPTE(tvirt); + uint32_t tpd = PDE(tvirt); + uint32_t tpt = PTE(tvirt); + + if (!(target[tpml4] & PF_PRESENT)) { + size_t targ = PagingPhysAllocate(); + target[tpml4] = targ | PF_PRESENT | PF_RW | PF_USER; + } + size_t *tpdpPT = (size_t *)(PTE_GET_ADDR(target[tpml4]) + HHDMoffset); + if (!(tpdpPT[tpdp] & PF_PRESENT)) { + size_t targ = PagingPhysAllocate(); + tpdpPT[tpdp] = targ | PF_PRESENT | PF_RW | PF_USER; + } + size_t *tpdPT = (size_t *)(PTE_GET_ADDR(tpdpPT[tpdp]) + HHDMoffset); + if (!(tpdPT[tpd] & PF_PRESENT)) { + size_t targ = PagingPhysAllocate(); + tpdPT[tpd] = targ | PF_PRESENT | PF_RW | PF_USER; + } + size_t *tptPT = (size_t *)(PTE_GET_ADDR(tpdPT[tpd]) + HHDMoffset); + tptPT[tpt] = (P_PHYS_ADDR(physTarget)) | PF_PRESENT | PF_RW | PF_USER; + + invalidate(virt); } } } } - spinlockCntReadRelease(&WLOCK_PAGING); + spinlockCntWriteRelease(&WLOCK_PAGING); } From 50aedb62c0d8018c51ab36e1d6747bab0d0ceed8 Mon Sep 17 00:00:00 2001 From: MichealFlick Date: Thu, 28 May 2026 08:57:58 +0200 Subject: [PATCH 08/12] Fix ext2Write memory leak: properly free old cache entries instead of just setting pointer to NULL --- src/kernel/filesystems/ext2/ext2_controller.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/kernel/filesystems/ext2/ext2_controller.c b/src/kernel/filesystems/ext2/ext2_controller.c index fd34971d..af61a1fd 100644 --- a/src/kernel/filesystems/ext2/ext2_controller.c +++ b/src/kernel/filesystems/ext2/ext2_controller.c @@ -398,8 +398,15 @@ size_t ext2Write(OpenFile *fd, uint8_t *buff, size_t limit) { ext2CachePush(ext2, dir); - // todo! memory leak! spinlockCntWriteAcquire(&dir->globalObject->WLOCK_CACHE); + Ext2CacheObject *cacheBrowse = dir->globalObject->firstCacheObj; + while (cacheBrowse) { + Ext2CacheObject *next = cacheBrowse->next; + VirtualFree(cacheBrowse->buff, + DivRoundUp((cacheBrowse->blocks + 1) * ext2->blockSize, BLOCK_SIZE)); + free(cacheBrowse); + cacheBrowse = next; + } dir->globalObject->firstCacheObj = 0; spinlockCntWriteRelease(&dir->globalObject->WLOCK_CACHE); From 0a0535f487490eeb5e4e44b0b9c29f392a01a4d0 Mon Sep 17 00:00:00 2001 From: MichealFlick Date: Thu, 28 May 2026 08:58:31 +0200 Subject: [PATCH 09/12] Fix taskInfoPdDiscard memory leak: actually free the struct and release spinlock when utilizedBy reaches 0. Reorder taskKill to set DEAD state before freeing page directory. --- src/kernel/multitasking/task.c | 6 ++---- src/kernel/multitasking/task_info.c | 7 ++----- 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/src/kernel/multitasking/task.c b/src/kernel/multitasking/task.c index 02c67fef..ecac1b87 100644 --- a/src/kernel/multitasking/task.c +++ b/src/kernel/multitasking/task.c @@ -239,14 +239,12 @@ void taskKill(uint32_t id, uint16_t ret) { // close any left open files taskInfoFilesDiscard(task->infoFiles, task); - // if (!parentVfork) - // PageDirectoryFree(task->pagedir); + task->state = TASK_STATE_DEAD; + taskInfoPdDiscard(task->infoPd); - // ^ only changes userspace locations so we don't need to change our pagedir // the "reaper" thread will finish everything in a safe context taskCallReaper(task); - task->state = TASK_STATE_DEAD; if (currentTask == task) { asm volatile("sti"); diff --git a/src/kernel/multitasking/task_info.c b/src/kernel/multitasking/task_info.c index 1f267079..7725c45b 100644 --- a/src/kernel/multitasking/task_info.c +++ b/src/kernel/multitasking/task_info.c @@ -85,11 +85,8 @@ void taskInfoPdDiscard(TaskInfoPagedir *target) { target->utilizedBy--; if (!target->utilizedBy) { PageDirectoryFree(target->pagedir); - // todo: find a safe way to free target - // cannot be done w/the current layout as it's done inside taskKill and the - // scheduler needs it in case it's switched in between (will point to - // invalid/unsafe memory). maybe with overrides but we'll see later when the - // system is more stable. + spinlockRelease(&target->LOCK_PD); + free(target); } else spinlockRelease(&target->LOCK_PD); } From 63f46359f5c743fd4952bf58f42bb7e0213660ee Mon Sep 17 00:00:00 2001 From: MichealFlick Date: Thu, 28 May 2026 08:59:07 +0200 Subject: [PATCH 10/12] Fix signal handling UB: cast 1 to sigset_t before shifting to avoid undefined behavior for signals >= 32 --- src/kernel/syscalls/signals.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/kernel/syscalls/signals.c b/src/kernel/syscalls/signals.c index 5f75b027..0e376682 100644 --- a/src/kernel/syscalls/signals.c +++ b/src/kernel/syscalls/signals.c @@ -164,7 +164,7 @@ int signalsPendingDecide(Task *task) { // find a single valid pending signal uint64_t pendingList = atomicBitmapGet(&task->sigPendingList); for (int i = 0; i < _NSIG; i++) { - if (pendingList & (1 << i) && !(task->sigBlockList & (1 << i))) + if (pendingList & ((sigset_t)1 << i) && !(task->sigBlockList & ((sigset_t)1 << i))) return i; } return -1; @@ -217,7 +217,7 @@ void signalsPendingHandleSys(void *taskPtr, uint64_t *rsp, // (also SA_NODEFER) sigset_t oldMask = task->sigBlockList; - task->sigBlockList |= (1 << signal) | atomicRead64(&action->sa_mask); + task->sigBlockList |= ((sigset_t)1 << signal) | atomicRead64(&action->sa_mask); atomicBitmapClear(&task->sigPendingList, signal); // now that it is blocked // establish a "safe" struct, that we could theoretically safely iretq to @@ -319,7 +319,7 @@ void signalsPendingHandleSched(void *taskPtr) { // (also SA_NODEFER) sigset_t oldMask = task->sigBlockList; - task->sigBlockList |= (1 << signal) | atomicRead64(&action->sa_mask); + task->sigBlockList |= ((sigset_t)1 << signal) | atomicRead64(&action->sa_mask); atomicBitmapClear(&task->sigPendingList, signal); // now that it is blocked AsmPassedInterrupt oldstate = {0}; @@ -441,7 +441,7 @@ size_t signalsSigreturnSyscall(void *taskPtr) { task->syscallRegs = 0; task->syscallRsp = 0; - task->sigBlockList = ucontext->oldmask & ~((1 << SIGKILL) | (1 << SIGSTOP)); + task->sigBlockList = ucontext->oldmask & ~(((sigset_t)1 << SIGKILL) | ((sigset_t)1 << SIGSTOP)); asm_finalize((size_t)iretqRsp, VirtualToPhysical((size_t)task->infoPd->pagedir)); From e4334fc6a702d422a7b7b8eb21ec72992ce8f37b Mon Sep 17 00:00:00 2001 From: MichealFlick Date: Thu, 28 May 2026 09:04:18 +0200 Subject: [PATCH 11/12] Fix ahciCmdIssue: add timeout to prevent infinite loop on hung commands --- src/kernel/drivers/ahci.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/kernel/drivers/ahci.c b/src/kernel/drivers/ahci.c index 15f637d7..0a3fe6ab 100644 --- a/src/kernel/drivers/ahci.c +++ b/src/kernel/drivers/ahci.c @@ -91,11 +91,16 @@ force_inline bool ahciCmdIssue(ahci *ahciPtr, HBA_PORT *port, int slot) { ahciPtr->cmdSlotsPreping &= ~(1 << slot); // Wait for completion + uint64_t deadline = timerTicks + 5000; while (1) { // In some longer duration reads, it may be helpful to spin on the DPS bit // in the PxIS port field as well (1 << 5) if ((port->ci & (1 << slot)) == 0) break; + if (timerTicks >= deadline) { + debugf("[ahci] Command timeout on slot %d!\n", slot); + return false; + } } return true; From 765deb3a6ea8f1ef00c1a6de723c51afd0525299 Mon Sep 17 00:00:00 2001 From: MichealFlick Date: Thu, 28 May 2026 09:05:41 +0200 Subject: [PATCH 12/12] Fix shebang execve: add recursion depth limit of 8 with ELOOP, convert to iterative loop, fix pre-existing buff leaks in error paths --- src/kernel/syscalls/linux/syscalls_proc.c | 148 +++++++++++++--------- 1 file changed, 86 insertions(+), 62 deletions(-) diff --git a/src/kernel/syscalls/linux/syscalls_proc.c b/src/kernel/syscalls/linux/syscalls_proc.c index 7984d37c..d9c5b77b 100644 --- a/src/kernel/syscalls/linux/syscalls_proc.c +++ b/src/kernel/syscalls/linux/syscalls_proc.c @@ -169,70 +169,94 @@ void freeArgvIfNeeded(char **argv) { } #define SYSCALL_EXECVE 59 -static size_t syscallExecve(char *filename, char **argv, char **envp) { - // todo: also check for leader & kill the whole thread group for this - assert(currentTask->id == currentTask->tgid); - - assert(argv[0]); // shebang support depends on it atm. check (dep) - dbgSysExtraf("filename{%s}", filename); - spinlockAcquire(¤tTask->infoFs->LOCK_FS); - char *filenameSanitized = fsSanitize(currentTask->infoFs->cwd, filename); - spinlockRelease(¤tTask->infoFs->LOCK_FS); - uint8_t *buff = calloc(256, 1); - - // do a read to check for alternatives, elfExecute() still does its checks - OpenFile *preScan = fsKernelOpen(filenameSanitized, O_RDONLY, 0); - if (!preScan) { - free(filenameSanitized); - return ERR(ENOENT); - } - size_t max = fsRead(preScan, buff, 255); // 1 less so there's always a \0 - fsKernelClose(preScan); - if (RET_IS_ERR(max)) { - free(filenameSanitized); - return max; - } - if (max > 2 && buff[0] == '#' && buff[1] == '!') { - // shebang detected! - char *arg2 = (char *)0; - int spaces = 0; - for (int i = 0; i < max; i++) { - if (buff[i] == ' ') { - if (spaces == 0) { - buff[i] = '\0'; // needs to be null terminated - arg2 = (char *)&buff[i + 1]; +#define SHEBANG_MAX_DEPTH 8 + +static size_t syscallExecve(char *filename, char **argvOriginal, char **envp) { + char **argv = argvOriginal; + int shebangDepth = 0; + char *filenameSanitized = NULL; + + while (1) { + // todo: also check for leader & kill the whole thread group for this + assert(currentTask->id == currentTask->tgid); + + assert(argv[0]); // shebang support depends on it atm. check (dep) + dbgSysExtraf("filename{%s}", filename); + spinlockAcquire(¤tTask->infoFs->LOCK_FS); + filenameSanitized = fsSanitize(currentTask->infoFs->cwd, filename); + spinlockRelease(¤tTask->infoFs->LOCK_FS); + uint8_t *buff = calloc(256, 1); + + OpenFile *preScan = fsKernelOpen(filenameSanitized, O_RDONLY, 0); + if (!preScan) { + free(buff); + free(filenameSanitized); + return ERR(ENOENT); + } + size_t max = fsRead(preScan, buff, 255); + fsKernelClose(preScan); + if (RET_IS_ERR(max)) { + free(buff); + free(filenameSanitized); + return max; + } + + if (max > 2 && buff[0] == '#' && buff[1] == '!') { + if (shebangDepth >= SHEBANG_MAX_DEPTH) { + free(buff); + free(filenameSanitized); + freeItemsIfNeeded(argv); + freeArgvIfNeeded(argv); + return ERR(ELOOP); + } + shebangDepth++; + + char *arg2 = (char *)0; + int spaces = 0; + for (int i = 0; i < max; i++) { + if (buff[i] == ' ') { + if (spaces == 0) { + buff[i] = '\0'; + arg2 = (char *)&buff[i + 1]; + } + spaces++; + } else if (buff[i] == '\n') { + buff[i] = '\0'; + break; } - spaces++; - } else if (buff[i] == '\n') { - buff[i] = '\0'; // for the last arg (if none, 256-255=1 calloc) - break; // no longer needed + don't risk above } + + int argc = 0; + while (argv[argc++]); + int amnt = arg2 ? 2 : 1; + char **injectedArgv = calloc((amnt + argc + 1) * sizeof(char *), 1); + memcpy(&injectedArgv[amnt], argv, argc * sizeof(char *)); + injectedArgv[amnt] = strdup(filename); + + freeArgvIfNeeded(argv); + + char *arg1 = (char *)&buff[2]; + if (arg2 && arg2[0] != '\0') + injectedArgv[1] = strdup(arg2); + injectedArgv[0] = strdup(arg1); + + free(buff); + free(filenameSanitized); + filename = injectedArgv[0]; + argv = injectedArgv; + continue; } - int argc = 0; - while (argv[argc++]) - ; // why not just pass argc. truly beyond me... - int amnt = arg2 ? 2 : 1; - char **injectedArgv = calloc((amnt + argc + 1) * sizeof(char *), 1); - memcpy(&injectedArgv[amnt], argv, argc * sizeof(char *)); - injectedArgv[amnt] = strdup(filename); // replace [0] w/original (dep) - - freeArgvIfNeeded(argv); // ! don't use argv anymore - - char *arg1 = (char *)&buff[2]; - if (arg2 && arg2[0] != '\0') - injectedArgv[1] = strdup(arg2); - injectedArgv[0] = strdup(arg1); - - free(buff); // ! don't use anything defined before after this - syscallExecve(injectedArgv[0], injectedArgv, envp); - assert(false); // won't return, injectedArgv & buff are above HHDM - } else if (max > 4 && buff[EI_MAG0] == ELFMAG0 && buff[EI_MAG1] == ELFMAG1 && - buff[EI_MAG2] == ELFMAG2 && buff[EI_MAG3] == ELFMAG3) { - // standard elf executable, go on - } else { - // both unnecessary, won't do anything w/them :^) - freeItemsIfNeeded(argv); // depends on the argv so NEEDS to be first + + if (max > 4 && buff[EI_MAG0] == ELFMAG0 && buff[EI_MAG1] == ELFMAG1 && + buff[EI_MAG2] == ELFMAG2 && buff[EI_MAG3] == ELFMAG3) { + free(buff); + break; + } + + free(buff); + free(filenameSanitized); + freeItemsIfNeeded(argv); freeArgvIfNeeded(argv); return ERR(ENOEXEC); } @@ -247,8 +271,8 @@ static size_t syscallExecve(char *filename, char **argv, char **envp) { free(arguments.valPlace); free(environment.ptrPlace); free(environment.valPlace); - freeItemsIfNeeded(argv); // depends on the argv so NEEDS to be first - freeArgvIfNeeded(argv); // ! don't use ANY argv after this + freeItemsIfNeeded(argv); + freeArgvIfNeeded(argv); if (!ret) return ERR(ENOENT);