From b44a7dfc6fa16e01f2497c9fa62c3926f94be174 Mon Sep 17 00:00:00 2001 From: Mimi Zohar Date: Mon, 28 Dec 2015 16:02:29 -0500 Subject: vfs: define a generic function to read a file from the kernel For a while it was looked down upon to directly read files from Linux. These days there exists a few mechanisms in the kernel that do just this though to load a file into a local buffer. There are minor but important checks differences on each. This patch set is the first attempt at resolving some of these differences. This patch introduces a common function for reading files from the kernel with the corresponding security post-read hook and function. Changelog v4+: - export security_kernel_post_read_file() - Fengguang Wu v3: - additional bounds checking - Luis v2: - To simplify patch review, re-ordered patches Signed-off-by: Mimi Zohar Reviewed-by: Luis R. Rodriguez Acked-by: Kees Cook Cc: Al Viro --- fs/exec.c | 53 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) (limited to 'fs') diff --git a/fs/exec.c b/fs/exec.c index dcd4ac7d3f1e..6b6668baa44a 100644 --- a/fs/exec.c +++ b/fs/exec.c @@ -56,6 +56,7 @@ #include #include #include +#include #include #include @@ -831,6 +832,58 @@ int kernel_read(struct file *file, loff_t offset, EXPORT_SYMBOL(kernel_read); +int kernel_read_file(struct file *file, void **buf, loff_t *size, + loff_t max_size) +{ + loff_t i_size, pos; + ssize_t bytes = 0; + int ret; + + if (!S_ISREG(file_inode(file)->i_mode) || max_size < 0) + return -EINVAL; + + i_size = i_size_read(file_inode(file)); + if (max_size > 0 && i_size > max_size) + return -EFBIG; + if (i_size <= 0) + return -EINVAL; + + *buf = vmalloc(i_size); + if (!*buf) + return -ENOMEM; + + pos = 0; + while (pos < i_size) { + bytes = kernel_read(file, pos, (char *)(*buf) + pos, + i_size - pos); + if (bytes < 0) { + ret = bytes; + goto out; + } + + if (bytes == 0) + break; + pos += bytes; + } + + if (pos != i_size) { + ret = -EIO; + goto out; + } + + ret = security_kernel_post_read_file(file, *buf, i_size); + if (!ret) + *size = pos; + +out: + if (ret < 0) { + vfree(*buf); + *buf = NULL; + } + return ret; +} +EXPORT_SYMBOL_GPL(kernel_read_file); + ssize_t read_code(struct file *file, unsigned long addr, loff_t pos, size_t len) { ssize_t res = vfs_read(file, (void __user *)addr, len, &pos); -- cgit v1.2.3 From bc8ca5b92d54f6f005fa73ad546f02fca26ddd85 Mon Sep 17 00:00:00 2001 From: Mimi Zohar Date: Sun, 24 Jan 2016 10:07:32 -0500 Subject: vfs: define kernel_read_file_id enumeration To differentiate between the kernel_read_file() callers, this patch defines a new enumeration named kernel_read_file_id and includes the caller identifier as an argument. Subsequent patches define READING_KEXEC_IMAGE, READING_KEXEC_INITRAMFS, READING_FIRMWARE, READING_MODULE, and READING_POLICY. Changelog v3: - Replace the IMA specific enumeration with a generic one. Signed-off-by: Mimi Zohar Acked-by: Kees Cook Acked-by: Luis R. Rodriguez Cc: Al Viro --- fs/exec.c | 4 ++-- include/linux/fs.h | 7 ++++++- include/linux/lsm_hooks.h | 4 +++- include/linux/security.h | 7 +++++-- security/security.c | 5 +++-- 5 files changed, 19 insertions(+), 8 deletions(-) (limited to 'fs') diff --git a/fs/exec.c b/fs/exec.c index 6b6668baa44a..1138dc502c77 100644 --- a/fs/exec.c +++ b/fs/exec.c @@ -833,7 +833,7 @@ int kernel_read(struct file *file, loff_t offset, EXPORT_SYMBOL(kernel_read); int kernel_read_file(struct file *file, void **buf, loff_t *size, - loff_t max_size) + loff_t max_size, enum kernel_read_file_id id) { loff_t i_size, pos; ssize_t bytes = 0; @@ -871,7 +871,7 @@ int kernel_read_file(struct file *file, void **buf, loff_t *size, goto out; } - ret = security_kernel_post_read_file(file, *buf, i_size); + ret = security_kernel_post_read_file(file, *buf, i_size, id); if (!ret) *size = pos; diff --git a/include/linux/fs.h b/include/linux/fs.h index 9a83d82b61ac..aa84bcb9c368 100644 --- a/include/linux/fs.h +++ b/include/linux/fs.h @@ -2576,8 +2576,13 @@ static inline void i_readcount_inc(struct inode *inode) #endif extern int do_pipe_flags(int *, int); +enum kernel_read_file_id { + READING_MAX_ID +}; + extern int kernel_read(struct file *, loff_t, char *, unsigned long); -extern int kernel_read_file(struct file *, void **, loff_t *, loff_t); +extern int kernel_read_file(struct file *, void **, loff_t *, loff_t, + enum kernel_read_file_id); extern ssize_t kernel_write(struct file *, const char *, size_t, loff_t); extern ssize_t __kernel_write(struct file *, const char *, size_t, loff_t *); extern struct file * open_exec(const char *); diff --git a/include/linux/lsm_hooks.h b/include/linux/lsm_hooks.h index f82631cc7248..2337f33913c1 100644 --- a/include/linux/lsm_hooks.h +++ b/include/linux/lsm_hooks.h @@ -567,6 +567,7 @@ * by the kernel. * @buf pointer to buffer containing the file contents. * @size length of the file contents. + * @id kernel read file identifier * Return 0 if permission is granted. * @task_fix_setuid: * Update the module's state after setting one or more of the user @@ -1464,7 +1465,8 @@ union security_list_options { int (*kernel_fw_from_file)(struct file *file, char *buf, size_t size); int (*kernel_module_request)(char *kmod_name); int (*kernel_module_from_file)(struct file *file); - int (*kernel_post_read_file)(struct file *file, char *buf, loff_t size); + int (*kernel_post_read_file)(struct file *file, char *buf, loff_t size, + enum kernel_read_file_id id); int (*task_fix_setuid)(struct cred *new, const struct cred *old, int flags); int (*task_setpgid)(struct task_struct *p, pid_t pgid); diff --git a/include/linux/security.h b/include/linux/security.h index f30f5647d1e1..b68ce94e4e00 100644 --- a/include/linux/security.h +++ b/include/linux/security.h @@ -28,6 +28,7 @@ #include #include #include +#include struct linux_binprm; struct cred; @@ -301,7 +302,8 @@ int security_kernel_create_files_as(struct cred *new, struct inode *inode); int security_kernel_fw_from_file(struct file *file, char *buf, size_t size); int security_kernel_module_request(char *kmod_name); int security_kernel_module_from_file(struct file *file); -int security_kernel_post_read_file(struct file *file, char *buf, loff_t size); +int security_kernel_post_read_file(struct file *file, char *buf, loff_t size, + enum kernel_read_file_id id); int security_task_fix_setuid(struct cred *new, const struct cred *old, int flags); int security_task_setpgid(struct task_struct *p, pid_t pgid); @@ -868,7 +870,8 @@ static inline int security_kernel_module_from_file(struct file *file) } static inline int security_kernel_post_read_file(struct file *file, - char *buf, loff_t size) + char *buf, loff_t size, + enum kernel_read_file_id id) { return 0; } diff --git a/security/security.c b/security/security.c index c98dd6bf4ebd..5b96eabaafd4 100644 --- a/security/security.c +++ b/security/security.c @@ -910,9 +910,10 @@ int security_kernel_module_from_file(struct file *file) return ima_module_check(file); } -int security_kernel_post_read_file(struct file *file, char *buf, loff_t size) +int security_kernel_post_read_file(struct file *file, char *buf, loff_t size, + enum kernel_read_file_id id) { - return call_int_hook(kernel_post_read_file, 0, file, buf, size); + return call_int_hook(kernel_post_read_file, 0, file, buf, size, id); } EXPORT_SYMBOL_GPL(security_kernel_post_read_file); -- cgit v1.2.3 From 09596b94f7d28595602482e69ed954deab707437 Mon Sep 17 00:00:00 2001 From: Mimi Zohar Date: Thu, 19 Nov 2015 12:39:22 -0500 Subject: vfs: define kernel_read_file_from_path This patch defines kernel_read_file_from_path(), a wrapper for the VFS common kernel_read_file(). Changelog: - revert error msg regression - reported by Sergey Senozhatsky - Separated from the IMA patch Signed-off-by: Mimi Zohar Acked-by: Kees Cook Acked-by: Luis R. Rodriguez Cc: Al Viro --- fs/exec.c | 19 +++++++++++++++++++ include/linux/fs.h | 2 ++ 2 files changed, 21 insertions(+) (limited to 'fs') diff --git a/fs/exec.c b/fs/exec.c index 1138dc502c77..64cb3bc788c1 100644 --- a/fs/exec.c +++ b/fs/exec.c @@ -884,6 +884,25 @@ out: } EXPORT_SYMBOL_GPL(kernel_read_file); +int kernel_read_file_from_path(char *path, void **buf, loff_t *size, + loff_t max_size, enum kernel_read_file_id id) +{ + struct file *file; + int ret; + + if (!path || !*path) + return -EINVAL; + + file = filp_open(path, O_RDONLY, 0); + if (IS_ERR(file)) + return PTR_ERR(file); + + ret = kernel_read_file(file, buf, size, max_size, id); + fput(file); + return ret; +} +EXPORT_SYMBOL_GPL(kernel_read_file_from_path); + ssize_t read_code(struct file *file, unsigned long addr, loff_t pos, size_t len) { ssize_t res = vfs_read(file, (void __user *)addr, len, &pos); diff --git a/include/linux/fs.h b/include/linux/fs.h index aa84bcb9c368..00fa5c45fd63 100644 --- a/include/linux/fs.h +++ b/include/linux/fs.h @@ -2583,6 +2583,8 @@ enum kernel_read_file_id { extern int kernel_read(struct file *, loff_t, char *, unsigned long); extern int kernel_read_file(struct file *, void **, loff_t *, loff_t, enum kernel_read_file_id); +extern int kernel_read_file_from_path(char *, void **, loff_t *, loff_t, + enum kernel_read_file_id); extern ssize_t kernel_write(struct file *, const char *, size_t, loff_t); extern ssize_t __kernel_write(struct file *, const char *, size_t, loff_t *); extern struct file * open_exec(const char *); -- cgit v1.2.3 From 39eeb4fb97f60dbdfc823c1a673a8844b9226b60 Mon Sep 17 00:00:00 2001 From: Mimi Zohar Date: Sat, 30 Jan 2016 22:23:26 -0500 Subject: security: define kernel_read_file hook The kernel_read_file security hook is called prior to reading the file into memory. Changelog v4+: - export security_kernel_read_file() Signed-off-by: Mimi Zohar Acked-by: Kees Cook Acked-by: Luis R. Rodriguez Acked-by: Casey Schaufler --- fs/exec.c | 4 ++++ include/linux/ima.h | 6 ++++++ include/linux/lsm_hooks.h | 8 ++++++++ include/linux/security.h | 7 +++++++ security/integrity/ima/ima_main.c | 16 ++++++++++++++++ security/security.c | 13 +++++++++++++ 6 files changed, 54 insertions(+) (limited to 'fs') diff --git a/fs/exec.c b/fs/exec.c index 64cb3bc788c1..8aaa38666119 100644 --- a/fs/exec.c +++ b/fs/exec.c @@ -842,6 +842,10 @@ int kernel_read_file(struct file *file, void **buf, loff_t *size, if (!S_ISREG(file_inode(file)->i_mode) || max_size < 0) return -EINVAL; + ret = security_kernel_read_file(file, id); + if (ret) + return ret; + i_size = i_size_read(file_inode(file)); if (max_size > 0 && i_size > max_size) return -EFBIG; diff --git a/include/linux/ima.h b/include/linux/ima.h index 7aea4863c244..6adcaea8101c 100644 --- a/include/linux/ima.h +++ b/include/linux/ima.h @@ -19,6 +19,7 @@ extern int ima_file_check(struct file *file, int mask, int opened); extern void ima_file_free(struct file *file); extern int ima_file_mmap(struct file *file, unsigned long prot); extern int ima_module_check(struct file *file); +extern int ima_read_file(struct file *file, enum kernel_read_file_id id); extern int ima_post_read_file(struct file *file, void *buf, loff_t size, enum kernel_read_file_id id); @@ -48,6 +49,11 @@ static inline int ima_module_check(struct file *file) return 0; } +static inline int ima_read_file(struct file *file, enum kernel_read_file_id id) +{ + return 0; +} + static inline int ima_post_read_file(struct file *file, void *buf, loff_t size, enum kernel_read_file_id id) { diff --git a/include/linux/lsm_hooks.h b/include/linux/lsm_hooks.h index 7d04a1220223..d32b7bd13635 100644 --- a/include/linux/lsm_hooks.h +++ b/include/linux/lsm_hooks.h @@ -552,6 +552,12 @@ * the kernel module to load. If the module is being loaded from a blob, * this argument will be NULL. * Return 0 if permission is granted. + * @kernel_read_file: + * Read a file specified by userspace. + * @file contains the file structure pointing to the file being read + * by the kernel. + * @id kernel read file identifier + * Return 0 if permission is granted. * @kernel_post_read_file: * Read a file specified by userspace. * @file contains the file structure pointing to the file being read @@ -1455,6 +1461,7 @@ union security_list_options { int (*kernel_create_files_as)(struct cred *new, struct inode *inode); int (*kernel_module_request)(char *kmod_name); int (*kernel_module_from_file)(struct file *file); + int (*kernel_read_file)(struct file *file, enum kernel_read_file_id id); int (*kernel_post_read_file)(struct file *file, char *buf, loff_t size, enum kernel_read_file_id id); int (*task_fix_setuid)(struct cred *new, const struct cred *old, @@ -1715,6 +1722,7 @@ struct security_hook_heads { struct list_head cred_transfer; struct list_head kernel_act_as; struct list_head kernel_create_files_as; + struct list_head kernel_read_file; struct list_head kernel_post_read_file; struct list_head kernel_module_request; struct list_head kernel_module_from_file; diff --git a/include/linux/security.h b/include/linux/security.h index cee1349e1155..071fb747fdbb 100644 --- a/include/linux/security.h +++ b/include/linux/security.h @@ -302,6 +302,7 @@ int security_kernel_act_as(struct cred *new, u32 secid); int security_kernel_create_files_as(struct cred *new, struct inode *inode); int security_kernel_module_request(char *kmod_name); int security_kernel_module_from_file(struct file *file); +int security_kernel_read_file(struct file *file, enum kernel_read_file_id id); int security_kernel_post_read_file(struct file *file, char *buf, loff_t size, enum kernel_read_file_id id); int security_task_fix_setuid(struct cred *new, const struct cred *old, @@ -863,6 +864,12 @@ static inline int security_kernel_module_from_file(struct file *file) return 0; } +static inline int security_kernel_read_file(struct file *file, + enum kernel_read_file_id id) +{ + return 0; +} + static inline int security_kernel_post_read_file(struct file *file, char *buf, loff_t size, enum kernel_read_file_id id) diff --git a/security/integrity/ima/ima_main.c b/security/integrity/ima/ima_main.c index e9651be17b72..bbb80df28fb1 100644 --- a/security/integrity/ima/ima_main.c +++ b/security/integrity/ima/ima_main.c @@ -337,6 +337,22 @@ int ima_module_check(struct file *file) return process_measurement(file, NULL, 0, MAY_EXEC, MODULE_CHECK, 0); } +/** + * ima_read_file - pre-measure/appraise hook decision based on policy + * @file: pointer to the file to be measured/appraised/audit + * @read_id: caller identifier + * + * Permit reading a file based on policy. The policy rules are written + * in terms of the policy identifier. Appraising the integrity of + * a file requires a file descriptor. + * + * For permission return 0, otherwise return -EACCES. + */ +int ima_read_file(struct file *file, enum kernel_read_file_id read_id) +{ + return 0; +} + /** * ima_post_read_file - in memory collect/appraise/audit measurement * @file: pointer to the file to be measured/appraised/audit diff --git a/security/security.c b/security/security.c index cd85be61c416..8e699f98a600 100644 --- a/security/security.c +++ b/security/security.c @@ -899,6 +899,17 @@ int security_kernel_module_from_file(struct file *file) return ima_module_check(file); } +int security_kernel_read_file(struct file *file, enum kernel_read_file_id id) +{ + int ret; + + ret = call_int_hook(kernel_read_file, 0, file, id); + if (ret) + return ret; + return ima_read_file(file, id); +} +EXPORT_SYMBOL_GPL(security_kernel_read_file); + int security_kernel_post_read_file(struct file *file, char *buf, loff_t size, enum kernel_read_file_id id) { @@ -1696,6 +1707,8 @@ struct security_hook_heads security_hook_heads = { LIST_HEAD_INIT(security_hook_heads.kernel_module_request), .kernel_module_from_file = LIST_HEAD_INIT(security_hook_heads.kernel_module_from_file), + .kernel_read_file = + LIST_HEAD_INIT(security_hook_heads.kernel_read_file), .kernel_post_read_file = LIST_HEAD_INIT(security_hook_heads.kernel_post_read_file), .task_fix_setuid = -- cgit v1.2.3 From b844f0ecbc5626ec26cfc70cb144a4c9b85dc3f2 Mon Sep 17 00:00:00 2001 From: Mimi Zohar Date: Mon, 1 Feb 2016 08:36:21 -0500 Subject: vfs: define kernel_copy_file_from_fd() This patch defines kernel_read_file_from_fd(), a wrapper for the VFS common kernel_read_file(). Changelog: - Separated from the kernel modules patch Acked-by: Kees Cook Acked-by: Luis R. Rodriguez Cc: Al Viro Signed-off-by: Mimi Zohar --- fs/exec.c | 16 ++++++++++++++++ include/linux/fs.h | 2 ++ 2 files changed, 18 insertions(+) (limited to 'fs') diff --git a/fs/exec.c b/fs/exec.c index 8aaa38666119..9bdf0edf570d 100644 --- a/fs/exec.c +++ b/fs/exec.c @@ -907,6 +907,22 @@ int kernel_read_file_from_path(char *path, void **buf, loff_t *size, } EXPORT_SYMBOL_GPL(kernel_read_file_from_path); +int kernel_read_file_from_fd(int fd, void **buf, loff_t *size, loff_t max_size, + enum kernel_read_file_id id) +{ + struct fd f = fdget(fd); + int ret = -EBADF; + + if (!f.file) + goto out; + + ret = kernel_read_file(f.file, buf, size, max_size, id); +out: + fdput(f); + return ret; +} +EXPORT_SYMBOL_GPL(kernel_read_file_from_fd); + ssize_t read_code(struct file *file, unsigned long addr, loff_t pos, size_t len) { ssize_t res = vfs_read(file, (void __user *)addr, len, &pos); diff --git a/include/linux/fs.h b/include/linux/fs.h index c8bc4d8c843f..9c85deae1bf2 100644 --- a/include/linux/fs.h +++ b/include/linux/fs.h @@ -2586,6 +2586,8 @@ extern int kernel_read_file(struct file *, void **, loff_t *, loff_t, enum kernel_read_file_id); extern int kernel_read_file_from_path(char *, void **, loff_t *, loff_t, enum kernel_read_file_id); +extern int kernel_read_file_from_fd(int, void **, loff_t *, loff_t, + enum kernel_read_file_id); extern ssize_t kernel_write(struct file *, const char *, size_t, loff_t); extern ssize_t __kernel_write(struct file *, const char *, size_t, loff_t *); extern struct file * open_exec(const char *); -- cgit v1.2.3