From 41c684581ac2df9939139a72c1300183c6536bb9 Mon Sep 17 00:00:00 2001 From: Ryan Breen Date: Sat, 10 Jan 2026 17:07:50 -0500 Subject: [PATCH] fix(cow): prevent deallocation of untracked frames The CoW fork implementation was causing kernel panics during signal delivery after child processes terminated. The root cause was that frame_decref() returned true for untracked frames, causing them to be freed even when they might still be in use. The crash occurred at kernel address 0xffffc97ffffffff0 with RSP=0xffffc97fffffffe0, indicating memory corruption in the kernel stack region (PML4[402]). Fix: Change frame_decref() to return false for untracked frames. This causes a small memory leak for child-allocated pages but prevents potential corruption from freeing frames that may still be in use by kernel structures or other processes. The conservative approach is necessary because untracked frames include: 1. Frames allocated by child processes after fork (safe to free) 2. Frames that were never part of CoW sharing (unsafe to free) Since we cannot distinguish between these cases, we leak rather than risk corruption. Further investigation is needed to properly track all frames that are safe to deallocate. Co-Authored-By: Claude Opus 4.5 --- kernel/src/memory/frame_metadata.rs | 17 ++++++++++++++--- kernel/src/process/process.rs | 4 +++- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/kernel/src/memory/frame_metadata.rs b/kernel/src/memory/frame_metadata.rs index d93b6e5..8ff04a1 100644 --- a/kernel/src/memory/frame_metadata.rs +++ b/kernel/src/memory/frame_metadata.rs @@ -76,9 +76,20 @@ pub fn frame_decref(frame: PhysFrame) -> bool { // old_count > 1, still shared false } else { - // Frame wasn't tracked - this is the sole owner - // Return true to indicate it can be freed - true + // Frame wasn't tracked in CoW metadata. + // This could be: + // 1. A frame allocated by a child process after fork (could be freed) + // 2. A frame that was never part of CoW sharing (might be unsafe to free) + // + // SAFETY: Don't free untracked frames. This causes a memory leak for + // child-allocated pages, but prevents potential corruption if the frame + // is still in use by something else. The root cause needs further + // investigation. + log::trace!( + "frame_decref: frame {:#x} not tracked, refusing to free (leak to prevent corruption)", + addr + ); + false } } diff --git a/kernel/src/process/process.rs b/kernel/src/process/process.rs index 3d9a08e..e5ebd0b 100644 --- a/kernel/src/process/process.rs +++ b/kernel/src/process/process.rs @@ -291,7 +291,9 @@ impl Process { let frame = PhysFrame::containing_address(phys_addr); // Decrement reference count - // If this was the last reference, deallocate the frame + // If this was the last reference (frame was tracked and refcount reached 0), + // deallocate the frame. frame_decref returns false for untracked frames + // to prevent corruption from freeing potentially in-use frames. if frame_decref(frame) { deallocate_frame(frame); freed_count += 1;