fs/kernel_file_read: Add "offset" arg for partial reads

To perform partial reads, callers of kernel_read_file*() must have a
non-NULL file_size argument and a preallocated buffer. The new "offset"
argument can then be used to seek to specific locations in the file to
fill the buffer to, at most, "buf_size" per call.

Where possible, the LSM hooks can report whether a full file has been
read or not so that the contents can be reasoned about.

Signed-off-by: Kees Cook <keescook@chromium.org>
Link: https://lore.kernel.org/r/20201002173828.2099543-14-keescook@chromium.org
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
This commit is contained in:
Kees Cook 2020-10-02 10:38:25 -07:00 committed by Greg Kroah-Hartman
parent 34736daeec
commit 0fa8e08464
7 changed files with 65 additions and 34 deletions

View File

@ -499,7 +499,7 @@ fw_get_filesystem_firmware(struct device *device, struct fw_priv *fw_priv,
fw_priv->size = 0; fw_priv->size = 0;
/* load firmware files from the mount namespace of init */ /* load firmware files from the mount namespace of init */
rc = kernel_read_file_from_path_initns(path, &buffer, msize, rc = kernel_read_file_from_path_initns(path, 0, &buffer, msize,
NULL, NULL,
READING_FIRMWARE); READING_FIRMWARE);
if (rc < 0) { if (rc < 0) {

View File

@ -9,6 +9,7 @@
* kernel_read_file() - read file contents into a kernel buffer * kernel_read_file() - read file contents into a kernel buffer
* *
* @file file to read from * @file file to read from
* @offset where to start reading from (see below).
* @buf pointer to a "void *" buffer for reading into (if * @buf pointer to a "void *" buffer for reading into (if
* *@buf is NULL, a buffer will be allocated, and * *@buf is NULL, a buffer will be allocated, and
* @buf_size will be ignored) * @buf_size will be ignored)
@ -19,19 +20,31 @@
* @id the kernel_read_file_id identifying the type of * @id the kernel_read_file_id identifying the type of
* file contents being read (for LSMs to examine) * file contents being read (for LSMs to examine)
* *
* @offset must be 0 unless both @buf and @file_size are non-NULL
* (i.e. the caller must be expecting to read partial file contents
* via an already-allocated @buf, in at most @buf_size chunks, and
* will be able to determine when the entire file was read by
* checking @file_size). This isn't a recommended way to read a
* file, though, since it is possible that the contents might
* change between calls to kernel_read_file().
*
* Returns number of bytes read (no single read will be bigger * Returns number of bytes read (no single read will be bigger
* than INT_MAX), or negative on error. * than INT_MAX), or negative on error.
* *
*/ */
int kernel_read_file(struct file *file, void **buf, int kernel_read_file(struct file *file, loff_t offset, void **buf,
size_t buf_size, size_t *file_size, size_t buf_size, size_t *file_size,
enum kernel_read_file_id id) enum kernel_read_file_id id)
{ {
loff_t i_size, pos; loff_t i_size, pos;
ssize_t bytes = 0; size_t copied;
void *allocated = NULL; void *allocated = NULL;
bool whole_file;
int ret; int ret;
if (offset != 0 && (!*buf || !file_size))
return -EINVAL;
if (!S_ISREG(file_inode(file)->i_mode)) if (!S_ISREG(file_inode(file)->i_mode))
return -EINVAL; return -EINVAL;
@ -39,19 +52,27 @@ int kernel_read_file(struct file *file, void **buf,
if (ret) if (ret)
return ret; return ret;
ret = security_kernel_read_file(file, id, true);
if (ret)
goto out;
i_size = i_size_read(file_inode(file)); i_size = i_size_read(file_inode(file));
if (i_size <= 0) { if (i_size <= 0) {
ret = -EINVAL; ret = -EINVAL;
goto out; goto out;
} }
if (i_size > INT_MAX || i_size > buf_size) { /* The file is too big for sane activities. */
if (i_size > INT_MAX) {
ret = -EFBIG; ret = -EFBIG;
goto out; goto out;
} }
/* The entire file cannot be read in one buffer. */
if (!file_size && offset == 0 && i_size > buf_size) {
ret = -EFBIG;
goto out;
}
whole_file = (offset == 0 && i_size <= buf_size);
ret = security_kernel_read_file(file, id, whole_file);
if (ret)
goto out;
if (file_size) if (file_size)
*file_size = i_size; *file_size = i_size;
@ -62,9 +83,14 @@ int kernel_read_file(struct file *file, void **buf,
goto out; goto out;
} }
pos = 0; pos = offset;
while (pos < i_size) { copied = 0;
bytes = kernel_read(file, *buf + pos, i_size - pos, &pos); while (copied < buf_size) {
ssize_t bytes;
size_t wanted = min_t(size_t, buf_size - copied,
i_size - pos);
bytes = kernel_read(file, *buf + copied, wanted, &pos);
if (bytes < 0) { if (bytes < 0) {
ret = bytes; ret = bytes;
goto out_free; goto out_free;
@ -72,14 +98,17 @@ int kernel_read_file(struct file *file, void **buf,
if (bytes == 0) if (bytes == 0)
break; break;
copied += bytes;
} }
if (pos != i_size) { if (whole_file) {
ret = -EIO; if (pos != i_size) {
goto out_free; ret = -EIO;
} goto out_free;
}
ret = security_kernel_post_read_file(file, *buf, i_size, id); ret = security_kernel_post_read_file(file, *buf, i_size, id);
}
out_free: out_free:
if (ret < 0) { if (ret < 0) {
@ -91,11 +120,11 @@ int kernel_read_file(struct file *file, void **buf,
out: out:
allow_write_access(file); allow_write_access(file);
return ret == 0 ? pos : ret; return ret == 0 ? copied : ret;
} }
EXPORT_SYMBOL_GPL(kernel_read_file); EXPORT_SYMBOL_GPL(kernel_read_file);
int kernel_read_file_from_path(const char *path, void **buf, int kernel_read_file_from_path(const char *path, loff_t offset, void **buf,
size_t buf_size, size_t *file_size, size_t buf_size, size_t *file_size,
enum kernel_read_file_id id) enum kernel_read_file_id id)
{ {
@ -109,14 +138,15 @@ int kernel_read_file_from_path(const char *path, void **buf,
if (IS_ERR(file)) if (IS_ERR(file))
return PTR_ERR(file); return PTR_ERR(file);
ret = kernel_read_file(file, buf, buf_size, file_size, id); ret = kernel_read_file(file, offset, buf, buf_size, file_size, id);
fput(file); fput(file);
return ret; return ret;
} }
EXPORT_SYMBOL_GPL(kernel_read_file_from_path); EXPORT_SYMBOL_GPL(kernel_read_file_from_path);
int kernel_read_file_from_path_initns(const char *path, void **buf, int kernel_read_file_from_path_initns(const char *path, loff_t offset,
size_t buf_size, size_t *file_size, void **buf, size_t buf_size,
size_t *file_size,
enum kernel_read_file_id id) enum kernel_read_file_id id)
{ {
struct file *file; struct file *file;
@ -135,14 +165,14 @@ int kernel_read_file_from_path_initns(const char *path, void **buf,
if (IS_ERR(file)) if (IS_ERR(file))
return PTR_ERR(file); return PTR_ERR(file);
ret = kernel_read_file(file, buf, buf_size, file_size, id); ret = kernel_read_file(file, offset, buf, buf_size, file_size, id);
fput(file); fput(file);
return ret; return ret;
} }
EXPORT_SYMBOL_GPL(kernel_read_file_from_path_initns); EXPORT_SYMBOL_GPL(kernel_read_file_from_path_initns);
int kernel_read_file_from_fd(int fd, void **buf, size_t buf_size, int kernel_read_file_from_fd(int fd, loff_t offset, void **buf,
size_t *file_size, size_t buf_size, size_t *file_size,
enum kernel_read_file_id id) enum kernel_read_file_id id)
{ {
struct fd f = fdget(fd); struct fd f = fdget(fd);
@ -151,7 +181,7 @@ int kernel_read_file_from_fd(int fd, void **buf, size_t buf_size,
if (!f.file) if (!f.file)
goto out; goto out;
ret = kernel_read_file(f.file, buf, buf_size, file_size, id); ret = kernel_read_file(f.file, offset, buf, buf_size, file_size, id);
out: out:
fdput(f); fdput(f);
return ret; return ret;

View File

@ -35,19 +35,19 @@ static inline const char *kernel_read_file_id_str(enum kernel_read_file_id id)
return kernel_read_file_str[id]; return kernel_read_file_str[id];
} }
int kernel_read_file(struct file *file, int kernel_read_file(struct file *file, loff_t offset,
void **buf, size_t buf_size, void **buf, size_t buf_size,
size_t *file_size, size_t *file_size,
enum kernel_read_file_id id); enum kernel_read_file_id id);
int kernel_read_file_from_path(const char *path, int kernel_read_file_from_path(const char *path, loff_t offset,
void **buf, size_t buf_size, void **buf, size_t buf_size,
size_t *file_size, size_t *file_size,
enum kernel_read_file_id id); enum kernel_read_file_id id);
int kernel_read_file_from_path_initns(const char *path, int kernel_read_file_from_path_initns(const char *path, loff_t offset,
void **buf, size_t buf_size, void **buf, size_t buf_size,
size_t *file_size, size_t *file_size,
enum kernel_read_file_id id); enum kernel_read_file_id id);
int kernel_read_file_from_fd(int fd, int kernel_read_file_from_fd(int fd, loff_t offset,
void **buf, size_t buf_size, void **buf, size_t buf_size,
size_t *file_size, size_t *file_size,
enum kernel_read_file_id id); enum kernel_read_file_id id);

View File

@ -221,7 +221,7 @@ kimage_file_prepare_segments(struct kimage *image, int kernel_fd, int initrd_fd,
int ret; int ret;
void *ldata; void *ldata;
ret = kernel_read_file_from_fd(kernel_fd, &image->kernel_buf, ret = kernel_read_file_from_fd(kernel_fd, 0, &image->kernel_buf,
INT_MAX, NULL, READING_KEXEC_IMAGE); INT_MAX, NULL, READING_KEXEC_IMAGE);
if (ret < 0) if (ret < 0)
return ret; return ret;
@ -241,7 +241,7 @@ kimage_file_prepare_segments(struct kimage *image, int kernel_fd, int initrd_fd,
#endif #endif
/* It is possible that there no initramfs is being loaded */ /* It is possible that there no initramfs is being loaded */
if (!(flags & KEXEC_FILE_NO_INITRAMFS)) { if (!(flags & KEXEC_FILE_NO_INITRAMFS)) {
ret = kernel_read_file_from_fd(initrd_fd, &image->initrd_buf, ret = kernel_read_file_from_fd(initrd_fd, 0, &image->initrd_buf,
INT_MAX, NULL, INT_MAX, NULL,
READING_KEXEC_INITRAMFS); READING_KEXEC_INITRAMFS);
if (ret < 0) if (ret < 0)

View File

@ -4054,7 +4054,7 @@ SYSCALL_DEFINE3(finit_module, int, fd, const char __user *, uargs, int, flags)
|MODULE_INIT_IGNORE_VERMAGIC)) |MODULE_INIT_IGNORE_VERMAGIC))
return -EINVAL; return -EINVAL;
err = kernel_read_file_from_fd(fd, &hdr, INT_MAX, NULL, err = kernel_read_file_from_fd(fd, 0, &hdr, INT_MAX, NULL,
READING_MODULE); READING_MODULE);
if (err < 0) if (err < 0)
return err; return err;

View File

@ -175,7 +175,7 @@ int __init integrity_load_x509(const unsigned int id, const char *path)
int rc; int rc;
key_perm_t perm; key_perm_t perm;
rc = kernel_read_file_from_path(path, &data, INT_MAX, NULL, rc = kernel_read_file_from_path(path, 0, &data, INT_MAX, NULL,
READING_X509_CERTIFICATE); READING_X509_CERTIFICATE);
if (rc < 0) { if (rc < 0) {
pr_err("Unable to open file: %s (%d)", path, rc); pr_err("Unable to open file: %s (%d)", path, rc);

View File

@ -284,7 +284,8 @@ static ssize_t ima_read_policy(char *path)
datap = path; datap = path;
strsep(&datap, "\n"); strsep(&datap, "\n");
rc = kernel_read_file_from_path(path, &data, INT_MAX, NULL, READING_POLICY); rc = kernel_read_file_from_path(path, 0, &data, INT_MAX, NULL,
READING_POLICY);
if (rc < 0) { if (rc < 0) {
pr_err("Unable to open file: %s (%d)", path, rc); pr_err("Unable to open file: %s (%d)", path, rc);
return rc; return rc;