mirror of
https://github.com/torvalds/linux.git
synced 2026-03-08 03:44:45 +01:00
mm: Add copy_remote_vm_str() for readng C strings from remote VM
Similar to `access_process_vm()` but specific to strings. Also chunks reads by page and utilizes `strscpy()` for handling null termination. The primary motivation for this change is to copy strings from a non-current task/process in BPF. There is already a helper `bpf_copy_from_user_task()`, which uses `access_process_vm()` but one to handle strings would be very helpful. Signed-off-by: Jordan Rome <linux@jordanrome.com> Signed-off-by: Andrii Nakryiko <andrii@kernel.org> Reviewed-by: Shakeel Butt <shakeel.butt@linux.dev> Link: https://lore.kernel.org/bpf/20250213152125.1837400-1-linux@jordanrome.com
This commit is contained in:
parent
ac13c50872
commit
f0b79944e6
3 changed files with 202 additions and 0 deletions
|
|
@ -2486,6 +2486,11 @@ 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);
|
||||
|
||||
#ifdef CONFIG_BPF_SYSCALL
|
||||
extern int copy_remote_vm_str(struct task_struct *tsk, unsigned long addr,
|
||||
void *buf, int len, unsigned int gup_flags);
|
||||
#endif
|
||||
|
||||
long get_user_pages_remote(struct mm_struct *mm,
|
||||
unsigned long start, unsigned long nr_pages,
|
||||
unsigned int gup_flags, struct page **pages,
|
||||
|
|
|
|||
118
mm/memory.c
118
mm/memory.c
|
|
@ -6803,6 +6803,124 @@ int access_process_vm(struct task_struct *tsk, unsigned long addr,
|
|||
}
|
||||
EXPORT_SYMBOL_GPL(access_process_vm);
|
||||
|
||||
#ifdef CONFIG_BPF_SYSCALL
|
||||
/*
|
||||
* 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;
|
||||
|
||||
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;
|
||||
|
||||
if (unlikely(len == 0))
|
||||
return 0;
|
||||
|
||||
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);
|
||||
#endif /* CONFIG_BPF_SYSCALL */
|
||||
|
||||
/*
|
||||
* Print the name of a VMA.
|
||||
*/
|
||||
|
|
|
|||
79
mm/nommu.c
79
mm/nommu.c
|
|
@ -1708,6 +1708,85 @@ int access_process_vm(struct task_struct *tsk, unsigned long addr, void *buf, in
|
|||
}
|
||||
EXPORT_SYMBOL_GPL(access_process_vm);
|
||||
|
||||
#ifdef CONFIG_BPF_SYSCALL
|
||||
/*
|
||||
* 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;
|
||||
|
||||
if (unlikely(len == 0))
|
||||
return 0;
|
||||
|
||||
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);
|
||||
#endif /* CONFIG_BPF_SYSCALL */
|
||||
|
||||
/**
|
||||
* nommu_shrink_inode_mappings - Shrink the shared mappings on an inode
|
||||
* @inode: The inode to check
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue