|  | /* | 
|  | * 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 <nanokernel.h> | 
|  | #include <init.h> | 
|  | #include <errno.h> | 
|  | #include <toolchain.h> | 
|  | #include <linker-defs.h> | 
|  | #include <misc/util.h> | 
|  | #include <misc/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: | 
|  | *((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); |