patch-2.2.0-pre8 linux/fs/proc/array.c

Next file: linux/fs/proc/root.c
Previous file: linux/fs/nfsd/vfs.c
Back to the patch index
Back to the overall index

diff -u --recursive --new-file v2.2.0-pre7/linux/fs/proc/array.c linux/fs/proc/array.c
@@ -42,6 +42,8 @@
  * Alan Cox	     :  security fixes. 
  *			<Alan.Cox@linux.org>
  *
+ * Andi Kleen	     :  Race Fixes. 	
+ *
  */
 
 #include <linux/types.h>
@@ -386,21 +388,46 @@
 	return sprintf(buffer, "%s\n", saved_command_line);
 }
 
-static unsigned long get_phys_addr(struct task_struct * p, unsigned long ptr)
+/* 
+ * Caller must release_mm the mm_struct later.
+ * You don't get any access to init_mm. 
+ */ 
+static struct mm_struct *get_mm_and_lock(int pid) 
+{ 
+	struct mm_struct *mm = NULL;  
+	struct task_struct *tsk; 
+
+	read_lock(&tasklist_lock); 
+	tsk = find_task_by_pid(pid); 
+	if (tsk && tsk->mm && tsk->mm != &init_mm)
+		mmget(mm = tsk->mm);  
+	read_unlock(&tasklist_lock);
+	if (mm != NULL) 
+		down(&mm->mmap_sem); 
+	return mm; 
+}
+
+static void release_mm(struct mm_struct *mm)
+{
+	up(&mm->mmap_sem); 
+	mmput(mm); 
+}
+
+static unsigned long get_phys_addr(struct mm_struct *mm, unsigned long ptr)
 {
 	pgd_t *page_dir;
 	pmd_t *page_middle;
 	pte_t pte;
 
-	if (!p || !p->mm || ptr >= TASK_SIZE)
+	if (ptr >= TASK_SIZE)
 		return 0;
 	/* Check for NULL pgd .. shouldn't happen! */
-	if (!p->mm->pgd) {
-		printk("get_phys_addr: pid %d has NULL pgd!\n", p->pid);
+	if (!mm->pgd) {
+		printk(KERN_DEBUG "missing pgd for mm %p\n", mm); 
 		return 0;
 	}
 
-	page_dir = pgd_offset(p->mm,ptr);
+	page_dir = pgd_offset(mm,ptr);
 	if (pgd_none(*page_dir))
 		return 0;
 	if (pgd_bad(*page_dir)) {
@@ -422,7 +449,7 @@
 	return pte_page(pte) + (ptr & ~PAGE_MASK);
 }
 
-static int get_array(struct task_struct *p, unsigned long start, unsigned long end, char * buffer)
+static int get_array(struct mm_struct *mm, unsigned long start, unsigned long end, char * buffer)
 {
 	unsigned long addr;
 	int size = 0, result = 0;
@@ -431,7 +458,7 @@
 	if (start >= end)
 		return result;
 	for (;;) {
-		addr = get_phys_addr(p, start);
+		addr = get_phys_addr(mm, start);
 		if (!addr)
 			return result;
 		do {
@@ -453,27 +480,28 @@
 
 static int get_env(int pid, char * buffer)
 {
-	struct task_struct *p;
-	
-	read_lock(&tasklist_lock);
-	p = find_task_by_pid(pid);
-	read_unlock(&tasklist_lock);	/* FIXME!! This should be done after the last use */
+	struct mm_struct *mm; 
+	int res = 0; 
 
-	if (!p || !p->mm)
-		return 0;
-	return get_array(p, p->mm->env_start, p->mm->env_end, buffer);
+	mm = get_mm_and_lock(pid); 
+	if (mm) {  
+		res = get_array(mm, mm->env_start, mm->env_end, buffer); 
+		release_mm(mm);
+	}
+	return res;
 }
 
 static int get_arg(int pid, char * buffer)
 {
-	struct task_struct *p;
+	struct mm_struct *mm; 
+	int res = 0; 
 
-	read_lock(&tasklist_lock);
-	p = find_task_by_pid(pid);
-	read_unlock(&tasklist_lock);	/* FIXME!! This should be done after the last use */
-	if (!p || !p->mm)
-		return 0;
-	return get_array(p, p->mm->arg_start, p->mm->arg_end, buffer);
+	mm = get_mm_and_lock(pid); 
+	if (mm) {
+		res = get_array(mm, mm->arg_start, mm->arg_end, buffer); 
+		release_mm(mm); 
+	} 
+	return res;
 }
 
 /*
@@ -726,11 +754,14 @@
 {
 	struct mm_struct * mm = p->mm;
 
-	if (mm && mm != &init_mm) {
-		struct vm_area_struct * vma = mm->mmap;
+	if (!mm) 
+		return buffer;
+	if (mm != &init_mm) {
+		struct vm_area_struct * vma;
 		unsigned long data = 0, stack = 0;
 		unsigned long exec = 0, lib = 0;
 
+		down(&mm->mmap_sem); 
 		for (vma = mm->mmap; vma; vma = vma->vm_next) {
 			unsigned long len = (vma->vm_end - vma->vm_start) >> 10;
 			if (!vma->vm_file) {
@@ -748,6 +779,7 @@
 				lib += len;
 			}
 		}	
+		up(&mm->mmap_sem); 
 		buffer += sprintf(buffer,
 			"VmSize:\t%8lu kB\n"
 			"VmLck:\t%8lu kB\n"
@@ -817,15 +849,35 @@
 			    cap_t(p->cap_effective));
 }
 
+static struct task_struct *grab_task(int pid, struct task_struct *dst) 
+{
+	struct task_struct *tsk = current; 
+	if (pid != tsk->pid) { 
+		read_lock(&tasklist_lock);
+		tsk = find_task_by_pid(pid);
+		if (tsk) { 
+			memcpy(dst, tsk, sizeof(struct task_struct));
+			tsk = dst;
+			if (tsk->mm && tsk->mm != &init_mm) 
+				mmget(tsk->mm);
+		}
+		read_unlock(&tasklist_lock);
+	}	
+	return tsk; 
+}
+
+static void release_task(struct task_struct *tsk)
+{
+	if (tsk != current && tsk->mm && tsk->mm != &init_mm)
+		mmput(tsk->mm); 
+}
 
 static int get_status(int pid, char * buffer)
 {
 	char * orig = buffer;
-	struct task_struct *tsk;
-
-	read_lock(&tasklist_lock);
-	tsk = find_task_by_pid(pid);
-	read_unlock(&tasklist_lock);	/* FIXME!! This should be done after the last use */
+	struct task_struct *tsk, mytask;
+	
+	tsk = grab_task(pid, &mytask); 
 	if (!tsk)
 		return 0;
 	buffer = task_name(tsk, buffer);
@@ -833,31 +885,34 @@
 	buffer = task_mem(tsk, buffer);
 	buffer = task_sig(tsk, buffer);
 	buffer = task_cap(tsk, buffer);
+	release_task(tsk); 
 	return buffer - orig;
 }
 
 static int get_stat(int pid, char * buffer)
 {
-	struct task_struct *tsk;
+	struct task_struct *tsk, mytask;
 	unsigned long vsize, eip, esp, wchan;
 	long priority, nice;
 	int tty_pgrp;
 	sigset_t sigign, sigcatch;
 	char state;
+	int res;
 
-	read_lock(&tasklist_lock);
-	tsk = find_task_by_pid(pid);
-	read_unlock(&tasklist_lock);	/* FIXME!! This should be done after the last use */
-	if (!tsk)
+	tsk = grab_task(pid, &mytask);  
+	if (!tsk) 
 		return 0;
 	state = *get_task_state(tsk);
 	vsize = eip = esp = 0;
 	if (tsk->mm && tsk->mm != &init_mm) {
-		struct vm_area_struct *vma = tsk->mm->mmap;
-		while (vma) {
+		struct vm_area_struct *vma;
+
+		down(&tsk->mm->mmap_sem);
+		for (vma = tsk->mm->mmap; vma; vma = vma->vm_next) {  
 			vsize += vma->vm_end - vma->vm_start;
-			vma = vma->vm_next;
 		}
+		up(&tsk->mm->mmap_sem); 
+		
 		eip = KSTK_EIP(tsk);
 		esp = KSTK_ESP(tsk);
 	}
@@ -878,7 +933,7 @@
 	nice = tsk->priority;
 	nice = 20 - (nice * 20 + DEF_PRIORITY / 2) / DEF_PRIORITY;
 
-	return sprintf(buffer,"%d (%s) %c %d %d %d %d %d %lu %lu \
+	res = sprintf(buffer,"%d (%s) %c %d %d %d %d %d %lu %lu \
 %lu %lu %lu %lu %lu %ld %ld %ld %ld %ld %ld %lu %lu %ld %lu %lu %lu %lu %lu \
 %lu %lu %lu %lu %lu %lu %lu %lu %d\n",
 		pid,
@@ -923,6 +978,9 @@
 		tsk->nswap,
 		tsk->cnswap,
 		tsk->exit_signal);
+
+	release_task(tsk);
+	return res;
 }
 		
 static inline void statm_pte_range(pmd_t * pmd, unsigned long address, unsigned long size,
@@ -1000,19 +1058,15 @@
 
 static int get_statm(int pid, char * buffer)
 {
-	struct task_struct *tsk;
 	int size=0, resident=0, share=0, trs=0, lrs=0, drs=0, dt=0;
+	struct mm_struct *mm;
 
-	read_lock(&tasklist_lock);
-	tsk = find_task_by_pid(pid);
-	read_unlock(&tasklist_lock);	/* FIXME!! This should be done after the last use */
-	if (!tsk)
-		return 0;
-	if (tsk->mm && tsk->mm != &init_mm) {
-		struct vm_area_struct * vma = tsk->mm->mmap;
+	mm = get_mm_and_lock(pid); 
+	if (mm) { 
+		struct vm_area_struct * vma = mm->mmap;
 
 		while (vma) {
-			pgd_t *pgd = pgd_offset(tsk->mm, vma->vm_start);
+			pgd_t *pgd = pgd_offset(mm, vma->vm_start);
 			int pages = 0, shared = 0, dirty = 0, total = 0;
 
 			statm_pgd_range(pgd, vma->vm_start, vma->vm_end, &pages, &shared, &dirty, &total);
@@ -1030,7 +1084,9 @@
 				drs += pages;
 			vma = vma->vm_next;
 		}
-	}
+		release_mm(mm); 
+	} else
+		return 0; 
 	return sprintf(buffer,"%d %d %d %d %d %d %d\n",
 		       size, resident, share, trs, lrs, drs, dt);
 }
@@ -1067,7 +1123,7 @@
 
 #define MAPS_LINE_MAX	MAPS_LINE_MAX8
 
-
+/* FIXME: this does not do proper mm locking */  
 static ssize_t read_maps (int pid, struct file * file, char * buf,
 			  size_t count, loff_t *ppos)
 {
@@ -1199,15 +1255,11 @@
 #ifdef __SMP__
 static int get_pidcpu(int pid, char * buffer)
 {
-	struct task_struct * tsk = current ;
+	struct task_struct * tsk, mytask;
 	int i, len;
 
-	read_lock(&tasklist_lock);
-	if (pid != tsk->pid)
-		tsk = find_task_by_pid(pid);
-	read_unlock(&tasklist_lock);	/* FIXME!! This should be done after the last use */
-
-	if (tsk == NULL)
+	tsk = grab_task(pid, &mytask);
+	if (!tsk)
 		return 0;
 
 	len = sprintf(buffer,
@@ -1221,6 +1273,7 @@
 			tsk->per_cpu_utime[cpu_logical_map(i)],
 			tsk->per_cpu_stime[cpu_logical_map(i)]);
 
+	release_task(tsk); 
 	return len;
 }
 #endif
@@ -1421,6 +1474,7 @@
 	ssize_t end;
 	unsigned int type, pid;
 	struct proc_dir_entry *dp;
+	int err;
 
 	if (count > PROC_BLOCK_SIZE)
 		count = PROC_BLOCK_SIZE;
@@ -1449,8 +1503,10 @@
 		return length;
 	}
 	if (start != NULL) {
+		if (length > count)
+			length = count;
 		/* We have had block-adjusting processing! */
-		copy_to_user(buf, start, length);
+		err = copy_to_user(buf, start, length);
 		*ppos += length;
 		count = length;
 	} else {
@@ -1462,11 +1518,11 @@
 		if (count + *ppos > length)
 			count = length - *ppos;
 		end = count + *ppos;
-		copy_to_user(buf, (char *) page + *ppos, count);
+		err = copy_to_user(buf, (char *) page + *ppos, count);
 		*ppos = end;
 	}
 	free_page(page);
-	return count;
+	return err ? -EFAULT : count;
 }
 
 static struct file_operations proc_array_operations = {

FUNET's LINUX-ADM group, linux-adm@nic.funet.fi
TCL-scripts by Sam Shen, slshen@lbl.gov