Skip to contentSkip to conversation

oom lca -- fork bombing killer v2.2

PATCH New Coywolf Qi Hunt 1 participant 1 message May 2, 2005

Subsystems (5)
lore
Share & tools
lore
#0 lore raw

PATCH 1/1 oom lca -- fork bombing killer v2.2

May 3, 2005
Coywolf Qi Hunt
[email protected]

Linux OOM LCA (Least Common Ancestor) Patch v2.2

A Fork Bombing Solution

This is oom lca killer v2.2. Here are the changes since the last release,

1. bug fix: moves DEBUG ahead of header files

2. warning fix: in pr_debug() format, %d -> %lu

3. bug fix: in badness(), changes to add 1/3 of a child's total_vm, so the parent's badness point isn't always greater than its children's.

4. Algorithm O(n) -> O(n^2)
in find_lca() and oom_kill_task() kill all descendants part

5. splits out reparent_to_init() cleanup into a separate patch. (merged)

6. bug fix: fix release memory waiting deadlock

7. other severe bug fix

I didn't find a proper header file for the last_chosen_parent declaration. Maybe in the future we create a new header file and move fs/proc/base.c::badness() prototype there too. (http://lkml.org/lkml/2005/3/30/262)

Todo

Make it SMP-safe

This patch protects my desktop computer from fork bombing attacks.
My desktop survived after these three bombers: (http://freeforge.net/~coywolf/pub/oom-bomb/)

a.sh
The current stock oom-killer cannot handle.

b.sh
Harder than a.sh

folkert
The hardest one. (from Folkert van Heusden <[email protected]>)

With a.sh or b.sh, my desktop survived perfectly, xfce, firefox and etc still running after oom kills offending processes, without touching any regular system processes.

With folkert, a lot of innocent processes got killed, but the system still survived. The killing of X server left the screen state messed up, but the keyboard still responds, caps lock and scroll lock LED's still work. klogd is still running. After ctrl+alt+del I can check the klog.

If swap is on, folkert brings the worst case. Sysrq-f could be used to trigger the oom earlier. In my case, it put my desktop unusable for 5~6 minutes until all 256M swap got used up and oom triggered.

In practice, this patch should be used together with fine tuned /proc/$pid/oom_adj to protect direct hardware access processes and other important processes.

Providers and individuals who run public shell servers or other services with end user processes will highly benefit from this functionality.

---

 include/linux/init_task.h |    3 
 include/linux/sched.h     |    9 +-
 kernel/exit.c             |   29 ++++++-
 kernel/fork.c             |    4 +
 mm/oom_kill.c             |  178 ++++++++++++++++++++++++++++++++--------------
 5 files changed, 160 insertions(+), 63 deletions(-)
include/linux/init_task.h +3 -0
diff -pruN 2.6.12-rc3-mm2/include/linux/init_task.h 2.6.12-rc3-mm2-cy/include/linux/init_task.h
@@ -90,6 +90,9 @@ extern struct group_info init_groups;
 	.parent		= &tsk,						\
 	.children	= LIST_HEAD_INIT(tsk.children),			\
 	.sibling	= LIST_HEAD_INIT(tsk.sibling),			\
+	.bio_parent	= &tsk,						\
+	.bio_children	= LIST_HEAD_INIT(tsk.bio_children),		\
+	.bio_sibling	= LIST_HEAD_INIT(tsk.bio_sibling),		\
 	.group_leader	= &tsk,						\
 	.group_info	= &init_groups,					\
 	.cap_effective	= CAP_INIT_EFF_SET,				\
include/linux/sched.h +4 -5
diff -pruN 2.6.12-rc3-mm2/include/linux/sched.h 2.6.12-rc3-mm2-cy/include/linux/sched.h
@@ -661,11 +661,7 @@ struct task_struct {
 	unsigned did_exec:1;
 	pid_t pid;
 	pid_t tgid;
-	/* 
-	 * pointers to (original) parent process, youngest child, younger sibling,
-	 * older sibling, respectively.  (p->father can be replaced with 
-	 * p->parent->pid)
-	 */
+
 	struct task_struct *real_parent; /* real parent process (when being debugged) */
 	struct task_struct *parent;	/* parent process */
 	/*
@@ -674,6 +670,9 @@ struct task_struct {
 	 */
 	struct list_head children;	/* list of my children */
 	struct list_head sibling;	/* linkage in my parent's children list */
+	struct task_struct *bio_parent;	/* biological parent process */
+	struct list_head bio_children;
+	struct list_head bio_sibling;
 	struct task_struct *group_leader;	/* threadgroup leader */
 
 	/* PID/PID hash table linkage. */
kernel/exit.c +25 -4
diff -pruN 2.6.12-rc3-mm2/kernel/exit.c 2.6.12-rc3-mm2-cy/kernel/exit.c
@@ -55,6 +55,7 @@ static void __unhash_process(struct task
 	}
 
 	REMOVE_LINKS(p);
+	list_del_init(&p->bio_sibling);
 }
 
 void release_task(struct task_struct * p)
@@ -224,6 +225,9 @@ static inline int has_stopped_jobs(int p
  * been inherited from a user process, so we reset them to sane values here.
  *
  * NOTE that reparent_to_init() gives the caller full capabilities.
+ *
+ * We change kernel threads' current->bio_*, thus find_lca()
+ * will not catch it.  -- coywolf
  */
 static inline void reparent_to_init(void)
 {
@@ -235,6 +239,9 @@ static inline void reparent_to_init(void
 	current->parent = child_reaper;
 	current->real_parent = child_reaper;
 	SET_LINKS(current);
+	list_del_init(&current->bio_sibling);
+	current->bio_parent = child_reaper;
+	list_add_tail(&current->bio_sibling, &child_reaper->bio_children);
 
 	/* Set the exit signal to SIGCHLD so we signal init on exit */
 	current->exit_signal = SIGCHLD;
@@ -514,7 +521,7 @@ static void exit_mm(struct task_struct *
 	mmput(mm);
 }
 
-static inline void choose_new_parent(task_t *p, task_t *reaper, task_t *child_reaper)
+static inline void choose_new_parent(task_t *p, task_t *reaper)
 {
 	/*
 	 * Make sure we're not reparenting to ourselves and that
@@ -591,6 +598,7 @@ static inline void reparent_thread(task_
 static inline void forget_original_parent(struct task_struct * father,
 					  struct list_head *to_release)
 {
+	extern struct task_struct *last_chosen_parent;
 	struct task_struct *p, *reaper = father;
 	struct list_head *_p, *_n;
 
@@ -611,7 +619,7 @@ static inline void forget_original_paren
 	 * Search them and reparent children.
 	 */
 	list_for_each_safe(_p, _n, &father->children) {
-		int ptrace;
+		long ptrace;
 		p = list_entry(_p,struct task_struct,sibling);
 
 		ptrace = p->ptrace;
@@ -621,7 +629,7 @@ static inline void forget_original_paren
 
 		if (father == p->real_parent) {
 			/* reparent with a reaper, real father it's us */
-			choose_new_parent(p, reaper, child_reaper);
+			choose_new_parent(p, reaper);
 			reparent_thread(p, father, 0);
 		} else {
 			/* reparent ptraced task to its real parent */
@@ -642,9 +650,22 @@ static inline void forget_original_paren
 	}
 	list_for_each_safe(_p, _n, &father->ptrace_children) {
 		p = list_entry(_p,struct task_struct,ptrace_list);
-		choose_new_parent(p, reaper, child_reaper);
+		choose_new_parent(p, reaper);
 		reparent_thread(p, father, 1);
 	}
+
+	list_for_each_safe(_p, _n,  &father->bio_children) {
+		p = list_entry(_p, struct task_struct, bio_sibling);
+		list_del_init(&p->bio_sibling);
+		p->bio_parent = father->bio_parent;
+		list_add_tail(&p->bio_sibling, &(p->bio_parent)->bio_children);
+	}
+
+	if (unlikely(father == last_chosen_parent)) {
+		last_chosen_parent = father->bio_parent;
+		if (last_chosen_parent->pid == 1)
+			last_chosen_parent = NULL;
+	}
 }
 
 /*
kernel/fork.c +4 -0
diff -pruN 2.6.12-rc3-mm2/kernel/fork.c 2.6.12-rc3-mm2-cy/kernel/fork.c
@@ -911,6 +911,8 @@ static task_t *copy_process(unsigned lon
 
 	INIT_LIST_HEAD(&p->children);
 	INIT_LIST_HEAD(&p->sibling);
+	INIT_LIST_HEAD(&p->bio_children);
+	INIT_LIST_HEAD(&p->bio_sibling);
 	p->vfork_done = NULL;
 	spin_lock_init(&p->alloc_lock);
 	spin_lock_init(&p->proc_lock);
@@ -1043,6 +1045,7 @@ static task_t *copy_process(unsigned lon
 	else
 		p->real_parent = current;
 	p->parent = p->real_parent;
+	p->bio_parent = current;
 
 	if (clone_flags & CLONE_THREAD) {
 		spin_lock(&current->sighand->siglock);
@@ -1093,6 +1096,7 @@ static task_t *copy_process(unsigned lon
 	p->ioprio = current->ioprio;
 
 	SET_LINKS(p);
+	list_add_tail(&p->bio_sibling, &current->bio_children);
 	if (unlikely(p->ptrace & PT_PTRACED))
 		__ptrace_link(p, current->parent);
 
mm/oom_kill.c +124 -54

279 lines not on this page · raw message