diff --git a/fs/proc/base.c b/fs/proc/base.c
index 8358c5330c0b1abd8530c583b75af33b47a74889..4aa9ce5df02ffc9b6daf9d1d47157f5aee660799 100644
--- a/fs/proc/base.c
+++ b/fs/proc/base.c
@@ -205,43 +205,16 @@ static int proc_root_link(struct dentry *dentry, struct path *path)
 	return result;
 }
 
-static ssize_t proc_pid_cmdline_read(struct file *file, char __user *buf,
-				     size_t _count, loff_t *pos)
+static ssize_t get_mm_cmdline(struct mm_struct *mm, char __user *buf,
+			      size_t count, loff_t *ppos)
 {
-	struct task_struct *tsk;
-	struct mm_struct *mm;
-	char *page;
-	unsigned long count = _count;
 	unsigned long arg_start, arg_end, env_start, env_end;
-	unsigned long len1, len2;
-	char __user *buf0 = buf;
-	struct {
-		unsigned long p;
-		unsigned long len;
-	} cmdline[2];
-	char c;
-	int rv;
-
-	BUG_ON(*pos < 0);
+	unsigned long pos, len;
+	char *page;
 
-	tsk = get_proc_task(file_inode(file));
-	if (!tsk)
-		return -ESRCH;
-	mm = get_task_mm(tsk);
-	put_task_struct(tsk);
-	if (!mm)
-		return 0;
 	/* Check if process spawned far enough to have cmdline. */
-	if (!mm->env_end) {
-		rv = 0;
-		goto out_mmput;
-	}
-
-	page = (char *)__get_free_page(GFP_KERNEL);
-	if (!page) {
-		rv = -ENOMEM;
-		goto out_mmput;
-	}
+	if (!mm->env_end)
+		return 0;
 
 	spin_lock(&mm->arg_lock);
 	arg_start = mm->arg_start;
@@ -250,97 +223,111 @@ static ssize_t proc_pid_cmdline_read(struct file *file, char __user *buf,
 	env_end = mm->env_end;
 	spin_unlock(&mm->arg_lock);
 
-	BUG_ON(arg_start > arg_end);
-	BUG_ON(env_start > env_end);
-
-	len1 = arg_end - arg_start;
-	len2 = env_end - env_start;
-
-	/* Empty ARGV. */
-	if (len1 == 0)
-		goto end;
+	if (arg_start >= arg_end)
+		return 0;
 
 	/*
-	 * Inherently racy -- command line shares address space
-	 * with code and data.
+	 * We have traditionally allowed the user to re-write
+	 * the argument strings and overflow the end result
+	 * into the environment section. But only do that if
+	 * the environment area is contiguous to the arguments.
 	 */
-	if (access_remote_vm(mm, arg_end - 1, &c, 1, FOLL_ANON) != 1)
-		goto end;
-
-	cmdline[0].p = arg_start;
-	cmdline[0].len = len1;
-	if (c == '\0') {
-		/* Command line (set of strings) occupies whole ARGV. */
-		cmdline[1].len = 0;
-	} else {
-		/*
-		 * Command line (1 string) occupies ARGV and
-		 * extends into ENVP.
-		 */
-		cmdline[1].p = env_start;
-		cmdline[1].len = len2;
-	}
+	if (env_start != arg_end || env_start >= env_end)
+		env_start = env_end = arg_end;
 
-	{
-		loff_t pos1 = *pos;
-		unsigned int i;
+	/* We're not going to care if "*ppos" has high bits set */
+	pos = arg_start + *ppos;
+
+	/* .. but we do check the result is in the proper range */
+	if (pos < arg_start || pos >= env_end)
+		return 0;
 
-		i = 0;
-		while (i < 2 && pos1 >= cmdline[i].len) {
-			pos1 -= cmdline[i].len;
-			i++;
+	/* .. and we never go past env_end */
+	if (env_end - pos < count)
+		count = env_end - pos;
+
+	page = (char *)__get_free_page(GFP_KERNEL);
+	if (!page)
+		return -ENOMEM;
+
+	len = 0;
+	while (count) {
+		int got;
+		size_t size = min_t(size_t, PAGE_SIZE, count);
+
+		got = access_remote_vm(mm, pos, page, size, FOLL_ANON);
+		if (got <= 0)
+			break;
+
+		/* Don't walk past a NUL character once you hit arg_end */
+		if (pos + got >= arg_end) {
+			int n = 0;
+
+			/*
+			 * If we started before 'arg_end' but ended up
+			 * at or after it, we start the NUL character
+			 * check at arg_end-1 (where we expect the normal
+			 * EOF to be).
+			 *
+			 * NOTE! This is smaller than 'got', because
+			 * pos + got >= arg_end
+			 */
+			if (pos < arg_end)
+				n = arg_end - pos - 1;
+
+			/* Cut off at first NUL after 'n' */
+			got = n + strnlen(page+n, got-n);
+			if (!got)
+				break;
 		}
-		while (i < 2) {
-			unsigned long p;
-			unsigned long len;
-
-			p = cmdline[i].p + pos1;
-			len = cmdline[i].len - pos1;
-			while (count > 0 && len > 0) {
-				unsigned int nr_read, nr_write;
-
-				nr_read = min3(count, len, PAGE_SIZE);
-				nr_read = access_remote_vm(mm, p, page, nr_read, FOLL_ANON);
-				if (nr_read == 0)
-					goto end;
-
-				/*
-				 * Command line can be shorter than whole ARGV
-				 * even if last "marker" byte says it is not.
-				 */
-				if (c == '\0')
-					nr_write = nr_read;
-				else
-					nr_write = strnlen(page, nr_read);
-
-				if (copy_to_user(buf, page, nr_write)) {
-					rv = -EFAULT;
-					goto out_free_page;
-				}
-
-				p	+= nr_write;
-				len	-= nr_write;
-				buf	+= nr_write;
-				count	-= nr_write;
-
-				if (nr_write < nr_read)
-					goto end;
-			}
 
-			/* Only first chunk can be read partially. */
-			pos1 = 0;
-			i++;
+		got -= copy_to_user(buf, page, got);
+		if (unlikely(!got)) {
+			if (!len)
+				len = -EFAULT;
+			break;
 		}
+		pos += got;
+		buf += got;
+		len += got;
+		count -= got;
 	}
 
-end:
-	*pos += buf - buf0;
-	rv = buf - buf0;
-out_free_page:
 	free_page((unsigned long)page);
-out_mmput:
+	return len;
+}
+
+static ssize_t get_task_cmdline(struct task_struct *tsk, char __user *buf,
+				size_t count, loff_t *pos)
+{
+	struct mm_struct *mm;
+	ssize_t ret;
+
+	mm = get_task_mm(tsk);
+	if (!mm)
+		return 0;
+
+	ret = get_mm_cmdline(mm, buf, count, pos);
 	mmput(mm);
-	return rv;
+	return ret;
+}
+
+static ssize_t proc_pid_cmdline_read(struct file *file, char __user *buf,
+				     size_t count, loff_t *pos)
+{
+	struct task_struct *tsk;
+	ssize_t ret;
+
+	BUG_ON(*pos < 0);
+
+	tsk = get_proc_task(file_inode(file));
+	if (!tsk)
+		return -ESRCH;
+	ret = get_task_cmdline(tsk, buf, count, pos);
+	put_task_struct(tsk);
+	if (ret > 0)
+		*pos += ret;
+	return ret;
 }
 
 static const struct file_operations proc_pid_cmdline_ops = {