

Patch from: Eric Lammerts <eric@lammerts.org>

On Tue, 28 Jan 2003, Brian Sullivan wrote:
> The problem I am experiencing is that after a certain number of mounts I
> get the error message "fork: Resource temporarily unavailable" on the
> command line.

> After much trouble shooting I realized that number of mounts/umount
> sequences I am limited to is the max number of processes for my user id. I
> confirmed this by using the "ulimit -u" command to lower my process limit.
> It appears the mount command is leaving someting in some sort of process
> table in kernel memory (nothing shows in ps or top or in /proc/#### as
> being left behind). Has anybody any sort of experience with this at all?
> Any suggestions?

It's a kernel bug (and easy to reproduce).
         
Every time you do a loop mount, a kernel thread is started (those
processes are called "loop0", "loop1", etc.). The problem is that when
it starts, it's counted as one of your processes *). Then, it's
changed to be a root-owned process without correcting that count **).

Patch below fixes the problem. It moves the bookkeeping of changing
current->user to a new function switch_uid() (which is now also used
by exec_usermodehelper() in kmod.c). The patch is tested.

Eric

*)  "atomic_inc(&p->user->processes);" in do_fork().
**) "this_task->user = INIT_USER;" in reparent_to_init().



 linux/sched.h |    1 +
 exit.c        |    2 +-
 kmod.c        |   10 +---------
 sched.c       |    0 
 sys.c         |   13 ++-----------
 user.c        |   17 +++++++++++++++++
 6 files changed, 22 insertions(+), 21 deletions(-)

diff -puN include/linux/sched.h~user-process-count-leak include/linux/sched.h
--- 25/include/linux/sched.h~user-process-count-leak	2003-02-09 18:25:42.000000000 -0800
+++ 25-akpm/include/linux/sched.h	2003-02-09 18:25:42.000000000 -0800
@@ -511,6 +511,7 @@ extern void __set_special_pids(pid_t ses
 /* per-UID process charging. */
 extern struct user_struct * alloc_uid(uid_t);
 extern void free_uid(struct user_struct *);
+extern void switch_uid(struct user_struct *);
 
 #include <asm/current.h>
 
diff -puN kernel/kmod.c~user-process-count-leak kernel/kmod.c
--- 25/kernel/kmod.c~user-process-count-leak	2003-02-09 18:25:42.000000000 -0800
+++ 25-akpm/kernel/kmod.c	2003-02-09 18:25:42.000000000 -0800
@@ -121,15 +121,7 @@ int exec_usermodehelper(char *program_pa
 		if (curtask->files->fd[i]) close(i);
 	}
 
-	/* Drop the "current user" thing */
-	{
-		struct user_struct *user = curtask->user;
-		curtask->user = INIT_USER;
-		atomic_inc(&INIT_USER->__count);
-		atomic_inc(&INIT_USER->processes);
-		atomic_dec(&user->processes);
-		free_uid(user);
-	}
+	switch_uid(INIT_USER);
 
 	/* Give kmod all effective privileges.. */
 	curtask->euid = curtask->fsuid = 0;
diff -puN kernel/sched.c~user-process-count-leak kernel/sched.c
diff -puN kernel/sys.c~user-process-count-leak kernel/sys.c
--- 25/kernel/sys.c~user-process-count-leak	2003-02-09 18:25:42.000000000 -0800
+++ 25-akpm/kernel/sys.c	2003-02-09 18:25:42.000000000 -0800
@@ -561,19 +561,12 @@ asmlinkage long sys_setgid(gid_t gid)
   
 static int set_user(uid_t new_ruid, int dumpclear)
 {
-	struct user_struct *new_user, *old_user;
+	struct user_struct *new_user;
 
-	/* What if a process setreuid()'s and this brings the
-	 * new uid over his NPROC rlimit?  We can check this now
-	 * cheaply with the new uid cache, so if it matters
-	 * we should be checking for it.  -DaveM
-	 */
 	new_user = alloc_uid(new_ruid);
 	if (!new_user)
 		return -EAGAIN;
-	old_user = current->user;
-	atomic_dec(&old_user->processes);
-	atomic_inc(&new_user->processes);
+	switch_uid(new_user);
 
 	if(dumpclear)
 	{
@@ -581,8 +574,6 @@ static int set_user(uid_t new_ruid, int 
 		wmb();
 	}
 	current->uid = new_ruid;
-	current->user = new_user;
-	free_uid(old_user);
 	return 0;
 }
 
diff -puN kernel/user.c~user-process-count-leak kernel/user.c
--- 25/kernel/user.c~user-process-count-leak	2003-02-09 18:25:42.000000000 -0800
+++ 25-akpm/kernel/user.c	2003-02-09 18:25:42.000000000 -0800
@@ -116,6 +116,23 @@ struct user_struct * alloc_uid(uid_t uid
 	return up;
 }
 
+void switch_uid(struct user_struct *new_user)
+{
+	struct user_struct *old_user;
+
+	/* What if a process setreuid()'s and this brings the
+	 * new uid over his NPROC rlimit?  We can check this now
+	 * cheaply with the new uid cache, so if it matters
+	 * we should be checking for it.  -DaveM
+	 */
+	old_user = current->user;
+	atomic_inc(&new_user->__count);
+	atomic_inc(&new_user->processes);
+	atomic_dec(&old_user->processes);
+	current->user = new_user;
+	free_uid(old_user);
+}
+
 
 static int __init uid_cache_init(void)
 {
diff -puN kernel/exit.c~user-process-count-leak kernel/exit.c
--- 25/kernel/exit.c~user-process-count-leak	2003-02-09 18:25:42.000000000 -0800
+++ 25-akpm/kernel/exit.c	2003-02-09 18:25:42.000000000 -0800
@@ -249,7 +249,7 @@ void reparent_to_init(void)
 	/* signals? */
 	security_task_reparent_to_init(current);
 	memcpy(current->rlim, init_task.rlim, sizeof(*(current->rlim)));
-	current->user = INIT_USER;
+	switch_uid(INIT_USER);
 
 	write_unlock_irq(&tasklist_lock);
 }

_
