| /* |
| * Copyright (c) 2015 Wind River Systems, Inc. |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| /** |
| * @file safe memory access routines, software implementation that verifies |
| * accesses are within memory region boundaries. |
| * |
| * @details See debug/Kconfig and the "Safe memory access" help for details. |
| */ |
| |
| #include <kernel.h> |
| #include <init.h> |
| #include <errno.h> |
| #include <toolchain.h> |
| #include <linker/linker-defs.h> |
| #include <misc/util.h> |
| #include <debug/mem_safe.h> |
| #include <string.h> |
| |
| #define NUM_REGIONS (CONFIG_MEM_SAFE_NUM_EXTRA_REGIONS + 2) |
| |
| /* |
| * The table of regions has the RO regions at the bottom and the RW regions at |
| * the top, and regions are added by moving the ro_end/rw_end pointers towards |
| * each other. The table is full when the pointers cross, i.e. when ro_end > |
| * rw_end. |
| */ |
| struct { |
| vaddr_t addr; |
| vaddr_t last_byte; |
| } mem_regions[NUM_REGIONS]; |
| |
| #define ro_base 0 |
| #define rw_base (NUM_REGIONS - 1) |
| |
| static int ro_end = ro_base; |
| static int rw_end = rw_base; |
| |
| #define IMAGE_ROM_START ((vaddr_t)&_image_rom_start) |
| #define IMAGE_ROM_END ((vaddr_t)&_image_rom_end) |
| |
| #define IMAGE_RAM_START ((vaddr_t)&_image_ram_start) |
| #define IMAGE_RAM_END ((vaddr_t)&_image_ram_end) |
| |
| #define IMAGE_TEXT_START ((vaddr_t)&_image_text_start) |
| #define IMAGE_TEXT_END ((vaddr_t)&_image_text_end) |
| |
| #define VALID_PERMISSION_MASK 0x00000001 /* permissions use only the lsb */ |
| |
| static inline void write_to_mem(void *dest, void *src, int width) |
| { |
| switch (width) { |
| case 4: |
| *((vaddr_t *)dest) = *((const vaddr_t *)src); |
| break; |
| case 2: |
| *((u16_t *)dest) = *((const u16_t *)src); |
| break; |
| case 1: |
| *((char *)dest) = *((const char *)src); |
| break; |
| } |
| } |
| |
| static inline int is_in_region(int slot, vaddr_t addr, vaddr_t end_addr) |
| { |
| vaddr_t region_start = mem_regions[slot].addr; |
| vaddr_t region_last_byte = mem_regions[slot].last_byte; |
| |
| return addr >= region_start && end_addr <= region_last_byte; |
| } |
| |
| static inline int is_in_a_ro_region(vaddr_t addr, vaddr_t end_addr) |
| { |
| int slot = ro_base; |
| |
| while (slot < ro_end && slot <= rw_end) { |
| if (is_in_region(slot, addr, end_addr)) { |
| return 1; |
| } |
| ++slot; |
| } |
| |
| return 0; |
| } |
| |
| static inline int is_in_a_rw_region(vaddr_t addr, vaddr_t end_addr) |
| { |
| int slot = rw_base; |
| |
| while (slot > rw_end && slot >= ro_end) { |
| if (is_in_region(slot, addr, end_addr)) { |
| return 1; |
| } |
| --slot; |
| } |
| |
| return 0; |
| } |
| |
| static inline int mem_probe_no_check(void *p, int perm, size_t num_bytes, |
| void *buf) |
| { |
| vaddr_t addr = (vaddr_t)p; |
| vaddr_t end_addr = addr + num_bytes - 1; |
| |
| int is_in_rw = is_in_a_rw_region(addr, end_addr); |
| int is_in_ro = is_in_a_ro_region(addr, end_addr); |
| |
| int valid_mem; |
| void *src, *dest; |
| |
| if (perm == SYS_MEM_SAFE_READ) { |
| dest = buf; src = p; |
| valid_mem = is_in_rw || is_in_ro; |
| } else { |
| dest = p; src = buf; |
| valid_mem = is_in_rw; |
| } |
| |
| if (likely(valid_mem)) { |
| write_to_mem(dest, src, num_bytes); |
| return 0; |
| } |
| return -EFAULT; |
| } |
| |
| static inline int is_perm_valid(int perm) |
| { |
| return !(perm & ~VALID_PERMISSION_MASK); |
| } |
| |
| static inline int is_num_bytes_valid(size_t num_bytes) |
| { |
| return is_power_of_two(num_bytes) && num_bytes <= sizeof(vaddr_t); |
| } |
| |
| int _mem_probe(void *p, int perm, size_t num_bytes, void *buf) |
| { |
| if (unlikely(!is_perm_valid(perm))) { |
| return -EINVAL; |
| } |
| |
| if (unlikely(!is_num_bytes_valid(num_bytes))) { |
| return -EINVAL; |
| } |
| |
| return mem_probe_no_check(p, perm, num_bytes, buf); |
| } |
| |
| static inline int mem_access(void *p, void *buf, size_t num_bytes, |
| int len, int perm) |
| { |
| char *p_char = p, *buf_char = buf, *p_end = ((char *)p + len); |
| |
| while (p_char < p_end) { |
| int error = mem_probe_no_check(p_char, perm, num_bytes, buf_char); |
| |
| if (unlikely(error < 0)) { |
| return error; |
| } |
| p_char += num_bytes; |
| buf_char += num_bytes; |
| } |
| |
| return 0; |
| } |
| |
| static inline int get_align(const u32_t value) |
| { |
| return (value & 1) ? 1 : (value & 2) ? 2 : 4; |
| } |
| |
| static inline int get_width(const void *p1, const void *p2, |
| size_t num_bytes, int width) |
| { |
| vaddr_t p1_addr = (vaddr_t)p1, p2_addr = (vaddr_t)p2; |
| |
| if (width == 0) { |
| u32_t align_check = num_bytes | p1_addr | p2_addr; |
| |
| return get_align(align_check); |
| } |
| |
| if (unlikely(p1_addr & (width - 1) || num_bytes & (width - 1))) { |
| return -EINVAL; |
| } |
| |
| return width; |
| } |
| |
| int _mem_safe_read(void *src, char *buf, size_t num_bytes, int width) |
| { |
| width = get_width(src, buf, num_bytes, width); |
| return unlikely(width < 0) ? -EINVAL : |
| mem_access(src, buf, width, num_bytes, SYS_MEM_SAFE_READ); |
| } |
| |
| int _mem_safe_write(void *dest, char *buf, size_t num_bytes, int width) |
| { |
| width = get_width(dest, buf, num_bytes, width); |
| return unlikely(width < 0) ? -EINVAL : |
| mem_access(dest, buf, width, num_bytes, SYS_MEM_SAFE_WRITE); |
| } |
| |
| #if defined(CONFIG_XIP) |
| int _mem_safe_write_to_text_section(void *dest, char *buf, size_t num_bytes) |
| { |
| ARG_UNUSED(dest); |
| ARG_UNUSED(buf); |
| ARG_UNUSED(num_bytes); |
| |
| /* cannot write to text section when it's in ROM */ |
| return -EFAULT; |
| } |
| #else |
| int _mem_safe_write_to_text_section(void *dest, char *buf, size_t num_bytes) |
| { |
| vaddr_t v = (vaddr_t)dest; |
| int is_in_text = ((v >= IMAGE_TEXT_START) && |
| ((v + num_bytes) <= IMAGE_TEXT_END)); |
| |
| if (unlikely(!is_in_text)) { |
| return -EFAULT; |
| } |
| |
| memcpy(dest, buf, num_bytes); |
| return 0; |
| } |
| #endif /* CONFIG_XIP */ |
| |
| int _mem_safe_region_add(void *addr, size_t num_bytes, int perm) |
| { |
| if (unlikely(!is_perm_valid(perm))) { |
| return -EINVAL; |
| } |
| |
| int slot; |
| int key = irq_lock(); |
| |
| if (unlikely(ro_end > rw_end)) { |
| irq_unlock(key); |
| return -ENOMEM; |
| } |
| |
| if (perm == SYS_MEM_SAFE_WRITE) { |
| slot = rw_end; |
| --rw_end; |
| } else { |
| slot = ro_end; |
| ++ro_end; |
| } |
| |
| mem_regions[slot].addr = (vaddr_t)addr; |
| mem_regions[slot].last_byte = mem_regions[slot].addr + num_bytes - 1; |
| |
| irq_unlock(key); |
| |
| return 0; |
| } |
| |
| static int init(struct device *unused) |
| { |
| void *addr; |
| size_t num_bytes; |
| |
| ARG_UNUSED(unused); |
| |
| addr = (void *)IMAGE_ROM_START; |
| num_bytes = (int)(IMAGE_ROM_END - IMAGE_ROM_START); |
| (void)_mem_safe_region_add(addr, num_bytes, SYS_MEM_SAFE_READ); |
| |
| addr = (void *)IMAGE_RAM_START; |
| num_bytes = (int)(IMAGE_RAM_END - IMAGE_RAM_START); |
| (void)_mem_safe_region_add(addr, num_bytes, SYS_MEM_SAFE_WRITE); |
| |
| return 0; |
| } |
| |
| SYS_INIT(init, PRE_KERNEL_1, CONFIG_KERNEL_INIT_PRIORITY_DEFAULT); |