mirror of
https://gitlab.com/sortix/sortix.git
synced 2023-02-13 20:55:38 -05:00
Enforce Control Flow Integrity using System Calls.
This commit is contained in:
parent
5351bd0719
commit
666b84846e
87 changed files with 768 additions and 110 deletions
|
@ -41,7 +41,9 @@ SORTIX_PORTS_DIR=$(make_dir_path_absolute "$SORTIX_PORTS_DIR")
|
|||
SORTIX_REPOSITORY_DIR=$(make_dir_path_absolute "$SORTIX_REPOSITORY_DIR")
|
||||
|
||||
# Decide the optimization options with which the ports will be built.
|
||||
if [ -z "${OPTLEVEL+x}" ]; then OPTLEVEL="-Os -s"; fi
|
||||
# -fno-ipa-ra prohibits the compiler from locally diverging from the calling
|
||||
# convention, which breaks CFI.
|
||||
if [ -z "${OPTLEVEL+x}" ]; then OPTLEVEL="-Os -s -fno-ipa-ra"; fi
|
||||
if [ -z "${PORTS_OPTLEVEL+x}" ]; then PORTS_OPTLEVEL="$OPTLEVEL"; fi
|
||||
if [ -z "${PORTS_CFLAGS+x}" ]; then PORTS_CFLAGS="$PORTS_OPTLEVEL"; fi
|
||||
if [ -z "${PORTS_CXXFLAGS+x}" ]; then PORTS_CXXFLAGS="$PORTS_OPTLEVEL"; fi
|
||||
|
|
|
@ -121,7 +121,9 @@ ifdef SYSROOT
|
|||
endif
|
||||
|
||||
# Determine default optimization level.
|
||||
DEFAULT_GENERIC_OPTLEVEL_BASE:=-Os -s
|
||||
# -fno-ipa-ra prohibits the compiler from locally diverging from the calling
|
||||
# convention, which breaks CFI.
|
||||
DEFAULT_GENERIC_OPTLEVEL_BASE:=-Os -s -fno-ipa-ra
|
||||
DEFAULT_OPTLEVEL_FOR_BUILD:=$(DEFAULT_GENERIC_OPTLEVEL_BASE)
|
||||
ifeq ($(BUILD_IS_SORTIX),1)
|
||||
DEFAULT_OPTLEVEL_FOR_BUILD+=
|
||||
|
@ -130,3 +132,8 @@ DEFAULT_OPTLEVEL:=$(DEFAULT_GENERIC_OPTLEVEL_BASE)
|
|||
ifeq ($(HOST_IS_SORTIX),1)
|
||||
DEFAULT_OPTLEVEL+=
|
||||
endif
|
||||
|
||||
# Enable Control Flow Integrity by default. The compiler wrapper will transform
|
||||
# the compiled assembly if this environment variable is set to 1.
|
||||
CFI_ENABLE?=1
|
||||
export CFI_ENABLE
|
||||
|
|
|
@ -207,11 +207,13 @@ vgafont.h: vgafont.f16
|
|||
|
||||
*.cpp */*.cpp */*/*.cpp: | kb/default-kblayout.h vgafont.h
|
||||
|
||||
# The kernel cannot use system calls to call itself, so it must be compiled
|
||||
# without CFI.
|
||||
%.o: %.cpp
|
||||
$(CXX) -c $< -o $@ $(CPPFLAGS) $(CXXFLAGS)
|
||||
CFI_ENABLE=0 $(CXX) -c $< -o $@ $(CPPFLAGS) $(CXXFLAGS)
|
||||
|
||||
%.o: %.S
|
||||
$(CXX) -c $< -o $@ $(CPPFLAGS) $(CXXFLAGS)
|
||||
CFI_ENABLE=0 $(CXX) -c $< -o $@ $(CPPFLAGS) $(CXXFLAGS)
|
||||
|
||||
clean:
|
||||
rm -f $(ALLOBJS) sortix.bin
|
||||
|
|
|
@ -52,6 +52,13 @@ struct ioctx_struct;
|
|||
typedef struct ioctx_struct ioctx_t;
|
||||
struct segment;
|
||||
|
||||
// The control flow graph is a set of such pairs.
|
||||
struct control_flow
|
||||
{
|
||||
uintptr_t from;
|
||||
uintptr_t to;
|
||||
};
|
||||
|
||||
class Process
|
||||
{
|
||||
friend void Process__OnLastThreadExit(void*);
|
||||
|
@ -82,6 +89,10 @@ private:
|
|||
Ref<MountTable> mtable;
|
||||
Ref<DescriptorTable> dtable;
|
||||
|
||||
// If this file is open, then the indirect control flow is recorded to it.
|
||||
public:
|
||||
Ref<Descriptor> cfi_dump;
|
||||
|
||||
public:
|
||||
Ref<ProcessTable> ptable;
|
||||
|
||||
|
@ -145,6 +156,13 @@ public:
|
|||
kthread_mutex_t segment_write_lock;
|
||||
kthread_mutex_t segment_lock;
|
||||
|
||||
// The control flow graph, how many entries it contains, and how many are room
|
||||
// for (for efficient expansion).
|
||||
public:
|
||||
struct control_flow* cfg;
|
||||
size_t cfg_length;
|
||||
size_t cfg_allocated;
|
||||
|
||||
public:
|
||||
kthread_mutex_t user_timers_lock;
|
||||
UserTimer user_timers[PROCESS_TIMER_NUM_MAX];
|
||||
|
|
|
@ -53,6 +53,28 @@ Thread* RunKernelThread(Process* process, void (*entry)(void*), void* user,
|
|||
size_t stacksize = 0);
|
||||
Thread* RunKernelThread(void (*entry)(void*), void* user, size_t stacksize = 0);
|
||||
|
||||
// Structure to identify each live setjmp buffers. The call_level is the call
|
||||
// stack depth of the setjmp caller. The setjmp buffer goes out of scope if that
|
||||
// function returns. rip is the address it will return to. jmpbuf_ptr is the
|
||||
// pointer to the user-space jmp_buf. Each function can have many jmp_buf
|
||||
// objects, so they must be kept track of separately.
|
||||
struct secure_setjmp
|
||||
{
|
||||
size_t call_level;
|
||||
uintptr_t rip;
|
||||
uintptr_t jmpbuf_ptr;
|
||||
};
|
||||
|
||||
// Limit protected shadow stacks to 4096 pointers. This works well in practice,
|
||||
// though some programs using a lot of recursion may not work. For simplicity,
|
||||
// this limit is currently hard-coded, but if nessesary, could be changed to
|
||||
// allocate a larger array on overflow.
|
||||
#define CFI_MAX_CALL_DEPTH 4096
|
||||
|
||||
// setjmp is rarely used, especially recursively, so 16 should be enough for all
|
||||
// sensible programs.
|
||||
#define CFI_MAX_SETJMP 16
|
||||
|
||||
class Thread
|
||||
{
|
||||
public:
|
||||
|
@ -85,6 +107,16 @@ public:
|
|||
bool signal_single;
|
||||
Clock execute_clock;
|
||||
Clock system_clock;
|
||||
// Per-thread call stack array. call_count is the current depth, the amount
|
||||
// of return pointers stored in calls. This is the protected shadow stack.
|
||||
size_t call_count;
|
||||
uintptr_t calls[CFI_MAX_CALL_DEPTH];
|
||||
// Per-thread live setjmp objects array. setjmp_count is the number of such
|
||||
// live entries. It is sorted in increasing call stack depth and elements
|
||||
// will be removed when they go out of function scope. This is the kernel
|
||||
// jmp_buf registation.
|
||||
size_t setjmp_count;
|
||||
struct secure_setjmp setjmps[CFI_MAX_SETJMP];
|
||||
|
||||
public:
|
||||
void HandleSignal(struct interrupt_context* intctx);
|
||||
|
|
|
@ -142,6 +142,11 @@ Process::Process()
|
|||
segment_write_lock = KTHREAD_MUTEX_INITIALIZER;
|
||||
segment_lock = KTHREAD_MUTEX_INITIALIZER;
|
||||
|
||||
// A new process does not have any CFG yet.
|
||||
cfg = NULL;
|
||||
cfg_length = 0;
|
||||
cfg_allocated = 0;
|
||||
|
||||
user_timers_lock = KTHREAD_MUTEX_INITIALIZER;
|
||||
memset(&user_timers, 0, sizeof(user_timers));
|
||||
// alarm_timer initialized in member constructor.
|
||||
|
@ -166,6 +171,11 @@ Process::~Process()
|
|||
assert(!mtable);
|
||||
assert(!cwd);
|
||||
assert(!root);
|
||||
// TODO: Investigate whether cfi_dump needs to be specially handled during
|
||||
// process shutdown.
|
||||
|
||||
// Delete the CFG.
|
||||
delete[] cfg;
|
||||
|
||||
assert(ptable);
|
||||
ptable->Free(pid);
|
||||
|
@ -680,6 +690,8 @@ Process* Process::Fork()
|
|||
kthread_mutex_lock(&ptrlock);
|
||||
clone->root = root;
|
||||
clone->cwd = cwd;
|
||||
// TODO: fork cfi_dump
|
||||
// TODO: fork cfg
|
||||
kthread_mutex_unlock(&ptrlock);
|
||||
|
||||
kthread_mutex_lock(&idlock);
|
||||
|
@ -742,6 +754,13 @@ void Process::ResetForExecute()
|
|||
signal_stack->ss_flags = SS_DISABLE;
|
||||
|
||||
ResetAddressSpace();
|
||||
|
||||
// A new program is being loaded into this process. Clean up the CFI state.
|
||||
cfi_dump.Reset();
|
||||
delete[] cfg;
|
||||
cfg = NULL;
|
||||
cfg_length = 0;
|
||||
cfg_allocated = 0;
|
||||
}
|
||||
|
||||
bool Process::MapSegment(struct segment* result, void* hint, size_t size,
|
||||
|
@ -786,6 +805,67 @@ int Process::Execute(const char* programname, const uint8_t* program,
|
|||
delete[] program_image_path;
|
||||
program_image_path = programname_clone; programname_clone = NULL;
|
||||
|
||||
// If there is a control flow graph, load it into the current process such
|
||||
// that it can be enforced.
|
||||
{
|
||||
char* cfg_path;
|
||||
if ( asprintf(&cfg_path, "%s.cfg", program_image_path) < 0 )
|
||||
return -1;
|
||||
ioctx_t ctx;
|
||||
SetupKernelIOCtx(&ctx);
|
||||
Ref<Descriptor> from = cfg_path[0] == '/' ? GetRoot() : GetCWD();
|
||||
Ref<Descriptor> cfg_desc = from->open(&ctx, cfg_path, O_READ, 0);
|
||||
free(cfg_path);
|
||||
// TODO: Potentially refuse to run securely on some errors, except some
|
||||
// safe ones like ENOENT. But which are safe?
|
||||
if ( cfg_desc )
|
||||
{
|
||||
struct stat st;
|
||||
if ( cfg_desc->stat(&ctx, &st) < 0 )
|
||||
return -1;
|
||||
// TODO: off_t to size_t truncation check.
|
||||
size_t length = st.st_size / sizeof(struct control_flow);
|
||||
size_t size = length * sizeof(struct control_flow);
|
||||
cfg = new struct control_flow[length];
|
||||
if ( !cfg )
|
||||
return -1;
|
||||
// TODO: read could technically read too little, read in a loop.
|
||||
if ( cfg_desc->read(&ctx, (uint8_t*) cfg, size) < (ssize_t) size )
|
||||
{
|
||||
delete[] cfg;
|
||||
cfg = NULL;
|
||||
return -1;
|
||||
}
|
||||
cfg_length = length;
|
||||
cfg_allocated = length;
|
||||
}
|
||||
}
|
||||
|
||||
// CFI recording is enabled by default, unless the CFI_RECORD environment
|
||||
// variable is set to 0.
|
||||
bool enable_cfg_record = true;
|
||||
for ( int i = 0; i < envc; i++ )
|
||||
{
|
||||
if ( !strcmp(envp[i], "CFI_RECORD=0") )
|
||||
enable_cfg_record = false;
|
||||
}
|
||||
|
||||
// If no CFG was loaded, and CFI recording is enabled, record all indirect
|
||||
// control flow to a trace file next to the program itself.
|
||||
if ( !cfg && enable_cfg_record )
|
||||
{
|
||||
char* cfi_path;
|
||||
if ( asprintf(&cfi_path, "%s.%ji.cfi", program_image_path, (intmax_t) pid) < 0 )
|
||||
return -1;
|
||||
ioctx_t ctx;
|
||||
SetupKernelIOCtx(&ctx);
|
||||
Ref<Descriptor> from = cfi_path[0] == '/' ? GetRoot() : GetCWD();
|
||||
cfi_dump = from->open(&ctx, cfi_path, O_WRITE | O_APPEND | O_CREATE, 0);
|
||||
free(cfi_path);
|
||||
if ( !cfi_dump )
|
||||
return -1;
|
||||
}
|
||||
|
||||
uintptr_t userspace_addr;
|
||||
size_t userspace_size;
|
||||
Memory::GetUserVirtualArea(&userspace_addr, &userspace_size);
|
||||
|
@ -1485,6 +1565,28 @@ pid_t sys_tfork(int flags, struct tfork* user_regs)
|
|||
memcpy(&thread->signal_mask, ®s.sigmask, sizeof(sigset_t));
|
||||
memcpy(&thread->signal_stack, ®s.altstack, sizeof(stack_t));
|
||||
|
||||
// If making a clone of the current process (fork), make a copy of the CFI
|
||||
// information as well. The registers of the new thread was set in the sfork
|
||||
// function in libc, which is written in asesembly, and it then calls the
|
||||
// tfork libc function which invokes the system call. That means we're one
|
||||
// more call level further down than the thread we are creating. The
|
||||
// call_count of the new thread is therefore one less than this thread.
|
||||
if ( making_process )
|
||||
{
|
||||
if ( curthread->call_count != 0 )
|
||||
{
|
||||
memcpy(thread->calls, curthread->calls, sizeof(thread->calls));
|
||||
thread->call_count = curthread->call_count - 1 /* subtract tfork syscall */;
|
||||
memcpy(thread->setjmps, curthread->setjmps, sizeof(thread->setjmps));
|
||||
thread->setjmp_count = curthread->setjmp_count;
|
||||
}
|
||||
else
|
||||
{
|
||||
// TODO: Ensure this logic is correct if the program loaded into
|
||||
// the current process is not CFI enabled.
|
||||
}
|
||||
}
|
||||
|
||||
StartKernelThread(thread);
|
||||
|
||||
return child_process->pid;
|
||||
|
|
|
@ -713,7 +713,7 @@ retry_another_signal:
|
|||
handler_regs.eip = (unsigned long) handler_ptr;
|
||||
handler_regs.eflags &= ~FLAGS_DIRECTION;
|
||||
#elif defined(__x86_64__)
|
||||
stack_location -= 128; /* Red zone. */
|
||||
stack_location -= 128 + 16; /* Red zone and two CFI stack parameters. */
|
||||
stack_location -= sizeof(stack_frame);
|
||||
stack_location &= ~(16UL-1UL); /* 16-byte align */
|
||||
struct stack_frame* stack = (struct stack_frame*) stack_location;
|
||||
|
@ -803,6 +803,25 @@ retry_another_signal:
|
|||
if ( (signal_single = signal_count == 1) )
|
||||
signal_single_frame = (uintptr_t) stack;
|
||||
|
||||
// TODO: This shouldn't be done for non-CFI programs.
|
||||
// Signals handlers are normally invoked by transferring control to the
|
||||
// signal handler, while setting up the stack such that the function returns
|
||||
// to a sigreturn tramboline, which invokes a system call that make the
|
||||
// thread return from the system. Since we're enforcing CFI, we will rather
|
||||
// fake a call to the signal handler from the sigreturn function. That makes
|
||||
// the signal handler return to the sigreturn function, which will then
|
||||
// invoke the system call and the signal is properly returned from.
|
||||
Thread* thread = CurrentThread();
|
||||
if ( CFI_MAX_CALL_DEPTH - thread->call_count < 2 )
|
||||
{
|
||||
process->ExitThroughSignal(SIGABRT);
|
||||
Log::PrintF("%s[%ji]: CFI violation: signal delivery secure stack overflow\n",
|
||||
process->program_image_path, (intmax_t) process->pid);
|
||||
kthread_exit();
|
||||
}
|
||||
thread->calls[thread->call_count++] = stack_frame.ucontext.uc_mcontext.gregs[REG_RIP];
|
||||
thread->calls[thread->call_count++] = (uintptr_t) process->sigreturn;
|
||||
|
||||
// Run the signal handler by returning to user-space.
|
||||
return;
|
||||
}
|
||||
|
@ -902,6 +921,18 @@ void Thread::HandleSigreturn(struct interrupt_context* intctx)
|
|||
DecodeMachineContext(&stack_frame.ucontext.uc_mcontext, &resume_regs);
|
||||
Scheduler::LoadInterruptedContext(intctx, &resume_regs);
|
||||
|
||||
// Restore the instruction pointer after returning from a signal using the
|
||||
// protected shadow stack, rather than from the state saved in user-space.
|
||||
Thread* thread = CurrentThread();
|
||||
if ( thread->call_count == 0 )
|
||||
{
|
||||
process->ExitThroughSignal(SIGABRT);
|
||||
Log::PrintF("%s[%ji]: CFI violation: signal return secure stack underflow\n",
|
||||
process->program_image_path, (intmax_t) process->pid);
|
||||
kthread_exit();
|
||||
}
|
||||
intctx->rip = thread->calls[--thread->call_count];
|
||||
|
||||
if ( signal_count != SIZE_MAX )
|
||||
signal_count--;
|
||||
signal_single = false;
|
||||
|
|
|
@ -100,6 +100,9 @@ Thread::Thread()
|
|||
sigemptyset(&signal_mask);
|
||||
memset(&signal_stack, 0, sizeof(signal_stack));
|
||||
signal_stack.ss_flags = SS_DISABLE;
|
||||
// Initialize the CFI information to empty arrays on thread creation.
|
||||
call_count = 0;
|
||||
setjmp_count = 0;
|
||||
// execute_clock initialized in member constructor.
|
||||
// system_clock initialized in member constructor.
|
||||
Time::InitializeThreadClocks(this);
|
||||
|
|
|
@ -337,6 +337,47 @@ thread_exit_handler:
|
|||
pushq $0 # err_code
|
||||
pushq $132 # int_no
|
||||
jmp interrupt_handler_prepare
|
||||
# Raw interrupt handlers for each of the 6 new CFI system calls. The interface
|
||||
# is described in kernel/interrupt.cpp at the real implementations. These are
|
||||
# just trambolines that remember which interrupt happened and then invokes the
|
||||
# common handler, which will invoke the real handlers after preserving
|
||||
# user-space registers.
|
||||
.global secure_call_handler
|
||||
.type secure_call_handler, @function
|
||||
secure_call_handler:
|
||||
pushq $0 # err_code
|
||||
pushq $0x90 # int_no
|
||||
jmp interrupt_handler_prepare
|
||||
.global secure_ret_handler
|
||||
.type secure_ret_handler, @function
|
||||
secure_ret_handler:
|
||||
pushq $0 # err_code
|
||||
pushq $0x91 # int_no
|
||||
jmp interrupt_handler_prepare
|
||||
.global secure_indirect_call_handler
|
||||
.type secure_indirect_call_handler, @function
|
||||
secure_indirect_call_handler:
|
||||
pushq $0 # err_code
|
||||
pushq $0x92 # int_no
|
||||
jmp interrupt_handler_prepare
|
||||
.global secure_indirect_jmp_handler
|
||||
.type secure_indirect_jmp_handler, @function
|
||||
secure_indirect_jmp_handler:
|
||||
pushq $0 # err_code
|
||||
pushq $0x93 # int_no
|
||||
jmp interrupt_handler_prepare
|
||||
.global secure_setjmp_handler
|
||||
.type secure_setjmp_handler, @function
|
||||
secure_setjmp_handler:
|
||||
pushq $0 # err_code
|
||||
pushq $0x94 # int_no
|
||||
jmp interrupt_handler_prepare
|
||||
.global secure_longjmp_handler
|
||||
.type secure_longjmp_handler, @function
|
||||
secure_longjmp_handler:
|
||||
pushq $0 # err_code
|
||||
pushq $0x95 # int_no
|
||||
jmp interrupt_handler_prepare
|
||||
|
||||
interrupt_handler_prepare:
|
||||
movq $1, asm_is_cpu_interrupted
|
||||
|
|
|
@ -20,11 +20,16 @@
|
|||
#include <assert.h>
|
||||
#include <errno.h>
|
||||
#include <msr.h>
|
||||
#include <setjmp.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <sortix/kernel/copy.h>
|
||||
#include <sortix/kernel/cpu.h>
|
||||
#include <sortix/kernel/descriptor.h>
|
||||
#include <sortix/kernel/interrupt.h>
|
||||
#include <sortix/kernel/ioctx.h>
|
||||
#include <sortix/kernel/kernel.h>
|
||||
#include <sortix/kernel/process.h>
|
||||
#include <sortix/kernel/scheduler.h>
|
||||
|
@ -91,6 +96,13 @@ extern "C" void interrupt_handler_null();
|
|||
extern "C" void syscall_handler();
|
||||
extern "C" void yield_cpu_handler();
|
||||
extern "C" void thread_exit_handler();
|
||||
// Forward declare the raw handlers in kernel/x64/interrupt.S.
|
||||
extern "C" void secure_call_handler();
|
||||
extern "C" void secure_ret_handler();
|
||||
extern "C" void secure_indirect_call_handler();
|
||||
extern "C" void secure_indirect_jmp_handler();
|
||||
extern "C" void secure_setjmp_handler();
|
||||
extern "C" void secure_longjmp_handler();
|
||||
|
||||
namespace Sortix {
|
||||
namespace Interrupt {
|
||||
|
@ -131,6 +143,211 @@ static struct interrupt_handler Scheduler__InterruptYieldCPU_handler;
|
|||
static struct interrupt_handler Signal__DispatchHandler_handler;
|
||||
static struct interrupt_handler Signal__ReturnHandler_handler;
|
||||
static struct interrupt_handler Scheduler__ThreadExitCPU_handler;
|
||||
// Storage for the registration of the CFI interrupt handers.
|
||||
static struct interrupt_handler CFI__secure_call_handler;
|
||||
static struct interrupt_handler CFI__secure_ret_handler;
|
||||
static struct interrupt_handler CFI__secure_indirect_call_handler;
|
||||
static struct interrupt_handler CFI__secure_indirect_jmp_handler;
|
||||
static struct interrupt_handler CFI__secure_setjmp_handler;
|
||||
static struct interrupt_handler CFI__secure_longjmp_handler;
|
||||
|
||||
// Terminates the process abnormally with SIGABRT due to a CFI violation.
|
||||
__attribute__((noreturn))
|
||||
static void CFIViolation(const char* event)
|
||||
{
|
||||
Process* process = CurrentProcess();
|
||||
process->ExitThroughSignal(SIGABRT);
|
||||
Log::PrintF("%s[%ji]: CFI violation: %s\n",
|
||||
process->program_image_path, (intmax_t) process->pid, event);
|
||||
kthread_exit();
|
||||
}
|
||||
|
||||
// The secure call system call. The destination address is in the rax register
|
||||
// and the return address is the current instruction (as the int $0x90
|
||||
// instruction has already been executed, rip will point to the next one). The
|
||||
// return pointer is saved in the call stack array, and rip is changed to the
|
||||
// destination address (rax). The stack is still decremented by 8, as the call
|
||||
// instruction normally would, but the memory is unchanged (uninitialized) and
|
||||
// unused (the called function would use secure ret). It's important to still
|
||||
// decrement the stack by 8 because the System V ABI for x86_64 requires 16-byte
|
||||
// stack alignment, which we must preserve, and the call instruction pushes 8
|
||||
// bytes. The rax register is safe to clobber because it is used for the return
|
||||
// value of the called function and there is no expectation that it would stay
|
||||
// intact.
|
||||
void SecureCallHandler(struct interrupt_context* intctx, void*)
|
||||
{
|
||||
Thread* thread = CurrentThread();
|
||||
if ( thread->call_count == CFI_MAX_CALL_DEPTH )
|
||||
CFIViolation("secure call stack overflow");
|
||||
thread->calls[thread->call_count++] = intctx->rip;
|
||||
intctx->rip = intctx->rax;
|
||||
intctx->rsp -= 8;
|
||||
}
|
||||
|
||||
// The secure return system call. There are no parameters. It simply pops the
|
||||
// saved return pointer in the call stack, transfers execution to that location
|
||||
// by setting rip, and then incrementing the stack by 8 as the ret instruction
|
||||
// would do. If any setjmp objects go out of scope, they are cleaned up.
|
||||
void SecureRetHandler(struct interrupt_context* intctx, void*)
|
||||
{
|
||||
Thread* thread = CurrentThread();
|
||||
if ( thread->call_count == 0 )
|
||||
CFIViolation("secure call stack underflow");
|
||||
intctx->rip = thread->calls[--thread->call_count];
|
||||
intctx->rsp += 8;
|
||||
// jmp_buf registations may have gone out of scope.
|
||||
while ( thread->setjmp_count &&
|
||||
thread->call_count < thread->setjmps[thread->setjmp_count-1].call_level )
|
||||
thread->setjmp_count--;
|
||||
}
|
||||
|
||||
// Control flow comparison function used for binary search of valid control
|
||||
// flow in the control flow graph.
|
||||
static int SearchControlFlow(const void* key_ptr, const void* cand_ptr)
|
||||
{
|
||||
const struct control_flow* key = (const struct control_flow*) key_ptr;
|
||||
const struct control_flow* cand = (const struct control_flow*) cand_ptr;
|
||||
if ( cand->from < key->from )
|
||||
return 1;
|
||||
if ( cand->from > key->from )
|
||||
return -1;
|
||||
if ( cand->to < key->to )
|
||||
return 1;
|
||||
if ( cand->to > key->to )
|
||||
return -1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Verifies control flow is in the control flow graph.
|
||||
static void CheckControlFlow(const struct control_flow* cf)
|
||||
{
|
||||
Process* process = CurrentProcess();
|
||||
// If recording, don't enforce the control flow graph, but record instead.
|
||||
if ( process->cfi_dump )
|
||||
{
|
||||
// Don't record this control flow if already seen.
|
||||
for ( size_t i = 0; i < process->cfg_length; i++ )
|
||||
{
|
||||
if ( process->cfg[i].from == cf->from &&
|
||||
process->cfg[i].to == cf->to )
|
||||
return;
|
||||
}
|
||||
// Double the control flow graph array in size if it is full.
|
||||
if ( process->cfg_length == process->cfg_allocated )
|
||||
{
|
||||
size_t new_allocated = process->cfg_allocated * 2;
|
||||
if ( new_allocated == 0 )
|
||||
new_allocated = 16;
|
||||
struct control_flow* new_cfg = new struct control_flow[new_allocated];
|
||||
if ( !new_cfg )
|
||||
CFIViolation("recording CFG: Out of memory");
|
||||
if ( process->cfg )
|
||||
memcpy(new_cfg, process->cfg, sizeof(struct control_flow) * process->cfg_length);
|
||||
delete[] process->cfg;
|
||||
process->cfg = new_cfg;
|
||||
process->cfg_allocated = new_allocated;
|
||||
}
|
||||
// Add the control flow to the (unsorted) control flow graph.
|
||||
process->cfg[process->cfg_length++] = *cf;
|
||||
ioctx_t ctx;
|
||||
SetupKernelIOCtx(&ctx);
|
||||
// Write the control flow to the trace file.
|
||||
if ( process->cfi_dump->write(&ctx, (const uint8_t*) cf, sizeof(*cf)) != sizeof(*cf) )
|
||||
CFIViolation("recording CFG: Trace file write failure");
|
||||
return;
|
||||
}
|
||||
|
||||
// If there is no control flow graph loaded, don't enforce.
|
||||
if ( !process->cfg )
|
||||
return;
|
||||
|
||||
// Use binary search to look up the control flow in the sorted control
|
||||
// flow graph.
|
||||
if ( bsearch(cf, process->cfg, process->cfg_length,
|
||||
sizeof(struct control_flow), SearchControlFlow) )
|
||||
return;
|
||||
|
||||
CFIViolation("control flow failure");
|
||||
}
|
||||
|
||||
// Call a function, but only after verifying the control flow using the
|
||||
// control flow graph. The destination is in the rax register.
|
||||
void SecureIndirectCallHandler(struct interrupt_context* intctx, void* ctx)
|
||||
{
|
||||
struct control_flow cf;
|
||||
cf.from = intctx->rip - 2 /* point to start of int 0x92, not the end */;
|
||||
cf.to = intctx->rax;
|
||||
CheckControlFlow(&cf);
|
||||
SecureCallHandler(intctx, ctx);
|
||||
}
|
||||
|
||||
// Jump to code, but only after verifying the control flow. The destination
|
||||
// is on the stack, after the red zone has skipped.
|
||||
void SecureIndirectJmpHandler(struct interrupt_context* intctx, void*)
|
||||
{
|
||||
uintptr_t dest;
|
||||
if ( !CopyFromUser(&dest, (uintptr_t*) intctx->rsp, sizeof(dest)) )
|
||||
CFIViolation("secure indirect jmp: Failed to read stack parameter");
|
||||
intctx->rsp += 8; // Unwind the parameter.
|
||||
intctx->rsp += 128; // Unwind the red zone.
|
||||
struct control_flow cf;
|
||||
cf.from = intctx->rip - 2 /* point to start of int 0x92, not the end */;
|
||||
cf.to = dest;
|
||||
CheckControlFlow(&cf);
|
||||
intctx->rip = dest;
|
||||
}
|
||||
|
||||
// The secure setjmp handler. Registers a jmp_buf object such that it can be
|
||||
// securely longjmp'd to later.
|
||||
void SecureSetJmpHandler(struct interrupt_context* intctx, void*)
|
||||
{
|
||||
Thread* thread = CurrentThread();
|
||||
// TODO: Check if overwriting existing setjmp.
|
||||
if ( thread->setjmp_count == 16 )
|
||||
CFIViolation("secure setjmp: jmp_buf registation stack overflow");
|
||||
if ( thread->call_count == 0 )
|
||||
CFIViolation("secure setjmp: secure stack underflow");
|
||||
struct secure_setjmp* setjmp = &thread->setjmps[thread->setjmp_count++];
|
||||
// Subtracted by one here, to remember the function that called setjmp, not
|
||||
// the libc setjmp function itself.
|
||||
setjmp->call_level = thread->call_count - 1;
|
||||
setjmp->rip = thread->calls[thread->call_count - 1];
|
||||
setjmp->jmpbuf_ptr = intctx->rdi;
|
||||
}
|
||||
|
||||
// The secure longjmp handler.
|
||||
void SecureLongJmpHandler(struct interrupt_context* intctx, void*)
|
||||
{
|
||||
Thread* thread = CurrentThread();
|
||||
// Search for a maatching registation.
|
||||
for ( size_t i = 0; i < thread->setjmp_count; i++ )
|
||||
{
|
||||
struct secure_setjmp* setjmp = &thread->setjmps[i];
|
||||
if ( setjmp->jmpbuf_ptr != intctx->rdi )
|
||||
continue;
|
||||
jmp_buf buf;
|
||||
if ( !CopyFromUser(&buf, (const jmp_buf*) setjmp->jmpbuf_ptr, sizeof(buf)) )
|
||||
CFIViolation("secure longjmp: Failed to read jmp_buf");
|
||||
// Restore all the registers from the jmp_buf.
|
||||
intctx->rbx = buf[0];
|
||||
intctx->rsp = buf[1];
|
||||
intctx->rbp = buf[2];
|
||||
intctx->r12 = buf[3];
|
||||
intctx->r13 = buf[4];
|
||||
intctx->r14 = buf[5];
|
||||
intctx->r15 = buf[6];
|
||||
intctx->rip = setjmp->rip;
|
||||
intctx->rax = intctx->rsi;
|
||||
// Unwind the protected shadow stack.
|
||||
thread->call_count = setjmp->call_level;
|
||||
// jmp_buf registations may have gone out of scope.
|
||||
while ( thread->setjmp_count &&
|
||||
thread->call_count < thread->setjmps[thread->setjmp_count-1].call_level )
|
||||
thread->setjmp_count--;
|
||||
return;
|
||||
}
|
||||
CFIViolation("secure longjmp: No such jmp_buf registation");
|
||||
}
|
||||
|
||||
// Temporarily to see if this is the source of the assertion failure.
|
||||
void DispatchHandlerWrap(struct interrupt_context* intctx, void* user)
|
||||
|
@ -243,6 +460,13 @@ void Init()
|
|||
RegisterRawHandler(130, isr130, true, true);
|
||||
RegisterRawHandler(131, isr131, true, true);
|
||||
RegisterRawHandler(132, thread_exit_handler, true, false);
|
||||
// Register the raw interrupt handlers for the 6 new CFI interrupts.
|
||||
RegisterRawHandler(0x90, secure_call_handler, true, true);
|
||||
RegisterRawHandler(0x91, secure_ret_handler, true, true);
|
||||
RegisterRawHandler(0x92, secure_indirect_call_handler, true, true);
|
||||
RegisterRawHandler(0x93, secure_indirect_jmp_handler, true, true);
|
||||
RegisterRawHandler(0x94, secure_setjmp_handler, true, true);
|
||||
RegisterRawHandler(0x95, secure_longjmp_handler, true, true);
|
||||
|
||||
Scheduler__InterruptYieldCPU_handler.handler = Scheduler::InterruptYieldCPU;
|
||||
RegisterHandler(129, &Scheduler__InterruptYieldCPU_handler);
|
||||
|
@ -252,6 +476,19 @@ void Init()
|
|||
RegisterHandler(131, &Signal__ReturnHandler_handler);
|
||||
Scheduler__ThreadExitCPU_handler.handler = Scheduler::ThreadExitCPU;
|
||||
RegisterHandler(132, &Scheduler__ThreadExitCPU_handler);
|
||||
CFI__secure_call_handler.handler = SecureCallHandler;
|
||||
// Register handles for each of the six new CFI interrupts.
|
||||
RegisterHandler(0x90, &CFI__secure_call_handler);
|
||||
CFI__secure_ret_handler.handler = SecureRetHandler;
|
||||
RegisterHandler(0x91, &CFI__secure_ret_handler);
|
||||
CFI__secure_indirect_call_handler.handler = SecureIndirectCallHandler;
|
||||
RegisterHandler(0x92, &CFI__secure_indirect_call_handler);
|
||||
CFI__secure_indirect_jmp_handler.handler = SecureIndirectJmpHandler;
|
||||
RegisterHandler(0x93, &CFI__secure_indirect_jmp_handler);
|
||||
CFI__secure_setjmp_handler.handler = SecureSetJmpHandler;
|
||||
RegisterHandler(0x94, &CFI__secure_setjmp_handler);
|
||||
CFI__secure_longjmp_handler.handler = SecureLongJmpHandler;
|
||||
RegisterHandler(0x95, &CFI__secure_longjmp_handler);
|
||||
|
||||
IDT::Set(interrupt_table, NUM_INTERRUPTS);
|
||||
|
||||
|
|
|
@ -807,13 +807,13 @@ headers:
|
|||
|
||||
# libk
|
||||
%.libk.o: %.c
|
||||
$(CC) -c $< -o $@ $(LIBK_CPPFLAGS) $(LIBK_FLAGS) $(LIBK_CFLAGS)
|
||||
CFI_ENABLE=0 $(CC) -c $< -o $@ $(LIBK_CPPFLAGS) $(LIBK_FLAGS) $(LIBK_CFLAGS)
|
||||
|
||||
%.libk.o: %.c++
|
||||
$(CXX) -c $< -o $@ $(LIBK_CPPFLAGS) $(LIBK_FLAGS) $(LIBK_CXXFLAGS)
|
||||
CFI_ENABLE=0 $(CXX) -c $< -o $@ $(LIBK_CPPFLAGS) $(LIBK_FLAGS) $(LIBK_CXXFLAGS)
|
||||
|
||||
%.libk.o: %.S
|
||||
$(CC) -c $< -o $@ $(LIBK_CPPFLAGS) $(LIBK_FLAGS) $(LIBK_CFLAGS)
|
||||
CFI_ENABLE=0 $(CC) -c $< -o $@ $(LIBK_CPPFLAGS) $(LIBK_FLAGS) $(LIBK_CFLAGS)
|
||||
|
||||
clean:
|
||||
rm -f *.o */*.o */*/*.o *.a
|
||||
|
|
|
@ -39,20 +39,24 @@ __start:
|
|||
pushq %rsi
|
||||
pushq %rdi
|
||||
pushq %rcx
|
||||
call initialize_standard_library
|
||||
mov $initialize_standard_library, %rax
|
||||
int $0x90
|
||||
|
||||
# Run the global constructors.
|
||||
call _init
|
||||
mov $_init, %rax
|
||||
int $0x90
|
||||
|
||||
# Run main
|
||||
popq %rdx # Note! envp is now %rdx (previously %rcx)
|
||||
popq %rdi
|
||||
popq %rsi
|
||||
addq $8, %rsp
|
||||
call main
|
||||
mov $main, %rax
|
||||
int $0x90
|
||||
|
||||
# Terminate the process with main's exit code.
|
||||
movl %eax, %edi
|
||||
call exit
|
||||
mov $exit, %rax
|
||||
int $0x90
|
||||
.size _start, .-_start
|
||||
.size __start, .-__start
|
||||
|
|
|
@ -20,9 +20,9 @@
|
|||
.section .init
|
||||
/* gcc will nicely put the contents of crtend.o's .init section here. */
|
||||
popq %rbp
|
||||
ret
|
||||
int $0x91
|
||||
|
||||
.section .fini
|
||||
/* gcc will nicely put the contents of crtend.o's .fini section here. */
|
||||
popq %rbp
|
||||
ret
|
||||
int $0x91
|
||||
|
|
|
@ -52,21 +52,24 @@ __sfork:
|
|||
popq %rax
|
||||
movq %rax, (17 * 8)(%rsi) # rflags
|
||||
movl $MSRID_FSBASE, %edi
|
||||
call rdmsr
|
||||
mov $rdmsr, %rax
|
||||
int $0x90
|
||||
movq 0(%rsp), %rsi
|
||||
movq %rax, (18 * 8)(%rsi) # fsbase
|
||||
movl $MSRID_GSBASE, %edi
|
||||
call rdmsr
|
||||
mov $rdmsr, %rax
|
||||
int $0x90
|
||||
movq 0(%rsp), %rsi
|
||||
movq %rax, (19 * 8)(%rsi) # gsbase
|
||||
movq 8(%rsp), %rdi
|
||||
|
||||
call tfork
|
||||
mov $tfork, %rax
|
||||
int $0x90
|
||||
|
||||
.Lafter_fork:
|
||||
# The value in %rax determines whether we are child or parent. There is no
|
||||
# need to clean up the stack from the above pushes, leaveq sets %rsp to %rbp
|
||||
# which does that for us.
|
||||
leaveq
|
||||
retq
|
||||
int $0x91
|
||||
.size __sfork, . - __sfork
|
||||
|
|
|
@ -50,21 +50,23 @@ sigsetjmp:
|
|||
lea (9 * 8)(%rdi), %rdx # oldset
|
||||
xor %esi, %esi # set
|
||||
xor %edi, %edi # how (ignored because set is NULL)
|
||||
call sigprocmask # assumes sigprocmask is per-thread on Sortix
|
||||
mov $sigprocmask, %rax # assumes sigprocmask is per-thread on Sortix
|
||||
int $0x90
|
||||
pop %rsi
|
||||
pop %rdi
|
||||
2: mov 0(%rsp), %rax
|
||||
mov %rbx, (0 * 8)(%rdi)
|
||||
mov %rsp, (1 * 8)(%rdi)
|
||||
2: mov %rbx, (0 * 8)(%rdi)
|
||||
mov %rsp, %rdx
|
||||
add $8, %rdx
|
||||
mov %rdx, (1 * 8)(%rdi)
|
||||
mov %rbp, (2 * 8)(%rdi)
|
||||
mov %r12, (3 * 8)(%rdi)
|
||||
mov %r13, (4 * 8)(%rdi)
|
||||
mov %r14, (5 * 8)(%rdi)
|
||||
mov %r15, (6 * 8)(%rdi)
|
||||
mov %rax, (7 * 8)(%rdi)
|
||||
mov %rsi, (8 * 8)(%rdi)
|
||||
int $0x94
|
||||
xorl %eax, %eax
|
||||
ret
|
||||
int $0x91
|
||||
.size sigsetjmp, . - sigsetjmp
|
||||
|
||||
.global longjmp
|
||||
|
@ -87,18 +89,9 @@ siglongjmp:
|
|||
leaq (9 * 8)(%rdi), %rsi # set
|
||||
movl $SIG_SETMASK, %edi # how
|
||||
xorl %edx, %edx # oldset
|
||||
call sigprocmask
|
||||
mov $sigprocmask, %rax
|
||||
int $0x90
|
||||
popq %rsi
|
||||
popq %rdi
|
||||
2: movq (0 * 8)(%rdi), %rbx
|
||||
movq (1 * 8)(%rdi), %rsp
|
||||
movq (2 * 8)(%rdi), %rbp
|
||||
movq (3 * 8)(%rdi), %r12
|
||||
movq (4 * 8)(%rdi), %r13
|
||||
movq (5 * 8)(%rdi), %r14
|
||||
movq (6 * 8)(%rdi), %r15
|
||||
movq (7 * 8)(%rdi), %rax
|
||||
movq %rax, 0(%rsp)
|
||||
movl %esi, %eax
|
||||
ret
|
||||
2: int $0x95
|
||||
.size siglongjmp, . - siglongjmp
|
||||
|
|
|
@ -37,5 +37,5 @@ asm_syscall: /* syscall num in %rax. */
|
|||
mov %ecx, errno@tpoff(%rsi)
|
||||
1:
|
||||
pop %rbp
|
||||
ret
|
||||
int $0x91
|
||||
.size asm_syscall, .-asm_syscall
|
||||
|
|
|
@ -21,4 +21,4 @@ ENTRY(__ieee754_acos)
|
|||
fxch %st(1)
|
||||
fpatan
|
||||
XMM_DOUBLE_EPILOGUE
|
||||
ret
|
||||
int $0x91
|
||||
|
|
|
@ -20,4 +20,4 @@ ENTRY(__ieee754_asin)
|
|||
fsqrt /* sqrt (1 - x^2) */
|
||||
fpatan
|
||||
XMM_DOUBLE_EPILOGUE
|
||||
ret
|
||||
int $0x91
|
||||
|
|
|
@ -15,4 +15,4 @@ ENTRY(__ieee754_atan2)
|
|||
fldl ARG_DOUBLE_TWO
|
||||
fpatan
|
||||
XMM_DOUBLE_EPILOGUE
|
||||
ret
|
||||
int $0x91
|
||||
|
|
|
@ -15,4 +15,4 @@ ENTRY(__ieee754_atan2f)
|
|||
flds ARG_FLOAT_TWO
|
||||
fpatan
|
||||
XMM_FLOAT_EPILOGUE
|
||||
ret
|
||||
int $0x91
|
||||
|
|
|
@ -88,7 +88,7 @@ ENTRY(__ieee754_exp)
|
|||
fldcw CWSTORE_SAV
|
||||
1:
|
||||
XMM_DOUBLE_EPILOGUE
|
||||
ret
|
||||
int $0x91
|
||||
x_Inf_or_NaN:
|
||||
/*
|
||||
* Return 0 if x is -Inf. Otherwise just return x, although the
|
||||
|
@ -100,9 +100,9 @@ x_Inf_or_NaN:
|
|||
jne x_not_minus_Inf
|
||||
fldz
|
||||
XMM_DOUBLE_EPILOGUE
|
||||
ret
|
||||
int $0x91
|
||||
|
||||
x_not_minus_Inf:
|
||||
fldl ARG_DOUBLE_ONE
|
||||
XMM_DOUBLE_EPILOGUE
|
||||
ret
|
||||
int $0x91
|
||||
|
|
|
@ -35,7 +35,7 @@ ENTRY(__ieee754_expf)
|
|||
fscale /* e^x */
|
||||
fstp %st(1)
|
||||
XMM_FLOAT_EPILOGUE
|
||||
ret
|
||||
int $0x91
|
||||
|
||||
x_Inf_or_NaN:
|
||||
/*
|
||||
|
@ -47,9 +47,9 @@ x_Inf_or_NaN:
|
|||
jne x_not_minus_Inf
|
||||
fldz
|
||||
XMM_FLOAT_EPILOGUE
|
||||
ret
|
||||
int $0x91
|
||||
|
||||
x_not_minus_Inf:
|
||||
flds ARG_FLOAT_ONE
|
||||
XMM_FLOAT_EPILOGUE
|
||||
ret
|
||||
int $0x91
|
||||
|
|
|
@ -20,4 +20,4 @@ ENTRY(__ieee754_fmod)
|
|||
jc 1b
|
||||
fstp %st(1)
|
||||
XMM_DOUBLE_EPILOGUE
|
||||
ret
|
||||
int $0x91
|
||||
|
|
|
@ -15,4 +15,4 @@ ENTRY(__ieee754_log)
|
|||
fldl ARG_DOUBLE_ONE
|
||||
fyl2x
|
||||
XMM_DOUBLE_EPILOGUE
|
||||
ret
|
||||
int $0x91
|
||||
|
|
|
@ -15,4 +15,4 @@ ENTRY(__ieee754_log10)
|
|||
fldl ARG_DOUBLE_ONE
|
||||
fyl2x
|
||||
XMM_DOUBLE_EPILOGUE
|
||||
ret
|
||||
int $0x91
|
||||
|
|
|
@ -15,4 +15,4 @@ ENTRY(__ieee754_log10f)
|
|||
flds ARG_FLOAT_ONE
|
||||
fyl2x
|
||||
XMM_FLOAT_EPILOGUE
|
||||
ret
|
||||
int $0x91
|
||||
|
|
|
@ -15,4 +15,4 @@ ENTRY(__ieee754_log2)
|
|||
fldl ARG_DOUBLE_ONE
|
||||
fyl2x
|
||||
XMM_DOUBLE_EPILOGUE
|
||||
ret
|
||||
int $0x91
|
||||
|
|
|
@ -15,4 +15,4 @@ ENTRY(__ieee754_log2f)
|
|||
flds ARG_FLOAT_ONE
|
||||
fyl2x
|
||||
XMM_FLOAT_EPILOGUE
|
||||
ret
|
||||
int $0x91
|
||||
|
|
|
@ -15,4 +15,4 @@ ENTRY(__ieee754_logf)
|
|||
flds ARG_FLOAT_ONE
|
||||
fyl2x
|
||||
XMM_FLOAT_EPILOGUE
|
||||
ret
|
||||
int $0x91
|
||||
|
|
|
@ -19,4 +19,4 @@ ENTRY(__ieee754_remainder)
|
|||
jc 1b
|
||||
fstp %st(1)
|
||||
XMM_DOUBLE_EPILOGUE
|
||||
ret
|
||||
int $0x91
|
||||
|
|
|
@ -19,4 +19,4 @@ ENTRY(__ieee754_remainderf)
|
|||
jc 1b
|
||||
fstp %st(1)
|
||||
XMM_FLOAT_EPILOGUE
|
||||
ret
|
||||
int $0x91
|
||||
|
|
|
@ -16,4 +16,4 @@ ENTRY(__ieee754_scalb)
|
|||
fscale
|
||||
fstp %st(1) /* bug fix for fp stack overflow */
|
||||
XMM_DOUBLE_EPILOGUE
|
||||
ret
|
||||
int $0x91
|
||||
|
|
|
@ -15,4 +15,4 @@ ENTRY(__ieee754_scalbf)
|
|||
flds ARG_FLOAT_ONE
|
||||
fscale
|
||||
XMM_FLOAT_EPILOGUE
|
||||
ret
|
||||
int $0x91
|
||||
|
|
|
@ -14,4 +14,4 @@ ENTRY(__ieee754_sqrt)
|
|||
#else
|
||||
sqrtsd %xmm0,%xmm0
|
||||
#endif
|
||||
ret
|
||||
int $0x91
|
||||
|
|
|
@ -14,4 +14,4 @@ ENTRY(__ieee754_sqrtf)
|
|||
#else
|
||||
sqrtss %xmm0,%xmm0
|
||||
#endif
|
||||
ret
|
||||
int $0x91
|
||||
|
|
|
@ -16,8 +16,8 @@ ENTRY(lrint)
|
|||
fistpl (%esp)
|
||||
movl (%esp),%eax
|
||||
leave
|
||||
ret
|
||||
int $0x91
|
||||
#else
|
||||
cvtsd2siq %xmm0,%rax
|
||||
ret
|
||||
int $0x91
|
||||
#endif
|
||||
|
|
|
@ -15,4 +15,4 @@ ENTRY(atan)
|
|||
fld1
|
||||
fpatan
|
||||
XMM_DOUBLE_EPILOGUE
|
||||
ret
|
||||
int $0x91
|
||||
|
|
|
@ -15,4 +15,4 @@ ENTRY(atanf)
|
|||
fld1
|
||||
fpatan
|
||||
XMM_FLOAT_EPILOGUE
|
||||
ret
|
||||
int $0x91
|
||||
|
|
|
@ -42,4 +42,4 @@ ENTRY(ceil)
|
|||
fstpl -8(%rsp)
|
||||
movsd -8(%rsp),%xmm0
|
||||
#endif
|
||||
ret
|
||||
int $0x91
|
||||
|
|
|
@ -40,4 +40,4 @@ ENTRY(ceilf)
|
|||
fstps -4(%rsp)
|
||||
movss -4(%rsp),%xmm0
|
||||
#endif
|
||||
ret
|
||||
int $0x91
|
||||
|
|
|
@ -35,4 +35,4 @@ ENTRY(copysign)
|
|||
pand %xmm3,%xmm0
|
||||
por %xmm1,%xmm0
|
||||
#endif
|
||||
ret
|
||||
int $0x91
|
||||
|
|
|
@ -34,4 +34,4 @@ ENTRY(copysignf)
|
|||
pand %xmm3,%xmm0
|
||||
por %xmm1,%xmm0
|
||||
#endif
|
||||
ret
|
||||
int $0x91
|
||||
|
|
|
@ -17,7 +17,7 @@ ENTRY(cos)
|
|||
andw $0x400,%ax
|
||||
jnz 1f
|
||||
XMM_DOUBLE_EPILOGUE
|
||||
ret
|
||||
int $0x91
|
||||
1: fldpi
|
||||
fadd %st(0)
|
||||
fxch %st(1)
|
||||
|
@ -28,4 +28,4 @@ ENTRY(cos)
|
|||
fstp %st(1)
|
||||
fcos
|
||||
XMM_DOUBLE_EPILOGUE
|
||||
ret
|
||||
int $0x91
|
||||
|
|
|
@ -15,4 +15,4 @@ ENTRY(cosf)
|
|||
flds ARG_FLOAT_ONE
|
||||
fcos
|
||||
XMM_FLOAT_EPILOGUE
|
||||
ret
|
||||
int $0x91
|
||||
|
|
|
@ -23,4 +23,4 @@ ENTRY(finite)
|
|||
cmpq %rdi,%rsi
|
||||
setne %al
|
||||
#endif
|
||||
ret
|
||||
int $0x91
|
||||
|
|
|
@ -22,4 +22,4 @@ ENTRY(finitef)
|
|||
cmpl $0x7ff00000,%esi
|
||||
setne %al
|
||||
#endif
|
||||
ret
|
||||
int $0x91
|
||||
|
|
|
@ -40,4 +40,4 @@ ENTRY(floor)
|
|||
fstpl -8(%rsp)
|
||||
movsd -8(%rsp),%xmm0
|
||||
#endif
|
||||
ret
|
||||
int $0x91
|
||||
|
|
|
@ -40,4 +40,4 @@ ENTRY(floorf)
|
|||
fstps -4(%rsp)
|
||||
movss -4(%rsp),%xmm0
|
||||
#endif
|
||||
ret
|
||||
int $0x91
|
||||
|
|
|
@ -29,4 +29,4 @@ ENTRY(ilogb)
|
|||
fistpl -8(%rsp)
|
||||
movl -8(%rsp),%eax
|
||||
#endif
|
||||
ret
|
||||
int $0x91
|
||||
|
|
|
@ -29,4 +29,4 @@ ENTRY(ilogbf)
|
|||
fistpl -4(%rsp)
|
||||
movl -4(%rsp),%eax
|
||||
#endif
|
||||
ret
|
||||
int $0x91
|
||||
|
|
|
@ -21,4 +21,4 @@ ENTRY(ilogbl)
|
|||
fistpl -4(%rsp)
|
||||
movl -4(%rsp), %eax
|
||||
#endif
|
||||
ret
|
||||
int $0x91
|
||||
|
|
|
@ -65,7 +65,7 @@ use_fyl2x:
|
|||
faddp
|
||||
fyl2x
|
||||
XMM_DOUBLE_EPILOGUE
|
||||
ret
|
||||
int $0x91
|
||||
|
||||
.align 4
|
||||
use_fyl2xp1:
|
||||
|
@ -73,4 +73,4 @@ use_fyl2xp1:
|
|||
fldl ARG_DOUBLE_ONE
|
||||
fyl2xp1
|
||||
XMM_DOUBLE_EPILOGUE
|
||||
ret
|
||||
int $0x91
|
||||
|
|
|
@ -65,7 +65,7 @@ use_fyl2x:
|
|||
faddp
|
||||
fyl2x
|
||||
XMM_FLOAT_EPILOGUE
|
||||
ret
|
||||
int $0x91
|
||||
|
||||
.align 4
|
||||
use_fyl2xp1:
|
||||
|
@ -73,4 +73,4 @@ use_fyl2xp1:
|
|||
flds ARG_FLOAT_ONE
|
||||
fyl2xp1
|
||||
XMM_FLOAT_EPILOGUE
|
||||
ret
|
||||
int $0x91
|
||||
|
|
|
@ -15,4 +15,4 @@ ENTRY(logb)
|
|||
fxtract
|
||||
fstp %st
|
||||
XMM_DOUBLE_EPILOGUE
|
||||
ret
|
||||
int $0x91
|
||||
|
|
|
@ -15,4 +15,4 @@ ENTRY(logbf)
|
|||
fxtract
|
||||
fstp %st
|
||||
XMM_FLOAT_EPILOGUE
|
||||
ret
|
||||
int $0x91
|
||||
|
|
|
@ -13,4 +13,4 @@ ENTRY(logbl)
|
|||
fldt ARG_LONG_DOUBLE_ONE
|
||||
fxtract
|
||||
fstp %st
|
||||
ret
|
||||
int $0x91
|
||||
|
|
|
@ -103,4 +103,4 @@ ENTRY(modf)
|
|||
L1:
|
||||
#endif
|
||||
leave
|
||||
ret
|
||||
int $0x91
|
||||
|
|
|
@ -14,4 +14,4 @@ ENTRY(rint)
|
|||
fldl ARG_DOUBLE_ONE
|
||||
frndint
|
||||
XMM_DOUBLE_EPILOGUE
|
||||
ret
|
||||
int $0x91
|
||||
|
|
|
@ -14,4 +14,4 @@ ENTRY(rintf)
|
|||
flds ARG_FLOAT_ONE
|
||||
frndint
|
||||
XMM_FLOAT_EPILOGUE
|
||||
ret
|
||||
int $0x91
|
||||
|
|
|
@ -27,4 +27,4 @@ ENTRY(_scalbn)
|
|||
fscale
|
||||
fstp %st(1) /* clean up stack */
|
||||
#endif
|
||||
ret
|
||||
int $0x91
|
||||
|
|
|
@ -27,4 +27,4 @@ ENTRY(_scalbnf)
|
|||
fscale
|
||||
fstp %st(1) /* clean up stack */
|
||||
#endif
|
||||
ret
|
||||
int $0x91
|
||||
|
|
|
@ -24,4 +24,4 @@ ENTRY(_scalbnl)
|
|||
fscale
|
||||
fstp %st(1) /* clean up stack */
|
||||
#endif
|
||||
ret
|
||||
int $0x91
|
||||
|
|
|
@ -15,4 +15,4 @@ ENTRY(significand)
|
|||
fxtract
|
||||
fstp %st(1)
|
||||
XMM_DOUBLE_EPILOGUE
|
||||
ret
|
||||
int $0x91
|
||||
|
|
|
@ -15,4 +15,4 @@ ENTRY(significandf)
|
|||
fxtract
|
||||
fstp %st(1)
|
||||
XMM_FLOAT_EPILOGUE
|
||||
ret
|
||||
int $0x91
|
||||
|
|
|
@ -17,7 +17,7 @@ ENTRY(sin)
|
|||
andw $0x400,%ax
|
||||
jnz 1f
|
||||
XMM_DOUBLE_EPILOGUE
|
||||
ret
|
||||
int $0x91
|
||||
1: fldpi
|
||||
fadd %st(0)
|
||||
fxch %st(1)
|
||||
|
@ -28,4 +28,4 @@ ENTRY(sin)
|
|||
fstp %st(1)
|
||||
fsin
|
||||
XMM_DOUBLE_EPILOGUE
|
||||
ret
|
||||
int $0x91
|
||||
|
|
|
@ -15,4 +15,4 @@ ENTRY(sinf)
|
|||
flds ARG_FLOAT_ONE
|
||||
fsin
|
||||
XMM_FLOAT_EPILOGUE
|
||||
ret
|
||||
int $0x91
|
||||
|
|
|
@ -18,7 +18,7 @@ ENTRY(tan)
|
|||
jnz 1f
|
||||
fstp %st(0)
|
||||
XMM_DOUBLE_EPILOGUE
|
||||
ret
|
||||
int $0x91
|
||||
1: fldpi
|
||||
fadd %st(0)
|
||||
fxch %st(1)
|
||||
|
@ -30,4 +30,4 @@ ENTRY(tan)
|
|||
fptan
|
||||
fstp %st(0)
|
||||
XMM_DOUBLE_EPILOGUE
|
||||
ret
|
||||
int $0x91
|
||||
|
|
|
@ -16,4 +16,4 @@ ENTRY(tanf)
|
|||
fptan
|
||||
fstp %st(0)
|
||||
XMM_FLOAT_EPILOGUE
|
||||
ret
|
||||
int $0x91
|
||||
|
|
|
@ -14,4 +14,4 @@ __signmask:
|
|||
ENTRY(fabs)
|
||||
movsd __signmask(%rip),%xmm1
|
||||
andpd %xmm1,%xmm0
|
||||
ret
|
||||
int $0x91
|
||||
|
|
|
@ -18,4 +18,4 @@ ENTRY(__flt_rounds)
|
|||
movl $0x2d, %eax /* 0x2d = 00.10.11.01 */
|
||||
sarl %cl, %eax /* 0,1,2,3 -> 1,3,2,0 */
|
||||
andl $3, %eax
|
||||
ret
|
||||
int $0x91
|
||||
|
|
|
@ -23,4 +23,4 @@ ENTRY(fpgetmask)
|
|||
movl -4(%rsp),%eax
|
||||
notl %eax
|
||||
andl $63,%eax
|
||||
ret
|
||||
int $0x91
|
||||
|
|
|
@ -22,4 +22,4 @@ ENTRY(fpgetprec)
|
|||
movl -4(%rsp),%eax
|
||||
rorl $8,%eax
|
||||
andl $3,%eax
|
||||
ret
|
||||
int $0x91
|
||||
|
|
|
@ -21,4 +21,4 @@ ENTRY(fpgetround)
|
|||
fnstcw -4(%rsp)
|
||||
movl -4(%rsp), %eax
|
||||
andl $0x00000c00, %eax
|
||||
ret
|
||||
int $0x91
|
||||
|
|
|
@ -24,4 +24,4 @@ ENTRY(fpgetsticky)
|
|||
movl -4(%rsp),%eax
|
||||
orl -8(%rsp),%eax
|
||||
andl $63,%eax
|
||||
ret
|
||||
int $0x91
|
||||
|
|
|
@ -41,4 +41,4 @@ ENTRY(fpsetmask)
|
|||
|
||||
notl %eax
|
||||
andl $0x0000003f, %eax
|
||||
ret
|
||||
int $0x91
|
||||
|
|
|
@ -36,4 +36,4 @@ ENTRY(fpsetprec)
|
|||
movl %edx,-4(%rsp)
|
||||
|
||||
fldcw -4(%rsp)
|
||||
ret
|
||||
int $0x91
|
||||
|
|
|
@ -39,4 +39,4 @@ ENTRY(fpsetround)
|
|||
movl %edx,-4(%rsp)
|
||||
ldmxcsr -4(%rsp)
|
||||
|
||||
ret
|
||||
int $0x91
|
||||
|
|
|
@ -42,4 +42,4 @@ ENTRY(fpsetsticky)
|
|||
|
||||
ldmxcsr -32(%rsp)
|
||||
fldenv -28(%rsp)
|
||||
ret
|
||||
int $0x91
|
||||
|
|
|
@ -4,7 +4,7 @@ include ../build-aux/compiler.mak
|
|||
include ../build-aux/version.mak
|
||||
include ../build-aux/dirs.mak
|
||||
|
||||
OPTLEVEL?=-g -O2
|
||||
OPTLEVEL?=$(DEFAULT_OPTLEVEL)
|
||||
CXXFLAGS?=$(OPTLEVEL)
|
||||
|
||||
CXXFLAGS:=$(CXXFLAGS) -std=gnu++0x -Wall -Wextra -fno-exceptions -fno-rtti -msse -msse2
|
||||
|
|
4
utils/.gitignore
vendored
4
utils/.gitignore
vendored
|
@ -1,6 +1,10 @@
|
|||
*.o
|
||||
basename
|
||||
cat
|
||||
cfg
|
||||
cfi-test
|
||||
cfi-test-2
|
||||
cfi-test-3
|
||||
chkblayout
|
||||
chmod
|
||||
chroot
|
||||
|
|
|
@ -13,6 +13,10 @@ CPPFLAGS:=$(CPPFLAGS) -DVERSIONSTR=\"$(VERSION)\"
|
|||
BINARIES_EXCEPT_INSTALL:=\
|
||||
basename \
|
||||
cat \
|
||||
cfg \
|
||||
cfi-test \
|
||||
cfi-test-2 \
|
||||
cfi-test-3 \
|
||||
chkblayout \
|
||||
chmod \
|
||||
chroot \
|
||||
|
@ -79,6 +83,9 @@ install: all
|
|||
mkdir -p $(DESTDIR)$(BINDIR)
|
||||
install $(BINARIES_EXCEPT_INSTALL) $(DESTDIR)$(BINDIR)
|
||||
install xinstall $(DESTDIR)$(BINDIR)/install
|
||||
mkdir -p $(DESTDIR)$(SBINDIR)
|
||||
install cfion $(DESTDIR)$(SBINDIR)/cfion
|
||||
install cfioff $(DESTDIR)$(SBINDIR)/cfioff
|
||||
|
||||
%: %.c
|
||||
$(CC) -std=gnu11 $(CFLAGS) $(CPPFLAGS) $< -o $@
|
||||
|
|
76
utils/cfg.c
Normal file
76
utils/cfg.c
Normal file
|
@ -0,0 +1,76 @@
|
|||
#include <sys/stat.h>
|
||||
|
||||
#include <err.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
struct control_flow
|
||||
{
|
||||
uintptr_t from;
|
||||
uintptr_t to;
|
||||
};
|
||||
|
||||
struct control_flow* cfg = NULL;
|
||||
size_t cfg_length = 0;
|
||||
|
||||
static int cfcmp(const void* a_ptr, const void* b_ptr)
|
||||
{
|
||||
const struct control_flow* a = (const struct control_flow*) a_ptr;
|
||||
const struct control_flow* b = (const struct control_flow*) b_ptr;
|
||||
if ( b->from < a->from )
|
||||
return 1;
|
||||
if ( b->from > a->from )
|
||||
return -1;
|
||||
if ( b->to < a->to )
|
||||
return 1;
|
||||
if ( b->to > a->to )
|
||||
return -1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int main(int argc, char* argv[])
|
||||
{
|
||||
for ( int i = 1; i < argc; i++ )
|
||||
{
|
||||
FILE* fp = fopen(argv[i], "r");
|
||||
if ( !fp )
|
||||
err(1, "%s", argv[i]);
|
||||
struct stat st;
|
||||
if ( fstat(fileno(fp), &st) < 0 )
|
||||
err(1, "stat: %s", argv[i]);
|
||||
size_t entries = st.st_size / sizeof(struct control_flow);
|
||||
size_t new_length = cfg_length + entries;
|
||||
size_t new_size = new_length * sizeof(struct control_flow);
|
||||
cfg = (struct control_flow*) realloc(cfg, new_size);
|
||||
if ( !cfg )
|
||||
err(1, "malloc");
|
||||
if ( fread(cfg + cfg_length, sizeof(struct control_flow), entries, fp) != entries )
|
||||
err(1, "%s", argv[i]);
|
||||
cfg_length = new_length;
|
||||
fclose(fp);
|
||||
}
|
||||
#if 0
|
||||
for ( size_t i = 0; i < cfg_length; i++ )
|
||||
fprintf(stderr, "[%zu] 0x%lX -> 0x%lX\n", i, cfg[i].from, cfg[i].to);
|
||||
#endif
|
||||
qsort(cfg, cfg_length, sizeof(struct control_flow), cfcmp);
|
||||
size_t new_length = 0;
|
||||
for ( size_t i = 0; i < cfg_length; i++ )
|
||||
{
|
||||
if ( i && cfg[i-1].from == cfg[i].from && cfg[i-1].to == cfg[i].to )
|
||||
continue;
|
||||
cfg[new_length++] = cfg[i];
|
||||
}
|
||||
cfg_length = new_length;
|
||||
fflush(stdout);
|
||||
if ( fwrite(cfg, sizeof(struct control_flow), cfg_length, stdout) != cfg_length )
|
||||
err(1, "stdout");
|
||||
if ( ferror(stdout) || fflush(stdout) == EOF )
|
||||
err(1, "stdout");
|
||||
#if 1
|
||||
for ( size_t i = 0; i < cfg_length; i++ )
|
||||
fprintf(stderr, "[%zu] 0x%lX -> 0x%lX\n", i, cfg[i].from, cfg[i].to);
|
||||
#endif
|
||||
return 0;
|
||||
}
|
19
utils/cfi-test-2.c
Normal file
19
utils/cfi-test-2.c
Normal file
|
@ -0,0 +1,19 @@
|
|||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#pragma GCC optimize 0
|
||||
|
||||
void gpf(void)
|
||||
{
|
||||
char buffer[16];
|
||||
memset(buffer, 0x11, 128); /* buffer overrun */
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
printf("begun\n");
|
||||
void (*gpf_ptr)(void) = gpf;
|
||||
gpf_ptr();
|
||||
printf("done\n");
|
||||
return 42;
|
||||
}
|
36
utils/cfi-test-3.c
Normal file
36
utils/cfi-test-3.c
Normal file
|
@ -0,0 +1,36 @@
|
|||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#pragma GCC optimize 0
|
||||
|
||||
void yes_function(int param)
|
||||
{
|
||||
printf("yes: %i\n", param);
|
||||
}
|
||||
|
||||
void no_function(int param)
|
||||
{
|
||||
if ( param )
|
||||
system("undesirable-command");
|
||||
printf("no: %i\n", param);
|
||||
}
|
||||
|
||||
void adversary(void)
|
||||
{
|
||||
uintptr_t array[1];
|
||||
for ( size_t i = 0; i < 16; i++ )
|
||||
array[i] = (uintptr_t) no_function; /* buffer overrun */
|
||||
(void) array;
|
||||
}
|
||||
|
||||
int main(int argc, char* argv[])
|
||||
{
|
||||
int param = 2 <= argc ? atoi(argv[1]) : 1;
|
||||
void (*ptr)(int) = no_function;
|
||||
if ( param )
|
||||
ptr = yes_function;
|
||||
adversary();
|
||||
ptr(param);
|
||||
return 0;
|
||||
}
|
18
utils/cfi-test.c
Normal file
18
utils/cfi-test.c
Normal file
|
@ -0,0 +1,18 @@
|
|||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#pragma GCC optimize 0
|
||||
|
||||
void gpf(void)
|
||||
{
|
||||
char buffer[16];
|
||||
memset(buffer, 0x11, 128); /* buffer overrun */
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
printf("begun\n");
|
||||
gpf();
|
||||
printf("done\n");
|
||||
return 42;
|
||||
}
|
9
utils/cfioff
Executable file
9
utils/cfioff
Executable file
|
@ -0,0 +1,9 @@
|
|||
#!/bin/sh
|
||||
for program in find grep rm; do
|
||||
rm -f "/bin/$program.cfg"
|
||||
done
|
||||
find / | \
|
||||
grep -E '\.cfg$' | \
|
||||
while read file; do
|
||||
rm -v "$file"
|
||||
done
|
14
utils/cfion
Executable file
14
utils/cfion
Executable file
|
@ -0,0 +1,14 @@
|
|||
#!/bin/sh
|
||||
find / | \
|
||||
grep -E '\.[[:digit:]]+\.cfi$' | \
|
||||
sed -E 's/\.[[:digit:]]+\.cfi//g' |
|
||||
sort -u |
|
||||
while read program; do
|
||||
echo "$program"
|
||||
cfg "$program".*.cfi > "$program.cfg.new" 2>/dev/null
|
||||
rm "$program".*.cfi
|
||||
mv "$program.cfg.new" "$program.0.cfi"
|
||||
if [ "$program" != "/bin/rm" ] && [ "$program" != "/bin/dash" ]; then
|
||||
cp "$program.0.cfi" "$program.cfg"
|
||||
fi
|
||||
done
|
Loading…
Add table
Reference in a new issue