diff mbox series

[bpf-next,v7,1/3] mm: add copy_remote_vm_str

Message ID 20250210221626.2098522-1-linux@jordanrome.com (mailing list archive)
State New
Headers show
Series [bpf-next,v7,1/3] mm: add copy_remote_vm_str | expand

Commit Message

Jordan Rome Feb. 10, 2025, 10:16 p.m. UTC
Similar to `access_process_vm` but specific to strings.
Also chunks reads by page and utilizes `strscpy`
for handling null termination.

Signed-off-by: Jordan Rome <linux@jordanrome.com>
---
 include/linux/mm.h |   3 ++
 mm/memory.c        | 119 +++++++++++++++++++++++++++++++++++++++++++++
 mm/nommu.c         |  73 +++++++++++++++++++++++++++
 3 files changed, 195 insertions(+)

--
2.43.5

Comments

Andrii Nakryiko Feb. 11, 2025, 10:07 p.m. UTC | #1
On Mon, Feb 10, 2025 at 2:23 PM Jordan Rome <linux@jordanrome.com> wrote:
>
> Similar to `access_process_vm` but specific to strings.
> Also chunks reads by page and utilizes `strscpy`
> for handling null termination.
>
> Signed-off-by: Jordan Rome <linux@jordanrome.com>
> ---
>  include/linux/mm.h |   3 ++
>  mm/memory.c        | 119 +++++++++++++++++++++++++++++++++++++++++++++
>  mm/nommu.c         |  73 +++++++++++++++++++++++++++
>  3 files changed, 195 insertions(+)
>
> diff --git a/include/linux/mm.h b/include/linux/mm.h
> index 7b1068ddcbb7..aee23d84ce01 100644
> --- a/include/linux/mm.h
> +++ b/include/linux/mm.h
> @@ -2486,6 +2486,9 @@ extern int access_process_vm(struct task_struct *tsk, unsigned long addr,
>  extern int access_remote_vm(struct mm_struct *mm, unsigned long addr,
>                 void *buf, int len, unsigned int gup_flags);
>
> +extern int copy_remote_vm_str(struct task_struct *tsk, unsigned long addr,
> +               void *buf, int len, unsigned int gup_flags);
> +
>  long get_user_pages_remote(struct mm_struct *mm,
>                            unsigned long start, unsigned long nr_pages,
>                            unsigned int gup_flags, struct page **pages,
> diff --git a/mm/memory.c b/mm/memory.c
> index 539c0f7c6d54..e9d8584a7f56 100644
> --- a/mm/memory.c
> +++ b/mm/memory.c
> @@ -6803,6 +6803,125 @@ int access_process_vm(struct task_struct *tsk, unsigned long addr,
>  }
>  EXPORT_SYMBOL_GPL(access_process_vm);
>
> +/*
> + * Copy a string from another process's address space as given in mm.
> + * If there is any error return -EFAULT.
> + */
> +static int __copy_remote_vm_str(struct mm_struct *mm, unsigned long addr,
> +                             void *buf, int len, unsigned int gup_flags)
> +{
> +       void *old_buf = buf;
> +       int err = 0;
> +
> +       *(char *)buf = '\0';

LGTM overall:

Acked-by: Andrii Nakryiko <andrii@kernel.org>

But note that all this unconditional buf access will be incorrect if
len == 0. So either all of that has to be guarded with `if (len)`,
just dropped, or declared unsupported, depending on what mm folks
think. BPF helper won't ever call with len == 0, so that's why my ack.

(And yes, it would be nice to hear from someone from the MM side at
this point, thank you!).

> +
> +       if (mmap_read_lock_killable(mm))
> +               return -EFAULT;
> +
> +       /* Untag the address before looking up the VMA */
> +       addr = untagged_addr_remote(mm, addr);
> +
> +       /* Avoid triggering the temporary warning in __get_user_pages */
> +       if (!vma_lookup(mm, addr)) {
> +               err = -EFAULT;
> +               goto out;
> +       }
> +

[...]
Shakeel Butt Feb. 12, 2025, 2:19 a.m. UTC | #2
On Tue, Feb 11, 2025 at 02:07:31PM -0800, Andrii Nakryiko wrote:
> On Mon, Feb 10, 2025 at 2:23 PM Jordan Rome <linux@jordanrome.com> wrote:
> >
> > Similar to `access_process_vm` but specific to strings.
> > Also chunks reads by page and utilizes `strscpy`
> > for handling null termination.
> >
> > Signed-off-by: Jordan Rome <linux@jordanrome.com>
> > ---
> >  include/linux/mm.h |   3 ++
> >  mm/memory.c        | 119 +++++++++++++++++++++++++++++++++++++++++++++
> >  mm/nommu.c         |  73 +++++++++++++++++++++++++++
> >  3 files changed, 195 insertions(+)
> >
> > diff --git a/include/linux/mm.h b/include/linux/mm.h
> > index 7b1068ddcbb7..aee23d84ce01 100644
> > --- a/include/linux/mm.h
> > +++ b/include/linux/mm.h
> > @@ -2486,6 +2486,9 @@ extern int access_process_vm(struct task_struct *tsk, unsigned long addr,
> >  extern int access_remote_vm(struct mm_struct *mm, unsigned long addr,
> >                 void *buf, int len, unsigned int gup_flags);
> >
> > +extern int copy_remote_vm_str(struct task_struct *tsk, unsigned long addr,
> > +               void *buf, int len, unsigned int gup_flags);
> > +
> >  long get_user_pages_remote(struct mm_struct *mm,
> >                            unsigned long start, unsigned long nr_pages,
> >                            unsigned int gup_flags, struct page **pages,
> > diff --git a/mm/memory.c b/mm/memory.c
> > index 539c0f7c6d54..e9d8584a7f56 100644
> > --- a/mm/memory.c
> > +++ b/mm/memory.c
> > @@ -6803,6 +6803,125 @@ int access_process_vm(struct task_struct *tsk, unsigned long addr,
> >  }
> >  EXPORT_SYMBOL_GPL(access_process_vm);
> >
> > +/*
> > + * Copy a string from another process's address space as given in mm.
> > + * If there is any error return -EFAULT.
> > + */
> > +static int __copy_remote_vm_str(struct mm_struct *mm, unsigned long addr,
> > +                             void *buf, int len, unsigned int gup_flags)
> > +{
> > +       void *old_buf = buf;
> > +       int err = 0;
> > +
> > +       *(char *)buf = '\0';
> 
> LGTM overall:
> 
> Acked-by: Andrii Nakryiko <andrii@kernel.org>
> 
> But note that all this unconditional buf access will be incorrect if
> len == 0. So either all of that has to be guarded with `if (len)`,
> just dropped, or declared unsupported, depending on what mm folks
> think. BPF helper won't ever call with len == 0, so that's why my ack.

I think early return 0 on len == 0 should be fine.
Jordan Rome Feb. 12, 2025, 5:33 p.m. UTC | #3
On Tue, Feb 11, 2025 at 9:19 PM Shakeel Butt <shakeel.butt@linux.dev> wrote:
>
> On Tue, Feb 11, 2025 at 02:07:31PM -0800, Andrii Nakryiko wrote:
> > On Mon, Feb 10, 2025 at 2:23 PM Jordan Rome <linux@jordanrome.com> wrote:
> > >
> > > Similar to `access_process_vm` but specific to strings.
> > > Also chunks reads by page and utilizes `strscpy`
> > > for handling null termination.
> > >
> > > Signed-off-by: Jordan Rome <linux@jordanrome.com>
> > > ---
> > >  include/linux/mm.h |   3 ++
> > >  mm/memory.c        | 119 +++++++++++++++++++++++++++++++++++++++++++++
> > >  mm/nommu.c         |  73 +++++++++++++++++++++++++++
> > >  3 files changed, 195 insertions(+)
> > >
> > > diff --git a/include/linux/mm.h b/include/linux/mm.h
> > > index 7b1068ddcbb7..aee23d84ce01 100644
> > > --- a/include/linux/mm.h
> > > +++ b/include/linux/mm.h
> > > @@ -2486,6 +2486,9 @@ extern int access_process_vm(struct task_struct *tsk, unsigned long addr,
> > >  extern int access_remote_vm(struct mm_struct *mm, unsigned long addr,
> > >                 void *buf, int len, unsigned int gup_flags);
> > >
> > > +extern int copy_remote_vm_str(struct task_struct *tsk, unsigned long addr,
> > > +               void *buf, int len, unsigned int gup_flags);
> > > +
> > >  long get_user_pages_remote(struct mm_struct *mm,
> > >                            unsigned long start, unsigned long nr_pages,
> > >                            unsigned int gup_flags, struct page **pages,
> > > diff --git a/mm/memory.c b/mm/memory.c
> > > index 539c0f7c6d54..e9d8584a7f56 100644
> > > --- a/mm/memory.c
> > > +++ b/mm/memory.c
> > > @@ -6803,6 +6803,125 @@ int access_process_vm(struct task_struct *tsk, unsigned long addr,
> > >  }
> > >  EXPORT_SYMBOL_GPL(access_process_vm);
> > >
> > > +/*
> > > + * Copy a string from another process's address space as given in mm.
> > > + * If there is any error return -EFAULT.
> > > + */
> > > +static int __copy_remote_vm_str(struct mm_struct *mm, unsigned long addr,
> > > +                             void *buf, int len, unsigned int gup_flags)
> > > +{
> > > +       void *old_buf = buf;
> > > +       int err = 0;
> > > +
> > > +       *(char *)buf = '\0';
> >
> > LGTM overall:
> >
> > Acked-by: Andrii Nakryiko <andrii@kernel.org>
> >
> > But note that all this unconditional buf access will be incorrect if
> > len == 0. So either all of that has to be guarded with `if (len)`,
> > just dropped, or declared unsupported, depending on what mm folks
> > think. BPF helper won't ever call with len == 0, so that's why my ack.
>
> I think early return 0 on len == 0 should be fine.

Ack.
Shakeel Butt Feb. 12, 2025, 10:25 p.m. UTC | #4
On Mon, Feb 10, 2025 at 02:16:24PM -0800, Jordan Rome wrote:
> Similar to `access_process_vm` but specific to strings.
> Also chunks reads by page and utilizes `strscpy`
> for handling null termination.
> 
> Signed-off-by: Jordan Rome <linux@jordanrome.com>

If you decide to send a newer version, my request would be to add some
more text in the commit message particularly on the motivation.
Otherwise this looks good to me.

Reviewed-by: Shakeel Butt <shakeel.butt@linux.dev>
diff mbox series

Patch

diff --git a/include/linux/mm.h b/include/linux/mm.h
index 7b1068ddcbb7..aee23d84ce01 100644
--- a/include/linux/mm.h
+++ b/include/linux/mm.h
@@ -2486,6 +2486,9 @@  extern int access_process_vm(struct task_struct *tsk, unsigned long addr,
 extern int access_remote_vm(struct mm_struct *mm, unsigned long addr,
 		void *buf, int len, unsigned int gup_flags);

+extern int copy_remote_vm_str(struct task_struct *tsk, unsigned long addr,
+		void *buf, int len, unsigned int gup_flags);
+
 long get_user_pages_remote(struct mm_struct *mm,
 			   unsigned long start, unsigned long nr_pages,
 			   unsigned int gup_flags, struct page **pages,
diff --git a/mm/memory.c b/mm/memory.c
index 539c0f7c6d54..e9d8584a7f56 100644
--- a/mm/memory.c
+++ b/mm/memory.c
@@ -6803,6 +6803,125 @@  int access_process_vm(struct task_struct *tsk, unsigned long addr,
 }
 EXPORT_SYMBOL_GPL(access_process_vm);

+/*
+ * Copy a string from another process's address space as given in mm.
+ * If there is any error return -EFAULT.
+ */
+static int __copy_remote_vm_str(struct mm_struct *mm, unsigned long addr,
+			      void *buf, int len, unsigned int gup_flags)
+{
+	void *old_buf = buf;
+	int err = 0;
+
+	*(char *)buf = '\0';
+
+	if (mmap_read_lock_killable(mm))
+		return -EFAULT;
+
+	/* Untag the address before looking up the VMA */
+	addr = untagged_addr_remote(mm, addr);
+
+	/* Avoid triggering the temporary warning in __get_user_pages */
+	if (!vma_lookup(mm, addr)) {
+		err = -EFAULT;
+		goto out;
+	}
+
+	while (len) {
+		int bytes, offset, retval;
+		void *maddr;
+		struct page *page;
+		struct vm_area_struct *vma = NULL;
+
+		page = get_user_page_vma_remote(mm, addr, gup_flags, &vma);
+
+		if (IS_ERR(page)) {
+			/*
+			 * Treat as a total failure for now until we decide how
+			 * to handle the CONFIG_HAVE_IOREMAP_PROT case and
+			 * stack expansion.
+			 */
+			*(char *)buf = '\0';
+			err = -EFAULT;
+			goto out;
+		}
+
+		bytes = len;
+		offset = addr & (PAGE_SIZE - 1);
+		if (bytes > PAGE_SIZE - offset)
+			bytes = PAGE_SIZE - offset;
+
+		maddr = kmap_local_page(page);
+		retval = strscpy(buf, maddr + offset, bytes);
+
+		if (retval >= 0) {
+			/* Found the end of the string */
+			buf += retval;
+			unmap_and_put_page(page, maddr);
+			break;
+		}
+
+		buf += bytes - 1;
+		/*
+		 * Because strscpy always NUL terminates we need to
+		 * copy the last byte in the page if we are going to
+		 * load more pages
+		 */
+		if (bytes != len) {
+			addr += bytes - 1;
+			copy_from_user_page(vma, page, addr, buf,
+					maddr + (PAGE_SIZE - 1), 1);
+
+			buf += 1;
+			addr += 1;
+		}
+		len -= bytes;
+
+		unmap_and_put_page(page, maddr);
+	}
+
+out:
+	mmap_read_unlock(mm);
+	if (err)
+		return err;
+
+	return buf - old_buf;
+}
+
+/**
+ * copy_remote_vm_str - copy a string from another process's address space.
+ * @tsk:	the task of the target address space
+ * @addr:	start address to read from
+ * @buf:	destination buffer
+ * @len:	number of bytes to copy
+ * @gup_flags:	flags modifying lookup behaviour
+ *
+ * The caller must hold a reference on @mm.
+ *
+ * Return: number of bytes copied from @addr (source) to @buf (destination);
+ * not including the trailing NUL. Always guaranteed to leave NUL-terminated
+ * buffer. On any error, return -EFAULT.
+ */
+int copy_remote_vm_str(struct task_struct *tsk, unsigned long addr,
+		void *buf, int len, unsigned int gup_flags)
+{
+	struct mm_struct *mm;
+	int ret;
+
+	mm = get_task_mm(tsk);
+	if (!mm) {
+		*(char *)buf = '\0';
+		return -EFAULT;
+	}
+
+	ret = __copy_remote_vm_str(mm, addr, buf, len, gup_flags);
+
+	mmput(mm);
+
+	return ret;
+}
+EXPORT_SYMBOL_GPL(copy_remote_vm_str);
+
 /*
  * Print the name of a VMA.
  */
diff --git a/mm/nommu.c b/mm/nommu.c
index baa79abdaf03..ec3bfcb35215 100644
--- a/mm/nommu.c
+++ b/mm/nommu.c
@@ -1708,6 +1708,79 @@  int access_process_vm(struct task_struct *tsk, unsigned long addr, void *buf, in
 }
 EXPORT_SYMBOL_GPL(access_process_vm);

+/*
+ * Copy a string from another process's address space as given in mm.
+ * If there is any error return -EFAULT.
+ */
+static int __copy_remote_vm_str(struct mm_struct *mm, unsigned long addr,
+			      void *buf, int len)
+{
+	unsigned long addr_end;
+	struct vm_area_struct *vma;
+	int ret = -EFAULT;
+
+	*(char *)buf = '\0';
+
+	if (mmap_read_lock_killable(mm))
+		return ret;
+
+	/* the access must start within one of the target process's mappings */
+	vma = find_vma(mm, addr);
+	if (!vma)
+		goto out;
+
+	if (check_add_overflow(addr, len, &addr_end))
+		goto out;
+	/* don't overrun this mapping */
+	if (addr_end > vma->vm_end)
+		len = vma->vm_end - addr;
+
+	/* only read mappings where it is permitted */
+	if (vma->vm_flags & VM_MAYREAD) {
+		ret = strscpy(buf, (char *)addr, len);
+		if (ret < 0)
+			ret = len - 1;
+	}
+
+out:
+	mmap_read_unlock(mm);
+	return ret;
+}
+
+/**
+ * copy_remote_vm_str - copy a string from another process's address space.
+ * @tsk:	the task of the target address space
+ * @addr:	start address to read from
+ * @buf:	destination buffer
+ * @len:	number of bytes to copy
+ * @gup_flags:	flags modifying lookup behaviour (unused)
+ *
+ * The caller must hold a reference on @mm.
+ *
+ * Return: number of bytes copied from @addr (source) to @buf (destination);
+ * not including the trailing NUL. Always guaranteed to leave NUL-terminated
+ * buffer. On any error, return -EFAULT.
+ */
+int copy_remote_vm_str(struct task_struct *tsk, unsigned long addr,
+		void *buf, int len, unsigned int gup_flags)
+{
+	struct mm_struct *mm;
+	int ret;
+
+	mm = get_task_mm(tsk);
+	if (!mm) {
+		*(char *)buf = '\0';
+		return -EFAULT;
+	}
+
+	ret = __copy_remote_vm_str(mm, addr, buf, len);
+
+	mmput(mm);
+
+	return ret;
+}
+EXPORT_SYMBOL_GPL(copy_remote_vm_str);
+
 /**
  * nommu_shrink_inode_mappings - Shrink the shared mappings on an inode
  * @inode: The inode to check