SerenityOS / serenity

The Serenity Operating System 🐞
https://serenityos.org
BSD 2-Clause "Simplified" License
29.64k stars 3.15k forks source link

Kernel panic after excessive fork()s #24627

Open brody-qq opened 6 days ago

brody-qq commented 6 days ago

I got the following kernel panic when running a program that repeatedly fork()s new processes:

425.338 [#0 ResourceGraph.Applet(31:31)]: test-fork-crash(3262) resident:436, shared:436, virtual:3075
425.338 [#0 ResourceGraph.Applet(31:31)]: Shell(40) resident:825, shared:825, virtual:2656
425.338 [#0 ResourceGraph.Applet(31:31)]: LaunchServer(39) resident:1452, shared:1452, virtual:4717
425.338 [#0 ResourceGraph.Applet(31:31)]: ClipboardHistory.Applet(37) resident:1681, shared:1681, virtual:7263
425.338 [#0 ResourceGraph.Applet(31:31)]: Terminal(36) resident:4362, shared:4362, virtual:9964
425.338 [#0 ResourceGraph.Applet(31:31)]: Clipboard(35) resident:456, shared:456, virtual:2101
425.338 [#0 ResourceGraph.Applet(31:31)]: Taskbar(34) resident:2741, shared:2741, virtual:8747
425.338 [#0 ResourceGraph.Applet(31:31)]: CrashDaemon(33) resident:578, shared:578, virtual:2429
425.338 [#0 ResourceGraph.Applet(31:31)]: Audio.Applet(32) resident:1704, shared:1704, virtual:7446
425.338 [#0 ResourceGraph.Applet(31:31)]: ResourceGraph.Applet(31) resident:1654, shared:1654, virtual:7239
425.338 [#0 ResourceGraph.Applet(31:31)]: FileManager (Desktop)(30) resident:3863, shared:3863, virtual:9860
425.338 [#0 ResourceGraph.Applet(31:31)]: Keymap.Applet(29) resident:1648, shared:1648, virtual:7242
425.338 [#0 ResourceGraph.Applet(31:31)]: ConfigServer(28) resident:558, shared:558, virtual:2258
425.338 [#0 ResourceGraph.Applet(31:31)]: WorkspacePicker.Applet(27) resident:1641, shared:1641, virtual:7235
425.338 [#0 ResourceGraph.Applet(31:31)]: AudioServer(26) resident:490, shared:490, virtual:3190
425.338 [#0 ResourceGraph.Applet(31:31)]: Network.Applet(24) resident:1653, shared:1653, virtual:7251
425.338 [#0 ResourceGraph.Applet(31:31)]: SystemServer(23) resident:472, shared:472, virtual:2145
425.338 [#0 ResourceGraph.Applet(31:31)]: DHCPClient(22) resident:439, shared:439, virtual:2097
425.338 [#0 ResourceGraph.Applet(31:31)]: Clipboard(15) resident:454, shared:454, virtual:2101
425.338 [#0 ResourceGraph.Applet(31:31)]: LookupServer(14) resident:490, shared:490, virtual:2160
425.338 [#0 ResourceGraph.Applet(31:31)]: LoginServer(13) resident:1768, shared:1768, virtual:7347
425.338 [#0 ResourceGraph.Applet(31:31)]: WindowServer(12) resident:6390, shared:6390, virtual:11171
425.338 [#0 ResourceGraph.Applet(31:31)]: DeviceMapper(11) resident:436, shared:436, virtual:2053
425.338 [#0 ResourceGraph.Applet(31:31)]: Network Task(9) resident:0, shared:0, virtual:0
425.338 [#0 ResourceGraph.Applet(31:31)]: SystemServer(8) resident:466, shared:466, virtual:2129
425.338 [#0 ResourceGraph.Applet(31:31)]: UHCI Hot Plug Task(7) resident:0, shared:0, virtual:0
425.338 [#0 ResourceGraph.Applet(31:31)]: UHCI Async Poll Task(6) resident:0, shared:0, virtual:0
425.338 [#0 ResourceGraph.Applet(31:31)]: Finalizer Task(5) resident:0, shared:0, virtual:0
425.338 [#0 ResourceGraph.Applet(31:31)]: VFS Sync Task(4) resident:0, shared:0, virtual:0
425.338 [#0 ResourceGraph.Applet(31:31)]: ATA WorkQueue Task(3) resident:0, shared:0, virtual:0
425.338 [#0 ResourceGraph.Applet(31:31)]: IO WorkQueue Task(2) resident:0, shared:0, virtual:0
426.141 [#0 ResourceGraph.Applet(31:31)]: SysFS: Could not refresh contents: Error(errno=12)
426.141 [#0 ResourceGraph.Applet(31:31)]: MM: Unable to commit 1024 pages, have only 19
[Finalizer Task(5:5)]: ASSERTION FAILED: m_thread_did_exit
[Finalizer Task(5:5)]: ././Kernel/Tasks/Thread.h:1117 in void* Kernel::Thread::JoinBlockerSet::exit_value() const
[Finalizer Task(5:5)]: KERNEL PANIC! :^(
[Finalizer Task(5:5)]: Aborted
[Finalizer Task(5:5)]: at ./Kernel/Arch/x86_64/CPU.cpp:36 in void abort()
[Finalizer Task(5:5)]: Kernel + 0x000000000096412a  Kernel::__panic(char const*, unsigned int, char const*) +0x9a
[Finalizer Task(5:5)]: Kernel + 0x0000000000ea2d06  abort +0x22e
[Finalizer Task(5:5)]: Kernel + 0x0000000000ea2ad8  abort +0x0
[Finalizer Task(5:5)]: Kernel + 0x0000000000dd5d2f  bool Kernel::Thread::BlockerSet::unblock_all_blockers_whose_conditions_are_met_locked<Kernel::Thread::JoinBlockerSet::do_unblock_joiner()::{lambda(Kernel::Thread::Blocker&, void*, bool&)#1}>(Kernel::Thread::JoinBlockerSet::do_unblock_joiner()::{lambda(Kernel::Thread::Blocker&, void*, bool&)#1}) [clone .isra.0] +0x41f
[Finalizer Task(5:5)]: Kernel + 0x0000000000dfb8b8  Kernel::Thread::finalize() +0x228
[Finalizer Task(5:5)]: Kernel + 0x0000000000dfca09  Kernel::Thread::finalize_dying_threads() +0xaf9
[Finalizer Task(5:5)]: Kernel + 0x0000000000d4309b  Kernel::finalizer_task(void*) +0x1db

You can reproduce this by applying the following diff and running test-fork-crash from the shell. Note that the crash takes a minute or two to happen, and it doesn't happen 100% of the time you run the program.

diff --git a/Userland/Utilities/CMakeLists.txt b/Userland/Utilities/CMakeLists.txt
index 7f86cfee06..bb5f27b131 100644
--- a/Userland/Utilities/CMakeLists.txt
+++ b/Userland/Utilities/CMakeLists.txt
@@ -155,6 +155,7 @@ target_link_libraries(telws PRIVATE LibProtocol LibLine LibURL)
 target_link_libraries(test-imap PRIVATE LibIMAP)
 target_link_libraries(test-jpeg-roundtrip PRIVATE LibGfx)
 target_link_libraries(test-pthread PRIVATE LibThreading)
+target_link_libraries(test-fork-crash PRIVATE LibThreading)
 target_link_libraries(touch PRIVATE LibFileSystem)
 target_link_libraries(unzip PRIVATE LibArchive LibCompress LibCrypto LibFileSystem)
 target_link_libraries(update-cpp-test-results PRIVATE LibCpp)
diff --git a/Userland/Utilities/test-fork-crash.cpp b/Userland/Utilities/test-fork-crash.cpp
new file mode 100644
index 0000000000..1e68517606
--- /dev/null
+++ b/Userland/Utilities/test-fork-crash.cpp
@@ -0,0 +1,36 @@
+#include <AK/Assertions.h>
+#include <LibMain/Main.h>
+#include <LibThreading/Thread.h>
+#include <errno.h>
+#include <pthread.h>
+#include <semaphore.h>
+#include <unistd.h>
+#include <sys/mman.h>
+#include <sys/wait.h>
+
+ErrorOr<int> serenity_main(Main::Arguments)
+{
+    NonnullRefPtr<Threading::Thread> forker_thread = TRY(Threading::Thread::try_create([&]() {
+        while(true) {
+            usleep(100);
+            pid_t pid = fork();
+            printf("forked\n");
+            switch(pid) {
+            case -1:
+                perror(nullptr);
+                exit(1);
+            case 0:
+                usleep(100);
+                exit(0);
+            default:
+                break;
+            }
+        }
+        return 0;
+    }));
+
+    forker_thread->start();
+    auto result = forker_thread->join();
+
+    return 0;
+}
supercomputer7 commented 13 hours ago

Yeah, I remember writing a fork bomb test a year ago and the result was pretty much the same. We should obviously not crash because of such behavior, but hogging on system resources (PID is definitely one of them, but also each process allocates a small area in kernel space) should be handled with some smart OOM handling trickery and probably a security feature :) Therefore, limiting processes from spawning endless amount of other processes could be integrated in the upcoming containers feature I'm working on.