diff options
-rw-r--r-- | include/linux/ima.h | 3 | ||||
-rw-r--r-- | include/linux/kexec.h | 9 | ||||
-rw-r--r-- | kernel/kexec_core.c | 54 | ||||
-rw-r--r-- | kernel/kexec_file.c | 33 | ||||
-rw-r--r-- | security/integrity/ima/Kconfig | 11 | ||||
-rw-r--r-- | security/integrity/ima/ima.h | 6 | ||||
-rw-r--r-- | security/integrity/ima/ima_kexec.c | 196 | ||||
-rw-r--r-- | security/integrity/ima/ima_queue.c | 5 |
8 files changed, 283 insertions, 34 deletions
diff --git a/include/linux/ima.h b/include/linux/ima.h index 0bae61a15b60..8e29cb4e6a01 100644 --- a/include/linux/ima.h +++ b/include/linux/ima.h @@ -32,6 +32,9 @@ static inline void ima_appraise_parse_cmdline(void) {} #ifdef CONFIG_IMA_KEXEC extern void ima_add_kexec_buffer(struct kimage *image); +extern void ima_kexec_post_load(struct kimage *image); +#else +static inline void ima_kexec_post_load(struct kimage *image) {} #endif #else diff --git a/include/linux/kexec.h b/include/linux/kexec.h index c8971861521a..53ef1b6c8712 100644 --- a/include/linux/kexec.h +++ b/include/linux/kexec.h @@ -369,6 +369,9 @@ struct kimage { phys_addr_t ima_buffer_addr; size_t ima_buffer_size; + + unsigned long ima_segment_index; + bool is_ima_segment_index_set; #endif /* Core ELF header buffer */ @@ -474,13 +477,19 @@ extern bool kexec_file_dbg_print; #define kexec_dprintk(fmt, arg...) \ do { if (kexec_file_dbg_print) pr_info(fmt, ##arg); } while (0) +extern void *kimage_map_segment(struct kimage *image, unsigned long addr, unsigned long size); +extern void kimage_unmap_segment(void *buffer); #else /* !CONFIG_KEXEC_CORE */ struct pt_regs; struct task_struct; +struct kimage; static inline void __crash_kexec(struct pt_regs *regs) { } static inline void crash_kexec(struct pt_regs *regs) { } static inline int kexec_should_crash(struct task_struct *p) { return 0; } static inline int kexec_crash_loaded(void) { return 0; } +static inline void *kimage_map_segment(struct kimage *image, unsigned long addr, unsigned long size) +{ return NULL; } +static inline void kimage_unmap_segment(void *buffer) { } #define kexec_in_progress false #endif /* CONFIG_KEXEC_CORE */ diff --git a/kernel/kexec_core.c b/kernel/kexec_core.c index 3e62b944c883..9c59fa480b0b 100644 --- a/kernel/kexec_core.c +++ b/kernel/kexec_core.c @@ -877,6 +877,60 @@ int kimage_load_segment(struct kimage *image, return result; } +void *kimage_map_segment(struct kimage *image, + unsigned long addr, unsigned long size) +{ + unsigned long src_page_addr, dest_page_addr = 0; + unsigned long eaddr = addr + size; + kimage_entry_t *ptr, entry; + struct page **src_pages; + unsigned int npages; + void *vaddr = NULL; + int i; + + /* + * Collect the source pages and map them in a contiguous VA range. + */ + npages = PFN_UP(eaddr) - PFN_DOWN(addr); + src_pages = kmalloc_array(npages, sizeof(*src_pages), GFP_KERNEL); + if (!src_pages) { + pr_err("Could not allocate ima pages array.\n"); + return NULL; + } + + i = 0; + for_each_kimage_entry(image, ptr, entry) { + if (entry & IND_DESTINATION) { + dest_page_addr = entry & PAGE_MASK; + } else if (entry & IND_SOURCE) { + if (dest_page_addr >= addr && dest_page_addr < eaddr) { + src_page_addr = entry & PAGE_MASK; + src_pages[i++] = + virt_to_page(__va(src_page_addr)); + if (i == npages) + break; + dest_page_addr += PAGE_SIZE; + } + } + } + + /* Sanity check. */ + WARN_ON(i < npages); + + vaddr = vmap(src_pages, npages, VM_MAP, PAGE_KERNEL); + kfree(src_pages); + + if (!vaddr) + pr_err("Could not map ima buffer.\n"); + + return vaddr; +} + +void kimage_unmap_segment(void *segment_buffer) +{ + vunmap(segment_buffer); +} + struct kexec_load_limit { /* Mutex protects the limit count. */ struct mutex mutex; diff --git a/kernel/kexec_file.c b/kernel/kexec_file.c index fba686487e3b..0adb645072aa 100644 --- a/kernel/kexec_file.c +++ b/kernel/kexec_file.c @@ -38,6 +38,21 @@ void set_kexec_sig_enforced(void) } #endif +#ifdef CONFIG_IMA_KEXEC +static bool check_ima_segment_index(struct kimage *image, int i) +{ + if (image->is_ima_segment_index_set && i == image->ima_segment_index) + return true; + else + return false; +} +#else +static bool check_ima_segment_index(struct kimage *image, int i) +{ + return false; +} +#endif + static int kexec_calculate_store_digests(struct kimage *image); /* Maximum size in bytes for kernel/initrd files. */ @@ -186,6 +201,15 @@ kimage_validate_signature(struct kimage *image) } #endif +static int kexec_post_load(struct kimage *image, unsigned long flags) +{ +#ifdef CONFIG_IMA_KEXEC + if (!(flags & KEXEC_FILE_ON_CRASH)) + ima_kexec_post_load(image); +#endif + return machine_kexec_post_load(image); +} + /* * In file mode list of segments is prepared by kernel. Copy relevant * data from user space, do error checking, prepare segment list @@ -413,7 +437,7 @@ SYSCALL_DEFINE5(kexec_file_load, int, kernel_fd, int, initrd_fd, kimage_terminate(image); - ret = machine_kexec_post_load(image); + ret = kexec_post_load(image, flags); if (ret) goto out; @@ -776,6 +800,13 @@ static int kexec_calculate_store_digests(struct kimage *image) if (ksegment->kbuf == pi->purgatory_buf) continue; + /* + * Skip the segment if ima_segment_index is set and matches + * the current index + */ + if (check_ima_segment_index(image, i)) + continue; + ret = crypto_shash_update(desc, ksegment->kbuf, ksegment->bufsz); if (ret) diff --git a/security/integrity/ima/Kconfig b/security/integrity/ima/Kconfig index 475c32615006..976e75f9b9ba 100644 --- a/security/integrity/ima/Kconfig +++ b/security/integrity/ima/Kconfig @@ -321,4 +321,15 @@ config IMA_DISABLE_HTABLE help This option disables htable to allow measurement of duplicate records. +config IMA_KEXEC_EXTRA_MEMORY_KB + int "Extra memory for IMA measurements added during kexec soft reboot" + range 0 40 + depends on IMA_KEXEC + default 0 + help + IMA_KEXEC_EXTRA_MEMORY_KB determines the extra memory to be + allocated (in kb) for IMA measurements added during kexec soft reboot. + If set to the default value of 0, an extra half page of memory for those + additional measurements will be allocated. + endif diff --git a/security/integrity/ima/ima.h b/security/integrity/ima/ima.h index e0489c6f7f59..e3d71d8d56e3 100644 --- a/security/integrity/ima/ima.h +++ b/security/integrity/ima/ima.h @@ -244,6 +244,12 @@ void ima_post_key_create_or_update(struct key *keyring, struct key *key, unsigned long flags, bool create); #endif +#ifdef CONFIG_IMA_KEXEC +void ima_measure_kexec_event(const char *event_name); +#else +static inline void ima_measure_kexec_event(const char *event_name) {} +#endif + /* * The default binary_runtime_measurements list format is defined as the * platform native format. The canonical format is defined as little-endian. diff --git a/security/integrity/ima/ima_kexec.c b/security/integrity/ima/ima_kexec.c index 9d45f4d26f73..7362f68f2d8b 100644 --- a/security/integrity/ima/ima_kexec.c +++ b/security/integrity/ima/ima_kexec.c @@ -12,66 +12,118 @@ #include <linux/kexec.h> #include <linux/of.h> #include <linux/ima.h> +#include <linux/reboot.h> +#include <asm/page.h> #include "ima.h" #ifdef CONFIG_IMA_KEXEC +#define IMA_KEXEC_EVENT_LEN 256 + +static bool ima_kexec_update_registered; +static struct seq_file ima_kexec_file; +static size_t kexec_segment_size; +static void *ima_kexec_buffer; + +static void ima_free_kexec_file_buf(struct seq_file *sf) +{ + vfree(sf->buf); + sf->buf = NULL; + sf->size = 0; + sf->read_pos = 0; + sf->count = 0; +} + +void ima_measure_kexec_event(const char *event_name) +{ + char ima_kexec_event[IMA_KEXEC_EVENT_LEN]; + size_t buf_size = 0; + long len; + int n; + + buf_size = ima_get_binary_runtime_size(); + len = atomic_long_read(&ima_htable.len); + + n = scnprintf(ima_kexec_event, IMA_KEXEC_EVENT_LEN, + "kexec_segment_size=%lu;ima_binary_runtime_size=%lu;" + "ima_runtime_measurements_count=%ld;", + kexec_segment_size, buf_size, len); + + ima_measure_critical_data("ima_kexec", event_name, ima_kexec_event, n, false, NULL, 0); +} + +static int ima_alloc_kexec_file_buf(size_t segment_size) +{ + /* + * kexec 'load' may be called multiple times. + * Free and realloc the buffer only if the segment_size is + * changed from the previous kexec 'load' call. + */ + if (ima_kexec_file.buf && ima_kexec_file.size == segment_size) + goto out; + + ima_free_kexec_file_buf(&ima_kexec_file); + + /* segment size can't change between kexec load and execute */ + ima_kexec_file.buf = vmalloc(segment_size); + if (!ima_kexec_file.buf) + return -ENOMEM; + + ima_kexec_file.size = segment_size; + +out: + ima_kexec_file.read_pos = 0; + ima_kexec_file.count = sizeof(struct ima_kexec_hdr); /* reserved space */ + ima_measure_kexec_event("kexec_load"); + + return 0; +} + static int ima_dump_measurement_list(unsigned long *buffer_size, void **buffer, unsigned long segment_size) { struct ima_queue_entry *qe; - struct seq_file file; struct ima_kexec_hdr khdr; int ret = 0; /* segment size can't change between kexec load and execute */ - file.buf = vmalloc(segment_size); - if (!file.buf) { - ret = -ENOMEM; - goto out; + if (!ima_kexec_file.buf) { + pr_err("Kexec file buf not allocated\n"); + return -EINVAL; } - file.file = NULL; - file.size = segment_size; - file.read_pos = 0; - file.count = sizeof(khdr); /* reserved space */ - memset(&khdr, 0, sizeof(khdr)); khdr.version = 1; /* This is an append-only list, no need to hold the RCU read lock */ list_for_each_entry_rcu(qe, &ima_measurements, later, true) { - if (file.count < file.size) { + if (ima_kexec_file.count < ima_kexec_file.size) { khdr.count++; - ima_measurements_show(&file, qe); + ima_measurements_show(&ima_kexec_file, qe); } else { ret = -EINVAL; break; } } - if (ret < 0) - goto out; - /* * fill in reserved space with some buffer details * (eg. version, buffer size, number of measurements) */ - khdr.buffer_size = file.count; + khdr.buffer_size = ima_kexec_file.count; if (ima_canonical_fmt) { khdr.version = cpu_to_le16(khdr.version); khdr.count = cpu_to_le64(khdr.count); khdr.buffer_size = cpu_to_le64(khdr.buffer_size); } - memcpy(file.buf, &khdr, sizeof(khdr)); + memcpy(ima_kexec_file.buf, &khdr, sizeof(khdr)); print_hex_dump_debug("ima dump: ", DUMP_PREFIX_NONE, 16, 1, - file.buf, file.count < 100 ? file.count : 100, + ima_kexec_file.buf, ima_kexec_file.count < 100 ? + ima_kexec_file.count : 100, true); - *buffer_size = file.count; - *buffer = file.buf; -out: - if (ret == -EINVAL) - vfree(file.buf); + *buffer_size = ima_kexec_file.count; + *buffer = ima_kexec_file.buf; + return ret; } @@ -87,32 +139,39 @@ void ima_add_kexec_buffer(struct kimage *image) .buf_min = 0, .buf_max = ULONG_MAX, .top_down = true }; unsigned long binary_runtime_size; + unsigned long extra_memory; /* use more understandable variable names than defined in kbuf */ + size_t kexec_buffer_size = 0; void *kexec_buffer = NULL; - size_t kexec_buffer_size; - size_t kexec_segment_size; int ret; + if (image->type == KEXEC_TYPE_CRASH) + return; + /* - * Reserve an extra half page of memory for additional measurements - * added during the kexec load. + * Reserve extra memory for measurements added during kexec. */ - binary_runtime_size = ima_get_binary_runtime_size(); + if (CONFIG_IMA_KEXEC_EXTRA_MEMORY_KB <= 0) + extra_memory = PAGE_SIZE / 2; + else + extra_memory = CONFIG_IMA_KEXEC_EXTRA_MEMORY_KB * 1024; + + binary_runtime_size = ima_get_binary_runtime_size() + extra_memory; + if (binary_runtime_size >= ULONG_MAX - PAGE_SIZE) kexec_segment_size = ULONG_MAX; else - kexec_segment_size = ALIGN(ima_get_binary_runtime_size() + - PAGE_SIZE / 2, PAGE_SIZE); + kexec_segment_size = ALIGN(binary_runtime_size, PAGE_SIZE); + if ((kexec_segment_size == ULONG_MAX) || ((kexec_segment_size >> PAGE_SHIFT) > totalram_pages() / 2)) { pr_err("Binary measurement list too large.\n"); return; } - ima_dump_measurement_list(&kexec_buffer_size, &kexec_buffer, - kexec_segment_size); - if (!kexec_buffer) { + ret = ima_alloc_kexec_file_buf(kexec_segment_size); + if (ret < 0) { pr_err("Not enough memory for the kexec measurement buffer.\n"); return; } @@ -120,6 +179,7 @@ void ima_add_kexec_buffer(struct kimage *image) kbuf.buffer = kexec_buffer; kbuf.bufsz = kexec_buffer_size; kbuf.memsz = kexec_segment_size; + image->is_ima_segment_index_set = false; ret = kexec_add_buffer(&kbuf); if (ret) { pr_err("Error passing over kexec measurement buffer.\n"); @@ -130,10 +190,80 @@ void ima_add_kexec_buffer(struct kimage *image) image->ima_buffer_addr = kbuf.mem; image->ima_buffer_size = kexec_segment_size; image->ima_buffer = kexec_buffer; + image->ima_segment_index = image->nr_segments - 1; + image->is_ima_segment_index_set = true; kexec_dprintk("kexec measurement buffer for the loaded kernel at 0x%lx.\n", kbuf.mem); } + +/* + * Called during kexec execute so that IMA can update the measurement list. + */ +static int ima_update_kexec_buffer(struct notifier_block *self, + unsigned long action, void *data) +{ + size_t buf_size = 0; + int ret = NOTIFY_OK; + void *buf = NULL; + + if (!kexec_in_progress) { + pr_info("No kexec in progress.\n"); + return ret; + } + + if (!ima_kexec_buffer) { + pr_err("Kexec buffer not set.\n"); + return ret; + } + + ret = ima_dump_measurement_list(&buf_size, &buf, kexec_segment_size); + + if (ret) + pr_err("Dump measurements failed. Error:%d\n", ret); + + if (buf_size != 0) + memcpy(ima_kexec_buffer, buf, buf_size); + + kimage_unmap_segment(ima_kexec_buffer); + ima_kexec_buffer = NULL; + + return ret; +} + +static struct notifier_block update_buffer_nb = { + .notifier_call = ima_update_kexec_buffer, + .priority = INT_MIN +}; + +/* + * Create a mapping for the source pages that contain the IMA buffer + * so we can update it later. + */ +void ima_kexec_post_load(struct kimage *image) +{ + if (ima_kexec_buffer) { + kimage_unmap_segment(ima_kexec_buffer); + ima_kexec_buffer = NULL; + } + + if (!image->ima_buffer_addr) + return; + + ima_kexec_buffer = kimage_map_segment(image, + image->ima_buffer_addr, + image->ima_buffer_size); + if (!ima_kexec_buffer) { + pr_err("Could not map measurements buffer.\n"); + return; + } + + if (!ima_kexec_update_registered) { + register_reboot_notifier(&update_buffer_nb); + ima_kexec_update_registered = true; + } +} + #endif /* IMA_KEXEC */ /* diff --git a/security/integrity/ima/ima_queue.c b/security/integrity/ima/ima_queue.c index 83d53824aa98..590637e81ad1 100644 --- a/security/integrity/ima/ima_queue.c +++ b/security/integrity/ima/ima_queue.c @@ -241,6 +241,11 @@ static int ima_reboot_notifier(struct notifier_block *nb, unsigned long action, void *data) { +#ifdef CONFIG_IMA_KEXEC + if (action == SYS_RESTART && data && !strcmp(data, "kexec reboot")) + ima_measure_kexec_event("kexec_execute"); +#endif + ima_measurements_suspend(); return NOTIFY_DONE; |