C语言fork炸弹如何防御?

99ANYc3cd6
预计阅读时长 13 分钟
位置: 首页 C语言 正文

在开始之前,必须强调,Fork Bomb 是一种极具破坏性的程序,它在任何现代操作系统上运行都会导致系统资源(主要是进程 ID 和内存)被迅速耗尽,最终使系统变得无响应,必须强制重启,请绝对不要在生产服务器、他人的计算机或任何你不拥有完全控制权的系统上运行此代码,此信息仅用于学习和理解系统原理的目的。

c语言fockbomb
(图片来源网络,侵删)

什么是 Fork Bomb?

Fork Bomb 是一种“拒绝服务”(Denial-of-Service, DoS)攻击,它本身不破坏数据,但通过耗尽系统关键资源,使系统无法为任何合法用户提供服务。

其核心原理非常简单:无限地、快速地创建新的进程。

在 Unix-like 系统(如 Linux, macOS)中,fork() 系统调用会创建一个当前进程的副本(子进程),这个子进程会继承父进程的代码、数据、文件描述符等,并拥有自己独立的进程 ID (PID)。

Fork Bomb 的策略就是让每个新创建的子进程立即再次执行 fork(),形成指数级的进程增长。

  • 第 1 秒:1 个进程(父进程)
  • 第 2 秒:2 个进程(父进程 + 1 个子进程)
  • 第 3 秒:4 个进程(2 个父进程 + 2 个子进程)
  • 第 4 秒:8 个进程
  • 第 N 秒:2^(N-1) 个进程

这个增长速度是灾难性的,几十个进程就会让普通电脑变得卡顿,几百个就可能让系统失去响应。

C 语言实现 Fork Bomb

最经典的 Fork Bomb 是一个递归执行的 shell 脚本,但用 C 语言实现它同样简单,并且因为编译后的二进制文件体积小、执行速度快,有时更具隐蔽性。

下面是一个最简单的 C 语言 Fork Bomb 代码:

// fork_bomb.c
#include <unistd.h>
int main() {
    while(1) {
        fork();
    }
    return 0;
}

代码解析:

  1. #include <unistd.h>: 包含了 fork() 函数所需的头文件。
  2. int main(): 程序的入口点。
  3. while(1): 这是一个无限循环,确保程序永远不会自己结束。
  4. fork(): 在循环体中,每次循环都会调用 fork(),这会创建一个新的子进程。
  5. 关键点:这个 fork() 调用发生在父进程中,当父进程创建子进程后,父进程和子进程都会从 fork() 的下一条语句开始继续执行,也就是说,它们都会再次进入 while(1) 循环,并各自再创建一个新的子进程。

执行流程图解:

初始状态: P0 (main)
P0 -> 进入 while(1) 循环
P0 -> 调用 fork()
      |
      +-- 创建 P1 (P0的子进程)
      |   P1 -> 进入 while(1) 循环
      |   P1 -> 调用 fork()
      |         |
      |         +-- 创建 P2 (P1的子进程)
      |         |   P2 -> 进入 while(1) 循环...
      |         |
      |         +-- 创建 P3 (P1的子进程)
      |             P3 -> 进入 while(1) 循环...
      |
      +-- P0 继续执行,回到 while(1) 循环
P0 -> 调用 fork()
      |
      +-- 创建 P4 (P0的子进程)
          P4 -> 进入 while(1) 循环...

很快,进程数量就会呈指数级爆炸式增长。

一个更“精巧”的版本

上面的版本虽然有效,但有一个小问题:父进程和子进程都在竞争 CPU 时间来创建更多的进程,一个更经典的 Fork Bomb 版本是让子进程立即去创建更多的进程,而父进程则可以退出,节省一点点资源。

// fork_bomb_v2.c
#include <unistd.h>
int main() {
    while(fork() > 0); // 父进程会一直循环创建子进程
                      // 子进程的 fork() 返回 0,所以会退出循环
    _exit(0);          // 所有子进程(包括最后一个没有子进程的)都退出
}

代码解析:

  1. while(fork() > 0):
    • fork() 的返回值:
      • 在父进程中,返回新创建子进程的 PID(一个大于 0 的整数)。
      • 在子进程中,返回 0。
      • 如果创建失败,返回 -1。
    • 这个循环的执行逻辑是:
      • 父进程fork() 返回一个大于 0 的值,while 条件为真,继续循环,创建下一个子进程。
      • 子进程fork() 返回 0,while 条件为假,循环结束。
  2. _exit(0): 当循环结束后(即对于所有子进程),它们会执行 _exit(0) 正常退出。

这个版本的效率更高,因为它将创建新进程的任务完全交给了父进程,子进程在创建后几乎不做什么事情就退出了,资源消耗更纯粹。

为什么 Fork Bomb 如此致命?

  1. 耗尽进程 ID (PID): 操作系统为每个进程分配一个唯一的 PID,PID 是一个有限的整数(例如在 Linux 上,默认最大是 32768),当所有 PID 都被用光时,系统就无法再创建任何新的进程,包括 initsystemd、登录 shell、ssh 守护进程等,整个系统管理机制瘫痪。
  2. 耗尽内存和 CPU:
    • 内存:每个进程都需要有自己的栈、堆、内核数据结构等,即使它们是空的,成千上万个进程会迅速耗尽系统的物理内存和交换空间。
    • CPU:操作系统内核需要不断地在这些进程之间进行上下文切换,这会消耗大量的 CPU 时间,导致系统整体响应速度变得极其缓慢,甚至完全无响应。
  3. 无法被轻易终止:一旦 Fork Bomb 爆炸,你会发现 ps 命令可能无法执行,或者输出成千上万行。kill 命令也难以使用,因为你需要先找到并杀死所有这些进程,而这本身就需要创建新的进程。

如何防护和应对?

防护(事前):

  1. 限制用户进程数:这是最有效的防护手段,可以通过修改 limits.conf 文件来限制每个用户可以创建的最大进程数。
    • 在 Linux 系统上,编辑 /etc/security/limits.conf 文件。
    • 添加或修改如下行:
      *               soft    nproc   100
      *               hard    nproc   200

      这表示所有用户的软限制是 100 个进程,硬限制是 200 个进程,当用户进程数达到软限制时,系统会发出警告,达到硬限制时,fork() 会失败。

  2. 使用资源管理工具:像 cgroups (Control Groups) 这样的内核功能可以限制一个进程组所能使用的资源,包括最大进程数。
  3. 谨慎使用 sudo:不要轻易给普通用户 sudo 权限,因为 Fork Bomb 可以轻易绕过普通用户的限制。

应对(事后):

一旦你的系统被 Fork Bomb 攻击,基本上已经“死了”,你无法登录进去执行任何命令,唯一的解决方法是强制重启

在某些情况下,你可以尝试通过物理访问或通过远程管理卡(如 iDRAC, iLO)来强制重启服务器,对于虚拟机,则可以由宿主机管理员直接强制关闭虚拟机。

Fork Bomb 是一个用极简代码实现巨大破坏力的经典例子,它深刻地揭示了操作系统进程管理的核心机制以及资源竞争的脆弱性,理解它的工作原理,有助于我们更好地编写健壮的程序,并学会如何保护系统免受此类攻击,再次强调,请务必负责任地使用这些知识

-- 展开阅读全文 --
头像
C语言如何实现killall命令功能?
« 上一篇 04-16
C语言中bioscom函数如何正确使用?
下一篇 » 04-16

相关文章

取消
微信二维码
支付宝二维码

目录[+]