/* * Copyright (c) 2015 Wind River Systems, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /** * @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 #include #include #include #include #include #include #include #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: *((uint16_t *)dest) = *((const uint16_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 uint32_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) { uint32_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);