| // SPDX-License-Identifier: GPL-2.0 | |
| #include <linux/kernel.h> | |
| #include <linux/errno.h> | |
| #include <linux/fs.h> | |
| #include <linux/file.h> | |
| #include <linux/mm.h> | |
| #include <linux/slab.h> | |
| #include <linux/nospec.h> | |
| #include <linux/hugetlb.h> | |
| #include <linux/compat.h> | |
| #include <linux/io_uring.h> | |
| #include <linux/io_uring/cmd.h> | |
| #include <uapi/linux/io_uring.h> | |
| #include "filetable.h" | |
| #include "io_uring.h" | |
| #include "openclose.h" | |
| #include "rsrc.h" | |
| #include "memmap.h" | |
| #include "register.h" | |
| struct io_rsrc_update { | |
| struct file *file; | |
| u64 arg; | |
| u32 nr_args; | |
| u32 offset; | |
| }; | |
| static struct io_rsrc_node *io_sqe_buffer_register(struct io_ring_ctx *ctx, | |
| struct iovec *iov); | |
| static int hpage_acct_ref(struct io_ring_ctx *ctx, struct page *hpage, | |
| bool *acct_new) | |
| { | |
| unsigned long key = (unsigned long) hpage; | |
| unsigned long count; | |
| void *entry; | |
| int ret; | |
| lockdep_assert_held(&ctx->uring_lock); | |
| entry = xa_load(&ctx->hpage_acct, key); | |
| if (entry) { | |
| *acct_new = false; | |
| count = xa_to_value(entry) + 1; | |
| } else { | |
| ret = xa_reserve(&ctx->hpage_acct, key, GFP_KERNEL_ACCOUNT); | |
| if (ret) | |
| return ret; | |
| *acct_new = true; | |
| count = 1; | |
| } | |
| xa_store(&ctx->hpage_acct, key, xa_mk_value(count), GFP_KERNEL_ACCOUNT); | |
| return 0; | |
| } | |
| static bool hpage_acct_unref(struct io_ring_ctx *ctx, struct page *hpage) | |
| { | |
| unsigned long key = (unsigned long) hpage; | |
| unsigned long count; | |
| void *entry; | |
| lockdep_assert_held(&ctx->uring_lock); | |
| entry = xa_load(&ctx->hpage_acct, key); | |
| if (WARN_ON_ONCE(!entry)) | |
| return false; | |
| count = xa_to_value(entry); | |
| if (count == 1) { | |
| xa_erase(&ctx->hpage_acct, key); | |
| return true; | |
| } | |
| xa_store(&ctx->hpage_acct, key, xa_mk_value(count - 1), GFP_KERNEL_ACCOUNT); | |
| return false; | |
| } | |
| /* only define max */ | |
| #define IORING_MAX_FIXED_FILES (1U << 20) | |
| #define IORING_MAX_REG_BUFFERS (1U << 14) | |
| #define IO_CACHED_BVECS_SEGS 32 | |
| int __io_account_mem(struct user_struct *user, unsigned long nr_pages) | |
| { | |
| unsigned long page_limit, cur_pages, new_pages; | |
| if (!nr_pages) | |
| return 0; | |
| /* Don't allow more pages than we can safely lock */ | |
| page_limit = rlimit(RLIMIT_MEMLOCK) >> PAGE_SHIFT; | |
| cur_pages = atomic_long_read(&user->locked_vm); | |
| do { | |
| new_pages = cur_pages + nr_pages; | |
| if (new_pages > page_limit) | |
| return -ENOMEM; | |
| } while (!atomic_long_try_cmpxchg(&user->locked_vm, | |
| &cur_pages, new_pages)); | |
| return 0; | |
| } | |
| void io_unaccount_mem(struct user_struct *user, struct mm_struct *mm_account, | |
| unsigned long nr_pages) | |
| { | |
| if (user) | |
| __io_unaccount_mem(user, nr_pages); | |
| if (mm_account) | |
| atomic64_sub(nr_pages, &mm_account->pinned_vm); | |
| } | |
| int io_account_mem(struct user_struct *user, struct mm_struct *mm_account, | |
| unsigned long nr_pages) | |
| { | |
| int ret; | |
| if (user) { | |
| ret = __io_account_mem(user, nr_pages); | |
| if (ret) | |
| return ret; | |
| } | |
| if (mm_account) | |
| atomic64_add(nr_pages, &mm_account->pinned_vm); | |
| return 0; | |
| } | |
| int io_validate_user_buf_range(u64 uaddr, u64 ulen) | |
| { | |
| unsigned long tmp, base = (unsigned long)uaddr; | |
| unsigned long acct_len = (unsigned long)PAGE_ALIGN(ulen); | |
| if (!ulen) | |
| return -EFAULT; | |
| /* 32-bit sanity checking */ | |
| if (ulen > ULONG_MAX || uaddr > ULONG_MAX) | |
| return -EFAULT; | |
| /* cap to 1TB for 64-bit */ | |
| if (ulen > SZ_1T) | |
| return -EINVAL; | |
| if (check_add_overflow(base, acct_len, &tmp)) | |
| return -EOVERFLOW; | |
| return 0; | |
| } | |
| static void io_release_ubuf(void *priv) | |
| { | |
| struct io_mapped_ubuf *imu = priv; | |
| unsigned int i; | |
| for (i = 0; i < imu->nr_bvecs; i++) { | |
| struct folio *folio = bvec_folio(&imu->bvec[i]); | |
| unpin_user_folio(folio, 1); | |
| } | |
| } | |
| static struct io_mapped_ubuf *io_alloc_imu(struct io_ring_ctx *ctx, | |
| int nr_bvecs) | |
| { | |
| if (nr_bvecs <= IO_CACHED_BVECS_SEGS) | |
| return io_cache_alloc(&ctx->imu_cache, GFP_KERNEL); | |
| return kvmalloc_flex(struct io_mapped_ubuf, bvec, nr_bvecs); | |
| } | |
| static void io_free_imu(struct io_ring_ctx *ctx, struct io_mapped_ubuf *imu) | |
| { | |
| if (imu->nr_bvecs <= IO_CACHED_BVECS_SEGS) | |
| io_cache_free(&ctx->imu_cache, imu); | |
| else | |
| kvfree(imu); | |
| } | |
| static unsigned long io_buffer_unaccount_pages(struct io_ring_ctx *ctx, | |
| struct io_mapped_ubuf *imu) | |
| { | |
| struct page *seen = NULL; | |
| unsigned long acct = 0; | |
| int i; | |
| if (imu->flags & IO_REGBUF_F_KBUF || !ctx->user) | |
| return 0; | |
| for (i = 0; i < imu->nr_bvecs; i++) { | |
| struct page *page = imu->bvec[i].bv_page; | |
| struct page *hpage; | |
| if (!PageCompound(page)) { | |
| acct++; | |
| continue; | |
| } | |
| hpage = compound_head(page); | |
| if (hpage == seen) | |
| continue; | |
| seen = hpage; | |
| /* Unaccount on last reference */ | |
| if (hpage_acct_unref(ctx, hpage)) | |
| acct += page_size(hpage) >> PAGE_SHIFT; | |
| cond_resched(); | |
| } | |
| return acct; | |
| } | |
| static void io_buffer_unmap(struct io_ring_ctx *ctx, struct io_mapped_ubuf *imu) | |
| { | |
| unsigned long acct_pages = 0; | |
| /* Always decrement, so it works for cloned buffers too */ | |
| acct_pages = io_buffer_unaccount_pages(ctx, imu); | |
| if (unlikely(refcount_read(&imu->refs) > 1)) { | |
| if (!refcount_dec_and_test(&imu->refs)) | |
| return; | |
| } | |
| if (acct_pages) | |
| io_unaccount_mem(ctx->user, ctx->mm_account, acct_pages); | |
| imu->release(imu->priv); | |
| io_free_imu(ctx, imu); | |
| } | |
| struct io_rsrc_node *io_rsrc_node_alloc(struct io_ring_ctx *ctx, int type) | |
| { | |
| struct io_rsrc_node *node; | |
| node = io_cache_alloc(&ctx->node_cache, GFP_KERNEL); | |
| if (node) { | |
| node->type = type; | |
| node->refs = 1; | |
| node->tag = 0; | |
| node->file_ptr = 0; | |
| } | |
| return node; | |
| } | |
| bool io_rsrc_cache_init(struct io_ring_ctx *ctx) | |
| { | |
| const int imu_cache_size = struct_size_t(struct io_mapped_ubuf, bvec, | |
| IO_CACHED_BVECS_SEGS); | |
| const int node_size = sizeof(struct io_rsrc_node); | |
| bool ret; | |
| ret = io_alloc_cache_init(&ctx->node_cache, IO_ALLOC_CACHE_MAX, | |
| node_size, 0); | |
| ret |= io_alloc_cache_init(&ctx->imu_cache, IO_ALLOC_CACHE_MAX, | |
| imu_cache_size, 0); | |
| return ret; | |
| } | |
| void io_rsrc_cache_free(struct io_ring_ctx *ctx) | |
| { | |
| io_alloc_cache_free(&ctx->node_cache, kfree); | |
| io_alloc_cache_free(&ctx->imu_cache, kvfree); | |
| } | |
| static void io_clear_table_tags(struct io_rsrc_data *data) | |
| { | |
| int i; | |
| for (i = 0; i < data->nr; i++) { | |
| struct io_rsrc_node *node = data->nodes[i]; | |
| if (node) | |
| node->tag = 0; | |
| } | |
| } | |
| __cold void io_rsrc_data_free(struct io_ring_ctx *ctx, | |
| struct io_rsrc_data *data) | |
| { | |
| if (!data->nr) | |
| return; | |
| while (data->nr--) { | |
| if (data->nodes[data->nr]) | |
| io_put_rsrc_node(ctx, data->nodes[data->nr]); | |
| } | |
| kvfree(data->nodes); | |
| data->nodes = NULL; | |
| data->nr = 0; | |
| } | |
| __cold int io_rsrc_data_alloc(struct io_rsrc_data *data, unsigned nr) | |
| { | |
| data->nodes = kvmalloc_objs(struct io_rsrc_node *, nr, | |
| GFP_KERNEL_ACCOUNT | __GFP_ZERO); | |
| if (data->nodes) { | |
| data->nr = nr; | |
| return 0; | |
| } | |
| return -ENOMEM; | |
| } | |
| static int __io_sqe_files_update(struct io_ring_ctx *ctx, | |
| struct io_uring_rsrc_update2 *up, | |
| unsigned nr_args) | |
| { | |
| u64 __user *tags = u64_to_user_ptr(up->tags); | |
| __s32 __user *fds = u64_to_user_ptr(up->data); | |
| int fd, i, err = 0; | |
| unsigned int done; | |
| if (!ctx->file_table.data.nr) | |
| return -ENXIO; | |
| if (up->offset + nr_args > ctx->file_table.data.nr) | |
| return -EINVAL; | |
| for (done = 0; done < nr_args; done++) { | |
| u64 tag = 0; | |
| if ((tags && copy_from_user(&tag, &tags[done], sizeof(tag))) || | |
| copy_from_user(&fd, &fds[done], sizeof(fd))) { | |
| err = -EFAULT; | |
| break; | |
| } | |
| if ((fd == IORING_REGISTER_FILES_SKIP || fd == -1) && tag) { | |
| err = -EINVAL; | |
| break; | |
| } | |
| if (fd == IORING_REGISTER_FILES_SKIP) | |
| continue; | |
| i = up->offset + done; | |
| if (i >= ctx->file_table.data.nr) | |
| break; | |
| i = array_index_nospec(i, ctx->file_table.data.nr); | |
| if (io_reset_rsrc_node(ctx, &ctx->file_table.data, i)) | |
| io_file_bitmap_clear(&ctx->file_table, i); | |
| if (fd != -1) { | |
| struct file *file = fget(fd); | |
| struct io_rsrc_node *node; | |
| if (!file) { | |
| err = -EBADF; | |
| break; | |
| } | |
| /* | |
| * Don't allow io_uring instances to be registered. | |
| */ | |
| if (io_is_uring_fops(file)) { | |
| fput(file); | |
| err = -EBADF; | |
| break; | |
| } | |
| node = io_rsrc_node_alloc(ctx, IORING_RSRC_FILE); | |
| if (!node) { | |
| err = -ENOMEM; | |
| fput(file); | |
| break; | |
| } | |
| ctx->file_table.data.nodes[i] = node; | |
| if (tag) | |
| node->tag = tag; | |
| io_fixed_file_set(node, file); | |
| io_file_bitmap_set(&ctx->file_table, i); | |
| } | |
| } | |
| return done ? done : err; | |
| } | |
| static int __io_sqe_buffers_update(struct io_ring_ctx *ctx, | |
| struct io_uring_rsrc_update2 *up, | |
| unsigned int nr_args) | |
| { | |
| u64 __user *tags = u64_to_user_ptr(up->tags); | |
| struct iovec fast_iov, *iov; | |
| struct iovec __user *uvec; | |
| u64 user_data = up->data; | |
| __u32 done; | |
| int i, err; | |
| if (!ctx->buf_table.nr) | |
| return -ENXIO; | |
| if (up->offset + nr_args > ctx->buf_table.nr) | |
| return -EINVAL; | |
| for (done = 0; done < nr_args; done++) { | |
| struct io_rsrc_node *node; | |
| u64 tag = 0; | |
| uvec = u64_to_user_ptr(user_data); | |
| iov = iovec_from_user(uvec, 1, 1, &fast_iov, io_is_compat(ctx)); | |
| if (IS_ERR(iov)) { | |
| err = PTR_ERR(iov); | |
| break; | |
| } | |
| if (tags && copy_from_user(&tag, &tags[done], sizeof(tag))) { | |
| err = -EFAULT; | |
| break; | |
| } | |
| node = io_sqe_buffer_register(ctx, iov); | |
| if (IS_ERR(node)) { | |
| err = PTR_ERR(node); | |
| break; | |
| } | |
| if (tag) { | |
| if (!node) { | |
| err = -EINVAL; | |
| break; | |
| } | |
| node->tag = tag; | |
| } | |
| i = array_index_nospec(up->offset + done, ctx->buf_table.nr); | |
| io_reset_rsrc_node(ctx, &ctx->buf_table, i); | |
| ctx->buf_table.nodes[i] = node; | |
| if (io_is_compat(ctx)) | |
| user_data += sizeof(struct compat_iovec); | |
| else | |
| user_data += sizeof(struct iovec); | |
| } | |
| return done ? done : err; | |
| } | |
| static int __io_register_rsrc_update(struct io_ring_ctx *ctx, unsigned type, | |
| struct io_uring_rsrc_update2 *up, | |
| unsigned nr_args) | |
| { | |
| __u32 tmp; | |
| lockdep_assert_held(&ctx->uring_lock); | |
| if (check_add_overflow(up->offset, nr_args, &tmp)) | |
| return -EOVERFLOW; | |
| switch (type) { | |
| case IORING_RSRC_FILE: | |
| return __io_sqe_files_update(ctx, up, nr_args); | |
| case IORING_RSRC_BUFFER: | |
| return __io_sqe_buffers_update(ctx, up, nr_args); | |
| } | |
| return -EINVAL; | |
| } | |
| int io_register_files_update(struct io_ring_ctx *ctx, void __user *arg, | |
| unsigned nr_args) | |
| { | |
| struct io_uring_rsrc_update2 up; | |
| if (!nr_args) | |
| return -EINVAL; | |
| memset(&up, 0, sizeof(up)); | |
| if (copy_from_user(&up, arg, sizeof(struct io_uring_rsrc_update))) | |
| return -EFAULT; | |
| if (up.resv || up.resv2) | |
| return -EINVAL; | |
| return __io_register_rsrc_update(ctx, IORING_RSRC_FILE, &up, nr_args); | |
| } | |
| int io_register_rsrc_update(struct io_ring_ctx *ctx, void __user *arg, | |
| unsigned size, unsigned type) | |
| { | |
| struct io_uring_rsrc_update2 up; | |
| if (size != sizeof(up)) | |
| return -EINVAL; | |
| if (copy_from_user(&up, arg, sizeof(up))) | |
| return -EFAULT; | |
| if (!up.nr || up.resv || up.resv2) | |
| return -EINVAL; | |
| return __io_register_rsrc_update(ctx, type, &up, up.nr); | |
| } | |
| __cold int io_register_rsrc(struct io_ring_ctx *ctx, void __user *arg, | |
| unsigned int size, unsigned int type) | |
| { | |
| struct io_uring_rsrc_register rr; | |
| /* keep it extendible */ | |
| if (size != sizeof(rr)) | |
| return -EINVAL; | |
| memset(&rr, 0, sizeof(rr)); | |
| if (copy_from_user(&rr, arg, size)) | |
| return -EFAULT; | |
| if (!rr.nr || rr.resv2) | |
| return -EINVAL; | |
| if (rr.flags & ~IORING_RSRC_REGISTER_SPARSE) | |
| return -EINVAL; | |
| switch (type) { | |
| case IORING_RSRC_FILE: | |
| if (rr.flags & IORING_RSRC_REGISTER_SPARSE && rr.data) | |
| break; | |
| return io_sqe_files_register(ctx, u64_to_user_ptr(rr.data), | |
| rr.nr, u64_to_user_ptr(rr.tags)); | |
| case IORING_RSRC_BUFFER: | |
| if (rr.flags & IORING_RSRC_REGISTER_SPARSE && rr.data) | |
| break; | |
| return io_sqe_buffers_register(ctx, u64_to_user_ptr(rr.data), | |
| rr.nr, u64_to_user_ptr(rr.tags)); | |
| } | |
| return -EINVAL; | |
| } | |
| int io_files_update_prep(struct io_kiocb *req, const struct io_uring_sqe *sqe) | |
| { | |
| struct io_rsrc_update *up = io_kiocb_to_cmd(req, struct io_rsrc_update); | |
| if (unlikely(req->flags & (REQ_F_FIXED_FILE | REQ_F_BUFFER_SELECT))) | |
| return -EINVAL; | |
| if (sqe->rw_flags || sqe->splice_fd_in) | |
| return -EINVAL; | |
| up->offset = READ_ONCE(sqe->off); | |
| up->nr_args = READ_ONCE(sqe->len); | |
| if (!up->nr_args) | |
| return -EINVAL; | |
| up->arg = READ_ONCE(sqe->addr); | |
| return 0; | |
| } | |
| static int io_files_update_with_index_alloc(struct io_kiocb *req, | |
| unsigned int issue_flags) | |
| { | |
| struct io_rsrc_update *up = io_kiocb_to_cmd(req, struct io_rsrc_update); | |
| __s32 __user *fds = u64_to_user_ptr(up->arg); | |
| unsigned int done; | |
| struct file *file; | |
| int ret, fd; | |
| if (!req->ctx->file_table.data.nr) | |
| return -ENXIO; | |
| for (done = 0; done < up->nr_args; done++) { | |
| if (get_user(fd, &fds[done])) { | |
| ret = -EFAULT; | |
| break; | |
| } | |
| file = fget(fd); | |
| if (!file) { | |
| ret = -EBADF; | |
| break; | |
| } | |
| ret = io_fixed_fd_install(req, issue_flags, file, | |
| IORING_FILE_INDEX_ALLOC); | |
| if (ret < 0) | |
| break; | |
| if (put_user(ret, &fds[done])) { | |
| __io_close_fixed(req->ctx, issue_flags, ret); | |
| ret = -EFAULT; | |
| break; | |
| } | |
| } | |
| if (done) | |
| return done; | |
| return ret; | |
| } | |
| int io_files_update(struct io_kiocb *req, unsigned int issue_flags) | |
| { | |
| struct io_rsrc_update *up = io_kiocb_to_cmd(req, struct io_rsrc_update); | |
| struct io_ring_ctx *ctx = req->ctx; | |
| struct io_uring_rsrc_update2 up2; | |
| int ret; | |
| up2.offset = up->offset; | |
| up2.data = up->arg; | |
| up2.nr = 0; | |
| up2.tags = 0; | |
| up2.resv = 0; | |
| up2.resv2 = 0; | |
| if (up->offset == IORING_FILE_INDEX_ALLOC) { | |
| ret = io_files_update_with_index_alloc(req, issue_flags); | |
| } else { | |
| io_ring_submit_lock(ctx, issue_flags); | |
| ret = __io_register_rsrc_update(ctx, IORING_RSRC_FILE, | |
| &up2, up->nr_args); | |
| io_ring_submit_unlock(ctx, issue_flags); | |
| } | |
| if (ret < 0) | |
| req_set_fail(req); | |
| io_req_set_res(req, ret, 0); | |
| return IOU_COMPLETE; | |
| } | |
| void io_free_rsrc_node(struct io_ring_ctx *ctx, struct io_rsrc_node *node) | |
| { | |
| if (node->tag) | |
| io_post_aux_cqe(ctx, node->tag, 0, 0); | |
| switch (node->type) { | |
| case IORING_RSRC_FILE: | |
| fput(io_slot_file(node)); | |
| break; | |
| case IORING_RSRC_BUFFER: | |
| io_buffer_unmap(ctx, node->buf); | |
| break; | |
| default: | |
| WARN_ON_ONCE(1); | |
| break; | |
| } | |
| io_cache_free(&ctx->node_cache, node); | |
| } | |
| int io_sqe_files_unregister(struct io_ring_ctx *ctx) | |
| { | |
| if (!ctx->file_table.data.nr) | |
| return -ENXIO; | |
| io_free_file_tables(ctx, &ctx->file_table); | |
| io_file_table_set_alloc_range(ctx, 0, 0); | |
| return 0; | |
| } | |
| int io_sqe_files_register(struct io_ring_ctx *ctx, void __user *arg, | |
| unsigned nr_args, u64 __user *tags) | |
| { | |
| __s32 __user *fds = (__s32 __user *) arg; | |
| struct file *file; | |
| int fd, ret; | |
| unsigned i; | |
| if (ctx->file_table.data.nr) | |
| return -EBUSY; | |
| if (!nr_args) | |
| return -EINVAL; | |
| if (nr_args > IORING_MAX_FIXED_FILES) | |
| return -EMFILE; | |
| if (nr_args > rlimit(RLIMIT_NOFILE)) | |
| return -EMFILE; | |
| if (!io_alloc_file_tables(ctx, &ctx->file_table, nr_args)) | |
| return -ENOMEM; | |
| for (i = 0; i < nr_args; i++) { | |
| struct io_rsrc_node *node; | |
| u64 tag = 0; | |
| ret = -EFAULT; | |
| if (tags && copy_from_user(&tag, &tags[i], sizeof(tag))) | |
| goto fail; | |
| if (fds && copy_from_user(&fd, &fds[i], sizeof(fd))) | |
| goto fail; | |
| /* allow sparse sets */ | |
| if (!fds || fd == -1) { | |
| ret = -EINVAL; | |
| if (tag) | |
| goto fail; | |
| continue; | |
| } | |
| file = fget(fd); | |
| ret = -EBADF; | |
| if (unlikely(!file)) | |
| goto fail; | |
| /* | |
| * Don't allow io_uring instances to be registered. | |
| */ | |
| if (io_is_uring_fops(file)) { | |
| fput(file); | |
| goto fail; | |
| } | |
| ret = -ENOMEM; | |
| node = io_rsrc_node_alloc(ctx, IORING_RSRC_FILE); | |
| if (!node) { | |
| fput(file); | |
| goto fail; | |
| } | |
| if (tag) | |
| node->tag = tag; | |
| ctx->file_table.data.nodes[i] = node; | |
| io_fixed_file_set(node, file); | |
| io_file_bitmap_set(&ctx->file_table, i); | |
| } | |
| /* default it to the whole table */ | |
| io_file_table_set_alloc_range(ctx, 0, ctx->file_table.data.nr); | |
| return 0; | |
| fail: | |
| io_clear_table_tags(&ctx->file_table.data); | |
| io_sqe_files_unregister(ctx); | |
| return ret; | |
| } | |
| int io_sqe_buffers_unregister(struct io_ring_ctx *ctx) | |
| { | |
| if (!ctx->buf_table.nr) | |
| return -ENXIO; | |
| io_rsrc_data_free(ctx, &ctx->buf_table); | |
| return 0; | |
| } | |
| /* | |
| * Undo hpage_acct_ref() calls made during io_buffer_account_pin() on failure. | |
| * This operates on the pages array since imu->bvec isn't populated yet. | |
| */ | |
| static void io_buffer_unaccount_hpages(struct io_ring_ctx *ctx, | |
| struct page **pages, int nr_pages) | |
| { | |
| struct page *seen = NULL; | |
| int i; | |
| if (!ctx->user) | |
| return; | |
| for (i = 0; i < nr_pages; i++) { | |
| struct page *hpage; | |
| if (!PageCompound(pages[i])) | |
| continue; | |
| hpage = compound_head(pages[i]); | |
| if (hpage == seen) | |
| continue; | |
| seen = hpage; | |
| hpage_acct_unref(ctx, hpage); | |
| cond_resched(); | |
| } | |
| } | |
| static int io_buffer_account_pin(struct io_ring_ctx *ctx, struct page **pages, | |
| int nr_pages) | |
| { | |
| unsigned long acct_pages = 0; | |
| struct page *seen = NULL; | |
| int i, ret; | |
| if (!ctx->user) | |
| return 0; | |
| for (i = 0; i < nr_pages; i++) { | |
| struct page *hpage; | |
| bool acct_new; | |
| if (!PageCompound(pages[i])) { | |
| acct_pages++; | |
| continue; | |
| } | |
| hpage = compound_head(pages[i]); | |
| if (hpage == seen) | |
| continue; | |
| seen = hpage; | |
| ret = hpage_acct_ref(ctx, hpage, &acct_new); | |
| if (ret) { | |
| io_buffer_unaccount_hpages(ctx, pages, i); | |
| return ret; | |
| } | |
| if (acct_new) | |
| acct_pages += page_size(hpage) >> PAGE_SHIFT; | |
| cond_resched(); | |
| } | |
| /* Try to account the memory */ | |
| if (acct_pages) { | |
| ret = io_account_mem(ctx->user, ctx->mm_account, acct_pages); | |
| if (ret) { | |
| /* Undo the refs we just added */ | |
| io_buffer_unaccount_hpages(ctx, pages, nr_pages); | |
| return ret; | |
| } | |
| } | |
| return 0; | |
| } | |
| static bool io_coalesce_buffer(struct page ***pages, int *nr_pages, | |
| struct io_imu_folio_data *data) | |
| { | |
| struct page **page_array = *pages, **new_array = NULL; | |
| unsigned nr_pages_left = *nr_pages; | |
| unsigned nr_folios = data->nr_folios; | |
| unsigned i, j; | |
| /* Store head pages only*/ | |
| new_array = kvmalloc_objs(struct page *, nr_folios); | |
| if (!new_array) | |
| return false; | |
| for (i = 0, j = 0; i < nr_folios; i++) { | |
| struct page *p = compound_head(page_array[j]); | |
| struct folio *folio = page_folio(p); | |
| unsigned int nr; | |
| WARN_ON_ONCE(i > 0 && p != page_array[j]); | |
| nr = i ? data->nr_pages_mid : data->nr_pages_head; | |
| nr = min(nr, nr_pages_left); | |
| /* Drop all but one ref, the entire folio will remain pinned. */ | |
| if (nr > 1) | |
| unpin_user_folio(folio, nr - 1); | |
| j += nr; | |
| nr_pages_left -= nr; | |
| new_array[i] = p; | |
| } | |
| WARN_ON_ONCE(j != *nr_pages); | |
| kvfree(page_array); | |
| *pages = new_array; | |
| *nr_pages = nr_folios; | |
| return true; | |
| } | |
| bool io_check_coalesce_buffer(struct page **page_array, int nr_pages, | |
| struct io_imu_folio_data *data) | |
| { | |
| struct folio *folio = page_folio(page_array[0]); | |
| unsigned int count = 1, nr_folios = 1; | |
| int i; | |
| data->nr_pages_mid = folio_nr_pages(folio); | |
| data->folio_shift = folio_shift(folio); | |
| data->first_folio_page_idx = folio_page_idx(folio, page_array[0]); | |
| /* | |
| * Check if pages are contiguous inside a folio, and all folios have | |
| * the same page count except for the head and tail. | |
| */ | |
| for (i = 1; i < nr_pages; i++) { | |
| if (page_folio(page_array[i]) == folio && | |
| page_array[i] == page_array[i-1] + 1) { | |
| count++; | |
| continue; | |
| } | |
| if (nr_folios == 1) { | |
| if (folio_page_idx(folio, page_array[i-1]) != | |
| data->nr_pages_mid - 1) | |
| return false; | |
| data->nr_pages_head = count; | |
| } else if (count != data->nr_pages_mid) { | |
| return false; | |
| } | |
| folio = page_folio(page_array[i]); | |
| if (folio_size(folio) != (1UL << data->folio_shift) || | |
| folio_page_idx(folio, page_array[i]) != 0) | |
| return false; | |
| count = 1; | |
| nr_folios++; | |
| } | |
| if (nr_folios == 1) | |
| data->nr_pages_head = count; | |
| data->nr_folios = nr_folios; | |
| return true; | |
| } | |
| static struct io_rsrc_node *io_sqe_buffer_register(struct io_ring_ctx *ctx, | |
| struct iovec *iov) | |
| { | |
| struct io_mapped_ubuf *imu = NULL; | |
| struct page **pages = NULL; | |
| struct io_rsrc_node *node; | |
| unsigned long off; | |
| size_t size; | |
| int ret, nr_pages, i; | |
| struct io_imu_folio_data data; | |
| bool coalesced = false; | |
| if (!iov->iov_base) { | |
| if (iov->iov_len) | |
| return ERR_PTR(-EFAULT); | |
| /* remove the buffer without installing a new one */ | |
| return NULL; | |
| } | |
| ret = io_validate_user_buf_range((unsigned long)iov->iov_base, | |
| iov->iov_len); | |
| if (ret) | |
| return ERR_PTR(ret); | |
| node = io_rsrc_node_alloc(ctx, IORING_RSRC_BUFFER); | |
| if (!node) | |
| return ERR_PTR(-ENOMEM); | |
| ret = -ENOMEM; | |
| pages = io_pin_pages((unsigned long) iov->iov_base, iov->iov_len, | |
| &nr_pages); | |
| if (IS_ERR(pages)) { | |
| ret = PTR_ERR(pages); | |
| pages = NULL; | |
| goto done; | |
| } | |
| /* If it's huge page(s), try to coalesce them into fewer bvec entries */ | |
| if (nr_pages > 1 && io_check_coalesce_buffer(pages, nr_pages, &data)) { | |
| if (data.nr_pages_mid != 1) | |
| coalesced = io_coalesce_buffer(&pages, &nr_pages, &data); | |
| } | |
| imu = io_alloc_imu(ctx, nr_pages); | |
| if (!imu) | |
| goto done; | |
| imu->nr_bvecs = nr_pages; | |
| ret = io_buffer_account_pin(ctx, pages, nr_pages); | |
| if (ret) | |
| goto done; | |
| size = iov->iov_len; | |
| /* store original address for later verification */ | |
| imu->ubuf = (unsigned long) iov->iov_base; | |
| imu->len = iov->iov_len; | |
| imu->folio_shift = PAGE_SHIFT; | |
| imu->release = io_release_ubuf; | |
| imu->priv = imu; | |
| imu->flags = 0; | |
| imu->dir = IO_BUF_DEST | IO_BUF_SOURCE; | |
| if (coalesced) | |
| imu->folio_shift = data.folio_shift; | |
| refcount_set(&imu->refs, 1); | |
| off = (unsigned long)iov->iov_base & ~PAGE_MASK; | |
| if (coalesced) | |
| off += data.first_folio_page_idx << PAGE_SHIFT; | |
| node->buf = imu; | |
| ret = 0; | |
| for (i = 0; i < nr_pages; i++) { | |
| size_t vec_len; | |
| vec_len = min_t(size_t, size, (1UL << imu->folio_shift) - off); | |
| bvec_set_page(&imu->bvec[i], pages[i], vec_len, off); | |
| off = 0; | |
| size -= vec_len; | |
| } | |
| done: | |
| if (ret) { | |
| if (imu) | |
| io_free_imu(ctx, imu); | |
| if (pages) { | |
| for (i = 0; i < nr_pages; i++) | |
| unpin_user_folio(page_folio(pages[i]), 1); | |
| } | |
| io_cache_free(&ctx->node_cache, node); | |
| node = ERR_PTR(ret); | |
| } | |
| kvfree(pages); | |
| return node; | |
| } | |
| int io_sqe_buffers_register(struct io_ring_ctx *ctx, void __user *arg, | |
| unsigned int nr_args, u64 __user *tags) | |
| { | |
| struct io_rsrc_data data; | |
| struct iovec fast_iov, *iov = &fast_iov; | |
| const struct iovec __user *uvec; | |
| int i, ret; | |
| BUILD_BUG_ON(IORING_MAX_REG_BUFFERS >= (1u << 16)); | |
| if (ctx->buf_table.nr) | |
| return -EBUSY; | |
| if (!nr_args || nr_args > IORING_MAX_REG_BUFFERS) | |
| return -EINVAL; | |
| ret = io_rsrc_data_alloc(&data, nr_args); | |
| if (ret) | |
| return ret; | |
| if (!arg) | |
| memset(iov, 0, sizeof(*iov)); | |
| for (i = 0; i < nr_args; i++) { | |
| struct io_rsrc_node *node; | |
| u64 tag = 0; | |
| if (arg) { | |
| uvec = (struct iovec __user *) arg; | |
| iov = iovec_from_user(uvec, 1, 1, &fast_iov, io_is_compat(ctx)); | |
| if (IS_ERR(iov)) { | |
| ret = PTR_ERR(iov); | |
| break; | |
| } | |
| if (io_is_compat(ctx)) | |
| arg += sizeof(struct compat_iovec); | |
| else | |
| arg += sizeof(struct iovec); | |
| } | |
| if (tags) { | |
| if (copy_from_user(&tag, &tags[i], sizeof(tag))) { | |
| ret = -EFAULT; | |
| break; | |
| } | |
| } | |
| node = io_sqe_buffer_register(ctx, iov); | |
| if (IS_ERR(node)) { | |
| ret = PTR_ERR(node); | |
| break; | |
| } | |
| if (tag) { | |
| if (!node) { | |
| ret = -EINVAL; | |
| break; | |
| } | |
| node->tag = tag; | |
| } | |
| data.nodes[i] = node; | |
| } | |
| ctx->buf_table = data; | |
| if (ret) { | |
| io_clear_table_tags(&ctx->buf_table); | |
| io_sqe_buffers_unregister(ctx); | |
| } | |
| return ret; | |
| } | |
| static struct io_mapped_ubuf *io_kernel_buffer_init(struct io_ring_ctx *ctx, | |
| unsigned int nr_bvecs, | |
| unsigned int total_bytes, | |
| u8 dir, | |
| void (*release)(void *), | |
| void *priv, | |
| unsigned int index) | |
| { | |
| struct io_rsrc_data *data = &ctx->buf_table; | |
| struct io_mapped_ubuf *imu; | |
| struct io_rsrc_node *node; | |
| if (index >= data->nr) | |
| return ERR_PTR(-EINVAL); | |
| index = array_index_nospec(index, data->nr); | |
| if (data->nodes[index]) | |
| return ERR_PTR(-EBUSY); | |
| node = io_rsrc_node_alloc(ctx, IORING_RSRC_BUFFER); | |
| if (!node) | |
| return ERR_PTR(-ENOMEM); | |
| imu = io_alloc_imu(ctx, nr_bvecs); | |
| if (!imu) { | |
| io_cache_free(&ctx->node_cache, node); | |
| return ERR_PTR(-ENOMEM); | |
| } | |
| imu->ubuf = 0; | |
| imu->len = total_bytes; | |
| imu->folio_shift = PAGE_SHIFT; | |
| imu->nr_bvecs = nr_bvecs; | |
| refcount_set(&imu->refs, 1); | |
| imu->release = release; | |
| imu->priv = priv; | |
| imu->dir = dir; | |
| imu->flags = IO_REGBUF_F_KBUF; | |
| node->buf = imu; | |
| data->nodes[index] = node; | |
| return imu; | |
| } | |
| int io_buffer_register_request(struct io_uring_cmd *cmd, struct request *rq, | |
| void (*release)(void *), unsigned int index, | |
| unsigned int issue_flags) | |
| { | |
| struct io_ring_ctx *ctx = cmd_to_io_kiocb(cmd)->ctx; | |
| struct req_iterator rq_iter; | |
| struct io_mapped_ubuf *imu; | |
| struct bio_vec bv; | |
| /* | |
| * blk_rq_nr_phys_segments() may overestimate the number of bvecs | |
| * but avoids needing to iterate over the bvecs | |
| */ | |
| unsigned int nr_bvecs = blk_rq_nr_phys_segments(rq); | |
| unsigned int total_bytes = blk_rq_bytes(rq); | |
| int ret = 0; | |
| io_ring_submit_lock(ctx, issue_flags); | |
| imu = io_kernel_buffer_init(ctx, nr_bvecs, total_bytes, | |
| 1 << rq_data_dir(rq), release, rq, index); | |
| if (IS_ERR(imu)) { | |
| ret = PTR_ERR(imu); | |
| goto unlock; | |
| } | |
| nr_bvecs = 0; | |
| rq_for_each_bvec(bv, rq, rq_iter) | |
| imu->bvec[nr_bvecs++] = bv; | |
| imu->nr_bvecs = nr_bvecs; | |
| unlock: | |
| io_ring_submit_unlock(ctx, issue_flags); | |
| return ret; | |
| } | |
| EXPORT_SYMBOL_GPL(io_buffer_register_request); | |
| /* | |
| * bvs is copied internally. caller may free it on return. | |
| */ | |
| int io_buffer_register_bvec(struct io_uring_cmd *cmd, const struct bio_vec *bvs, | |
| unsigned int nr_bvecs, void (*release)(void *), | |
| void *priv, u8 dir, unsigned int index, | |
| unsigned int issue_flags) | |
| { | |
| struct io_ring_ctx *ctx = cmd_to_io_kiocb(cmd)->ctx; | |
| struct io_mapped_ubuf *imu; | |
| struct bio_vec *bvec; | |
| unsigned int i, total_bytes = 0; | |
| int ret = 0; | |
| for (i = 0; i < nr_bvecs; i++) | |
| total_bytes += bvs[i].bv_len; | |
| io_ring_submit_lock(ctx, issue_flags); | |
| imu = io_kernel_buffer_init(ctx, nr_bvecs, total_bytes, dir, release, | |
| priv, index); | |
| if (IS_ERR(imu)) { | |
| ret = PTR_ERR(imu); | |
| goto unlock; | |
| } | |
| bvec = imu->bvec; | |
| for (i = 0; i < nr_bvecs; i++) | |
| bvec[i] = bvs[i]; | |
| unlock: | |
| io_ring_submit_unlock(ctx, issue_flags); | |
| return ret; | |
| } | |
| EXPORT_SYMBOL_GPL(io_buffer_register_bvec); | |
| int io_buffer_unregister(struct io_uring_cmd *cmd, unsigned int index, | |
| unsigned int issue_flags) | |
| { | |
| struct io_ring_ctx *ctx = cmd_to_io_kiocb(cmd)->ctx; | |
| struct io_rsrc_data *data = &ctx->buf_table; | |
| struct io_rsrc_node *node; | |
| int ret = 0; | |
| io_ring_submit_lock(ctx, issue_flags); | |
| if (index >= data->nr) { | |
| ret = -EINVAL; | |
| goto unlock; | |
| } | |
| index = array_index_nospec(index, data->nr); | |
| node = data->nodes[index]; | |
| if (!node) { | |
| ret = -EINVAL; | |
| goto unlock; | |
| } | |
| if (!(node->buf->flags & IO_REGBUF_F_KBUF)) { | |
| ret = -EBUSY; | |
| goto unlock; | |
| } | |
| io_put_rsrc_node(ctx, node); | |
| data->nodes[index] = NULL; | |
| unlock: | |
| io_ring_submit_unlock(ctx, issue_flags); | |
| return ret; | |
| } | |
| EXPORT_SYMBOL_GPL(io_buffer_unregister); | |
| static int validate_fixed_range(u64 buf_addr, size_t len, | |
| const struct io_mapped_ubuf *imu) | |
| { | |
| u64 buf_end; | |
| if (unlikely(check_add_overflow(buf_addr, (u64)len, &buf_end))) | |
| return -EFAULT; | |
| /* not inside the mapped region */ | |
| if (unlikely(buf_addr < imu->ubuf || buf_end > (imu->ubuf + imu->len))) | |
| return -EFAULT; | |
| if (unlikely(len > MAX_RW_COUNT)) | |
| return -EFAULT; | |
| return 0; | |
| } | |
| static int io_import_kbuf(int ddir, struct iov_iter *iter, | |
| struct io_mapped_ubuf *imu, size_t len, size_t offset) | |
| { | |
| size_t count = len + offset; | |
| iov_iter_bvec(iter, ddir, imu->bvec, imu->nr_bvecs, count); | |
| iov_iter_advance(iter, offset); | |
| return 0; | |
| } | |
| static int io_import_fixed(int ddir, struct iov_iter *iter, | |
| struct io_mapped_ubuf *imu, | |
| u64 buf_addr, size_t len) | |
| { | |
| const struct bio_vec *bvec; | |
| size_t folio_mask; | |
| unsigned nr_segs; | |
| size_t offset; | |
| int ret; | |
| ret = validate_fixed_range(buf_addr, len, imu); | |
| if (unlikely(ret)) | |
| return ret; | |
| if (!(imu->dir & (1 << ddir))) | |
| return -EFAULT; | |
| if (unlikely(!len)) { | |
| iov_iter_bvec(iter, ddir, NULL, 0, 0); | |
| return 0; | |
| } | |
| offset = buf_addr - imu->ubuf; | |
| if (imu->flags & IO_REGBUF_F_KBUF) | |
| return io_import_kbuf(ddir, iter, imu, len, offset); | |
| /* | |
| * Don't use iov_iter_advance() here, as it's really slow for | |
| * using the latter parts of a big fixed buffer - it iterates | |
| * over each segment manually. We can cheat a bit here for user | |
| * registered nodes, because we know that: | |
| * | |
| * 1) it's a BVEC iter, we set it up | |
| * 2) all bvecs are the same in size, except potentially the | |
| * first and last bvec | |
| */ | |
| folio_mask = (1UL << imu->folio_shift) - 1; | |
| bvec = imu->bvec; | |
| if (offset >= bvec->bv_len) { | |
| unsigned long seg_skip; | |
| /* skip first vec */ | |
| offset -= bvec->bv_len; | |
| seg_skip = 1 + (offset >> imu->folio_shift); | |
| bvec += seg_skip; | |
| offset &= folio_mask; | |
| } | |
| nr_segs = (offset + len + bvec->bv_offset + folio_mask) >> imu->folio_shift; | |
| iov_iter_bvec(iter, ddir, bvec, nr_segs, len); | |
| iter->iov_offset = offset; | |
| return 0; | |
| } | |
| inline struct io_rsrc_node *io_find_buf_node(struct io_kiocb *req, | |
| unsigned issue_flags) | |
| { | |
| struct io_ring_ctx *ctx = req->ctx; | |
| struct io_rsrc_node *node; | |
| if (req->flags & REQ_F_BUF_NODE) | |
| return req->buf_node; | |
| req->flags |= REQ_F_BUF_NODE; | |
| io_ring_submit_lock(ctx, issue_flags); | |
| node = io_rsrc_node_lookup(&ctx->buf_table, req->buf_index); | |
| if (node) { | |
| node->refs++; | |
| req->buf_node = node; | |
| io_ring_submit_unlock(ctx, issue_flags); | |
| return node; | |
| } | |
| req->flags &= ~REQ_F_BUF_NODE; | |
| io_ring_submit_unlock(ctx, issue_flags); | |
| return NULL; | |
| } | |
| int io_import_reg_buf(struct io_kiocb *req, struct iov_iter *iter, | |
| u64 buf_addr, size_t len, int ddir, | |
| unsigned issue_flags) | |
| { | |
| struct io_rsrc_node *node; | |
| node = io_find_buf_node(req, issue_flags); | |
| if (!node) | |
| return -EFAULT; | |
| return io_import_fixed(ddir, iter, node->buf, buf_addr, len); | |
| } | |
| static int io_buffer_acct_cloned_hpages(struct io_ring_ctx *ctx, | |
| struct io_mapped_ubuf *imu) | |
| { | |
| struct page *seen = NULL; | |
| int i, ret = 0; | |
| if (imu->flags & IO_REGBUF_F_KBUF || !ctx->user) | |
| return 0; | |
| for (i = 0; i < imu->nr_bvecs; i++) { | |
| struct page *page = imu->bvec[i].bv_page; | |
| struct page *hpage; | |
| bool acct_new; | |
| if (!PageCompound(page)) | |
| continue; | |
| hpage = compound_head(page); | |
| if (hpage == seen) | |
| continue; | |
| seen = hpage; | |
| /* Atomically add reference for cloned buffer */ | |
| ret = hpage_acct_ref(ctx, hpage, &acct_new); | |
| if (ret) | |
| break; | |
| cond_resched(); | |
| } | |
| if (!ret) | |
| return 0; | |
| /* Undo refs we added for bvecs [0..i) */ | |
| seen = NULL; | |
| for (int j = 0; j < i; j++) { | |
| struct page *p = imu->bvec[j].bv_page; | |
| struct page *hp; | |
| if (!PageCompound(p)) | |
| continue; | |
| hp = compound_head(p); | |
| if (hp == seen) | |
| continue; | |
| seen = hp; | |
| hpage_acct_unref(ctx, hp); | |
| } | |
| return ret; | |
| } | |
| /* Lock two rings at once. The rings must be different! */ | |
| static void lock_two_rings(struct io_ring_ctx *ctx1, struct io_ring_ctx *ctx2) | |
| { | |
| if (ctx1 > ctx2) | |
| swap(ctx1, ctx2); | |
| mutex_lock(&ctx1->uring_lock); | |
| mutex_lock_nested(&ctx2->uring_lock, SINGLE_DEPTH_NESTING); | |
| } | |
| /* Both rings are locked by the caller. */ | |
| static int io_clone_buffers(struct io_ring_ctx *ctx, struct io_ring_ctx *src_ctx, | |
| struct io_uring_clone_buffers *arg) | |
| { | |
| struct io_rsrc_data data; | |
| int i, ret, off, nr; | |
| unsigned int nbufs; | |
| lockdep_assert_held(&ctx->uring_lock); | |
| lockdep_assert_held(&src_ctx->uring_lock); | |
| /* | |
| * Accounting state is shared between the two rings; that only works if | |
| * both rings are accounted towards the same counters. | |
| */ | |
| if (ctx->user != src_ctx->user || ctx->mm_account != src_ctx->mm_account) | |
| return -EINVAL; | |
| /* if offsets are given, must have nr specified too */ | |
| if (!arg->nr && (arg->dst_off || arg->src_off)) | |
| return -EINVAL; | |
| /* not allowed unless REPLACE is set */ | |
| if (ctx->buf_table.nr && !(arg->flags & IORING_REGISTER_DST_REPLACE)) | |
| return -EBUSY; | |
| nbufs = src_ctx->buf_table.nr; | |
| if (!nbufs) | |
| return -ENXIO; | |
| if (!arg->nr) | |
| arg->nr = nbufs; | |
| else if (arg->nr > nbufs) | |
| return -EINVAL; | |
| else if (arg->nr > IORING_MAX_REG_BUFFERS) | |
| return -EINVAL; | |
| if (check_add_overflow(arg->nr, arg->src_off, &off) || off > nbufs) | |
| return -EOVERFLOW; | |
| if (check_add_overflow(arg->nr, arg->dst_off, &nbufs)) | |
| return -EOVERFLOW; | |
| if (nbufs > IORING_MAX_REG_BUFFERS) | |
| return -EINVAL; | |
| ret = io_rsrc_data_alloc(&data, max(nbufs, ctx->buf_table.nr)); | |
| if (ret) | |
| return ret; | |
| /* Copy original dst nodes from before the cloned range */ | |
| for (i = 0; i < min(arg->dst_off, ctx->buf_table.nr); i++) { | |
| struct io_rsrc_node *node = ctx->buf_table.nodes[i]; | |
| if (node) { | |
| data.nodes[i] = node; | |
| node->refs++; | |
| } | |
| } | |
| off = arg->dst_off; | |
| i = arg->src_off; | |
| nr = arg->nr; | |
| while (nr--) { | |
| struct io_rsrc_node *dst_node, *src_node; | |
| src_node = io_rsrc_node_lookup(&src_ctx->buf_table, i); | |
| if (!src_node) { | |
| dst_node = NULL; | |
| } else { | |
| dst_node = io_rsrc_node_alloc(ctx, IORING_RSRC_BUFFER); | |
| if (!dst_node) { | |
| io_rsrc_data_free(ctx, &data); | |
| return -ENOMEM; | |
| } | |
| refcount_inc(&src_node->buf->refs); | |
| dst_node->buf = src_node->buf; | |
| /* track compound references to clones */ | |
| ret = io_buffer_acct_cloned_hpages(ctx, src_node->buf); | |
| if (ret) { | |
| refcount_dec(&src_node->buf->refs); | |
| io_cache_free(&ctx->node_cache, dst_node); | |
| io_rsrc_data_free(ctx, &data); | |
| return ret; | |
| } | |
| } | |
| data.nodes[off++] = dst_node; | |
| i++; | |
| } | |
| /* Copy original dst nodes from after the cloned range */ | |
| for (i = nbufs; i < ctx->buf_table.nr; i++) { | |
| struct io_rsrc_node *node = ctx->buf_table.nodes[i]; | |
| if (node) { | |
| data.nodes[i] = node; | |
| node->refs++; | |
| } | |
| } | |
| /* | |
| * If asked for replace, put the old table. data->nodes[] holds both | |
| * old and new nodes at this point. | |
| */ | |
| if (arg->flags & IORING_REGISTER_DST_REPLACE) | |
| io_rsrc_data_free(ctx, &ctx->buf_table); | |
| /* | |
| * ctx->buf_table must be empty now - either the contents are being | |
| * replaced and we just freed the table, or the contents are being | |
| * copied to a ring that does not have buffers yet (checked at function | |
| * entry). | |
| */ | |
| WARN_ON_ONCE(ctx->buf_table.nr); | |
| ctx->buf_table = data; | |
| return 0; | |
| } | |
| /* | |
| * Copy the registered buffers from the source ring whose file descriptor | |
| * is given in the src_fd to the current ring. This is identical to registering | |
| * the buffers with ctx, except faster as mappings already exist. | |
| * | |
| * Since the memory is already accounted once, don't account it again. | |
| */ | |
| int io_register_clone_buffers(struct io_ring_ctx *ctx, void __user *arg) | |
| { | |
| struct io_uring_clone_buffers buf; | |
| struct io_ring_ctx *src_ctx; | |
| bool registered_src; | |
| struct file *file; | |
| int ret; | |
| if (copy_from_user(&buf, arg, sizeof(buf))) | |
| return -EFAULT; | |
| if (buf.flags & ~(IORING_REGISTER_SRC_REGISTERED|IORING_REGISTER_DST_REPLACE)) | |
| return -EINVAL; | |
| if (!(buf.flags & IORING_REGISTER_DST_REPLACE) && ctx->buf_table.nr) | |
| return -EBUSY; | |
| if (memchr_inv(buf.pad, 0, sizeof(buf.pad))) | |
| return -EINVAL; | |
| registered_src = (buf.flags & IORING_REGISTER_SRC_REGISTERED) != 0; | |
| file = io_uring_ctx_get_file(buf.src_fd, registered_src); | |
| if (IS_ERR(file)) | |
| return PTR_ERR(file); | |
| src_ctx = file->private_data; | |
| if (src_ctx != ctx) { | |
| mutex_unlock(&ctx->uring_lock); | |
| lock_two_rings(ctx, src_ctx); | |
| if (src_ctx->submitter_task && | |
| src_ctx->submitter_task != current) { | |
| ret = -EEXIST; | |
| goto out; | |
| } | |
| } | |
| ret = io_clone_buffers(ctx, src_ctx, &buf); | |
| out: | |
| if (src_ctx != ctx) | |
| mutex_unlock(&src_ctx->uring_lock); | |
| if (!registered_src) | |
| fput(file); | |
| return ret; | |
| } | |
| void io_vec_free(struct iou_vec *iv) | |
| { | |
| if (!iv->iovec) | |
| return; | |
| kfree(iv->iovec); | |
| iv->iovec = NULL; | |
| iv->nr = 0; | |
| } | |
| int io_vec_realloc(struct iou_vec *iv, unsigned nr_entries) | |
| { | |
| gfp_t gfp = GFP_KERNEL_ACCOUNT | __GFP_NOWARN; | |
| struct iovec *iov; | |
| iov = kmalloc_objs(iov[0], nr_entries, gfp); | |
| if (!iov) | |
| return -ENOMEM; | |
| io_vec_free(iv); | |
| iv->iovec = iov; | |
| iv->nr = nr_entries; | |
| return 0; | |
| } | |
| static int io_vec_fill_bvec(int ddir, struct iov_iter *iter, | |
| struct io_mapped_ubuf *imu, | |
| struct iovec *iovec, unsigned nr_iovs, | |
| struct iou_vec *vec) | |
| { | |
| unsigned long folio_size = 1UL << imu->folio_shift; | |
| unsigned long folio_mask = folio_size - 1; | |
| struct bio_vec *res_bvec = vec->bvec; | |
| size_t total_len = 0; | |
| unsigned bvec_idx = 0; | |
| unsigned iov_idx; | |
| for (iov_idx = 0; iov_idx < nr_iovs; iov_idx++) { | |
| size_t iov_len = iovec[iov_idx].iov_len; | |
| u64 buf_addr = (u64)(uintptr_t)iovec[iov_idx].iov_base; | |
| struct bio_vec *src_bvec; | |
| size_t offset; | |
| int ret; | |
| ret = validate_fixed_range(buf_addr, iov_len, imu); | |
| if (unlikely(ret)) | |
| return ret; | |
| if (unlikely(!iov_len)) | |
| return -EFAULT; | |
| if (unlikely(check_add_overflow(total_len, iov_len, &total_len))) | |
| return -EOVERFLOW; | |
| offset = buf_addr - imu->ubuf; | |
| /* | |
| * Only the first bvec can have non zero bv_offset, account it | |
| * here and work with full folios below. | |
| */ | |
| offset += imu->bvec[0].bv_offset; | |
| src_bvec = imu->bvec + (offset >> imu->folio_shift); | |
| offset &= folio_mask; | |
| for (; iov_len; offset = 0, bvec_idx++, src_bvec++) { | |
| size_t seg_size = min_t(size_t, iov_len, | |
| folio_size - offset); | |
| bvec_set_page(&res_bvec[bvec_idx], | |
| src_bvec->bv_page, seg_size, offset); | |
| iov_len -= seg_size; | |
| } | |
| } | |
| if (total_len > MAX_RW_COUNT) | |
| return -EINVAL; | |
| iov_iter_bvec(iter, ddir, res_bvec, bvec_idx, total_len); | |
| return 0; | |
| } | |
| static int io_estimate_bvec_size(struct iovec *iov, unsigned nr_iovs, | |
| struct io_mapped_ubuf *imu) | |
| { | |
| unsigned shift = imu->folio_shift; | |
| size_t max_segs = 0; | |
| unsigned i; | |
| for (i = 0; i < nr_iovs; i++) { | |
| max_segs += (iov[i].iov_len >> shift) + 2; | |
| if (max_segs > INT_MAX) | |
| return -EOVERFLOW; | |
| } | |
| return max_segs; | |
| } | |
| static int io_vec_fill_kern_bvec(int ddir, struct iov_iter *iter, | |
| struct io_mapped_ubuf *imu, | |
| struct iovec *iovec, unsigned nr_iovs, | |
| struct iou_vec *vec) | |
| { | |
| const struct bio_vec *src_bvec = imu->bvec; | |
| struct bio_vec *res_bvec = vec->bvec; | |
| unsigned res_idx = 0; | |
| size_t total_len = 0; | |
| unsigned iov_idx; | |
| for (iov_idx = 0; iov_idx < nr_iovs; iov_idx++) { | |
| size_t offset = (size_t)(uintptr_t)iovec[iov_idx].iov_base; | |
| size_t iov_len = iovec[iov_idx].iov_len; | |
| struct bvec_iter bi = { | |
| .bi_size = offset + iov_len, | |
| }; | |
| struct bio_vec bv; | |
| bvec_iter_advance(src_bvec, &bi, offset); | |
| for_each_mp_bvec(bv, src_bvec, bi, bi) | |
| res_bvec[res_idx++] = bv; | |
| total_len += iov_len; | |
| } | |
| iov_iter_bvec(iter, ddir, res_bvec, res_idx, total_len); | |
| return 0; | |
| } | |
| static int iov_kern_bvec_size(const struct iovec *iov, | |
| const struct io_mapped_ubuf *imu, | |
| unsigned int *nr_seg) | |
| { | |
| size_t offset = (size_t)(uintptr_t)iov->iov_base; | |
| const struct bio_vec *bvec = imu->bvec; | |
| int start = 0, i = 0; | |
| size_t off = 0; | |
| int ret; | |
| ret = validate_fixed_range(offset, iov->iov_len, imu); | |
| if (unlikely(ret)) | |
| return ret; | |
| for (i = 0; off < offset + iov->iov_len && i < imu->nr_bvecs; | |
| off += bvec[i].bv_len, i++) { | |
| if (offset >= off && offset < off + bvec[i].bv_len) | |
| start = i; | |
| } | |
| *nr_seg = i - start; | |
| return 0; | |
| } | |
| static int io_kern_bvec_size(struct iovec *iov, unsigned nr_iovs, | |
| struct io_mapped_ubuf *imu, unsigned *nr_segs) | |
| { | |
| unsigned max_segs = 0; | |
| size_t total_len = 0; | |
| unsigned i; | |
| int ret; | |
| *nr_segs = 0; | |
| for (i = 0; i < nr_iovs; i++) { | |
| if (unlikely(!iov[i].iov_len)) | |
| return -EFAULT; | |
| if (unlikely(check_add_overflow(total_len, iov[i].iov_len, | |
| &total_len))) | |
| return -EOVERFLOW; | |
| ret = iov_kern_bvec_size(&iov[i], imu, &max_segs); | |
| if (unlikely(ret)) | |
| return ret; | |
| *nr_segs += max_segs; | |
| } | |
| if (total_len > MAX_RW_COUNT) | |
| return -EINVAL; | |
| return 0; | |
| } | |
| int io_import_reg_vec(int ddir, struct iov_iter *iter, | |
| struct io_kiocb *req, struct iou_vec *vec, | |
| unsigned nr_iovs, unsigned issue_flags) | |
| { | |
| struct io_rsrc_node *node; | |
| struct io_mapped_ubuf *imu; | |
| unsigned iovec_off; | |
| struct iovec *iov; | |
| unsigned nr_segs; | |
| node = io_find_buf_node(req, issue_flags); | |
| if (!node) | |
| return -EFAULT; | |
| imu = node->buf; | |
| if (!(imu->dir & (1 << ddir))) | |
| return -EFAULT; | |
| iovec_off = vec->nr - nr_iovs; | |
| iov = vec->iovec + iovec_off; | |
| if (imu->flags & IO_REGBUF_F_KBUF) { | |
| int ret = io_kern_bvec_size(iov, nr_iovs, imu, &nr_segs); | |
| if (unlikely(ret)) | |
| return ret; | |
| } else { | |
| int ret = io_estimate_bvec_size(iov, nr_iovs, imu); | |
| if (ret < 0) | |
| return ret; | |
| nr_segs = ret; | |
| } | |
| if (sizeof(struct bio_vec) > sizeof(struct iovec)) { | |
| size_t bvec_bytes; | |
| if (check_mul_overflow((size_t)nr_segs, sizeof(struct bio_vec), | |
| &bvec_bytes) || | |
| check_add_overflow(bvec_bytes, sizeof(*iov) - 1, | |
| &bvec_bytes)) | |
| return -EOVERFLOW; | |
| nr_segs = bvec_bytes / sizeof(*iov); | |
| nr_segs += nr_iovs; | |
| } | |
| if (nr_segs > vec->nr) { | |
| struct iou_vec tmp_vec = {}; | |
| int ret; | |
| ret = io_vec_realloc(&tmp_vec, nr_segs); | |
| if (ret) | |
| return ret; | |
| iovec_off = tmp_vec.nr - nr_iovs; | |
| memcpy(tmp_vec.iovec + iovec_off, iov, sizeof(*iov) * nr_iovs); | |
| io_vec_free(vec); | |
| *vec = tmp_vec; | |
| iov = vec->iovec + iovec_off; | |
| req->flags |= REQ_F_NEED_CLEANUP; | |
| } | |
| if (imu->flags & IO_REGBUF_F_KBUF) | |
| return io_vec_fill_kern_bvec(ddir, iter, imu, iov, nr_iovs, vec); | |
| return io_vec_fill_bvec(ddir, iter, imu, iov, nr_iovs, vec); | |
| } | |
| int io_prep_reg_iovec(struct io_kiocb *req, struct iou_vec *iv, | |
| const struct iovec __user *uvec, size_t uvec_segs) | |
| { | |
| struct iovec *iov; | |
| int iovec_off, ret; | |
| void *res; | |
| if (uvec_segs > iv->nr) { | |
| ret = io_vec_realloc(iv, uvec_segs); | |
| if (ret) | |
| return ret; | |
| req->flags |= REQ_F_NEED_CLEANUP; | |
| } | |
| /* pad iovec to the right */ | |
| iovec_off = iv->nr - uvec_segs; | |
| iov = iv->iovec + iovec_off; | |
| res = iovec_from_user(uvec, uvec_segs, uvec_segs, iov, | |
| io_is_compat(req->ctx)); | |
| if (IS_ERR(res)) | |
| return PTR_ERR(res); | |
| req->flags |= REQ_F_IMPORT_BUFFER; | |
| return 0; | |
| } |