18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
28c2ecf20Sopenharmony_ci#include <linux/fs.h>
38c2ecf20Sopenharmony_ci#include <linux/fs_struct.h>
48c2ecf20Sopenharmony_ci#include <linux/kernel_read_file.h>
58c2ecf20Sopenharmony_ci#include <linux/security.h>
68c2ecf20Sopenharmony_ci#include <linux/vmalloc.h>
78c2ecf20Sopenharmony_ci
88c2ecf20Sopenharmony_ci/**
98c2ecf20Sopenharmony_ci * kernel_read_file() - read file contents into a kernel buffer
108c2ecf20Sopenharmony_ci *
118c2ecf20Sopenharmony_ci * @file	file to read from
128c2ecf20Sopenharmony_ci * @offset	where to start reading from (see below).
138c2ecf20Sopenharmony_ci * @buf		pointer to a "void *" buffer for reading into (if
148c2ecf20Sopenharmony_ci *		*@buf is NULL, a buffer will be allocated, and
158c2ecf20Sopenharmony_ci *		@buf_size will be ignored)
168c2ecf20Sopenharmony_ci * @buf_size	size of buf, if already allocated. If @buf not
178c2ecf20Sopenharmony_ci *		allocated, this is the largest size to allocate.
188c2ecf20Sopenharmony_ci * @file_size	if non-NULL, the full size of @file will be
198c2ecf20Sopenharmony_ci *		written here.
208c2ecf20Sopenharmony_ci * @id		the kernel_read_file_id identifying the type of
218c2ecf20Sopenharmony_ci *		file contents being read (for LSMs to examine)
228c2ecf20Sopenharmony_ci *
238c2ecf20Sopenharmony_ci * @offset must be 0 unless both @buf and @file_size are non-NULL
248c2ecf20Sopenharmony_ci * (i.e. the caller must be expecting to read partial file contents
258c2ecf20Sopenharmony_ci * via an already-allocated @buf, in at most @buf_size chunks, and
268c2ecf20Sopenharmony_ci * will be able to determine when the entire file was read by
278c2ecf20Sopenharmony_ci * checking @file_size). This isn't a recommended way to read a
288c2ecf20Sopenharmony_ci * file, though, since it is possible that the contents might
298c2ecf20Sopenharmony_ci * change between calls to kernel_read_file().
308c2ecf20Sopenharmony_ci *
318c2ecf20Sopenharmony_ci * Returns number of bytes read (no single read will be bigger
328c2ecf20Sopenharmony_ci * than INT_MAX), or negative on error.
338c2ecf20Sopenharmony_ci *
348c2ecf20Sopenharmony_ci */
358c2ecf20Sopenharmony_ciint kernel_read_file(struct file *file, loff_t offset, void **buf,
368c2ecf20Sopenharmony_ci		     size_t buf_size, size_t *file_size,
378c2ecf20Sopenharmony_ci		     enum kernel_read_file_id id)
388c2ecf20Sopenharmony_ci{
398c2ecf20Sopenharmony_ci	loff_t i_size, pos;
408c2ecf20Sopenharmony_ci	size_t copied;
418c2ecf20Sopenharmony_ci	void *allocated = NULL;
428c2ecf20Sopenharmony_ci	bool whole_file;
438c2ecf20Sopenharmony_ci	int ret;
448c2ecf20Sopenharmony_ci
458c2ecf20Sopenharmony_ci	if (offset != 0 && (!*buf || !file_size))
468c2ecf20Sopenharmony_ci		return -EINVAL;
478c2ecf20Sopenharmony_ci
488c2ecf20Sopenharmony_ci	if (!S_ISREG(file_inode(file)->i_mode))
498c2ecf20Sopenharmony_ci		return -EINVAL;
508c2ecf20Sopenharmony_ci
518c2ecf20Sopenharmony_ci	ret = deny_write_access(file);
528c2ecf20Sopenharmony_ci	if (ret)
538c2ecf20Sopenharmony_ci		return ret;
548c2ecf20Sopenharmony_ci
558c2ecf20Sopenharmony_ci	i_size = i_size_read(file_inode(file));
568c2ecf20Sopenharmony_ci	if (i_size <= 0) {
578c2ecf20Sopenharmony_ci		ret = -EINVAL;
588c2ecf20Sopenharmony_ci		goto out;
598c2ecf20Sopenharmony_ci	}
608c2ecf20Sopenharmony_ci	/* The file is too big for sane activities. */
618c2ecf20Sopenharmony_ci	if (i_size > INT_MAX) {
628c2ecf20Sopenharmony_ci		ret = -EFBIG;
638c2ecf20Sopenharmony_ci		goto out;
648c2ecf20Sopenharmony_ci	}
658c2ecf20Sopenharmony_ci	/* The entire file cannot be read in one buffer. */
668c2ecf20Sopenharmony_ci	if (!file_size && offset == 0 && i_size > buf_size) {
678c2ecf20Sopenharmony_ci		ret = -EFBIG;
688c2ecf20Sopenharmony_ci		goto out;
698c2ecf20Sopenharmony_ci	}
708c2ecf20Sopenharmony_ci
718c2ecf20Sopenharmony_ci	whole_file = (offset == 0 && i_size <= buf_size);
728c2ecf20Sopenharmony_ci	ret = security_kernel_read_file(file, id, whole_file);
738c2ecf20Sopenharmony_ci	if (ret)
748c2ecf20Sopenharmony_ci		goto out;
758c2ecf20Sopenharmony_ci
768c2ecf20Sopenharmony_ci	if (file_size)
778c2ecf20Sopenharmony_ci		*file_size = i_size;
788c2ecf20Sopenharmony_ci
798c2ecf20Sopenharmony_ci	if (!*buf)
808c2ecf20Sopenharmony_ci		*buf = allocated = vmalloc(i_size);
818c2ecf20Sopenharmony_ci	if (!*buf) {
828c2ecf20Sopenharmony_ci		ret = -ENOMEM;
838c2ecf20Sopenharmony_ci		goto out;
848c2ecf20Sopenharmony_ci	}
858c2ecf20Sopenharmony_ci
868c2ecf20Sopenharmony_ci	pos = offset;
878c2ecf20Sopenharmony_ci	copied = 0;
888c2ecf20Sopenharmony_ci	while (copied < buf_size) {
898c2ecf20Sopenharmony_ci		ssize_t bytes;
908c2ecf20Sopenharmony_ci		size_t wanted = min_t(size_t, buf_size - copied,
918c2ecf20Sopenharmony_ci					      i_size - pos);
928c2ecf20Sopenharmony_ci
938c2ecf20Sopenharmony_ci		bytes = kernel_read(file, *buf + copied, wanted, &pos);
948c2ecf20Sopenharmony_ci		if (bytes < 0) {
958c2ecf20Sopenharmony_ci			ret = bytes;
968c2ecf20Sopenharmony_ci			goto out_free;
978c2ecf20Sopenharmony_ci		}
988c2ecf20Sopenharmony_ci
998c2ecf20Sopenharmony_ci		if (bytes == 0)
1008c2ecf20Sopenharmony_ci			break;
1018c2ecf20Sopenharmony_ci		copied += bytes;
1028c2ecf20Sopenharmony_ci	}
1038c2ecf20Sopenharmony_ci
1048c2ecf20Sopenharmony_ci	if (whole_file) {
1058c2ecf20Sopenharmony_ci		if (pos != i_size) {
1068c2ecf20Sopenharmony_ci			ret = -EIO;
1078c2ecf20Sopenharmony_ci			goto out_free;
1088c2ecf20Sopenharmony_ci		}
1098c2ecf20Sopenharmony_ci
1108c2ecf20Sopenharmony_ci		ret = security_kernel_post_read_file(file, *buf, i_size, id);
1118c2ecf20Sopenharmony_ci	}
1128c2ecf20Sopenharmony_ci
1138c2ecf20Sopenharmony_ciout_free:
1148c2ecf20Sopenharmony_ci	if (ret < 0) {
1158c2ecf20Sopenharmony_ci		if (allocated) {
1168c2ecf20Sopenharmony_ci			vfree(*buf);
1178c2ecf20Sopenharmony_ci			*buf = NULL;
1188c2ecf20Sopenharmony_ci		}
1198c2ecf20Sopenharmony_ci	}
1208c2ecf20Sopenharmony_ci
1218c2ecf20Sopenharmony_ciout:
1228c2ecf20Sopenharmony_ci	allow_write_access(file);
1238c2ecf20Sopenharmony_ci	return ret == 0 ? copied : ret;
1248c2ecf20Sopenharmony_ci}
1258c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(kernel_read_file);
1268c2ecf20Sopenharmony_ci
1278c2ecf20Sopenharmony_ciint kernel_read_file_from_path(const char *path, loff_t offset, void **buf,
1288c2ecf20Sopenharmony_ci			       size_t buf_size, size_t *file_size,
1298c2ecf20Sopenharmony_ci			       enum kernel_read_file_id id)
1308c2ecf20Sopenharmony_ci{
1318c2ecf20Sopenharmony_ci	struct file *file;
1328c2ecf20Sopenharmony_ci	int ret;
1338c2ecf20Sopenharmony_ci
1348c2ecf20Sopenharmony_ci	if (!path || !*path)
1358c2ecf20Sopenharmony_ci		return -EINVAL;
1368c2ecf20Sopenharmony_ci
1378c2ecf20Sopenharmony_ci	file = filp_open(path, O_RDONLY, 0);
1388c2ecf20Sopenharmony_ci	if (IS_ERR(file))
1398c2ecf20Sopenharmony_ci		return PTR_ERR(file);
1408c2ecf20Sopenharmony_ci
1418c2ecf20Sopenharmony_ci	ret = kernel_read_file(file, offset, buf, buf_size, file_size, id);
1428c2ecf20Sopenharmony_ci	fput(file);
1438c2ecf20Sopenharmony_ci	return ret;
1448c2ecf20Sopenharmony_ci}
1458c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(kernel_read_file_from_path);
1468c2ecf20Sopenharmony_ci
1478c2ecf20Sopenharmony_ciint kernel_read_file_from_path_initns(const char *path, loff_t offset,
1488c2ecf20Sopenharmony_ci				      void **buf, size_t buf_size,
1498c2ecf20Sopenharmony_ci				      size_t *file_size,
1508c2ecf20Sopenharmony_ci				      enum kernel_read_file_id id)
1518c2ecf20Sopenharmony_ci{
1528c2ecf20Sopenharmony_ci	struct file *file;
1538c2ecf20Sopenharmony_ci	struct path root;
1548c2ecf20Sopenharmony_ci	int ret;
1558c2ecf20Sopenharmony_ci
1568c2ecf20Sopenharmony_ci	if (!path || !*path)
1578c2ecf20Sopenharmony_ci		return -EINVAL;
1588c2ecf20Sopenharmony_ci
1598c2ecf20Sopenharmony_ci	task_lock(&init_task);
1608c2ecf20Sopenharmony_ci	get_fs_root(init_task.fs, &root);
1618c2ecf20Sopenharmony_ci	task_unlock(&init_task);
1628c2ecf20Sopenharmony_ci
1638c2ecf20Sopenharmony_ci	file = file_open_root(&root, path, O_RDONLY, 0);
1648c2ecf20Sopenharmony_ci	path_put(&root);
1658c2ecf20Sopenharmony_ci	if (IS_ERR(file))
1668c2ecf20Sopenharmony_ci		return PTR_ERR(file);
1678c2ecf20Sopenharmony_ci
1688c2ecf20Sopenharmony_ci	ret = kernel_read_file(file, offset, buf, buf_size, file_size, id);
1698c2ecf20Sopenharmony_ci	fput(file);
1708c2ecf20Sopenharmony_ci	return ret;
1718c2ecf20Sopenharmony_ci}
1728c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(kernel_read_file_from_path_initns);
1738c2ecf20Sopenharmony_ci
1748c2ecf20Sopenharmony_ciint kernel_read_file_from_fd(int fd, loff_t offset, void **buf,
1758c2ecf20Sopenharmony_ci			     size_t buf_size, size_t *file_size,
1768c2ecf20Sopenharmony_ci			     enum kernel_read_file_id id)
1778c2ecf20Sopenharmony_ci{
1788c2ecf20Sopenharmony_ci	struct fd f = fdget(fd);
1798c2ecf20Sopenharmony_ci	int ret = -EBADF;
1808c2ecf20Sopenharmony_ci
1818c2ecf20Sopenharmony_ci	if (!f.file || !(f.file->f_mode & FMODE_READ))
1828c2ecf20Sopenharmony_ci		goto out;
1838c2ecf20Sopenharmony_ci
1848c2ecf20Sopenharmony_ci	ret = kernel_read_file(f.file, offset, buf, buf_size, file_size, id);
1858c2ecf20Sopenharmony_ciout:
1868c2ecf20Sopenharmony_ci	fdput(f);
1878c2ecf20Sopenharmony_ci	return ret;
1888c2ecf20Sopenharmony_ci}
1898c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(kernel_read_file_from_fd);
190