blob: f9243c3b56b6afe624294ef606041df57b02c40c [file] [log] [blame]
/*
* 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-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:
*((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);