Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

bpf: Add bpf_copy_from_user_task_str kfunc #8326

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions include/linux/mm.h
Original file line number Diff line number Diff line change
Expand Up @@ -2484,6 +2484,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_str_from_process_vm(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,
Expand Down
46 changes: 46 additions & 0 deletions kernel/bpf/helpers.c
Original file line number Diff line number Diff line change
Expand Up @@ -3072,6 +3072,51 @@ __bpf_kfunc void bpf_local_irq_restore(unsigned long *flags__irq_flag)
local_irq_restore(*flags__irq_flag);
}

/**
* bpf_copy_from_user_task_str() - Copy a string from an task's address space
* @dst: Destination address, in kernel space. This buffer must be
* at least @dst__sz bytes long.
* @dst__sz: Maximum number of bytes to copy, includes the trailing NUL.
* @unsafe_ptr__ign: Source address in the task's address space.
* @tsk: The task whose address space will be used
* @flags: The only supported flag is BPF_F_PAD_ZEROS
*
* Copies a NULL-terminated string from a task's address space to BPF space.
* If user string is too long this will still ensure zero termination in the
* dst buffer unless buffer size is 0.
*
* If BPF_F_PAD_ZEROS flag is set, memset the tail of @dst to 0 on success and
* memset all of @dst on failure.
*/
__bpf_kfunc int bpf_copy_from_user_task_str(void *dst, u32 dst__sz, const void __user *unsafe_ptr__ign, struct task_struct *tsk, u64 flags)
{
int count = dst__sz - 1;
int ret = 0;

if (unlikely(flags & ~BPF_F_PAD_ZEROS))
return -EINVAL;

if (unlikely(!dst__sz))
return 0;

ret = copy_str_from_process_vm(tsk, (unsigned long)unsafe_ptr__ign, dst, count, 0);

if (ret <= 0) {
if (flags & BPF_F_PAD_ZEROS)
memset((char *)dst, 0, dst__sz);
return ret;
}

if (ret < count) {
if (flags & BPF_F_PAD_ZEROS)
memset((char *)dst + ret, 0, dst__sz - ret);
} else {
((char *)dst)[count] = '\0';
}

return ret + 1;
}

__bpf_kfunc_end_defs();

BTF_KFUNCS_START(generic_btf_ids)
Expand Down Expand Up @@ -3164,6 +3209,7 @@ BTF_ID_FLAGS(func, bpf_iter_bits_new, KF_ITER_NEW)
BTF_ID_FLAGS(func, bpf_iter_bits_next, KF_ITER_NEXT | KF_RET_NULL)
BTF_ID_FLAGS(func, bpf_iter_bits_destroy, KF_ITER_DESTROY)
BTF_ID_FLAGS(func, bpf_copy_from_user_str, KF_SLEEPABLE)
BTF_ID_FLAGS(func, bpf_copy_from_user_task_str, KF_SLEEPABLE)
BTF_ID_FLAGS(func, bpf_get_kmem_cache)
BTF_ID_FLAGS(func, bpf_iter_kmem_cache_new, KF_ITER_NEW | KF_SLEEPABLE)
BTF_ID_FLAGS(func, bpf_iter_kmem_cache_next, KF_ITER_NEXT | KF_RET_NULL | KF_SLEEPABLE)
Expand Down
101 changes: 101 additions & 0 deletions mm/memory.c
Original file line number Diff line number Diff line change
Expand Up @@ -6673,6 +6673,75 @@ static int __access_remote_vm(struct mm_struct *mm, unsigned long addr,
return buf - old_buf;
}

/*
* Copy a string from another process's address space as given in mm.
* Don't return partial results. If there is any error return -EFAULT.
*/
static int __copy_str_from_remote_vm(struct mm_struct *mm, unsigned long addr,
void *buf, int len, unsigned int gup_flags)
{
void *old_buf = buf;
int err = 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)) {
mmap_read_unlock(mm);
return -EFAULT;
}

while (len) {
int bytes, offset, retval;
void *maddr;
struct vm_area_struct *vma = NULL;
struct page *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.
*/
err = -EFAULT;
break;
}

bytes = len;
offset = addr & (PAGE_SIZE - 1);
if (bytes > PAGE_SIZE - offset)
bytes = PAGE_SIZE - offset;

maddr = kmap_local_page(page);
retval = strncpy_from_user(buf, (const char __user *)addr, bytes);
unmap_and_put_page(page, maddr);

if (retval < 0) {
err = retval;
break;
}

len -= retval;
buf += retval;
addr += retval;

/* Found the end of the string */
if (retval < bytes)
break;
}
mmap_read_unlock(mm);

if (err)
return err;

return buf - old_buf;
}

/**
* access_remote_vm - access another process' address space
* @mm: the mm_struct of the target address space
Expand Down Expand Up @@ -6714,6 +6783,38 @@ int access_process_vm(struct task_struct *tsk, unsigned long addr,
}
EXPORT_SYMBOL_GPL(access_process_vm);

/**
* copy_str_from_process_vm - copy a string from another process's address space.
* @tsk: the task of the target address space
* @addr: start address to access
* @buf: source or destination buffer
* @len: number of bytes to transfer
* @gup_flags: flags modifying lookup behaviour
*
* The caller must hold a reference on @mm.
*
* Return: number of bytes copied from source to destination. If the string
* is shorter than @len then return the length of the string.
* On any error, return -EFAULT.
*/
int copy_str_from_process_vm(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)
return -EFAULT;

ret = __copy_str_from_remote_vm(mm, addr, buf, len, gup_flags);

mmput(mm);

return ret;
}
EXPORT_SYMBOL_GPL(copy_str_from_process_vm);

/*
* Print the name of a VMA.
*/
Expand Down
7 changes: 7 additions & 0 deletions tools/testing/selftests/bpf/prog_tests/bpf_iter.c
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@
#include "bpf_iter_ksym.skel.h"
#include "bpf_iter_sockmap.skel.h"

static char test_data[] = "test_data";

static void test_btf_id_or_null(void)
{
struct bpf_iter_test_kern3 *skel;
Expand Down Expand Up @@ -328,12 +330,17 @@ static void test_task_sleepable(void)
if (!ASSERT_OK_PTR(skel, "bpf_iter_tasks__open_and_load"))
return;

skel->bss->user_ptr = test_data;
do_dummy_read(skel->progs.dump_task_sleepable);

ASSERT_GT(skel->bss->num_expected_failure_copy_from_user_task, 0,
"num_expected_failure_copy_from_user_task");
ASSERT_GT(skel->bss->num_success_copy_from_user_task, 0,
"num_success_copy_from_user_task");
ASSERT_GT(skel->bss->num_expected_failure_copy_from_user_task_str, 0,
"num_expected_failure_copy_from_user_task_str");
ASSERT_GT(skel->bss->num_success_copy_from_user_task_str, 0,
"num_success_copy_from_user_task_str");

bpf_iter_tasks__destroy(skel);
}
Expand Down
1 change: 1 addition & 0 deletions tools/testing/selftests/bpf/prog_tests/read_vsyscall.c
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ struct read_ret_desc {
{ .name = "copy_from_user", .ret = -EFAULT },
{ .name = "copy_from_user_task", .ret = -EFAULT },
{ .name = "copy_from_user_str", .ret = -EFAULT },
{ .name = "copy_from_user_task_str", .ret = -EFAULT },
};

void test_read_vsyscall(void)
Expand Down
55 changes: 55 additions & 0 deletions tools/testing/selftests/bpf/progs/bpf_iter_tasks.c
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ char _license[] SEC("license") = "GPL";
uint32_t tid = 0;
int num_unknown_tid = 0;
int num_known_tid = 0;
void *user_ptr = 0;

SEC("iter/task")
int dump_task(struct bpf_iter__task *ctx)
Expand All @@ -35,7 +36,9 @@ int dump_task(struct bpf_iter__task *ctx)
}

int num_expected_failure_copy_from_user_task = 0;
int num_expected_failure_copy_from_user_task_str = 0;
int num_success_copy_from_user_task = 0;
int num_success_copy_from_user_task_str = 0;

SEC("iter.s/task")
int dump_task_sleepable(struct bpf_iter__task *ctx)
Expand All @@ -44,6 +47,9 @@ int dump_task_sleepable(struct bpf_iter__task *ctx)
struct task_struct *task = ctx->task;
static const char info[] = " === END ===";
struct pt_regs *regs;
char task_str1[10] = "aaaaaaaaaa";
char task_str2[10], task_str3[10];
char task_str4[20] = "aaaaaaaaaaaaaaaaaaaa";
void *ptr;
uint32_t user_data = 0;
int ret;
Expand Down Expand Up @@ -78,8 +84,57 @@ int dump_task_sleepable(struct bpf_iter__task *ctx)
BPF_SEQ_PRINTF(seq, "%s\n", info);
return 0;
}

++num_success_copy_from_user_task;

/* Read an invalid pointer and ensure we get an error */
ptr = NULL;
ret = bpf_copy_from_user_task_str((char *)task_str1, sizeof(task_str1), ptr, task, 0);
if (ret >= 0 || task_str1[9] != 'a') {
BPF_SEQ_PRINTF(seq, "%s\n", info);
return 0;
}

/* Read an invalid pointer and ensure we get error with pad zeros flag */
ptr = NULL;
ret = bpf_copy_from_user_task_str((char *)task_str1, sizeof(task_str1), ptr, task, BPF_F_PAD_ZEROS);
if (ret >= 0 || task_str1[9] != '\0') {
BPF_SEQ_PRINTF(seq, "%s\n", info);
return 0;
}

++num_expected_failure_copy_from_user_task_str;

/* Same length as the string */
ret = bpf_copy_from_user_task_str((char *)task_str2, 10, user_ptr, task, 0);
if (bpf_strncmp(task_str2, 10, "test_data\0") != 0 || ret != 10) {
BPF_SEQ_PRINTF(seq, "%s\n", info);
return 0;
}

/* Shorter length than the string */
ret = bpf_copy_from_user_task_str((char *)task_str3, 9, user_ptr, task, 0);
if (bpf_strncmp(task_str3, 9, "test_dat\0") != 0 || ret != 9) {
BPF_SEQ_PRINTF(seq, "%s\n", info);
return 0;
}

/* Longer length than the string */
ret = bpf_copy_from_user_task_str((char *)task_str4, 20, user_ptr, task, 0);
if (bpf_strncmp(task_str4, 10, "test_data\0") != 0 || ret != 10 || task_str4[sizeof(task_str4) - 1] != 'a') {
BPF_SEQ_PRINTF(seq, "%s\n", info);
return 0;
}

/* Longer length than the string with pad zeros flag */
ret = bpf_copy_from_user_task_str((char *)task_str4, 20, user_ptr, task, BPF_F_PAD_ZEROS);
if (bpf_strncmp(task_str4, 10, "test_data\0") != 0 || ret != 10 || task_str4[sizeof(task_str4) - 1] != '\0') {
BPF_SEQ_PRINTF(seq, "%s\n", info);
return 0;
}

++num_success_copy_from_user_task_str;

if (ctx->meta->seq_num == 0)
BPF_SEQ_PRINTF(seq, " tgid gid data\n");

Expand Down
6 changes: 4 additions & 2 deletions tools/testing/selftests/bpf/progs/read_vsyscall.c
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,15 @@

int target_pid = 0;
void *user_ptr = 0;
int read_ret[9];
int read_ret[10];

char _license[] SEC("license") = "GPL";

/*
* This is the only kfunc, the others are helpers
* These are the kfuncs, the others are helpers
*/
int bpf_copy_from_user_str(void *dst, u32, const void *, u64) __weak __ksym;
int bpf_copy_from_user_task_str(void *dst, u32, const void *, struct task_struct *, u64) __weak __ksym;

SEC("fentry/" SYS_PREFIX "sys_nanosleep")
int do_probe_read(void *ctx)
Expand Down Expand Up @@ -47,6 +48,7 @@ int do_copy_from_user(void *ctx)
read_ret[7] = bpf_copy_from_user_task(buf, sizeof(buf), user_ptr,
bpf_get_current_task_btf(), 0);
read_ret[8] = bpf_copy_from_user_str((char *)buf, sizeof(buf), user_ptr, 0);
read_ret[9] = bpf_copy_from_user_task_str((char *)buf, sizeof(buf), user_ptr, bpf_get_current_task_btf(), 0);

return 0;
}
Loading