kernel: introduce object validation mechanism

All system calls made from userspace which involve pointers to kernel
objects (including device drivers) will need to have those pointers
validated; userspace should never be able to crash the kernel by passing
it garbage.

The actual validation with _k_object_validate() will be in the system
call receiver code, which doesn't exist yet.

- CONFIG_USERSPACE introduced. We are somewhat far away from having an
  end-to-end implementation, but at least need a Kconfig symbol to
  guard the incoming code with. Formal documentation doesn't exist yet
  either, but will appear later down the road once the implementation is
  mostly finalized.

- In the memory region for RAM, the data section has been moved last,
  past bss and noinit. This ensures that inserting generated tables
  with addresses of kernel objects does not change the addresses of
  those objects (which would make the table invalid)

- The DWARF debug information in the generated ELF binary is parsed to
  fetch the locations of all kernel objects and pass this to gperf to
  create a perfect hash table of their memory addresses.

- The generated gperf code doesn't know that we are exclusively working
  with memory addresses and uses memory inefficently. A post-processing
  script process_gperf.py adjusts the generated code before it is
  compiled to work with pointer values directly and not strings
  containing them.

- _k_object_init() calls inserted into the init functions for the set of
  kernel object types we are going to support so far

Issue: ZEP-2187
Signed-off-by: Andrew Boie <andrew.p.boie@intel.com>
This commit is contained in:
Andrew Boie 2017-08-22 13:15:23 -07:00 committed by Andrew Boie
commit 945af95f42
26 changed files with 1188 additions and 89 deletions

View file

@ -893,6 +893,8 @@ DEPRECATION_WARNING_STR := \
WARN_ABOUT_DEPRECATION := $(if $(CONFIG_BOARD_DEPRECATED),echo -e \
-n $(DEPRECATION_WARNING_STR),true)
GENERATED_KERNEL_OBJECT_FILES :=
ifeq ($(ARCH),x86)
include $(srctree)/arch/x86/Makefile.idt
ifeq ($(CONFIG_X86_MMU),y)
@ -907,6 +909,10 @@ ifeq ($(CONFIG_GEN_ISR_TABLES),y)
include $(srctree)/arch/common/Makefile.gen_isr_tables
endif
ifeq ($(CONFIG_USERSPACE),y)
include $(srctree)/arch/common/Makefile.kobjects
endif
ifneq ($(GENERATED_KERNEL_OBJECT_FILES),)
# Identical rule to linker.cmd, but we also define preprocessor LINKER_PASS2.

View file

@ -0,0 +1,57 @@
GEN_KOBJ_LIST := $(srctree)/scripts/gen_kobject_list.py
PROCESS_GPERF := $(srctree)/scripts/process_gperf.py
OBJ_LIST := kobject_hash.gperf
OUTPUT_SRC_PRE := kobject_hash_preprocessed.c
OUTPUT_SRC := kobject_hash.c
OUTPUT_OBJ := kobject_hash.o
OUTPUT_OBJ_RENAMED := kobject_hash_renamed.o
SCRIPT_EXTRA_ARGS :=
ifeq ($(KBUILD_VERBOSE),1)
SCRIPT_EXTRA_ARGS += --verbose
endif
# Scan the kernel binary's DWARF information to produce a table of
# kernel objects which we will pass to gperf
quiet_cmd_gen_kobj_list = KOBJ $@
cmd_gen_kobj_list = $(GEN_KOBJ_LIST) --kernel $< --output $@ \
$(SCRIPT_EXTRA_ARGS)
$(OBJ_LIST): $(PREBUILT_KERNEL) $(GEN_KOBJ_LIST)
$(call cmd,gen_kobj_list)
# Generate C code which implements a perfect hashtable based on our
# table of kernel objects
quiet_cmd_gperf = GPERF $@
cmd_gperf = gperf --output-file=$@ $<
$(OUTPUT_SRC_PRE): $(OBJ_LIST)
$(call cmd,gperf)
# For our purposes, the code/data generated by gperf is not optimal.
# This script adjusts the generated .c file to greatly reduce the amount
# of code/data generated since we know we are always working with
# pointer values
quiet_cmd_process_gperf = PROCESS $@
cmd_process_gperf = $(PROCESS_GPERF) -i $< -o $@ $(SCRIPT_EXTRA_ARGS)
$(OUTPUT_SRC): $(OUTPUT_SRC_PRE) $(PROCESS_GPERF)
$(call cmd,process_gperf)
# We need precise control of where generated text/data ends up in the final
# kernel image. Disable function/data sections and use objcopy to move
# generated data into special section names
$(OUTPUT_OBJ): KBUILD_CFLAGS += -fno-function-sections -fno-data-sections
quiet_cmd_kobject_objcopy = OBJCOPY $@
cmd_kobject_objcopy = $(OBJCOPY) \
--rename-section .data=.kobject_data.data \
--rename-section .rodata=.kobject_data.rodata \
--rename-section .text=.kobject_data.text \
$< $@
$(OUTPUT_OBJ_RENAMED): $(OUTPUT_OBJ)
$(call cmd,kobject_objcopy)
GENERATED_KERNEL_OBJECT_FILES += $(OUTPUT_OBJ_RENAMED)

View file

@ -117,6 +117,9 @@ SECTIONS
*(.text)
*(".text.*")
*(.gnu.linkonce.t.*)
#include <linker/kobject-text.ld>
} GROUP_LINK_IN(ROMABLE_REGION)
_image_text_end = .;
@ -148,6 +151,9 @@ SECTIONS
/* Located in project source directory */
#include <custom-rodata.ld>
#endif
#include <linker/kobject-rom.ld>
/*
* For XIP images, in order to avoid the situation when __data_rom_start
* is 32-bit aligned, but the actual data is placed right after rodata
@ -208,30 +214,6 @@ SECTIONS
__app_ram_end = .;
#endif /* CONFIG_APPLICATION_MEMORY */
SECTION_DATA_PROLOGUE(_DATA_SECTION_NAME,,)
{
#ifndef CONFIG_APPLICATION_MEMORY
_image_ram_start = .;
#endif
__kernel_ram_start = .;
__data_ram_start = .;
KERNEL_INPUT_SECTION(.data)
KERNEL_INPUT_SECTION(".data.*")
#ifdef CONFIG_CUSTOM_RWDATA_LD
/* Located in project source directory */
#include <custom-rwdata.ld>
#endif
} GROUP_DATA_LINK_IN(RAMABLE_REGION, ROMABLE_REGION)
__data_rom_start = LOADADDR(_DATA_SECTION_NAME);
#include <linker/common-ram.ld>
__data_ram_end = .;
SECTION_DATA_PROLOGUE(_BSS_SECTION_NAME,(NOLOAD),)
{
/*
@ -240,6 +222,11 @@ SECTIONS
*/
. = ALIGN(4);
__bss_start = .;
#ifndef CONFIG_APPLICATION_MEMORY
_image_ram_start = .;
#endif
__kernel_ram_start = .;
KERNEL_INPUT_SECTION(.bss)
KERNEL_INPUT_SECTION(".bss.*")
KERNEL_INPUT_SECTION(COMMON)
@ -265,6 +252,27 @@ SECTIONS
} GROUP_LINK_IN(RAMABLE_REGION)
SECTION_DATA_PROLOGUE(_DATA_SECTION_NAME,,)
{
__data_ram_start = .;
KERNEL_INPUT_SECTION(.data)
KERNEL_INPUT_SECTION(".data.*")
#ifdef CONFIG_CUSTOM_RWDATA_LD
/* Located in project source directory */
#include <custom-rwdata.ld>
#endif
} GROUP_DATA_LINK_IN(RAMABLE_REGION, ROMABLE_REGION)
__data_rom_start = LOADADDR(_DATA_SECTION_NAME);
#include <linker/common-ram.ld>
#include <linker/kobject.ld>
__data_ram_end = .;
/* Define linker symbols */
_image_ram_end = .;

View file

@ -83,6 +83,9 @@ SECTIONS
*(.eini)
KEEP(*(.openocd_dbg))
KEEP(*(".openocd_dbg.*"))
#include <linker/kobject-text.ld>
} GROUP_LINK_IN(ROMABLE_REGION)
_image_text_end = .;
@ -95,7 +98,6 @@ SECTIONS
*(.rodata)
*(".rodata.*")
*(.gnu.linkonce.r.*)
. = ALIGN(8);
_idt_base_address = .;
#ifdef LINKER_PASS2
@ -119,15 +121,20 @@ SECTIONS
#include <custom-rodata.ld>
#endif
#include <linker/kobject-rom.ld>
} GROUP_LINK_IN(ROMABLE_REGION)
_image_rodata_end = .;
/* This align directive needs to be after __data_rom_start or XIP
* won't be copying the data from the right LMA
*/
MMU_PAGE_ALIGN
#ifdef CONFIG_XIP
/* Kernel ROM extends to the end of flash. Need to do this to program
* the MMU
*/
_image_rom_end = _image_rom_start + KB(CONFIG_ROM_SIZE);
#else
/* ROM ends here, position counter will now be in RAM areas */
_image_rom_end = .;
#endif
_image_rom_size = _image_rom_end - _image_rom_start;
GROUP_END(ROMABLE_REGION)
@ -170,68 +177,17 @@ SECTIONS
__app_ram_size = __app_ram_end - __app_ram_start;
#endif /* CONFIG_APPLICATION_MEMORY */
SECTION_DATA_PROLOGUE(_DATA_SECTION_NAME, (OPTIONAL),)
{
#ifndef CONFIG_APPLICATION_MEMORY
_image_ram_start = .;
#endif
__kernel_ram_start = .;
__data_ram_start = .;
KERNEL_INPUT_SECTION(.data)
KERNEL_INPUT_SECTION(".data.*")
*(".kernel.*")
#ifdef CONFIG_CUSTOM_RWDATA_LD
/* Located in project source directory */
#include <custom-rwdata.ld>
#endif
#ifdef CONFIG_GDT_DYNAMIC
KEEP(*(.tss))
. = ALIGN(8);
_gdt = .;
#ifdef LINKER_PASS2
KEEP(*(gdt_ram_data))
#else /* LINKER_PASS2 */
#ifdef CONFIG_X86_STACK_PROTECTION
#define GDT_NUM_ENTRIES 5
#else /* CONFIG_X86_STACK_PROTECTION */
#define GDT_NUM_ENTRIES 3
#endif /* CONFIG_X86_STACK_PROTECTION */
. += GDT_NUM_ENTRIES * 8;
#endif /* LINKER_PASS2 */
#endif /* CONFIG_GDT_DYNAMIC */
#ifdef CONFIG_X86_MMU
/* Page Tables are located here if MMU is enabled.*/
MMU_PAGE_ALIGN
__mmu_tables_start = .;
KEEP(*(.mmu_data));
__mmu_tables_end = .;
#endif
. = ALIGN(4);
} GROUP_DATA_LINK_IN(RAMABLE_REGION, ROMABLE_REGION)
__data_rom_start = LOADADDR(_DATA_SECTION_NAME);
#include <linker/common-ram.ld>
__data_ram_end = .;
SECTION_PROLOGUE(_BSS_SECTION_NAME, (NOLOAD OPTIONAL),)
{
/*
* For performance, BSS section is forced to be both 4 byte aligned and
* a multiple of 4 bytes.
*/
. = ALIGN(4);
#ifndef CONFIG_APPLICATION_MEMORY
_image_ram_start = .;
#endif
__kernel_ram_start = .;
__bss_start = .;
KERNEL_INPUT_SECTION(.bss)
@ -269,6 +225,67 @@ SECTIONS
} GROUP_DATA_LINK_IN(RAMABLE_REGION, RAMABLE_REGION)
SECTION_DATA_PROLOGUE(_DATA_SECTION_NAME, (OPTIONAL),)
{
__data_ram_start = .;
KERNEL_INPUT_SECTION(.data)
KERNEL_INPUT_SECTION(".data.*")
*(".kernel.*")
#ifdef CONFIG_CUSTOM_RWDATA_LD
/* Located in project source directory */
#include <custom-rwdata.ld>
#endif
#ifdef CONFIG_GDT_DYNAMIC
KEEP(*(.tss))
. = ALIGN(8);
_gdt = .;
#ifdef LINKER_PASS2
KEEP(*(gdt_ram_data))
#else /* LINKER_PASS2 */
#ifdef CONFIG_X86_STACK_PROTECTION
#define GDT_NUM_ENTRIES 5
#else /* CONFIG_X86_STACK_PROTECTION */
#define GDT_NUM_ENTRIES 3
#endif /* CONFIG_X86_STACK_PROTECTION */
. += GDT_NUM_ENTRIES * 8;
#endif /* LINKER_PASS2 */
#endif /* CONFIG_GDT_DYNAMIC */
. = ALIGN(4);
} GROUP_DATA_LINK_IN(RAMABLE_REGION, ROMABLE_REGION)
__data_rom_start = LOADADDR(_DATA_SECTION_NAME);
#include <linker/common-ram.ld>
#ifdef CONFIG_X86_MMU
/* Can't really predict the size of this section. Anything after this
* should not be affected if addresses change between builds (currently
* just the gperf tables which is fine).
*
* However, __mmu_tables_start *must* remain stable between builds,
* we can't have anything shifting the memory map beforehand.
*/
SECTION_DATA_PROLOGUE(mmu_tables, (OPTIONAL),)
{
/* Page Tables are located here if MMU is enabled.*/
MMU_PAGE_ALIGN
__mmu_tables_start = .;
KEEP(*(.mmu_data));
__mmu_tables_end = .;
} GROUP_DATA_LINK_IN(RAMABLE_REGION, ROMABLE_REGION)
#endif
#include <linker/kobject.ld>
__data_ram_end = .;
/* All unused memory also owned by the kernel for heaps */
__kernel_ram_end = PHYS_RAM_ADDR + KB(CONFIG_RAM_SIZE);
__kernel_ram_size = __kernel_ram_end - __kernel_ram_start;

View file

@ -123,6 +123,103 @@ struct k_timer;
struct k_poll_event;
struct k_poll_signal;
enum k_objects {
K_OBJ_ALERT,
K_OBJ_DELAYED_WORK,
K_OBJ_MEM_SLAB,
K_OBJ_MSGQ,
K_OBJ_MUTEX,
K_OBJ_PIPE,
K_OBJ_SEM,
K_OBJ_STACK,
K_OBJ_THREAD,
K_OBJ_TIMER,
K_OBJ_WORK,
K_OBJ_WORK_Q,
K_OBJ_LAST
};
#ifdef CONFIG_USERSPACE
/* Table generated by gperf, these objects are retrieved via
* _k_object_find() */
struct _k_object {
char *name;
char perms[CONFIG_MAX_THREAD_BYTES];
char type;
char flags;
} __packed;
#define K_OBJ_FLAG_INITIALIZED BIT(0)
/**
* Ensure a system object is a valid object of the expected type
*
* Searches for the object and ensures that it is indeed an object
* of the expected type, that the caller has the right permissions on it,
* and that the object has been initialized.
*
* This function is intended to be called on the kernel-side system
* call handlers to validate kernel object pointers passed in from
* userspace.
*
* @param obj Address of the kernel object
* @param otype Expected type of the kernel object
* @param init If true, this is for an init function and we will not error
* out if the object is not initialized
* @return 0 If the object is valid
* -EBADF if not a valid object of the specified type
* -EPERM If the caller does not have permissions
* -EINVAL Object is not intitialized
*/
int _k_object_validate(void *obj, enum k_objects otype, int init);
/**
* Lookup a kernel object and init its metadata if it exists
*
* Calling this on an object will make it usable from userspace.
* Intended to be called as the last statement in kernel object init
* functions.
*
* @param object Address of the kernel object
*/
void _k_object_init(void *obj);
/**
* grant a thread access to a kernel object
*
* The thread will be granted access to the object if the caller is from
* supervisor mode, or the caller is from user mode AND has permissions
* on the object already.
*
* @param object Address of kernel object
* @param thread Thread to grant access to the object
*/
void k_object_grant_access(void *object, struct k_thread *thread);
#else
static inline int _k_object_validate(void *obj, enum k_objects otype, int init)
{
ARG_UNUSED(obj);
ARG_UNUSED(otype);
ARG_UNUSED(init);
return 0;
}
static inline void _k_object_init(void *obj)
{
ARG_UNUSED(obj);
}
static inline void k_object_grant_access(void *object, struct k_thread *thread)
{
ARG_UNUSED(object);
ARG_UNUSED(thread);
}
#endif /* CONFIG_USERSPACE */
/* timeouts */
struct _timeout;
@ -196,7 +293,6 @@ struct _thread_base {
/* this thread's entry in a timeout queue */
struct _timeout timeout;
#endif
};
typedef struct _thread_base _thread_base_t;
@ -2022,6 +2118,7 @@ static inline void k_work_init(struct k_work *work, k_work_handler_t handler)
{
atomic_clear_bit(work->flags, K_WORK_STATE_PENDING);
work->handler = handler;
_k_object_init(work);
}
/**

View file

@ -22,6 +22,17 @@
_static_thread_data_list_end = .;
} GROUP_DATA_LINK_IN(RAMABLE_REGION, ROMABLE_REGION)
#ifdef CONFIG_USERSPACE
/* All kernel objects within are assumed to be either completely
* initialized at build time, or initialized automatically at runtime
* via iteration before the POST_KERNEL phase.
*
* These two symbols only used by gen_kobject_list.py
*/
_static_kernel_objects_begin = .;
#endif /* CONFIG_USERSPACE */
SECTION_DATA_PROLOGUE(_k_timer_area, (OPTIONAL), SUBALIGN(4))
{
_k_timer_list_start = .;
@ -176,3 +187,7 @@
KEEP(*(SORT_BY_NAME(".net_l2.data*")))
__net_l2_data_end = .;
} GROUP_DATA_LINK_IN(RAMABLE_REGION, ROMABLE_REGION)
#ifdef CONFIG_USERSPACE
_static_kernel_objects_end = .;
#endif

View file

@ -0,0 +1,13 @@
/*
* Copyright (c) 2017 Intel Corporation
*
* SPDX-License-Identifier: Apache-2.0
*/
#ifdef CONFIG_USERSPACE
/* Kept in RAM on non-XIP */
#ifdef CONFIG_XIP
*(".kobject_data.rodata*")
#endif
#endif /* CONFIG_USERSPACE */

View file

@ -0,0 +1,25 @@
#ifndef KOBJECT_TEXT_AREA
#if defined(CONFIG_DEBUG) || defined(CONFIG_STACK_CANARIES)
#define KOBJECT_TEXT_AREA 256
#else
#define KOBJECT_TEXT_AREA 78
#endif
#endif
#ifdef CONFIG_USERSPACE
/* We need to reserve room for the gperf generated hash functions.
* Fortunately, unlike the data tables, the size of the code is
* reasonably predictable; on x86 usually about 44 bytes with -Os.
*
* The linker will error out complaining that the location pointer
* is moving backwards if the reserved room isn't large enough.
*/
_kobject_text_area_start = .;
*(".kobject_data.text*")
_kobject_text_area_end = .;
#ifndef LINKER_PASS2
PROVIDE(_k_object_find = .);
#endif
. += KOBJECT_TEXT_AREA - (_kobject_text_area_end - _kobject_text_area_start);
#endif /* CONFIG_USERSPACE */

36
include/linker/kobject.ld Normal file
View file

@ -0,0 +1,36 @@
/*
* Copyright (c) 2017 Intel Corporation
*
* SPDX-License-Identifier: Apache-2.0
*/
#ifdef CONFIG_USERSPACE
/* Constraints:
*
* - changes to the size of this section between build phases
* *must not* shift the memory address of any kernel obejcts,
* since it contains a hashtable of the memory addresses of those
* kernel objects
*
* - It is OK if this section itself is shifted in between builds; for
* example some arches may precede this section with generated MMU
* page tables which are also unpredictable in size.
*
* The size of the
* gperf tables is both a function of the number of kernel objects,
* *and* the specific memory addresses being hashed. It is not something
* that can be predicted without actually building and compling it.
*/
SECTION_DATA_PROLOGUE(kobject_data, (OPTIONAL),)
{
*(".kobject_data.data*")
/* This is also unpredictable in size, and has the same constraints.
* On XIP systems this will get put at the very end of ROM.
*/
#ifndef CONFIG_XIP
*(".kobject_data.rodata*")
#endif
} GROUP_DATA_LINK_IN(RAMABLE_REGION, ROMABLE_REGION)
#endif /* CONFIG_USERSPACE */

View file

@ -354,6 +354,26 @@ config NUM_PIPE_ASYNC_MSGS
Setting this option to 0 disables support for asynchronous
pipe messages.
config USERSPACE
bool "Enable user-level threads"
default n
help
When enabled, threads may be created or dropped down to user mode,
which has significantly restricted permissions and must interact
with the kernel via system calls. See Zephyr documentation for more
details about this feature.
config MAX_THREAD_BYTES
int "Bytes to use when tracking object thread permissions"
default 2
depends on USERSPACE
help
Every kernel object will have an associated bitfield to store
thread permissions for that object. This controls the size of the
bitfield (in bytes) and imposes a limit on how many threads can
be created in the system.
endmenu
menu "Memory Pool Options"

View file

@ -37,3 +37,4 @@ lib-$(CONFIG_SYS_CLOCK_EXISTS) += timer.o
lib-$(CONFIG_ATOMIC_OPERATIONS_C) += atomic_c.o
lib-$(CONFIG_POLL) += poll.o
lib-$(CONFIG_PTHREAD_IPC) += pthread.o
lib-$(CONFIG_USERSPACE) += userspace.o

View file

@ -69,6 +69,8 @@ void k_alert_init(struct k_alert *alert, k_alert_handler_t handler,
alert->work_item = (struct k_work)_K_WORK_INITIALIZER(_alert_deliver);
k_sem_init(&alert->sem, 0, max_num_pending_alerts);
SYS_TRACING_OBJ_INIT(k_alert, alert);
_k_object_init(alert);
}
void k_alert_send(struct k_alert *alert)

View file

@ -300,6 +300,7 @@ static void prepare_multithreading(struct k_thread *dummy_thread)
CONFIG_MAIN_THREAD_PRIORITY, K_ESSENTIAL);
_mark_thread_as_started(_main_thread);
_add_thread_to_ready_q(_main_thread);
_k_object_init(_main_thread);
#ifdef CONFIG_MULTITHREADING
_new_thread(_idle_thread, _idle_stack,
@ -307,6 +308,7 @@ static void prepare_multithreading(struct k_thread *dummy_thread)
K_LOWEST_THREAD_PRIO, K_ESSENTIAL);
_mark_thread_as_started(_idle_thread);
_add_thread_to_ready_q(_idle_thread);
_k_object_init(_idle_thread);
#endif
initialize_timeouts();
@ -351,8 +353,11 @@ FUNC_NORETURN void _Cstart(void)
#ifdef CONFIG_ARCH_HAS_CUSTOM_SWAP_TO_MAIN
struct k_thread *dummy_thread = NULL;
#else
struct k_thread dummy_thread_memory;
struct k_thread *dummy_thread = &dummy_thread_memory;
/* Normally, kernel objects are not allowed on the stack, special case
* here since this is just being used to bootstrap the first _Swap()
*/
char dummy_thread_memory[sizeof(struct k_thread)];
struct k_thread *dummy_thread = (struct k_thread *)&dummy_thread_memory;
#endif
/*

View file

@ -79,6 +79,8 @@ void k_mem_slab_init(struct k_mem_slab *slab, void *buffer,
create_free_list(slab);
sys_dlist_init(&slab->wait_q);
SYS_TRACING_OBJ_INIT(k_mem_slab, slab);
_k_object_init(slab);
}
int k_mem_slab_alloc(struct k_mem_slab *slab, void **mem, s32_t timeout)

View file

@ -58,6 +58,8 @@ void k_msgq_init(struct k_msgq *q, char *buffer,
q->used_msgs = 0;
sys_dlist_init(&q->wait_q);
SYS_TRACING_OBJ_INIT(k_msgq, q);
_k_object_init(q);
}
int k_msgq_put(struct k_msgq *q, void *data, s32_t timeout)

View file

@ -77,6 +77,7 @@ void k_mutex_init(struct k_mutex *mutex)
sys_dlist_init(&mutex->wait_q);
SYS_TRACING_OBJ_INIT(k_mutex, mutex);
_k_object_init(mutex);
}
static int new_prio_for_inheritance(int target, int limit)

View file

@ -136,6 +136,7 @@ void k_pipe_init(struct k_pipe *pipe, unsigned char *buffer, size_t size)
sys_dlist_init(&pipe->wait_q.writers);
sys_dlist_init(&pipe->wait_q.readers);
SYS_TRACING_OBJ_INIT(k_pipe, pipe);
_k_object_init(pipe);
}
/**

View file

@ -65,6 +65,8 @@ void k_sem_init(struct k_sem *sem, unsigned int initial_count,
#endif
SYS_TRACING_OBJ_INIT(k_sem, sem);
_k_object_init(sem);
}

View file

@ -51,6 +51,7 @@ void k_stack_init(struct k_stack *stack, u32_t *buffer, int num_entries)
stack->top = stack->base + num_entries;
SYS_TRACING_OBJ_INIT(k_stack, stack);
_k_object_init(stack);
}
void k_stack_push(struct k_stack *stack, u32_t data)

View file

@ -258,6 +258,7 @@ k_tid_t k_thread_create(struct k_thread *new_thread,
__ASSERT(!_is_in_isr(), "Threads may not be created in ISRs");
_new_thread(new_thread, stack, stack_size, entry, p1, p2, p3,
prio, options);
_k_object_init(new_thread);
schedule_new_thread(new_thread, delay);
return new_thread;
@ -424,6 +425,7 @@ void _init_static_threads(void)
thread_data->init_options);
thread_data->init_thread->init_data = thread_data;
_k_object_init(thread_data->init_thread);
}
_sched_lock();

View file

@ -103,6 +103,8 @@ void k_timer_init(struct k_timer *timer,
SYS_TRACING_OBJ_INIT(k_timer, timer);
timer->user_data = NULL;
_k_object_init(timer);
}

188
kernel/userspace.c Normal file
View file

@ -0,0 +1,188 @@
/*
* Copyright (c) 2017 Intel Corporation
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <kernel.h>
#include <string.h>
#include <misc/printk.h>
#include <kernel_structs.h>
#include <sys_io.h>
/**
* Kernel object validation function
*
* Retrieve metadata for a kernel object. This function is implemented in
* the gperf script footer, see gen_kobject_list.py
*
* @param obj Address of kernel object to get metadata
* @return Kernel object's metadata, or NULL if the parameter wasn't the
* memory address of a kernel object
*/
extern struct _k_object *_k_object_find(void *obj);
const char *otype_to_str(enum k_objects otype)
{
/* -fdata-sections doesn't work right except in very very recent
* GCC and these literal strings would appear in the binary even if
* otype_to_str was omitted by the linker
*/
#ifdef CONFIG_PRINTK
switch (otype) {
case K_OBJ_ALERT:
return "k_alert";
case K_OBJ_DELAYED_WORK:
return "k_delayed_work";
case K_OBJ_MEM_SLAB:
return "k_mem_slab";
case K_OBJ_MSGQ:
return "k_msgq";
case K_OBJ_MUTEX:
return "k_mutex";
case K_OBJ_PIPE:
return "k_pipe";
case K_OBJ_SEM:
return "k_sem";
case K_OBJ_STACK:
return "k_stack";
case K_OBJ_THREAD:
return "k_thread";
case K_OBJ_TIMER:
return "k_timer";
case K_OBJ_WORK:
return "k_work";
case K_OBJ_WORK_Q:
return "k_work_q";
default:
return "?";
}
#else
ARG_UNUSED(otype);
return NULL;
#endif
}
/* Stub functions, to be filled in forthcoming patch sets */
static void set_thread_perms(struct _k_object *ko, struct k_thread *thread)
{
ARG_UNUSED(ko);
ARG_UNUSED(thread);
/* STUB */
}
static int test_thread_perms(struct _k_object *ko)
{
ARG_UNUSED(ko);
/* STUB */
return 1;
}
static int _is_thread_user(void)
{
/* STUB */
return 0;
}
void k_object_grant_access(void *object, struct k_thread *thread)
{
struct _k_object *ko = _k_object_find(object);
if (!ko) {
if (_is_thread_user()) {
printk("granting access to non-existent kernel object %p\n",
object);
k_oops();
} else {
/* Supervisor threads may at times instantiate objects
* that ignore rules on where they can live. Such
* objects won't ever be usable from userspace, but
* we shouldn't explode.
*/
return;
}
}
/* userspace can't grant access to objects unless it already has
* access to that object
*/
if (_is_thread_user() && !test_thread_perms(ko)) {
printk("insufficient permissions in current thread %p\n",
_current);
printk("Cannot grant access to %s %p for thread %p\n",
otype_to_str(ko->type), object, thread);
k_oops();
}
set_thread_perms(ko, thread);
}
int _k_object_validate(void *obj, enum k_objects otype, int init)
{
struct _k_object *ko;
ko = _k_object_find(obj);
if (!ko || ko->type != otype) {
printk("%p is not a %s\n", obj, otype_to_str(otype));
return -EBADF;
}
/* Uninitialized objects are not owned by anyone. However if an
* object is initialized, and the caller is from userspace, then
* we need to assert that the user thread has sufficient permissions
* to re-initialize.
*/
if (ko->flags & K_OBJ_FLAG_INITIALIZED && _is_thread_user() &&
!test_thread_perms(ko)) {
printk("thread %p does not have permission on %s %p\n",
_current, otype_to_str(otype), obj);
return -EPERM;
}
/* If we are not initializing an object, and the object is not
* initialized, we should freak out
*/
if (!init && !(ko->flags & K_OBJ_FLAG_INITIALIZED)) {
printk("%p used before initialization\n", obj);
return -EINVAL;
}
return 0;
}
void _k_object_init(void *object)
{
struct _k_object *ko;
/* By the time we get here, if the caller was from userspace, all the
* necessary checks have been done in _k_object_validate(), which takes
* place before the object is initialized.
*
* This function runs after the object has been initialized and
* finalizes it
*/
ko = _k_object_find(object);
if (!ko) {
/* Supervisor threads can ignore rules about kernel objects
* and may declare them on stacks, etc. Such objects will never
* be usable from userspace, but we shouldn't explode.
*/
return;
}
memset(ko->perms, 0, CONFIG_MAX_THREAD_BYTES);
set_thread_perms(ko, _current);
ko->flags |= K_OBJ_FLAG_INITIALIZED;
}

View file

@ -50,9 +50,9 @@ void k_work_q_start(struct k_work_q *work_q, k_thread_stack_t stack,
size_t stack_size, int prio)
{
k_queue_init(&work_q->queue);
k_thread_create(&work_q->thread, stack, stack_size, work_q_main,
work_q, 0, 0, prio, 0, 0);
_k_object_init(work_q);
}
#ifdef CONFIG_SYS_CLOCK_EXISTS
@ -70,6 +70,8 @@ void k_delayed_work_init(struct k_delayed_work *work, k_work_handler_t handler)
k_work_init(&work->work, handler);
_init_timeout(&work->timeout, work_timeout);
work->work_q = NULL;
_k_object_init(work);
}
int k_delayed_work_submit_to_queue(struct k_work_q *work_q,

443
scripts/gen_kobject_list.py Executable file
View file

@ -0,0 +1,443 @@
#!/usr/bin/env python3
#
# Copyright (c) 2017 Intel Corporation
#
# SPDX-License-Identifier: Apache-2.0
import sys
import argparse
import pprint
import os
import struct
from distutils.version import LooseVersion
import elftools
from elftools.elf.elffile import ELFFile
from elftools.dwarf import descriptions
from elftools.elf.sections import SymbolTableSection
if LooseVersion(elftools.__version__) < LooseVersion('0.24'):
sys.stderr.write("pyelftools is out of date, need version 0.24 or later\n")
sys.exit(1)
kobjects = {
"k_alert" : "K_OBJ_ALERT",
"k_delayed_work" : "K_OBJ_DELAYED_WORK",
"k_mem_slab" : "K_OBJ_MEM_SLAB",
"k_msgq" : "K_OBJ_MSGQ",
"k_mutex" : "K_OBJ_MUTEX",
"k_pipe" : "K_OBJ_PIPE",
"k_sem" : "K_OBJ_SEM",
"k_stack" : "K_OBJ_STACK",
"k_thread" : "K_OBJ_THREAD",
"k_timer" : "K_OBJ_TIMER",
"k_work" : "K_OBJ_WORK",
"k_work_q" : "K_OBJ_WORK_Q",
}
DW_OP_addr = 0x3
DW_OP_fbreg = 0x91
# Global type environment. Populated by pass 1.
type_env = {}
# --- debug stuff ---
scr = os.path.basename(sys.argv[0])
def debug(text):
if not args.verbose:
return
sys.stdout.write(scr + ": " + text + "\n")
def error(text):
sys.stderr.write("%s ERROR: %s\n" % (scr, text))
sys.exit(1)
def debug_die(die, text):
fn, ln = get_filename_lineno(die)
debug(str(die))
debug("File '%s', line %d:" % (fn, ln))
debug(" %s" % text)
# --- type classes ----
class ArrayType:
def __init__(self, offset, num_members, member_type):
self.num_members = num_members
self.member_type = member_type
self.offset = offset
def __repr__(self):
return "<array of %d, size %d>" % (self.member_type, self.num_members)
def has_kobject(self):
if self.member_type not in type_env:
return False
return type_env[self.member_type].has_kobject()
def get_kobjects(self, addr):
mt = type_env[self.member_type]
objs = []
for i in range(self.num_members):
objs.extend(mt.get_kobjects(addr + (i * mt.size)))
return objs
class AggregateTypeMember:
def __init__(self, offset, member_name, member_type, member_offset):
self.member_name = member_name
self.member_type = member_type
self.member_offset = member_offset
def __repr__(self):
return "<member %s, type %d, offset %d>" % (self.member_name,
self.member_type, self.member_offset)
def has_kobject(self):
if self.member_type not in type_env:
return False
return type_env[self.member_type].has_kobject()
def get_kobjects(self, addr):
mt = type_env[self.member_type]
return mt.get_kobjects(addr + self.member_offset)
class AggregateType:
def __init__(self, offset, name, size):
self.name = name
self.size = size
self.offset = offset
self.members = []
def add_member(self, member):
self.members.append(member)
def __repr__(self):
return "<struct %s, with %s>" % (self.name, self.members)
def has_kobject(self):
result = False
bad_members = []
for member in self.members:
if member.has_kobject():
result = True
else:
bad_members.append(member)
# Don't need to consider this again, just remove it
for bad_member in bad_members:
self.members.remove(bad_member)
return result
def get_kobjects(self, addr):
objs = []
for member in self.members:
objs.extend(member.get_kobjects(addr))
return objs
class KobjectType:
def __init__(self, offset, name, size):
self.name = name
self.size = size
self.offset = offset
def __repr__(self):
return "<kobject %s>" % self.name
def has_kobject(self):
return True
def get_kobjects(self, addr):
return [(addr, kobjects[self.name])]
# --- helper functions for getting data from DIEs ---
def die_get_name(die):
if not 'DW_AT_name' in die.attributes:
return None
return die.attributes["DW_AT_name"].value.decode("utf-8")
def die_get_type_offset(die):
return die.attributes["DW_AT_type"].value + die.cu.cu_offset
def die_get_byte_size(die):
if not 'DW_AT_byte_size' in die.attributes:
return 0
return die.attributes["DW_AT_byte_size"].value
def analyze_die_struct(die):
name = die_get_name(die) or "<anon>"
offset = die.offset
size = die_get_byte_size(die)
# Incomplete type
if not size:
return
if name not in kobjects:
at = AggregateType(offset, name, size)
type_env[offset] = at
for child in die.iter_children():
if child.tag != "DW_TAG_member":
continue
child_type = die_get_type_offset(child)
member_offset = child.attributes["DW_AT_data_member_location"].value
cname = die_get_name(child) or "<anon>"
m = AggregateTypeMember(child.offset, cname, child_type,
member_offset)
at.add_member(m)
return
type_env[offset] = KobjectType(offset, name, size)
def analyze_die_array(die):
type_offset = die_get_type_offset(die)
elements = 1
size_found = False
for child in die.iter_children():
if child.tag != "DW_TAG_subrange_type":
continue
if "DW_AT_upper_bound" not in child.attributes:
continue
ub = child.attributes["DW_AT_upper_bound"]
if not ub.form.startswith("DW_FORM_data"):
continue
size_found = True
elements = elements * (ub.value + 1)
if not size_found:
return
type_env[die.offset] = ArrayType(die.offset, elements, type_offset)
def get_filename_lineno(die):
lp_header = die.dwarfinfo.line_program_for_CU(die.cu).header
files = lp_header["file_entry"]
includes = lp_header["include_directory"]
fileinfo = files[die.attributes["DW_AT_decl_file"].value - 1]
filename = fileinfo.name.decode("utf-8")
filedir = includes[fileinfo.dir_index - 1].decode("utf-8")
path = os.path.join(filedir, filename)
lineno = die.attributes["DW_AT_decl_line"].value
return (path, lineno)
def find_kobjects(elf, syms):
if not elf.has_dwarf_info():
sys.stderr.write("ELF file has no DWARF information\n");
sys.exit(1)
kram_start = syms["__kernel_ram_start"]
kram_end = syms["__kernel_ram_end"]
di = elf.get_dwarf_info()
variables = []
# Step 1: collect all type information.
for CU in di.iter_CUs():
CU_path = CU.get_top_DIE().get_full_path()
lp = di.line_program_for_CU(CU)
for idx, die in enumerate(CU.iter_DIEs()):
# Unions are disregarded, kernel objects should never be union
# members since the memory is not dedicated to that object and
# could be something else
if die.tag == "DW_TAG_structure_type":
analyze_die_struct(die)
elif die.tag == "DW_TAG_array_type":
analyze_die_array(die)
elif die.tag == "DW_TAG_variable":
variables.append(die)
# Step 2: filter type_env to only contain kernel objects, or structs and
# arrays of kernel objects
bad_offsets = []
for offset, type_object in type_env.items():
if not type_object.has_kobject():
bad_offsets.append(offset)
for offset in bad_offsets:
del type_env[offset]
# Step 3: Now that we know all the types we are looking for, examine
# all variables
all_objs = []
# Gross hack, see below
work_q_found = False
for die in variables:
name = die_get_name(die)
if not name:
continue
type_offset = die_get_type_offset(die)
# Is this a kernel object, or a structure containing kernel objects?
if type_offset not in type_env:
continue
if "DW_AT_declaration" in die.attributes:
# FIXME: why does k_sys_work_q not resolve an address in the DWARF
# data??? Every single instance it finds is an extern definition
# but not the actual instance in system_work_q.c
# Is there something weird about how lib-y stuff is linked?
if name == "k_sys_work_q" and not work_q_found and name in syms:
addr = syms[name]
work_q_found = True
else:
continue
else:
if "DW_AT_location" not in die.attributes:
debug_die(die, "No location information for object '%s'; possibly stack allocated"
% name)
continue
loc = die.attributes["DW_AT_location"]
if loc.form != "DW_FORM_exprloc":
debug_die(die, "kernel object '%s' unexpected location format" % name)
continue
opcode = loc.value[0]
if opcode != DW_OP_addr:
# Check if frame pointer offset DW_OP_fbreg
if opcode == DW_OP_fbreg:
debug_die(die, "kernel object '%s' found on stack" % name)
else:
debug_die(die, "kernel object '%s' unexpected exprloc opcode %s"
% (name, hex(opcode)))
continue
addr = (loc.value[1] | (loc.value[2] << 8) | (loc.value[3] << 16) |
(loc.value[4] << 24))
if addr < kram_start or addr >= kram_end:
if addr == 0:
# Never linked; gc-sections deleted it
continue
debug_die(die, "object '%s' found in invalid location %s" %
(name, hex(addr)));
continue
type_obj = type_env[type_offset]
objs = type_obj.get_kobjects(addr)
all_objs.extend(objs)
debug("symbol '%s' at %s contains %d object(s)" % (name, hex(addr),
len(objs)))
debug("found %d kernel object instances total" % len(all_objs))
return all_objs
header = """%compare-lengths
%define lookup-function-name _k_object_lookup
%language=ANSI-C
%struct-type
%{
#include <kernel.h>
#include <string.h>
%}
struct _k_object;
%%
"""
# Different versions of gperf have different prototypes for the lookup function,
# best to implement the wrapper here. The pointer value itself is turned into
# a string, we told gperf to expect binary strings that are not NULL-terminated.
footer = """%%
struct _k_object *_k_object_find(void *obj)
{
return _k_object_lookup((const char *)obj, sizeof(void *));
}
"""
def write_gperf_table(fp, objs, static_begin, static_end):
fp.write(header)
for obj_addr, obj_type in objs:
# pre-initialized objects fall within this memory range, they are
# either completely initialized at build time, or done automatically
# at boot during some PRE_KERNEL_* phase
initialized = obj_addr >= static_begin and obj_addr < static_end
byte_str = struct.pack("<I" if args.little_endian else ">I", obj_addr)
fp.write("\"")
for byte in byte_str:
val = "\\x%02x" % byte
fp.write(val)
fp.write("\",{},%s,%s\n" % (obj_type,
"K_OBJ_FLAG_INITIALIZED" if initialized else "0"))
fp.write(footer)
def get_symbols(obj):
for section in obj.iter_sections():
if isinstance(section, SymbolTableSection):
return {sym.name: sym.entry.st_value
for sym in section.iter_symbols()}
raise LookupError("Could not find symbol table")
def parse_args():
global args
parser = argparse.ArgumentParser(description = __doc__,
formatter_class = argparse.RawDescriptionHelpFormatter)
parser.add_argument("-k", "--kernel", required=True,
help="Input zephyr ELF binary")
parser.add_argument("-o", "--output", required=True,
help="Output list of kernel object addresses for gperf use")
parser.add_argument("-v", "--verbose", action="store_true",
help="Print extra debugging information")
args = parser.parse_args()
def main():
parse_args()
with open(args.kernel, "rb") as fp:
elf = ELFFile(fp)
args.little_endian = elf.little_endian
syms = get_symbols(elf)
objs = find_kobjects(elf, syms)
with open(args.output, "w") as fp:
write_gperf_table(fp, objs, syms["_static_kernel_objects_begin"],
syms["_static_kernel_objects_end"])
if __name__ == "__main__":
main()

150
scripts/process_gperf.py Executable file
View file

@ -0,0 +1,150 @@
#!/usr/bin/env python3
#
# Copyright (c) 2017 Intel Corporation
#
# SPDX-License-Identifier: Apache-2.0
import sys
import argparse
import os
import re
from distutils.version import LooseVersion
# --- debug stuff ---
"""
gperf C file post-processor
We use gperf to build up a perfect hashtable of pointer values. The way gperf
does this is to create a table 'wordlist' indexed by a string repreesentation
of a pointer address, and then doing memcmp() on a string passed in for
comparison
We are exclusively working with 4-byte pointer values. This script adjusts
the generated code so that we work with pointers directly and not strings.
This saves a considerable amount of space.
"""
def debug(text):
if not args.verbose:
return
sys.stdout.write(os.path.basename(sys.argv[0]) + ": " + text + "\n")
def error(text):
sys.stderr.write(os.path.basename(sys.argv[0]) + " ERROR: " + text + "\n")
sys.exit(1)
def warn(text):
sys.stdout.write(os.path.basename(sys.argv[0]) + " WARNING: " + text + "\n")
def reformat_str(match_obj):
addr_str = match_obj.group(0)
# Nip quotes
addr_str = addr_str[1:-1]
addr_vals = [0, 0, 0, 0]
ctr = 3
i = 0
while (True):
if i >= len(addr_str):
break
if addr_str[i] == "\\":
if addr_str[i+1].isdigit():
# Octal escape sequence
val_str = addr_str[i+1:i+4]
addr_vals[ctr] = int(val_str, 8)
i += 4
else:
# Char value that had to be escaped by C string rules
addr_vals[ctr] = ord(addr_str[i+1])
i += 2
else:
addr_vals[ctr] = ord(addr_str[i])
i += 1
ctr -= 1
return "(char *)0x%02x%02x%02x%02x" % tuple(addr_vals)
def process_line(line, fp):
if line.startswith("#"):
fp.write(line)
return
# Set the lookup function to static inline so it gets rolled into
# _k_object_find(), nothing else will use it
if re.search("struct _k_object [*]$", line):
fp.write("static inline " + line)
return
m = re.search("gperf version (.*) [*][/]$", line)
if m:
v = LooseVersion(m.groups()[0])
v_lo = LooseVersion("3.0")
v_hi = LooseVersion("3.1")
if (v < v_lo or v > v_hi):
warn("gperf %s is not tested, versions %s through %s supported" %
(v, v_lo, v_hi))
# Replace length lookups with constant len of 4 since we're always
# looking at pointers
line = re.sub(r'lengthtable[[]key[]]', r'4', line)
# Empty wordlist entries to have NULLs instead of ""
line = re.sub(r'[{]["]["][}]', r'{}', line)
# Suppress a compiler warning since this table is no longer necessary
line = re.sub(r'static unsigned char lengthtable',
r'static unsigned char __unused lengthtable', line)
# drop all use of register keyword, let compiler figure that out,
# we have to do this since we change stuff to take the address of some
# parameters
line = re.sub(r'register', r'', line)
# Hashing the address of the string
line = re.sub(r"hash [(]str, len[)]",
r"hash((const char *)&str, len)", line)
# Just compare pointers directly instead of using memcmp
if re.search("if [(][*]str", line):
fp.write(" if (str == s)\n")
return
# Take the strings with the binary information for the pointer values,
# and just turn them into pointers
line = re.sub(r'["].*["]', reformat_str, line)
fp.write(line)
def parse_args():
global args
parser = argparse.ArgumentParser(description = __doc__,
formatter_class = argparse.RawDescriptionHelpFormatter)
parser.add_argument("-i", "--input", required=True,
help="Input C file from gperf")
parser.add_argument("-o", "--output", required=True,
help="Output C file with processing done")
parser.add_argument("-v", "--verbose", action="store_true",
help="Print extra debugging information")
args = parser.parse_args()
def main():
parse_args()
with open(args.input, "r") as in_fp, open(args.output, "w") as out_fp:
for line in in_fp.readlines():
process_line(line, out_fp)
if __name__ == "__main__":
main()

View file

@ -482,7 +482,8 @@ class SizeCalculator:
"_k_fifo_area", "_k_lifo_area", "_k_stack_area",
"_k_msgq_area", "_k_mbox_area", "_k_pipe_area",
"net_if", "net_if_event", "net_stack", "net_l2_data",
"_k_queue_area", "_net_buf_pool_area", "app_datas" ]
"_k_queue_area", "_net_buf_pool_area", "app_datas",
"kobject_data", "mmu_tables"]
# These get copied into RAM only on non-XIP
ro_sections = ["text", "ctors", "init_array", "reset",
"rodata", "devconfig", "net_l2", "vector"]