| // SPDX-License-Identifier: GPL-2.0-only |
| /* |
| * Ioctl to read verity metadata |
| * |
| * Copyright 2021 Google LLC |
| */ |
| |
| #include "fsverity_private.h" |
| |
| #include <linux/backing-dev.h> |
| #include <linux/highmem.h> |
| #include <linux/sched/signal.h> |
| #include <linux/uaccess.h> |
| |
| static int fsverity_read_merkle_tree(struct inode *inode, |
| const struct fsverity_info *vi, |
| void __user *buf, u64 offset, int length) |
| { |
| const struct fsverity_operations *vops = inode->i_sb->s_vop; |
| u64 end_offset; |
| unsigned int offs_in_page; |
| pgoff_t index, last_index; |
| int retval = 0; |
| int err = 0; |
| |
| end_offset = min(offset + length, vi->tree_params.tree_size); |
| if (offset >= end_offset) |
| return 0; |
| offs_in_page = offset_in_page(offset); |
| last_index = (end_offset - 1) >> PAGE_SHIFT; |
| |
| /* |
| * Iterate through each Merkle tree page in the requested range and copy |
| * the requested portion to userspace. Note that the Merkle tree block |
| * size isn't important here, as we are returning a byte stream; i.e., |
| * we can just work with pages even if the tree block size != PAGE_SIZE. |
| */ |
| for (index = offset >> PAGE_SHIFT; index <= last_index; index++) { |
| unsigned long num_ra_pages = |
| min_t(unsigned long, last_index - index + 1, |
| inode->i_sb->s_bdi->io_pages); |
| unsigned int bytes_to_copy = min_t(u64, end_offset - offset, |
| PAGE_SIZE - offs_in_page); |
| struct page *page; |
| const void *virt; |
| |
| page = vops->read_merkle_tree_page(inode, index, num_ra_pages); |
| if (IS_ERR(page)) { |
| err = PTR_ERR(page); |
| fsverity_err(inode, |
| "Error %d reading Merkle tree page %lu", |
| err, index); |
| break; |
| } |
| |
| virt = kmap(page); |
| if (copy_to_user(buf, virt + offs_in_page, bytes_to_copy)) { |
| kunmap(page); |
| put_page(page); |
| err = -EFAULT; |
| break; |
| } |
| kunmap(page); |
| put_page(page); |
| |
| retval += bytes_to_copy; |
| buf += bytes_to_copy; |
| offset += bytes_to_copy; |
| |
| if (fatal_signal_pending(current)) { |
| err = -EINTR; |
| break; |
| } |
| cond_resched(); |
| offs_in_page = 0; |
| } |
| return retval ? retval : err; |
| } |
| |
| /* Copy the requested portion of the buffer to userspace. */ |
| static int fsverity_read_buffer(void __user *dst, u64 offset, int length, |
| const void *src, size_t src_length) |
| { |
| if (offset >= src_length) |
| return 0; |
| src += offset; |
| src_length -= offset; |
| |
| length = min_t(size_t, length, src_length); |
| |
| if (copy_to_user(dst, src, length)) |
| return -EFAULT; |
| |
| return length; |
| } |
| |
| static int fsverity_read_descriptor(struct inode *inode, |
| void __user *buf, u64 offset, int length) |
| { |
| struct fsverity_descriptor *desc; |
| size_t desc_size; |
| int res; |
| |
| res = fsverity_get_descriptor(inode, &desc, &desc_size); |
| if (res) |
| return res; |
| |
| /* don't include the signature */ |
| desc_size = offsetof(struct fsverity_descriptor, signature); |
| desc->sig_size = 0; |
| |
| res = fsverity_read_buffer(buf, offset, length, desc, desc_size); |
| |
| kfree(desc); |
| return res; |
| } |
| |
| static int fsverity_read_signature(struct inode *inode, |
| void __user *buf, u64 offset, int length) |
| { |
| struct fsverity_descriptor *desc; |
| size_t desc_size; |
| int res; |
| |
| res = fsverity_get_descriptor(inode, &desc, &desc_size); |
| if (res) |
| return res; |
| |
| if (desc->sig_size == 0) { |
| res = -ENODATA; |
| goto out; |
| } |
| |
| /* |
| * Include only the signature. Note that fsverity_get_descriptor() |
| * already verified that sig_size is in-bounds. |
| */ |
| res = fsverity_read_buffer(buf, offset, length, desc->signature, |
| le32_to_cpu(desc->sig_size)); |
| out: |
| kfree(desc); |
| return res; |
| } |
| |
| /** |
| * fsverity_ioctl_read_metadata() - read verity metadata from a file |
| * @filp: file to read the metadata from |
| * @uarg: user pointer to fsverity_read_metadata_arg |
| * |
| * Return: length read on success, 0 on EOF, -errno on failure |
| */ |
| int fsverity_ioctl_read_metadata(struct file *filp, const void __user *uarg) |
| { |
| struct inode *inode = file_inode(filp); |
| const struct fsverity_info *vi; |
| struct fsverity_read_metadata_arg arg; |
| int length; |
| void __user *buf; |
| |
| vi = fsverity_get_info(inode); |
| if (!vi) |
| return -ENODATA; /* not a verity file */ |
| /* |
| * Note that we don't have to explicitly check that the file is open for |
| * reading, since verity files can only be opened for reading. |
| */ |
| |
| if (copy_from_user(&arg, uarg, sizeof(arg))) |
| return -EFAULT; |
| |
| if (arg.__reserved) |
| return -EINVAL; |
| |
| /* offset + length must not overflow. */ |
| if (arg.offset + arg.length < arg.offset) |
| return -EINVAL; |
| |
| /* Ensure that the return value will fit in INT_MAX. */ |
| length = min_t(u64, arg.length, INT_MAX); |
| |
| buf = u64_to_user_ptr(arg.buf_ptr); |
| |
| switch (arg.metadata_type) { |
| case FS_VERITY_METADATA_TYPE_MERKLE_TREE: |
| return fsverity_read_merkle_tree(inode, vi, buf, arg.offset, |
| length); |
| case FS_VERITY_METADATA_TYPE_DESCRIPTOR: |
| return fsverity_read_descriptor(inode, buf, arg.offset, length); |
| case FS_VERITY_METADATA_TYPE_SIGNATURE: |
| return fsverity_read_signature(inode, buf, arg.offset, length); |
| default: |
| return -EINVAL; |
| } |
| } |
| EXPORT_SYMBOL_GPL(fsverity_ioctl_read_metadata); |