From 59cc8e0a906ea23190922e5e0252e5b5a60d70c2 Mon Sep 17 00:00:00 2001 From: Peter Zijlstra Date: Tue, 18 Aug 2020 15:57:38 +0200 Subject: module: Properly propagate MODULE_STATE_COMING failure Now that notifiers got unbroken; use the proper interface to handle notifier errors and propagate them. There were already MODULE_STATE_COMING notifiers that failed; notably: - jump_label_module_notifier() - tracepoint_module_notify() - bpf_event_notify() By propagating this error, we fix those users. Signed-off-by: Peter Zijlstra (Intel) Signed-off-by: Ingo Molnar Reviewed-by: Miroslav Benes Acked-by: Jessica Yu Acked-by: Josh Poimboeuf Link: https://lore.kernel.org/r/20200818135804.444372853@infradead.org --- kernel/module.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) (limited to 'kernel/module.c') diff --git a/kernel/module.c b/kernel/module.c index 1c5cff34d9f2..3c465cf31d08 100644 --- a/kernel/module.c +++ b/kernel/module.c @@ -3792,9 +3792,13 @@ static int prepare_coming_module(struct module *mod) if (err) return err; - blocking_notifier_call_chain(&module_notify_list, - MODULE_STATE_COMING, mod); - return 0; + err = blocking_notifier_call_chain_robust(&module_notify_list, + MODULE_STATE_COMING, MODULE_STATE_GOING, mod); + err = notifier_to_errno(err); + if (err) + klp_module_going(mod); + + return err; } static int unknown_module_param_cb(char *param, char *val, const char *modname, -- cgit v1.3-18-gd494 From 9183c3f9ed710a8edf1a61e8a96d497258d26e08 Mon Sep 17 00:00:00 2001 From: Josh Poimboeuf Date: Tue, 18 Aug 2020 15:57:42 +0200 Subject: static_call: Add inline static call infrastructure Add infrastructure for an arch-specific CONFIG_HAVE_STATIC_CALL_INLINE option, which is a faster version of CONFIG_HAVE_STATIC_CALL. At runtime, the static call sites are patched directly, rather than using the out-of-line trampolines. Compared to out-of-line static calls, the performance benefits are more modest, but still measurable. Steven Rostedt did some tracepoint measurements: https://lkml.kernel.org/r/20181126155405.72b4f718@gandalf.local.home This code is heavily inspired by the jump label code (aka "static jumps"), as some of the concepts are very similar. For more details, see the comments in include/linux/static_call.h. [peterz: simplified interface; merged trampolines] Signed-off-by: Josh Poimboeuf Signed-off-by: Peter Zijlstra (Intel) Signed-off-by: Ingo Molnar Reviewed-by: Steven Rostedt (VMware) Cc: Linus Torvalds Link: https://lore.kernel.org/r/20200818135804.684334440@infradead.org --- arch/Kconfig | 4 + include/asm-generic/vmlinux.lds.h | 7 + include/linux/module.h | 5 + include/linux/static_call.h | 36 ++++- include/linux/static_call_types.h | 13 ++ kernel/Makefile | 1 + kernel/module.c | 5 + kernel/static_call.c | 303 ++++++++++++++++++++++++++++++++++++++ 8 files changed, 373 insertions(+), 1 deletion(-) create mode 100644 kernel/static_call.c (limited to 'kernel/module.c') diff --git a/arch/Kconfig b/arch/Kconfig index 806e6df7b47c..2c4936ac620a 100644 --- a/arch/Kconfig +++ b/arch/Kconfig @@ -978,6 +978,10 @@ config ARCH_HAS_VDSO_DATA config HAVE_STATIC_CALL bool +config HAVE_STATIC_CALL_INLINE + bool + depends on HAVE_STATIC_CALL + source "kernel/gcov/Kconfig" source "scripts/gcc-plugins/Kconfig" diff --git a/include/asm-generic/vmlinux.lds.h b/include/asm-generic/vmlinux.lds.h index 5430febd34be..0088a5cd6a40 100644 --- a/include/asm-generic/vmlinux.lds.h +++ b/include/asm-generic/vmlinux.lds.h @@ -388,6 +388,12 @@ KEEP(*(__jump_table)) \ __stop___jump_table = .; +#define STATIC_CALL_DATA \ + . = ALIGN(8); \ + __start_static_call_sites = .; \ + KEEP(*(.static_call_sites)) \ + __stop_static_call_sites = .; + /* * Allow architectures to handle ro_after_init data on their * own by defining an empty RO_AFTER_INIT_DATA. @@ -398,6 +404,7 @@ __start_ro_after_init = .; \ *(.data..ro_after_init) \ JUMP_TABLE_DATA \ + STATIC_CALL_DATA \ __end_ro_after_init = .; #endif diff --git a/include/linux/module.h b/include/linux/module.h index e30ed5fa33a7..a29187f7c360 100644 --- a/include/linux/module.h +++ b/include/linux/module.h @@ -25,6 +25,7 @@ #include #include #include +#include #include #include @@ -498,6 +499,10 @@ struct module { unsigned long *kprobe_blacklist; unsigned int num_kprobe_blacklist; #endif +#ifdef CONFIG_HAVE_STATIC_CALL_INLINE + int num_static_call_sites; + struct static_call_site *static_call_sites; +#endif #ifdef CONFIG_LIVEPATCH bool klp; /* Is this a livepatch module? */ diff --git a/include/linux/static_call.h b/include/linux/static_call.h index d8892dff2e91..0d7f9efaa3b2 100644 --- a/include/linux/static_call.h +++ b/include/linux/static_call.h @@ -95,7 +95,41 @@ extern void arch_static_call_transform(void *site, void *tramp, void *func); STATIC_CALL_TRAMP_ADDR(name), func); \ }) -#if defined(CONFIG_HAVE_STATIC_CALL) +#ifdef CONFIG_HAVE_STATIC_CALL_INLINE + +struct static_call_mod { + struct static_call_mod *next; + struct module *mod; /* for vmlinux, mod == NULL */ + struct static_call_site *sites; +}; + +struct static_call_key { + void *func; + struct static_call_mod *mods; +}; + +extern void __static_call_update(struct static_call_key *key, void *tramp, void *func); +extern int static_call_mod_init(struct module *mod); + +#define DEFINE_STATIC_CALL(name, _func) \ + DECLARE_STATIC_CALL(name, _func); \ + struct static_call_key STATIC_CALL_KEY(name) = { \ + .func = _func, \ + .mods = NULL, \ + }; \ + ARCH_DEFINE_STATIC_CALL_TRAMP(name, _func) + +#define static_call(name) __static_call(name) + +#define EXPORT_STATIC_CALL(name) \ + EXPORT_SYMBOL(STATIC_CALL_KEY(name)); \ + EXPORT_SYMBOL(STATIC_CALL_TRAMP(name)) + +#define EXPORT_STATIC_CALL_GPL(name) \ + EXPORT_SYMBOL_GPL(STATIC_CALL_KEY(name)); \ + EXPORT_SYMBOL_GPL(STATIC_CALL_TRAMP(name)) + +#elif defined(CONFIG_HAVE_STATIC_CALL) struct static_call_key { void *func; diff --git a/include/linux/static_call_types.h b/include/linux/static_call_types.h index 5ed249dc47d3..408d345d83e1 100644 --- a/include/linux/static_call_types.h +++ b/include/linux/static_call_types.h @@ -2,14 +2,27 @@ #ifndef _STATIC_CALL_TYPES_H #define _STATIC_CALL_TYPES_H +#include #include #define STATIC_CALL_KEY_PREFIX __SCK__ +#define STATIC_CALL_KEY_PREFIX_STR __stringify(STATIC_CALL_KEY_PREFIX) +#define STATIC_CALL_KEY_PREFIX_LEN (sizeof(STATIC_CALL_KEY_PREFIX_STR) - 1) #define STATIC_CALL_KEY(name) __PASTE(STATIC_CALL_KEY_PREFIX, name) #define STATIC_CALL_TRAMP_PREFIX __SCT__ #define STATIC_CALL_TRAMP_PREFIX_STR __stringify(STATIC_CALL_TRAMP_PREFIX) +#define STATIC_CALL_TRAMP_PREFIX_LEN (sizeof(STATIC_CALL_TRAMP_PREFIX_STR) - 1) #define STATIC_CALL_TRAMP(name) __PASTE(STATIC_CALL_TRAMP_PREFIX, name) #define STATIC_CALL_TRAMP_STR(name) __stringify(STATIC_CALL_TRAMP(name)) +/* + * The static call site table needs to be created by external tooling (objtool + * or a compiler plugin). + */ +struct static_call_site { + s32 addr; + s32 key; +}; + #endif /* _STATIC_CALL_TYPES_H */ diff --git a/kernel/Makefile b/kernel/Makefile index 9a20016d4900..b74820d8b264 100644 --- a/kernel/Makefile +++ b/kernel/Makefile @@ -111,6 +111,7 @@ obj-$(CONFIG_CPU_PM) += cpu_pm.o obj-$(CONFIG_BPF) += bpf/ obj-$(CONFIG_KCSAN) += kcsan/ obj-$(CONFIG_SHADOW_CALL_STACK) += scs.o +obj-$(CONFIG_HAVE_STATIC_CALL_INLINE) += static_call.o obj-$(CONFIG_PERF_EVENTS) += events/ diff --git a/kernel/module.c b/kernel/module.c index 3c465cf31d08..c075a18103fb 100644 --- a/kernel/module.c +++ b/kernel/module.c @@ -3274,6 +3274,11 @@ static int find_module_sections(struct module *mod, struct load_info *info) mod->kprobe_blacklist = section_objs(info, "_kprobe_blacklist", sizeof(unsigned long), &mod->num_kprobe_blacklist); +#endif +#ifdef CONFIG_HAVE_STATIC_CALL_INLINE + mod->static_call_sites = section_objs(info, ".static_call_sites", + sizeof(*mod->static_call_sites), + &mod->num_static_call_sites); #endif mod->extable = section_objs(info, "__ex_table", sizeof(*mod->extable), &mod->num_exentries); diff --git a/kernel/static_call.c b/kernel/static_call.c new file mode 100644 index 000000000000..d24349244675 --- /dev/null +++ b/kernel/static_call.c @@ -0,0 +1,303 @@ +// SPDX-License-Identifier: GPL-2.0 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +extern struct static_call_site __start_static_call_sites[], + __stop_static_call_sites[]; + +static bool static_call_initialized; + +#define STATIC_CALL_INIT 1UL + +/* mutex to protect key modules/sites */ +static DEFINE_MUTEX(static_call_mutex); + +static void static_call_lock(void) +{ + mutex_lock(&static_call_mutex); +} + +static void static_call_unlock(void) +{ + mutex_unlock(&static_call_mutex); +} + +static inline void *static_call_addr(struct static_call_site *site) +{ + return (void *)((long)site->addr + (long)&site->addr); +} + + +static inline struct static_call_key *static_call_key(const struct static_call_site *site) +{ + return (struct static_call_key *) + (((long)site->key + (long)&site->key) & ~STATIC_CALL_INIT); +} + +/* These assume the key is word-aligned. */ +static inline bool static_call_is_init(struct static_call_site *site) +{ + return ((long)site->key + (long)&site->key) & STATIC_CALL_INIT; +} + +static inline void static_call_set_init(struct static_call_site *site) +{ + site->key = ((long)static_call_key(site) | STATIC_CALL_INIT) - + (long)&site->key; +} + +static int static_call_site_cmp(const void *_a, const void *_b) +{ + const struct static_call_site *a = _a; + const struct static_call_site *b = _b; + const struct static_call_key *key_a = static_call_key(a); + const struct static_call_key *key_b = static_call_key(b); + + if (key_a < key_b) + return -1; + + if (key_a > key_b) + return 1; + + return 0; +} + +static void static_call_site_swap(void *_a, void *_b, int size) +{ + long delta = (unsigned long)_a - (unsigned long)_b; + struct static_call_site *a = _a; + struct static_call_site *b = _b; + struct static_call_site tmp = *a; + + a->addr = b->addr - delta; + a->key = b->key - delta; + + b->addr = tmp.addr + delta; + b->key = tmp.key + delta; +} + +static inline void static_call_sort_entries(struct static_call_site *start, + struct static_call_site *stop) +{ + sort(start, stop - start, sizeof(struct static_call_site), + static_call_site_cmp, static_call_site_swap); +} + +void __static_call_update(struct static_call_key *key, void *tramp, void *func) +{ + struct static_call_site *site, *stop; + struct static_call_mod *site_mod; + + cpus_read_lock(); + static_call_lock(); + + if (key->func == func) + goto done; + + key->func = func; + + arch_static_call_transform(NULL, tramp, func); + + /* + * If uninitialized, we'll not update the callsites, but they still + * point to the trampoline and we just patched that. + */ + if (WARN_ON_ONCE(!static_call_initialized)) + goto done; + + for (site_mod = key->mods; site_mod; site_mod = site_mod->next) { + struct module *mod = site_mod->mod; + + if (!site_mod->sites) { + /* + * This can happen if the static call key is defined in + * a module which doesn't use it. + */ + continue; + } + + stop = __stop_static_call_sites; + +#ifdef CONFIG_MODULES + if (mod) { + stop = mod->static_call_sites + + mod->num_static_call_sites; + } +#endif + + for (site = site_mod->sites; + site < stop && static_call_key(site) == key; site++) { + void *site_addr = static_call_addr(site); + + if (static_call_is_init(site)) { + /* + * Don't write to call sites which were in + * initmem and have since been freed. + */ + if (!mod && system_state >= SYSTEM_RUNNING) + continue; + if (mod && !within_module_init((unsigned long)site_addr, mod)) + continue; + } + + if (!kernel_text_address((unsigned long)site_addr)) { + WARN_ONCE(1, "can't patch static call site at %pS", + site_addr); + continue; + } + + arch_static_call_transform(site_addr, NULL, func); + } + } + +done: + static_call_unlock(); + cpus_read_unlock(); +} +EXPORT_SYMBOL_GPL(__static_call_update); + +static int __static_call_init(struct module *mod, + struct static_call_site *start, + struct static_call_site *stop) +{ + struct static_call_site *site; + struct static_call_key *key, *prev_key = NULL; + struct static_call_mod *site_mod; + + if (start == stop) + return 0; + + static_call_sort_entries(start, stop); + + for (site = start; site < stop; site++) { + void *site_addr = static_call_addr(site); + + if ((mod && within_module_init((unsigned long)site_addr, mod)) || + (!mod && init_section_contains(site_addr, 1))) + static_call_set_init(site); + + key = static_call_key(site); + if (key != prev_key) { + prev_key = key; + + site_mod = kzalloc(sizeof(*site_mod), GFP_KERNEL); + if (!site_mod) + return -ENOMEM; + + site_mod->mod = mod; + site_mod->sites = site; + site_mod->next = key->mods; + key->mods = site_mod; + } + + arch_static_call_transform(site_addr, NULL, key->func); + } + + return 0; +} + +#ifdef CONFIG_MODULES + +static int static_call_add_module(struct module *mod) +{ + return __static_call_init(mod, mod->static_call_sites, + mod->static_call_sites + mod->num_static_call_sites); +} + +static void static_call_del_module(struct module *mod) +{ + struct static_call_site *start = mod->static_call_sites; + struct static_call_site *stop = mod->static_call_sites + + mod->num_static_call_sites; + struct static_call_key *key, *prev_key = NULL; + struct static_call_mod *site_mod, **prev; + struct static_call_site *site; + + for (site = start; site < stop; site++) { + key = static_call_key(site); + if (key == prev_key) + continue; + + prev_key = key; + + for (prev = &key->mods, site_mod = key->mods; + site_mod && site_mod->mod != mod; + prev = &site_mod->next, site_mod = site_mod->next) + ; + + if (!site_mod) + continue; + + *prev = site_mod->next; + kfree(site_mod); + } +} + +static int static_call_module_notify(struct notifier_block *nb, + unsigned long val, void *data) +{ + struct module *mod = data; + int ret = 0; + + cpus_read_lock(); + static_call_lock(); + + switch (val) { + case MODULE_STATE_COMING: + ret = static_call_add_module(mod); + if (ret) { + WARN(1, "Failed to allocate memory for static calls"); + static_call_del_module(mod); + } + break; + case MODULE_STATE_GOING: + static_call_del_module(mod); + break; + } + + static_call_unlock(); + cpus_read_unlock(); + + return notifier_from_errno(ret); +} + +static struct notifier_block static_call_module_nb = { + .notifier_call = static_call_module_notify, +}; + +#endif /* CONFIG_MODULES */ + +static void __init static_call_init(void) +{ + int ret; + + if (static_call_initialized) + return; + + cpus_read_lock(); + static_call_lock(); + ret = __static_call_init(NULL, __start_static_call_sites, + __stop_static_call_sites); + static_call_unlock(); + cpus_read_unlock(); + + if (ret) { + pr_err("Failed to allocate memory for static_call!\n"); + BUG(); + } + + static_call_initialized = true; + +#ifdef CONFIG_MODULES + register_module_notifier(&static_call_module_nb); +#endif +} +early_initcall(static_call_init); -- cgit v1.3-18-gd494 From 14721add58ef267344bee254bc276c9139b7b665 Mon Sep 17 00:00:00 2001 From: Qu Wenruo Date: Wed, 2 Sep 2020 14:46:19 +0800 Subject: module: Add more error message for failed kernel module loading When kernel module loading failed, user space only get one of the following error messages: - ENOEXEC This is the most confusing one. From corrupted ELF header to bad WRITE|EXEC flags check introduced by in module_enforce_rwx_sections() all returns this error number. - EPERM This is for blacklisted modules. But mod doesn't do extra explain on this error either. - ENOMEM The only error which needs no explain. This means, if a user got "Exec format error" from modprobe, it provides no meaningful way for the user to debug, and will take extra time communicating to get extra info. So this patch will add extra error messages for -ENOEXEC and -EPERM errors, allowing user to do better debugging and reporting. Reviewed-by: Lucas De Marchi Signed-off-by: Qu Wenruo Signed-off-by: Jessica Yu --- kernel/module.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) (limited to 'kernel/module.c') diff --git a/kernel/module.c b/kernel/module.c index 1c5cff34d9f2..2c00059ac1c9 100644 --- a/kernel/module.c +++ b/kernel/module.c @@ -2096,8 +2096,11 @@ static int module_enforce_rwx_sections(Elf_Ehdr *hdr, Elf_Shdr *sechdrs, int i; for (i = 0; i < hdr->e_shnum; i++) { - if ((sechdrs[i].sh_flags & shf_wx) == shf_wx) + if ((sechdrs[i].sh_flags & shf_wx) == shf_wx) { + pr_err("%s: section %s (index %d) has invalid WRITE|EXEC flags\n", + mod->name, secstrings + sechdrs[i].sh_name, i); return -ENOEXEC; + } } return 0; @@ -3825,8 +3828,10 @@ static int load_module(struct load_info *info, const char __user *uargs, char *after_dashes; err = elf_header_check(info); - if (err) + if (err) { + pr_err("Module has invalid ELF header\n"); goto free_copy; + } err = setup_load_info(info, flags); if (err) @@ -3834,6 +3839,7 @@ static int load_module(struct load_info *info, const char __user *uargs, if (blacklisted(info->name)) { err = -EPERM; + pr_err("Module %s is blacklisted\n", info->name); goto free_copy; } -- cgit v1.3-18-gd494 From c307459b9d1fcb8bbf3ea5a4162979532322ef77 Mon Sep 17 00:00:00 2001 From: Kees Cook Date: Fri, 2 Oct 2020 10:38:13 -0700 Subject: fs/kernel_read_file: Remove FIRMWARE_PREALLOC_BUFFER enum FIRMWARE_PREALLOC_BUFFER is a "how", not a "what", and confuses the LSMs that are interested in filtering between types of things. The "how" should be an internal detail made uninteresting to the LSMs. Fixes: a098ecd2fa7d ("firmware: support loading into a pre-allocated buffer") Fixes: fd90bc559bfb ("ima: based on policy verify firmware signatures (pre-allocated buffer)") Fixes: 4f0496d8ffa3 ("ima: based on policy warn about loading firmware (pre-allocated buffer)") Signed-off-by: Kees Cook Reviewed-by: Mimi Zohar Reviewed-by: Luis Chamberlain Acked-by: Scott Branden Cc: stable@vger.kernel.org Link: https://lore.kernel.org/r/20201002173828.2099543-2-keescook@chromium.org Signed-off-by: Greg Kroah-Hartman --- drivers/base/firmware_loader/main.c | 5 ++--- fs/exec.c | 7 ++++--- include/linux/fs.h | 2 +- kernel/module.c | 2 +- security/integrity/digsig.c | 2 +- security/integrity/ima/ima_fs.c | 2 +- security/integrity/ima/ima_main.c | 6 ++---- 7 files changed, 12 insertions(+), 14 deletions(-) (limited to 'kernel/module.c') diff --git a/drivers/base/firmware_loader/main.c b/drivers/base/firmware_loader/main.c index 63b9714a0154..b0ec2721f55d 100644 --- a/drivers/base/firmware_loader/main.c +++ b/drivers/base/firmware_loader/main.c @@ -470,14 +470,12 @@ fw_get_filesystem_firmware(struct device *device, struct fw_priv *fw_priv, int i, len; int rc = -ENOENT; char *path; - enum kernel_read_file_id id = READING_FIRMWARE; size_t msize = INT_MAX; void *buffer = NULL; /* Already populated data member means we're loading into a buffer */ if (!decompress && fw_priv->data) { buffer = fw_priv->data; - id = READING_FIRMWARE_PREALLOC_BUFFER; msize = fw_priv->allocated_size; } @@ -501,7 +499,8 @@ fw_get_filesystem_firmware(struct device *device, struct fw_priv *fw_priv, /* load firmware files from the mount namespace of init */ rc = kernel_read_file_from_path_initns(path, &buffer, - &size, msize, id); + &size, msize, + READING_FIRMWARE); if (rc) { if (rc != -ENOENT) dev_warn(device, "loading %s failed with error %d\n", diff --git a/fs/exec.c b/fs/exec.c index a91003e28eaa..9233cd50dc4c 100644 --- a/fs/exec.c +++ b/fs/exec.c @@ -954,6 +954,7 @@ int kernel_read_file(struct file *file, void **buf, loff_t *size, { loff_t i_size, pos; ssize_t bytes = 0; + void *allocated = NULL; int ret; if (!S_ISREG(file_inode(file)->i_mode) || max_size < 0) @@ -977,8 +978,8 @@ int kernel_read_file(struct file *file, void **buf, loff_t *size, goto out; } - if (id != READING_FIRMWARE_PREALLOC_BUFFER) - *buf = vmalloc(i_size); + if (!*buf) + *buf = allocated = vmalloc(i_size); if (!*buf) { ret = -ENOMEM; goto out; @@ -1007,7 +1008,7 @@ int kernel_read_file(struct file *file, void **buf, loff_t *size, out_free: if (ret < 0) { - if (id != READING_FIRMWARE_PREALLOC_BUFFER) { + if (allocated) { vfree(*buf); *buf = NULL; } diff --git a/include/linux/fs.h b/include/linux/fs.h index 7519ae003a08..7336e22d0c5d 100644 --- a/include/linux/fs.h +++ b/include/linux/fs.h @@ -2858,10 +2858,10 @@ static inline void i_readcount_inc(struct inode *inode) #endif extern int do_pipe_flags(int *, int); +/* This is a list of *what* is being read, not *how*. */ #define __kernel_read_file_id(id) \ id(UNKNOWN, unknown) \ id(FIRMWARE, firmware) \ - id(FIRMWARE_PREALLOC_BUFFER, firmware) \ id(FIRMWARE_EFI_EMBEDDED, firmware) \ id(MODULE, kernel-module) \ id(KEXEC_IMAGE, kexec-image) \ diff --git a/kernel/module.c b/kernel/module.c index 1c5cff34d9f2..b2808acac46b 100644 --- a/kernel/module.c +++ b/kernel/module.c @@ -4035,7 +4035,7 @@ SYSCALL_DEFINE3(finit_module, int, fd, const char __user *, uargs, int, flags) { struct load_info info = { }; loff_t size; - void *hdr; + void *hdr = NULL; int err; err = may_init_module(); diff --git a/security/integrity/digsig.c b/security/integrity/digsig.c index e9cbadade74b..ac02b7632353 100644 --- a/security/integrity/digsig.c +++ b/security/integrity/digsig.c @@ -169,7 +169,7 @@ int __init integrity_add_key(const unsigned int id, const void *data, int __init integrity_load_x509(const unsigned int id, const char *path) { - void *data; + void *data = NULL; loff_t size; int rc; key_perm_t perm; diff --git a/security/integrity/ima/ima_fs.c b/security/integrity/ima/ima_fs.c index e3fcad871861..15a44c5022f7 100644 --- a/security/integrity/ima/ima_fs.c +++ b/security/integrity/ima/ima_fs.c @@ -272,7 +272,7 @@ static const struct file_operations ima_ascii_measurements_ops = { static ssize_t ima_read_policy(char *path) { - void *data; + void *data = NULL; char *datap; loff_t size; int rc, pathlen = strlen(path); diff --git a/security/integrity/ima/ima_main.c b/security/integrity/ima/ima_main.c index 8a91711ca79b..2f187784c5bc 100644 --- a/security/integrity/ima/ima_main.c +++ b/security/integrity/ima/ima_main.c @@ -611,19 +611,17 @@ void ima_post_path_mknod(struct dentry *dentry) int ima_read_file(struct file *file, enum kernel_read_file_id read_id) { /* - * READING_FIRMWARE_PREALLOC_BUFFER - * * Do devices using pre-allocated memory run the risk of the * firmware being accessible to the device prior to the completion * of IMA's signature verification any more than when using two - * buffers? + * buffers? It may be desirable to include the buffer address + * in this API and walk all the dma_map_single() mappings to check. */ return 0; } const int read_idmap[READING_MAX_ID] = { [READING_FIRMWARE] = FIRMWARE_CHECK, - [READING_FIRMWARE_PREALLOC_BUFFER] = FIRMWARE_CHECK, [READING_MODULE] = MODULE_CHECK, [READING_KEXEC_IMAGE] = KEXEC_KERNEL_CHECK, [READING_KEXEC_INITRAMFS] = KEXEC_INITRAMFS_CHECK, -- cgit v1.3-18-gd494 From b89999d004931ab2e5123611ace7dab77328f8d6 Mon Sep 17 00:00:00 2001 From: Scott Branden Date: Fri, 2 Oct 2020 10:38:15 -0700 Subject: fs/kernel_read_file: Split into separate include file Move kernel_read_file* out of linux/fs.h to its own linux/kernel_read_file.h include file. That header gets pulled in just about everywhere and doesn't really need functions not related to the general fs interface. Suggested-by: Christoph Hellwig Signed-off-by: Scott Branden Signed-off-by: Kees Cook Reviewed-by: Christoph Hellwig Reviewed-by: Mimi Zohar Reviewed-by: Luis Chamberlain Acked-by: Greg Kroah-Hartman Acked-by: James Morris Link: https://lore.kernel.org/r/20200706232309.12010-2-scott.branden@broadcom.com Link: https://lore.kernel.org/r/20201002173828.2099543-4-keescook@chromium.org Signed-off-by: Greg Kroah-Hartman --- drivers/base/firmware_loader/main.c | 1 + fs/exec.c | 1 + include/linux/fs.h | 38 --------------------------- include/linux/ima.h | 1 + include/linux/kernel_read_file.h | 51 +++++++++++++++++++++++++++++++++++++ include/linux/security.h | 1 + kernel/kexec_file.c | 1 + kernel/module.c | 1 + security/integrity/digsig.c | 1 + security/integrity/ima/ima_fs.c | 1 + security/integrity/ima/ima_main.c | 1 + security/integrity/ima/ima_policy.c | 1 + security/loadpin/loadpin.c | 1 + security/security.c | 1 + security/selinux/hooks.c | 1 + 15 files changed, 64 insertions(+), 38 deletions(-) create mode 100644 include/linux/kernel_read_file.h (limited to 'kernel/module.c') diff --git a/drivers/base/firmware_loader/main.c b/drivers/base/firmware_loader/main.c index b0ec2721f55d..8c6ea389afcf 100644 --- a/drivers/base/firmware_loader/main.c +++ b/drivers/base/firmware_loader/main.c @@ -12,6 +12,7 @@ #include #include +#include #include #include #include diff --git a/fs/exec.c b/fs/exec.c index 9233cd50dc4c..c454af329413 100644 --- a/fs/exec.c +++ b/fs/exec.c @@ -23,6 +23,7 @@ * formats. */ +#include #include #include #include diff --git a/include/linux/fs.h b/include/linux/fs.h index 3fb7af12d033..0885d53afb11 100644 --- a/include/linux/fs.h +++ b/include/linux/fs.h @@ -2858,44 +2858,6 @@ static inline void i_readcount_inc(struct inode *inode) #endif extern int do_pipe_flags(int *, int); -/* This is a list of *what* is being read, not *how* nor *where*. */ -#define __kernel_read_file_id(id) \ - id(UNKNOWN, unknown) \ - id(FIRMWARE, firmware) \ - id(MODULE, kernel-module) \ - id(KEXEC_IMAGE, kexec-image) \ - id(KEXEC_INITRAMFS, kexec-initramfs) \ - id(POLICY, security-policy) \ - id(X509_CERTIFICATE, x509-certificate) \ - id(MAX_ID, ) - -#define __fid_enumify(ENUM, dummy) READING_ ## ENUM, -#define __fid_stringify(dummy, str) #str, - -enum kernel_read_file_id { - __kernel_read_file_id(__fid_enumify) -}; - -static const char * const kernel_read_file_str[] = { - __kernel_read_file_id(__fid_stringify) -}; - -static inline const char *kernel_read_file_id_str(enum kernel_read_file_id id) -{ - if ((unsigned)id >= READING_MAX_ID) - return kernel_read_file_str[READING_UNKNOWN]; - - return kernel_read_file_str[id]; -} - -extern int kernel_read_file(struct file *, void **, loff_t *, loff_t, - enum kernel_read_file_id); -extern int kernel_read_file_from_path(const char *, void **, loff_t *, loff_t, - enum kernel_read_file_id); -extern int kernel_read_file_from_path_initns(const 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_read(struct file *, void *, size_t, loff_t *); ssize_t __kernel_read(struct file *file, void *buf, size_t count, loff_t *pos); extern ssize_t kernel_write(struct file *, const void *, size_t, loff_t *); diff --git a/include/linux/ima.h b/include/linux/ima.h index d15100de6cdd..64804f78408b 100644 --- a/include/linux/ima.h +++ b/include/linux/ima.h @@ -7,6 +7,7 @@ #ifndef _LINUX_IMA_H #define _LINUX_IMA_H +#include #include #include #include diff --git a/include/linux/kernel_read_file.h b/include/linux/kernel_read_file.h new file mode 100644 index 000000000000..78cf3d7dc835 --- /dev/null +++ b/include/linux/kernel_read_file.h @@ -0,0 +1,51 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef _LINUX_KERNEL_READ_FILE_H +#define _LINUX_KERNEL_READ_FILE_H + +#include +#include + +/* This is a list of *what* is being read, not *how* nor *where*. */ +#define __kernel_read_file_id(id) \ + id(UNKNOWN, unknown) \ + id(FIRMWARE, firmware) \ + id(MODULE, kernel-module) \ + id(KEXEC_IMAGE, kexec-image) \ + id(KEXEC_INITRAMFS, kexec-initramfs) \ + id(POLICY, security-policy) \ + id(X509_CERTIFICATE, x509-certificate) \ + id(MAX_ID, ) + +#define __fid_enumify(ENUM, dummy) READING_ ## ENUM, +#define __fid_stringify(dummy, str) #str, + +enum kernel_read_file_id { + __kernel_read_file_id(__fid_enumify) +}; + +static const char * const kernel_read_file_str[] = { + __kernel_read_file_id(__fid_stringify) +}; + +static inline const char *kernel_read_file_id_str(enum kernel_read_file_id id) +{ + if ((unsigned int)id >= READING_MAX_ID) + return kernel_read_file_str[READING_UNKNOWN]; + + return kernel_read_file_str[id]; +} + +int kernel_read_file(struct file *file, + void **buf, loff_t *size, loff_t max_size, + enum kernel_read_file_id id); +int kernel_read_file_from_path(const char *path, + void **buf, loff_t *size, loff_t max_size, + enum kernel_read_file_id id); +int kernel_read_file_from_path_initns(const char *path, + void **buf, loff_t *size, loff_t max_size, + enum kernel_read_file_id id); +int kernel_read_file_from_fd(int fd, + void **buf, loff_t *size, loff_t max_size, + enum kernel_read_file_id id); + +#endif /* _LINUX_KERNEL_READ_FILE_H */ diff --git a/include/linux/security.h b/include/linux/security.h index 0a0a03b36a3b..42df0d9b4c37 100644 --- a/include/linux/security.h +++ b/include/linux/security.h @@ -23,6 +23,7 @@ #ifndef __LINUX_SECURITY_H #define __LINUX_SECURITY_H +#include #include #include #include diff --git a/kernel/kexec_file.c b/kernel/kexec_file.c index ca40bef75a61..1cc82557f4c1 100644 --- a/kernel/kexec_file.c +++ b/kernel/kexec_file.c @@ -24,6 +24,7 @@ #include #include #include +#include #include #include #include "kexec_internal.h" diff --git a/kernel/module.c b/kernel/module.c index b2808acac46b..4218abd272ee 100644 --- a/kernel/module.c +++ b/kernel/module.c @@ -18,6 +18,7 @@ #include #include #include +#include #include #include #include diff --git a/security/integrity/digsig.c b/security/integrity/digsig.c index ac02b7632353..f8869be45d8f 100644 --- a/security/integrity/digsig.c +++ b/security/integrity/digsig.c @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include diff --git a/security/integrity/ima/ima_fs.c b/security/integrity/ima/ima_fs.c index 15a44c5022f7..e13ffece3726 100644 --- a/security/integrity/ima/ima_fs.c +++ b/security/integrity/ima/ima_fs.c @@ -13,6 +13,7 @@ */ #include +#include #include #include #include diff --git a/security/integrity/ima/ima_main.c b/security/integrity/ima/ima_main.c index 2f187784c5bc..5f89970c5ab7 100644 --- a/security/integrity/ima/ima_main.c +++ b/security/integrity/ima/ima_main.c @@ -18,6 +18,7 @@ #include #include #include +#include #include #include #include diff --git a/security/integrity/ima/ima_policy.c b/security/integrity/ima/ima_policy.c index b4de33074b37..3b0b43e18ecf 100644 --- a/security/integrity/ima/ima_policy.c +++ b/security/integrity/ima/ima_policy.c @@ -9,6 +9,7 @@ #include #include +#include #include #include #include diff --git a/security/loadpin/loadpin.c b/security/loadpin/loadpin.c index 670a1aebb8a1..163c48216d13 100644 --- a/security/loadpin/loadpin.c +++ b/security/loadpin/loadpin.c @@ -11,6 +11,7 @@ #include #include +#include #include #include #include diff --git a/security/security.c b/security/security.c index 70a7ad357bc6..19d3150f68f4 100644 --- a/security/security.c +++ b/security/security.c @@ -16,6 +16,7 @@ #include #include #include +#include #include #include #include diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c index a340986aa92e..96f5f8b3b9f0 100644 --- a/security/selinux/hooks.c +++ b/security/selinux/hooks.c @@ -24,6 +24,7 @@ #include #include #include +#include #include #include #include -- cgit v1.3-18-gd494 From f7a4f689bca6072492626938aad6dd2f32c5bf97 Mon Sep 17 00:00:00 2001 From: Kees Cook Date: Fri, 2 Oct 2020 10:38:17 -0700 Subject: fs/kernel_read_file: Remove redundant size argument In preparation for refactoring kernel_read_file*(), remove the redundant "size" argument which is not needed: it can be included in the return code, with callers adjusted. (VFS reads already cannot be larger than INT_MAX.) Signed-off-by: Kees Cook Reviewed-by: Mimi Zohar Reviewed-by: Luis Chamberlain Reviewed-by: James Morris Acked-by: Scott Branden Link: https://lore.kernel.org/r/20201002173828.2099543-6-keescook@chromium.org Signed-off-by: Greg Kroah-Hartman --- drivers/base/firmware_loader/main.c | 10 ++++++---- fs/kernel_read_file.c | 20 +++++++++----------- include/linux/kernel_read_file.h | 8 ++++---- kernel/kexec_file.c | 14 +++++++------- kernel/module.c | 7 +++---- security/integrity/digsig.c | 5 +++-- security/integrity/ima/ima_fs.c | 6 ++++-- 7 files changed, 36 insertions(+), 34 deletions(-) (limited to 'kernel/module.c') diff --git a/drivers/base/firmware_loader/main.c b/drivers/base/firmware_loader/main.c index 8c6ea389afcf..6df1bdcfeb9d 100644 --- a/drivers/base/firmware_loader/main.c +++ b/drivers/base/firmware_loader/main.c @@ -467,7 +467,7 @@ fw_get_filesystem_firmware(struct device *device, struct fw_priv *fw_priv, size_t in_size, const void *in_buffer)) { - loff_t size; + size_t size; int i, len; int rc = -ENOENT; char *path; @@ -499,10 +499,9 @@ fw_get_filesystem_firmware(struct device *device, struct fw_priv *fw_priv, fw_priv->size = 0; /* load firmware files from the mount namespace of init */ - rc = kernel_read_file_from_path_initns(path, &buffer, - &size, msize, + rc = kernel_read_file_from_path_initns(path, &buffer, msize, READING_FIRMWARE); - if (rc) { + if (rc < 0) { if (rc != -ENOENT) dev_warn(device, "loading %s failed with error %d\n", path, rc); @@ -511,6 +510,9 @@ fw_get_filesystem_firmware(struct device *device, struct fw_priv *fw_priv, path); continue; } + size = rc; + rc = 0; + dev_dbg(device, "Loading firmware from %s\n", path); if (decompress) { dev_dbg(device, "f/w decompressing %s\n", diff --git a/fs/kernel_read_file.c b/fs/kernel_read_file.c index 54d972d4befc..dc28a8def597 100644 --- a/fs/kernel_read_file.c +++ b/fs/kernel_read_file.c @@ -5,7 +5,7 @@ #include #include -int kernel_read_file(struct file *file, void **buf, loff_t *size, +int kernel_read_file(struct file *file, void **buf, loff_t max_size, enum kernel_read_file_id id) { loff_t i_size, pos; @@ -29,7 +29,7 @@ int kernel_read_file(struct file *file, void **buf, loff_t *size, ret = -EINVAL; goto out; } - if (i_size > SIZE_MAX || (max_size > 0 && i_size > max_size)) { + if (i_size > INT_MAX || (max_size > 0 && i_size > max_size)) { ret = -EFBIG; goto out; } @@ -59,8 +59,6 @@ int kernel_read_file(struct file *file, void **buf, loff_t *size, } ret = security_kernel_post_read_file(file, *buf, i_size, id); - if (!ret) - *size = pos; out_free: if (ret < 0) { @@ -72,11 +70,11 @@ out_free: out: allow_write_access(file); - return ret; + return ret == 0 ? pos : ret; } EXPORT_SYMBOL_GPL(kernel_read_file); -int kernel_read_file_from_path(const char *path, void **buf, loff_t *size, +int kernel_read_file_from_path(const char *path, void **buf, loff_t max_size, enum kernel_read_file_id id) { struct file *file; @@ -89,14 +87,14 @@ int kernel_read_file_from_path(const char *path, void **buf, loff_t *size, if (IS_ERR(file)) return PTR_ERR(file); - ret = kernel_read_file(file, buf, size, max_size, id); + ret = kernel_read_file(file, buf, max_size, id); fput(file); return ret; } EXPORT_SYMBOL_GPL(kernel_read_file_from_path); int kernel_read_file_from_path_initns(const char *path, void **buf, - loff_t *size, loff_t max_size, + loff_t max_size, enum kernel_read_file_id id) { struct file *file; @@ -115,13 +113,13 @@ int kernel_read_file_from_path_initns(const char *path, void **buf, if (IS_ERR(file)) return PTR_ERR(file); - ret = kernel_read_file(file, buf, size, max_size, id); + ret = kernel_read_file(file, buf, max_size, id); fput(file); return ret; } EXPORT_SYMBOL_GPL(kernel_read_file_from_path_initns); -int kernel_read_file_from_fd(int fd, void **buf, loff_t *size, loff_t max_size, +int kernel_read_file_from_fd(int fd, void **buf, loff_t max_size, enum kernel_read_file_id id) { struct fd f = fdget(fd); @@ -130,7 +128,7 @@ int kernel_read_file_from_fd(int fd, void **buf, loff_t *size, loff_t max_size, if (!f.file) goto out; - ret = kernel_read_file(f.file, buf, size, max_size, id); + ret = kernel_read_file(f.file, buf, max_size, id); out: fdput(f); return ret; diff --git a/include/linux/kernel_read_file.h b/include/linux/kernel_read_file.h index 78cf3d7dc835..0ca0bdbed1bd 100644 --- a/include/linux/kernel_read_file.h +++ b/include/linux/kernel_read_file.h @@ -36,16 +36,16 @@ static inline const char *kernel_read_file_id_str(enum kernel_read_file_id id) } int kernel_read_file(struct file *file, - void **buf, loff_t *size, loff_t max_size, + void **buf, loff_t max_size, enum kernel_read_file_id id); int kernel_read_file_from_path(const char *path, - void **buf, loff_t *size, loff_t max_size, + void **buf, loff_t max_size, enum kernel_read_file_id id); int kernel_read_file_from_path_initns(const char *path, - void **buf, loff_t *size, loff_t max_size, + void **buf, loff_t max_size, enum kernel_read_file_id id); int kernel_read_file_from_fd(int fd, - void **buf, loff_t *size, loff_t max_size, + void **buf, loff_t max_size, enum kernel_read_file_id id); #endif /* _LINUX_KERNEL_READ_FILE_H */ diff --git a/kernel/kexec_file.c b/kernel/kexec_file.c index 1cc82557f4c1..b20cfde8a01d 100644 --- a/kernel/kexec_file.c +++ b/kernel/kexec_file.c @@ -220,13 +220,12 @@ kimage_file_prepare_segments(struct kimage *image, int kernel_fd, int initrd_fd, { int ret; void *ldata; - loff_t size; ret = kernel_read_file_from_fd(kernel_fd, &image->kernel_buf, - &size, INT_MAX, READING_KEXEC_IMAGE); - if (ret) + INT_MAX, READING_KEXEC_IMAGE); + if (ret < 0) return ret; - image->kernel_buf_len = size; + image->kernel_buf_len = ret; /* Call arch image probe handlers */ ret = arch_kexec_kernel_image_probe(image, image->kernel_buf, @@ -243,11 +242,12 @@ kimage_file_prepare_segments(struct kimage *image, int kernel_fd, int initrd_fd, /* It is possible that there no initramfs is being loaded */ if (!(flags & KEXEC_FILE_NO_INITRAMFS)) { ret = kernel_read_file_from_fd(initrd_fd, &image->initrd_buf, - &size, INT_MAX, + INT_MAX, READING_KEXEC_INITRAMFS); - if (ret) + if (ret < 0) goto out; - image->initrd_buf_len = size; + image->initrd_buf_len = ret; + ret = 0; } if (cmdline_len) { diff --git a/kernel/module.c b/kernel/module.c index 4218abd272ee..9faa6322f17b 100644 --- a/kernel/module.c +++ b/kernel/module.c @@ -4035,7 +4035,6 @@ SYSCALL_DEFINE3(init_module, void __user *, umod, SYSCALL_DEFINE3(finit_module, int, fd, const char __user *, uargs, int, flags) { struct load_info info = { }; - loff_t size; void *hdr = NULL; int err; @@ -4049,12 +4048,12 @@ SYSCALL_DEFINE3(finit_module, int, fd, const char __user *, uargs, int, flags) |MODULE_INIT_IGNORE_VERMAGIC)) return -EINVAL; - err = kernel_read_file_from_fd(fd, &hdr, &size, INT_MAX, + err = kernel_read_file_from_fd(fd, &hdr, INT_MAX, READING_MODULE); - if (err) + if (err < 0) return err; info.hdr = hdr; - info.len = size; + info.len = err; return load_module(&info, uargs, flags); } diff --git a/security/integrity/digsig.c b/security/integrity/digsig.c index f8869be45d8f..97661ffabc4e 100644 --- a/security/integrity/digsig.c +++ b/security/integrity/digsig.c @@ -171,16 +171,17 @@ int __init integrity_add_key(const unsigned int id, const void *data, int __init integrity_load_x509(const unsigned int id, const char *path) { void *data = NULL; - loff_t size; + size_t size; int rc; key_perm_t perm; - rc = kernel_read_file_from_path(path, &data, &size, 0, + rc = kernel_read_file_from_path(path, &data, 0, READING_X509_CERTIFICATE); if (rc < 0) { pr_err("Unable to open file: %s (%d)", path, rc); return rc; } + size = rc; perm = (KEY_POS_ALL & ~KEY_POS_SETATTR) | KEY_USR_VIEW | KEY_USR_READ; diff --git a/security/integrity/ima/ima_fs.c b/security/integrity/ima/ima_fs.c index e13ffece3726..602f52717757 100644 --- a/security/integrity/ima/ima_fs.c +++ b/security/integrity/ima/ima_fs.c @@ -275,7 +275,7 @@ static ssize_t ima_read_policy(char *path) { void *data = NULL; char *datap; - loff_t size; + size_t size; int rc, pathlen = strlen(path); char *p; @@ -284,11 +284,13 @@ static ssize_t ima_read_policy(char *path) datap = path; strsep(&datap, "\n"); - rc = kernel_read_file_from_path(path, &data, &size, 0, READING_POLICY); + rc = kernel_read_file_from_path(path, &data, 0, READING_POLICY); if (rc < 0) { pr_err("Unable to open file: %s (%d)", path, rc); return rc; } + size = rc; + rc = 0; datap = data; while (size > 0 && (p = strsep(&datap, "\n"))) { -- cgit v1.3-18-gd494 From 885352881f11f1f3113d8eb877786bcb6d720544 Mon Sep 17 00:00:00 2001 From: Kees Cook Date: Fri, 2 Oct 2020 10:38:19 -0700 Subject: fs/kernel_read_file: Add file_size output argument In preparation for adding partial read support, add an optional output argument to kernel_read_file*() that reports the file size so callers can reason more easily about their reading progress. Signed-off-by: Kees Cook Reviewed-by: Mimi Zohar Reviewed-by: Luis Chamberlain Reviewed-by: James Morris Acked-by: Scott Branden Link: https://lore.kernel.org/r/20201002173828.2099543-8-keescook@chromium.org Signed-off-by: Greg Kroah-Hartman --- drivers/base/firmware_loader/main.c | 1 + fs/kernel_read_file.c | 19 +++++++++++++------ include/linux/kernel_read_file.h | 4 ++++ kernel/kexec_file.c | 4 ++-- kernel/module.c | 2 +- security/integrity/digsig.c | 2 +- security/integrity/ima/ima_fs.c | 2 +- 7 files changed, 23 insertions(+), 11 deletions(-) (limited to 'kernel/module.c') diff --git a/drivers/base/firmware_loader/main.c b/drivers/base/firmware_loader/main.c index 6df1bdcfeb9d..d9a180148c4b 100644 --- a/drivers/base/firmware_loader/main.c +++ b/drivers/base/firmware_loader/main.c @@ -500,6 +500,7 @@ fw_get_filesystem_firmware(struct device *device, struct fw_priv *fw_priv, /* load firmware files from the mount namespace of init */ rc = kernel_read_file_from_path_initns(path, &buffer, msize, + NULL, READING_FIRMWARE); if (rc < 0) { if (rc != -ENOENT) diff --git a/fs/kernel_read_file.c b/fs/kernel_read_file.c index e21a76001fff..2e29c38eb4df 100644 --- a/fs/kernel_read_file.c +++ b/fs/kernel_read_file.c @@ -14,6 +14,8 @@ * @buf_size will be ignored) * @buf_size size of buf, if already allocated. If @buf not * allocated, this is the largest size to allocate. + * @file_size if non-NULL, the full size of @file will be + * written here. * @id the kernel_read_file_id identifying the type of * file contents being read (for LSMs to examine) * @@ -22,7 +24,8 @@ * */ int kernel_read_file(struct file *file, void **buf, - size_t buf_size, enum kernel_read_file_id id) + size_t buf_size, size_t *file_size, + enum kernel_read_file_id id) { loff_t i_size, pos; ssize_t bytes = 0; @@ -49,6 +52,8 @@ int kernel_read_file(struct file *file, void **buf, ret = -EFBIG; goto out; } + if (file_size) + *file_size = i_size; if (!*buf) *buf = allocated = vmalloc(i_size); @@ -91,7 +96,8 @@ out: EXPORT_SYMBOL_GPL(kernel_read_file); int kernel_read_file_from_path(const char *path, void **buf, - size_t buf_size, enum kernel_read_file_id id) + size_t buf_size, size_t *file_size, + enum kernel_read_file_id id) { struct file *file; int ret; @@ -103,14 +109,14 @@ int kernel_read_file_from_path(const char *path, void **buf, if (IS_ERR(file)) return PTR_ERR(file); - ret = kernel_read_file(file, buf, buf_size, id); + ret = kernel_read_file(file, buf, buf_size, file_size, id); fput(file); return ret; } EXPORT_SYMBOL_GPL(kernel_read_file_from_path); int kernel_read_file_from_path_initns(const char *path, void **buf, - size_t buf_size, + size_t buf_size, size_t *file_size, enum kernel_read_file_id id) { struct file *file; @@ -129,13 +135,14 @@ int kernel_read_file_from_path_initns(const char *path, void **buf, if (IS_ERR(file)) return PTR_ERR(file); - ret = kernel_read_file(file, buf, buf_size, id); + ret = kernel_read_file(file, buf, buf_size, file_size, id); fput(file); return ret; } EXPORT_SYMBOL_GPL(kernel_read_file_from_path_initns); int kernel_read_file_from_fd(int fd, void **buf, size_t buf_size, + size_t *file_size, enum kernel_read_file_id id) { struct fd f = fdget(fd); @@ -144,7 +151,7 @@ int kernel_read_file_from_fd(int fd, void **buf, size_t buf_size, if (!f.file) goto out; - ret = kernel_read_file(f.file, buf, buf_size, id); + ret = kernel_read_file(f.file, buf, buf_size, file_size, id); out: fdput(f); return ret; diff --git a/include/linux/kernel_read_file.h b/include/linux/kernel_read_file.h index 910039e7593e..023293eaf948 100644 --- a/include/linux/kernel_read_file.h +++ b/include/linux/kernel_read_file.h @@ -37,15 +37,19 @@ static inline const char *kernel_read_file_id_str(enum kernel_read_file_id id) int kernel_read_file(struct file *file, void **buf, size_t buf_size, + size_t *file_size, enum kernel_read_file_id id); int kernel_read_file_from_path(const char *path, void **buf, size_t buf_size, + size_t *file_size, enum kernel_read_file_id id); int kernel_read_file_from_path_initns(const char *path, void **buf, size_t buf_size, + size_t *file_size, enum kernel_read_file_id id); int kernel_read_file_from_fd(int fd, void **buf, size_t buf_size, + size_t *file_size, enum kernel_read_file_id id); #endif /* _LINUX_KERNEL_READ_FILE_H */ diff --git a/kernel/kexec_file.c b/kernel/kexec_file.c index b20cfde8a01d..ee51c1028658 100644 --- a/kernel/kexec_file.c +++ b/kernel/kexec_file.c @@ -222,7 +222,7 @@ kimage_file_prepare_segments(struct kimage *image, int kernel_fd, int initrd_fd, void *ldata; ret = kernel_read_file_from_fd(kernel_fd, &image->kernel_buf, - INT_MAX, READING_KEXEC_IMAGE); + INT_MAX, NULL, READING_KEXEC_IMAGE); if (ret < 0) return ret; image->kernel_buf_len = ret; @@ -242,7 +242,7 @@ kimage_file_prepare_segments(struct kimage *image, int kernel_fd, int initrd_fd, /* It is possible that there no initramfs is being loaded */ if (!(flags & KEXEC_FILE_NO_INITRAMFS)) { ret = kernel_read_file_from_fd(initrd_fd, &image->initrd_buf, - INT_MAX, + INT_MAX, NULL, READING_KEXEC_INITRAMFS); if (ret < 0) goto out; diff --git a/kernel/module.c b/kernel/module.c index 9faa6322f17b..0f11eaed047e 100644 --- a/kernel/module.c +++ b/kernel/module.c @@ -4048,7 +4048,7 @@ SYSCALL_DEFINE3(finit_module, int, fd, const char __user *, uargs, int, flags) |MODULE_INIT_IGNORE_VERMAGIC)) return -EINVAL; - err = kernel_read_file_from_fd(fd, &hdr, INT_MAX, + err = kernel_read_file_from_fd(fd, &hdr, INT_MAX, NULL, READING_MODULE); if (err < 0) return err; diff --git a/security/integrity/digsig.c b/security/integrity/digsig.c index 04f779c4f5ed..8a523dfd7fd7 100644 --- a/security/integrity/digsig.c +++ b/security/integrity/digsig.c @@ -175,7 +175,7 @@ int __init integrity_load_x509(const unsigned int id, const char *path) int rc; key_perm_t perm; - rc = kernel_read_file_from_path(path, &data, INT_MAX, + rc = kernel_read_file_from_path(path, &data, INT_MAX, NULL, READING_X509_CERTIFICATE); if (rc < 0) { pr_err("Unable to open file: %s (%d)", path, rc); diff --git a/security/integrity/ima/ima_fs.c b/security/integrity/ima/ima_fs.c index 692b83e82edf..5fc56ccb6678 100644 --- a/security/integrity/ima/ima_fs.c +++ b/security/integrity/ima/ima_fs.c @@ -284,7 +284,7 @@ static ssize_t ima_read_policy(char *path) datap = path; strsep(&datap, "\n"); - rc = kernel_read_file_from_path(path, &data, INT_MAX, READING_POLICY); + rc = kernel_read_file_from_path(path, &data, INT_MAX, NULL, READING_POLICY); if (rc < 0) { pr_err("Unable to open file: %s (%d)", path, rc); return rc; -- cgit v1.3-18-gd494 From b64fcae74b6d6940d14243c963ab0089e8f0d82d Mon Sep 17 00:00:00 2001 From: Kees Cook Date: Fri, 2 Oct 2020 10:38:20 -0700 Subject: LSM: Introduce kernel_post_load_data() hook There are a few places in the kernel where LSMs would like to have visibility into the contents of a kernel buffer that has been loaded or read. While security_kernel_post_read_file() (which includes the buffer) exists as a pairing for security_kernel_read_file(), no such hook exists to pair with security_kernel_load_data(). Earlier proposals for just using security_kernel_post_read_file() with a NULL file argument were rejected (i.e. "file" should always be valid for the security_..._file hooks, but it appears at least one case was left in the kernel during earlier refactoring. (This will be fixed in a subsequent patch.) Since not all cases of security_kernel_load_data() can have a single contiguous buffer made available to the LSM hook (e.g. kexec image segments are separately loaded), there needs to be a way for the LSM to reason about its expectations of the hook coverage. In order to handle this, add a "contents" argument to the "kernel_load_data" hook that indicates if the newly added "kernel_post_load_data" hook will be called with the full contents once loaded. That way, LSMs requiring full contents can choose to unilaterally reject "kernel_load_data" with contents=false (which is effectively the existing hook coverage), but when contents=true they can allow it and later evaluate the "kernel_post_load_data" hook once the buffer is loaded. With this change, LSMs can gain coverage over non-file-backed data loads (e.g. init_module(2) and firmware userspace helper), which will happen in subsequent patches. Additionally prepare IMA to start processing these cases. Signed-off-by: Kees Cook Reviewed-by: KP Singh Link: https://lore.kernel.org/r/20201002173828.2099543-9-keescook@chromium.org Signed-off-by: Greg Kroah-Hartman --- drivers/base/firmware_loader/fallback.c | 2 +- drivers/base/firmware_loader/fallback_platform.c | 2 +- include/linux/ima.h | 13 +++++++++++-- include/linux/lsm_hook_defs.h | 4 +++- include/linux/lsm_hooks.h | 10 ++++++++++ include/linux/security.h | 14 ++++++++++++-- kernel/kexec.c | 2 +- kernel/module.c | 2 +- security/integrity/ima/ima_main.c | 24 +++++++++++++++++++++++- security/loadpin/loadpin.c | 2 +- security/security.c | 20 +++++++++++++++++--- security/selinux/hooks.c | 2 +- 12 files changed, 82 insertions(+), 15 deletions(-) (limited to 'kernel/module.c') diff --git a/drivers/base/firmware_loader/fallback.c b/drivers/base/firmware_loader/fallback.c index 283ca2de76d4..bff4717cc6b5 100644 --- a/drivers/base/firmware_loader/fallback.c +++ b/drivers/base/firmware_loader/fallback.c @@ -613,7 +613,7 @@ static bool fw_run_sysfs_fallback(u32 opt_flags) return false; /* Also permit LSMs and IMA to fail firmware sysfs fallback */ - ret = security_kernel_load_data(LOADING_FIRMWARE); + ret = security_kernel_load_data(LOADING_FIRMWARE, false); if (ret < 0) return false; diff --git a/drivers/base/firmware_loader/fallback_platform.c b/drivers/base/firmware_loader/fallback_platform.c index 6958ab1a8059..a12c79d47efc 100644 --- a/drivers/base/firmware_loader/fallback_platform.c +++ b/drivers/base/firmware_loader/fallback_platform.c @@ -17,7 +17,7 @@ int firmware_fallback_platform(struct fw_priv *fw_priv, u32 opt_flags) if (!(opt_flags & FW_OPT_FALLBACK_PLATFORM)) return -ENOENT; - rc = security_kernel_load_data(LOADING_FIRMWARE); + rc = security_kernel_load_data(LOADING_FIRMWARE, false); if (rc) return rc; diff --git a/include/linux/ima.h b/include/linux/ima.h index 64804f78408b..af9fb8c5f16a 100644 --- a/include/linux/ima.h +++ b/include/linux/ima.h @@ -20,7 +20,9 @@ extern void ima_post_create_tmpfile(struct inode *inode); extern void ima_file_free(struct file *file); extern int ima_file_mmap(struct file *file, unsigned long prot); extern int ima_file_mprotect(struct vm_area_struct *vma, unsigned long prot); -extern int ima_load_data(enum kernel_load_data_id id); +extern int ima_load_data(enum kernel_load_data_id id, bool contents); +extern int ima_post_load_data(char *buf, loff_t size, + enum kernel_load_data_id id, char *description); 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); @@ -78,7 +80,14 @@ static inline int ima_file_mprotect(struct vm_area_struct *vma, return 0; } -static inline int ima_load_data(enum kernel_load_data_id id) +static inline int ima_load_data(enum kernel_load_data_id id, bool contents) +{ + return 0; +} + +static inline int ima_post_load_data(char *buf, loff_t size, + enum kernel_load_data_id id, + char *description) { return 0; } diff --git a/include/linux/lsm_hook_defs.h b/include/linux/lsm_hook_defs.h index 2a8c74d99015..83c6f1f5cc1e 100644 --- a/include/linux/lsm_hook_defs.h +++ b/include/linux/lsm_hook_defs.h @@ -184,7 +184,9 @@ LSM_HOOK(void, LSM_RET_VOID, cred_getsecid, const struct cred *c, u32 *secid) LSM_HOOK(int, 0, kernel_act_as, struct cred *new, u32 secid) LSM_HOOK(int, 0, kernel_create_files_as, struct cred *new, struct inode *inode) LSM_HOOK(int, 0, kernel_module_request, char *kmod_name) -LSM_HOOK(int, 0, kernel_load_data, enum kernel_load_data_id id) +LSM_HOOK(int, 0, kernel_load_data, enum kernel_load_data_id id, bool contents) +LSM_HOOK(int, 0, kernel_post_load_data, char *buf, loff_t size, + enum kernel_read_file_id id, char *description) LSM_HOOK(int, 0, kernel_read_file, struct file *file, enum kernel_read_file_id id) LSM_HOOK(int, 0, kernel_post_read_file, struct file *file, char *buf, diff --git a/include/linux/lsm_hooks.h b/include/linux/lsm_hooks.h index 9e2e3e63719d..6bb4f1a0158c 100644 --- a/include/linux/lsm_hooks.h +++ b/include/linux/lsm_hooks.h @@ -635,7 +635,17 @@ * @kernel_load_data: * Load data provided by userspace. * @id kernel load data identifier + * @contents if a subsequent @kernel_post_load_data will be called. * Return 0 if permission is granted. + * @kernel_post_load_data: + * Load data provided by a non-file source (usually userspace buffer). + * @buf pointer to buffer containing the data contents. + * @size length of the data contents. + * @id kernel load data identifier + * @description a text description of what was loaded, @id-specific + * Return 0 if permission is granted. + * This must be paired with a prior @kernel_load_data call that had + * @contents set to true. * @kernel_read_file: * Read a file specified by userspace. * @file contains the file structure pointing to the file being read diff --git a/include/linux/security.h b/include/linux/security.h index 42df0d9b4c37..51c8e4e6b7cc 100644 --- a/include/linux/security.h +++ b/include/linux/security.h @@ -387,7 +387,10 @@ void security_cred_getsecid(const struct cred *c, u32 *secid); 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_load_data(enum kernel_load_data_id id); +int security_kernel_load_data(enum kernel_load_data_id id, bool contents); +int security_kernel_post_load_data(char *buf, loff_t size, + enum kernel_load_data_id id, + char *description); 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); @@ -1014,7 +1017,14 @@ static inline int security_kernel_module_request(char *kmod_name) return 0; } -static inline int security_kernel_load_data(enum kernel_load_data_id id) +static inline int security_kernel_load_data(enum kernel_load_data_id id, bool contents) +{ + return 0; +} + +static inline int security_kernel_post_load_data(char *buf, loff_t size, + enum kernel_load_data_id id, + char *description) { return 0; } diff --git a/kernel/kexec.c b/kernel/kexec.c index f977786fe498..c82c6c06f051 100644 --- a/kernel/kexec.c +++ b/kernel/kexec.c @@ -205,7 +205,7 @@ static inline int kexec_load_check(unsigned long nr_segments, return -EPERM; /* Permit LSMs and IMA to fail the kexec */ - result = security_kernel_load_data(LOADING_KEXEC_IMAGE); + result = security_kernel_load_data(LOADING_KEXEC_IMAGE, false); if (result < 0) return result; diff --git a/kernel/module.c b/kernel/module.c index 0f11eaed047e..f47209e0fde6 100644 --- a/kernel/module.c +++ b/kernel/module.c @@ -3014,7 +3014,7 @@ static int copy_module_from_user(const void __user *umod, unsigned long len, if (info->len < sizeof(*(info->hdr))) return -ENOEXEC; - err = security_kernel_load_data(LOADING_MODULE); + err = security_kernel_load_data(LOADING_MODULE, false); if (err) return err; diff --git a/security/integrity/ima/ima_main.c b/security/integrity/ima/ima_main.c index 5f89970c5ab7..9dd9c5f4d736 100644 --- a/security/integrity/ima/ima_main.c +++ b/security/integrity/ima/ima_main.c @@ -676,6 +676,8 @@ int ima_post_read_file(struct file *file, void *buf, loff_t size, /** * ima_load_data - appraise decision based on policy * @id: kernel load data caller identifier + * @contents: whether the full contents will be available in a later + * call to ima_post_load_data(). * * Callers of this LSM hook can not measure, appraise, or audit the * data provided by userspace. Enforce policy rules requring a file @@ -683,7 +685,7 @@ int ima_post_read_file(struct file *file, void *buf, loff_t size, * * For permission return 0, otherwise return -EACCES. */ -int ima_load_data(enum kernel_load_data_id id) +int ima_load_data(enum kernel_load_data_id id, bool contents) { bool ima_enforce, sig_enforce; @@ -723,6 +725,26 @@ int ima_load_data(enum kernel_load_data_id id) return 0; } +/** + * ima_post_load_data - appraise decision based on policy + * @buf: pointer to in memory file contents + * @size: size of in memory file contents + * @id: kernel load data caller identifier + * @description: @id-specific description of contents + * + * Measure/appraise/audit in memory buffer based on policy. Policy rules + * are written in terms of a policy identifier. + * + * On success return 0. On integrity appraisal error, assuming the file + * is in policy and IMA-appraisal is in enforcing mode, return -EACCES. + */ +int ima_post_load_data(char *buf, loff_t size, + enum kernel_load_data_id load_id, + char *description) +{ + return 0; +} + /* * process_buffer_measurement - Measure the buffer to ima log. * @inode: inode associated with the object being measured (NULL for KEY_CHECK) diff --git a/security/loadpin/loadpin.c b/security/loadpin/loadpin.c index 163c48216d13..28782412febb 100644 --- a/security/loadpin/loadpin.c +++ b/security/loadpin/loadpin.c @@ -177,7 +177,7 @@ static int loadpin_read_file(struct file *file, enum kernel_read_file_id id) return 0; } -static int loadpin_load_data(enum kernel_load_data_id id) +static int loadpin_load_data(enum kernel_load_data_id id, bool contents) { return loadpin_read_file(NULL, (enum kernel_read_file_id) id); } diff --git a/security/security.c b/security/security.c index 19d3150f68f4..531b855826fc 100644 --- a/security/security.c +++ b/security/security.c @@ -1695,17 +1695,31 @@ int security_kernel_post_read_file(struct file *file, char *buf, loff_t size, } EXPORT_SYMBOL_GPL(security_kernel_post_read_file); -int security_kernel_load_data(enum kernel_load_data_id id) +int security_kernel_load_data(enum kernel_load_data_id id, bool contents) { int ret; - ret = call_int_hook(kernel_load_data, 0, id); + ret = call_int_hook(kernel_load_data, 0, id, contents); if (ret) return ret; - return ima_load_data(id); + return ima_load_data(id, contents); } EXPORT_SYMBOL_GPL(security_kernel_load_data); +int security_kernel_post_load_data(char *buf, loff_t size, + enum kernel_load_data_id id, + char *description) +{ + int ret; + + ret = call_int_hook(kernel_post_load_data, 0, buf, size, id, + description); + if (ret) + return ret; + return ima_post_load_data(buf, size, id, description); +} +EXPORT_SYMBOL_GPL(security_kernel_post_load_data); + int security_task_fix_setuid(struct cred *new, const struct cred *old, int flags) { diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c index 96f5f8b3b9f0..558beee97d8d 100644 --- a/security/selinux/hooks.c +++ b/security/selinux/hooks.c @@ -4018,7 +4018,7 @@ static int selinux_kernel_read_file(struct file *file, return rc; } -static int selinux_kernel_load_data(enum kernel_load_data_id id) +static int selinux_kernel_load_data(enum kernel_load_data_id id, bool contents) { int rc = 0; -- cgit v1.3-18-gd494 From 38f901735a9e2b3d182fc04a1dfcdb0d3325ea5d Mon Sep 17 00:00:00 2001 From: Kees Cook Date: Fri, 2 Oct 2020 10:38:22 -0700 Subject: module: Call security_kernel_post_load_data() Now that there is an API for checking loaded contents for modules loaded without a file, call into the LSM hooks. Signed-off-by: Kees Cook Reviewed-by: KP Singh Acked-by: Jessica Yu Link: https://lore.kernel.org/r/20201002173828.2099543-11-keescook@chromium.org Signed-off-by: Greg Kroah-Hartman --- kernel/module.c | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) (limited to 'kernel/module.c') diff --git a/kernel/module.c b/kernel/module.c index f47209e0fde6..adfa21dd3842 100644 --- a/kernel/module.c +++ b/kernel/module.c @@ -3014,7 +3014,7 @@ static int copy_module_from_user(const void __user *umod, unsigned long len, if (info->len < sizeof(*(info->hdr))) return -ENOEXEC; - err = security_kernel_load_data(LOADING_MODULE, false); + err = security_kernel_load_data(LOADING_MODULE, true); if (err) return err; @@ -3024,11 +3024,17 @@ static int copy_module_from_user(const void __user *umod, unsigned long len, return -ENOMEM; if (copy_chunked_from_user(info->hdr, umod, info->len) != 0) { - vfree(info->hdr); - return -EFAULT; + err = -EFAULT; + goto out; } - return 0; + err = security_kernel_post_load_data((char *)info->hdr, info->len, + LOADING_MODULE, "init_module"); +out: + if (err) + vfree(info->hdr); + + return err; } static void free_copy(struct load_info *info) -- cgit v1.3-18-gd494 From 0fa8e084648779eeb8929ae004301b3acf3bad84 Mon Sep 17 00:00:00 2001 From: Kees Cook Date: Fri, 2 Oct 2020 10:38:25 -0700 Subject: 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 Link: https://lore.kernel.org/r/20201002173828.2099543-14-keescook@chromium.org Signed-off-by: Greg Kroah-Hartman --- drivers/base/firmware_loader/main.c | 2 +- fs/kernel_read_file.c | 78 +++++++++++++++++++++++++------------ include/linux/kernel_read_file.h | 8 ++-- kernel/kexec_file.c | 4 +- kernel/module.c | 2 +- security/integrity/digsig.c | 2 +- security/integrity/ima/ima_fs.c | 3 +- 7 files changed, 65 insertions(+), 34 deletions(-) (limited to 'kernel/module.c') diff --git a/drivers/base/firmware_loader/main.c b/drivers/base/firmware_loader/main.c index d9a180148c4b..79f86466d472 100644 --- a/drivers/base/firmware_loader/main.c +++ b/drivers/base/firmware_loader/main.c @@ -499,7 +499,7 @@ fw_get_filesystem_firmware(struct device *device, struct fw_priv *fw_priv, fw_priv->size = 0; /* 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, READING_FIRMWARE); if (rc < 0) { diff --git a/fs/kernel_read_file.c b/fs/kernel_read_file.c index d73bc3fa710a..90d255fbdd9b 100644 --- a/fs/kernel_read_file.c +++ b/fs/kernel_read_file.c @@ -9,6 +9,7 @@ * kernel_read_file() - read file contents into a kernel buffer * * @file file to read from + * @offset where to start reading from (see below). * @buf pointer to a "void *" buffer for reading into (if * *@buf is NULL, a buffer will be allocated, and * @buf_size will be ignored) @@ -19,19 +20,31 @@ * @id the kernel_read_file_id identifying the type of * 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 * 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, enum kernel_read_file_id id) { loff_t i_size, pos; - ssize_t bytes = 0; + size_t copied; void *allocated = NULL; + bool whole_file; int ret; + if (offset != 0 && (!*buf || !file_size)) + return -EINVAL; + if (!S_ISREG(file_inode(file)->i_mode)) return -EINVAL; @@ -39,19 +52,27 @@ int kernel_read_file(struct file *file, void **buf, if (ret) return ret; - ret = security_kernel_read_file(file, id, true); - if (ret) - goto out; - i_size = i_size_read(file_inode(file)); if (i_size <= 0) { ret = -EINVAL; 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; + 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) *file_size = i_size; @@ -62,9 +83,14 @@ int kernel_read_file(struct file *file, void **buf, goto out; } - pos = 0; - while (pos < i_size) { - bytes = kernel_read(file, *buf + pos, i_size - pos, &pos); + pos = offset; + copied = 0; + 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) { ret = bytes; goto out_free; @@ -72,14 +98,17 @@ int kernel_read_file(struct file *file, void **buf, if (bytes == 0) break; + copied += bytes; } - if (pos != i_size) { - ret = -EIO; - goto out_free; - } + if (whole_file) { + if (pos != i_size) { + 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: if (ret < 0) { @@ -91,11 +120,11 @@ out_free: out: allow_write_access(file); - return ret == 0 ? pos : ret; + return ret == 0 ? copied : ret; } 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, 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)) 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); return ret; } EXPORT_SYMBOL_GPL(kernel_read_file_from_path); -int kernel_read_file_from_path_initns(const char *path, void **buf, - size_t buf_size, size_t *file_size, +int kernel_read_file_from_path_initns(const char *path, loff_t offset, + void **buf, size_t buf_size, + size_t *file_size, enum kernel_read_file_id id) { struct file *file; @@ -135,14 +165,14 @@ int kernel_read_file_from_path_initns(const char *path, void **buf, if (IS_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); return ret; } EXPORT_SYMBOL_GPL(kernel_read_file_from_path_initns); -int kernel_read_file_from_fd(int fd, void **buf, size_t buf_size, - size_t *file_size, +int kernel_read_file_from_fd(int fd, loff_t offset, void **buf, + size_t buf_size, size_t *file_size, enum kernel_read_file_id id) { 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) 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: fdput(f); return ret; diff --git a/include/linux/kernel_read_file.h b/include/linux/kernel_read_file.h index 023293eaf948..575ffa1031d3 100644 --- a/include/linux/kernel_read_file.h +++ b/include/linux/kernel_read_file.h @@ -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]; } -int kernel_read_file(struct file *file, +int kernel_read_file(struct file *file, loff_t offset, void **buf, size_t buf_size, size_t *file_size, 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, size_t *file_size, 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, size_t *file_size, 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, size_t *file_size, enum kernel_read_file_id id); diff --git a/kernel/kexec_file.c b/kernel/kexec_file.c index ee51c1028658..84f7316792a7 100644 --- a/kernel/kexec_file.c +++ b/kernel/kexec_file.c @@ -221,7 +221,7 @@ kimage_file_prepare_segments(struct kimage *image, int kernel_fd, int initrd_fd, int ret; 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); if (ret < 0) return ret; @@ -241,7 +241,7 @@ kimage_file_prepare_segments(struct kimage *image, int kernel_fd, int initrd_fd, #endif /* It is possible that there no initramfs is being loaded */ 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, READING_KEXEC_INITRAMFS); if (ret < 0) diff --git a/kernel/module.c b/kernel/module.c index adfa21dd3842..9c578e44abe7 100644 --- a/kernel/module.c +++ b/kernel/module.c @@ -4054,7 +4054,7 @@ SYSCALL_DEFINE3(finit_module, int, fd, const char __user *, uargs, int, flags) |MODULE_INIT_IGNORE_VERMAGIC)) 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); if (err < 0) return err; diff --git a/security/integrity/digsig.c b/security/integrity/digsig.c index 8a523dfd7fd7..0f518dcfde05 100644 --- a/security/integrity/digsig.c +++ b/security/integrity/digsig.c @@ -175,7 +175,7 @@ int __init integrity_load_x509(const unsigned int id, const char *path) int rc; 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); if (rc < 0) { pr_err("Unable to open file: %s (%d)", path, rc); diff --git a/security/integrity/ima/ima_fs.c b/security/integrity/ima/ima_fs.c index 5fc56ccb6678..ea8ff8a07b36 100644 --- a/security/integrity/ima/ima_fs.c +++ b/security/integrity/ima/ima_fs.c @@ -284,7 +284,8 @@ static ssize_t ima_read_policy(char *path) datap = path; 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) { pr_err("Unable to open file: %s (%d)", path, rc); return rc; -- cgit v1.3-18-gd494 From fdf09ab887829cd1b671e45d9549f8ec1ffda0fa Mon Sep 17 00:00:00 2001 From: Daniel Jordan Date: Thu, 8 Oct 2020 13:32:20 -0400 Subject: module: statically initialize init section freeing data Corentin hit the following workqueue warning when running with CRYPTO_MANAGER_EXTRA_TESTS: WARNING: CPU: 2 PID: 147 at kernel/workqueue.c:1473 __queue_work+0x3b8/0x3d0 Modules linked in: ghash_generic CPU: 2 PID: 147 Comm: modprobe Not tainted 5.6.0-rc1-next-20200214-00068-g166c9264f0b1-dirty #545 Hardware name: Pine H64 model A (DT) pc : __queue_work+0x3b8/0x3d0 Call trace: __queue_work+0x3b8/0x3d0 queue_work_on+0x6c/0x90 do_init_module+0x188/0x1f0 load_module+0x1d00/0x22b0 I wasn't able to reproduce on x86 or rpi 3b+. This is WARN_ON(!list_empty(&work->entry)) from __queue_work(), and it happens because the init_free_wq work item isn't initialized in time for a crypto test that requests the gcm module. Some crypto tests were recently moved earlier in boot as explained in commit c4741b230597 ("crypto: run initcalls for generic implementations earlier"), which went into mainline less than two weeks before the Fixes commit. Avoid the warning by statically initializing init_free_wq and the corresponding llist. Link: https://lore.kernel.org/lkml/20200217204803.GA13479@Red/ Fixes: 1a7b7d922081 ("modules: Use vmalloc special flag") Reported-by: Corentin Labbe Tested-by: Corentin Labbe Tested-on: sun50i-h6-pine-h64 Tested-on: imx8mn-ddr4-evk Tested-on: sun50i-a64-bananapi-m64 Reviewed-by: Eric Biggers Signed-off-by: Daniel Jordan Signed-off-by: Jessica Yu --- kernel/module.c | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) (limited to 'kernel/module.c') diff --git a/kernel/module.c b/kernel/module.c index 2c00059ac1c9..7ff2dd301bd1 100644 --- a/kernel/module.c +++ b/kernel/module.c @@ -91,8 +91,9 @@ EXPORT_SYMBOL_GPL(module_mutex); static LIST_HEAD(modules); /* Work queue for freeing init sections in success case */ -static struct work_struct init_free_wq; -static struct llist_head init_free_list; +static void do_free_init(struct work_struct *w); +static DECLARE_WORK(init_free_wq, do_free_init); +static LLIST_HEAD(init_free_list); #ifdef CONFIG_MODULES_TREE_LOOKUP @@ -3582,14 +3583,6 @@ static void do_free_init(struct work_struct *w) } } -static int __init modules_wq_init(void) -{ - INIT_WORK(&init_free_wq, do_free_init); - init_llist_head(&init_free_list); - return 0; -} -module_init(modules_wq_init); - /* * This is where the real work happens. * -- cgit v1.3-18-gd494