function-call on protected stack - Page 1
Forum section: electronics / general

author: DiTBho
author_rank: Super Contributor
timestamp: December 23, 2020, 06:17:16 pm
content: Spin off from *the other* topic

author: DiTBho
author_rank: Super Contributor
timestamp: December 23, 2020, 06:18:05 pm
content: Pure software solution (MIPS example)MIPS offers these two instructionsCode:Jump And Linkjal addr:ra <= PC_nextPC_next <= addrCode:Jump to Registerjr $rPC_next <= $rcaller: jal addr can be used to invoke a functioncalled: jr $r can be used to return to the callerThis simple mechanism can use the stack to save the return address, and here MIPS doesn't provide any specific "stack-operations", so every register can be used for mimicpushandpopbehavior.So, since you can use two different registers as "stack pointers", you can have to stacks: one dedicated to RA, one dedicated to function' parameters, function' local variables, and function' answers.Code:Load word (32bit)lw $t,c($s)EA = $s + c$t = memory_read_u32Code:Store word (32bit)sw $d,c($s)EA = $s + cmemory_write_u32, $dcallerCode:#as function' body stack pointer, fbosp, choose one of the available register#as function' *RA* stack pointer, frasp, choose one of the available registerlw $p, #arameter0sw $p, #offset0($fbosp)...lw $p, #arameterNsw $p, #offsetN($fbosp)addiu $fbosp, $fbosp, #adjustment (sizeof(parameter0)+sizeof(parameter1)+ ....)jal addrcalledCode:# jal has already copied the return address into $ra, so save it into the dedicated stacksw $ra,#0($frasp)...# it's time to return, reload the ra from the dedicated stacklw $ra,#0($frasp)jr $ra

author: DiTBho
author_rank: Super Contributor
timestamp: December 23, 2020, 06:22:23 pm
content: This solution needs special Hardware"RAS" stands for "Return Address Stack", sorry I am addicted to acronymsdedicated RAS_area { begin, end }dedicated RAS_pt pointer, hidden not visible to user, only the kernel can access itdedicated RAS_push special store instruction, it implicitly operates on the hidden RAS_ptdedicated RAS_pop special load instruction, it implicitly operates on the hidden RAS_ptdedicated RAS_pt_mov protected instructions to manipulate RAS_sp, only the kernel can use themdedicated RAS_area_mov protected instructions to manipulate RAS_area.*, only the kernel can use themspecial logic to trap exception if the RAS area is pointed by common lw/sw IO instructionsspecial trap-exception in the list of possible exceptions that can be raised: except_RAS_area_violationcallerCode:jal addrcalledCode:RAS_push # save RA into the RA stack.... do stuffRAS_pop # load RA from the RA stackJ (RA) # returnCode:special instructions to be implementedRAS_push:RAS_pt++RAS_sw RA, (RAS_pt)RAS_pop:RAS_lw RA, (RAS_pt)RAS_sp--special logic (only when CPU_MODE is "user" ):common-load/store use EA, Effective Addresswhen load_IOif EA is within { RAS_area.begin ... RAS_area.end } then trap, otherwise okwhen store_IOif EA is within { RAS_area.begin ... RAS_area.end } then trap, otherwise okRAS-load/store (RAS_push/RAS_pop) use EA, Effective Addresswhen load_IOif EA is within { RAS_area.begin ... RAS_area.end } then ok, otherwise trapwhen store_IOif EA is within { RAS_area.begin ... RAS_area.end } then ok, otherwise trapHere you have common load/store, and dedicated protected stack-load/storeOnly the kernel and exception ISRs can use RAS_sp_mov and directly access RAS_pt.So in a multi-task-OS, RAS_pt can be saved as part of the task's context.These needed to be added then to the task'context:RAS_area.beginRAS_area.endRAS_pt

author: Nominal Animal
author_rank: Super Contributor
timestamp: December 24, 2020, 04:31:48 pm
content: Motorola 6809 has two 16-bit stacks: S (system) and U (user).The JSR instruction pushes the instruction pointer to the S stack, and jumps to the specified address.The RTS instruction pops the instruction pointer off the S stack.There are two push and two pop instructions: PSHS and PULS that operate on the S stack, and PSHU and PULU that operate on the U stack.However, there are no restrictions on the use of S or U stacks.If you have a flat memory model, with one data address space, then two stacks just add complexity without any real benefits. If you wish to use a fixed region for the stacks, you might think of having one grow upwards and the other downwards, but then, why not just use one stack for both?If you use multiple regions for the stacks, you need to be careful in balancing their sizes. Let's call the data stack to address stack size ratior. Ifroptimally reflects real world usage, then the ratio of the amount of data used in each stack is alsor. Ifris too small, you'll run out of data stack before you run out of address stack. Ifris too large, you'll run out of address stack before you run out of data stack.But,ris not a constant! It is completely dependent on the call chains, because different functions need different amounts of data stack, but only one slot in the address stack. So, there is always a tradeoff, an estimate onr, that means more memory reserved (for two stacks) than is actually needed, compared to the amount of memory reserved for one combined stack. How annoying!On the other hand, the 6809 does not force anyone to use the U stack for data; the S stack can also be used for data. Then, the U stack can be the current data object/stream/whatever being manipulated. But that is not what this thread is about.If you have paged virtual memory, you can reserve a lot of address space for each stack, but not back it by RAM before it is actually needed. If you have only 32-bit address space, this is a lot of reserved address space; potentially several megabytes off of 4096-megabyte address space. It might not sound much at first. (On 32- and 64-bit Intel architectures, most Linux distributions default to 8 MiB stacks per thread.) But what about when you have multiple threads running in the same process, each with their own stacks? In Linux, the default thread stack means that a 32-bit process is limited to around 130 threads, because there just isn't enoughaddress spacefor more thread stacks! You can still have lots of RAM unused (or as page cache, which amounts to the same thing, really), but no continuous address space left.The minimum working stack on x86 is 16,384 bytes (PTHREAD_STACK_MIN from <limits.h>), and a 32-bit process can easily have a thousand threads if needed, if the stack size is adjusted (pthread_attr_init(), pthread_attr_setstacksize(), and using the attribute set for creating threads with pthread_create()). (Indeed, in Linux, user and group IDs are per-thread properties – just "hidden" by the POSIX C API, and maintained across all threads using a dedicated POSIX realtime signal –, so if one uses freestanding code, that could even make sense for some particular network service. Right now, process parallelism is used however, which has its own benefits (separate address spaces!) and downsides (interprocess communication needed).)Because of this, I don't think splitting the return address stack from the data stack makes sense, unless a segmented memory model is used (where the stacks are in completely separate memory address spaces, i.e. pointerppoints to different data depending on which segment it is used for),orthe address space is huge, at least 48-bit, preferably 64-bit, so that the address space has lots and lots more room than physical RAM, and one will always run out of accessible RAM before running out of address space to use that RAM with.On an embedded system, say a microcontroller, it does not seem sensible to protect return addresses (or stack), because there is no real userspace there. It can be very useful to split privileged parts from unprivileged parts – for example, a web http/https service stack does not need access to anywhere but unprivileged buffers and the TCP/IP stack provided by the privileged part –, and even have a stack guard (in all function preambles in the unprivileged part), though. Personally, I'd love to have the ability for the privileged part to completely restart the unprivileged part. (Normally a reboot suffices, though, with acceptable effects for the human users.)

author: DiTBho
author_rank: Super Contributor
timestamp: December 24, 2020, 05:57:53 pm
content: Quote from: Nominal Animal on December 24, 2020, 04:31:48 pmEven 68k works this wayTwo stacks, the same behavior about JSR and RTS, but then everything is on the stack, parameters, all the function' local variable, the return values, and the return address, and this is what you don't want to have because it's "buffer overflow" exploitable.The only solution for these architectures is to copy what JSR pushes on the stack into a second stack, and gets it just before RTS.

author: DiTBho
author_rank: Super Contributor
timestamp: December 24, 2020, 06:14:18 pm
content: 68K software solution (workaround?)Code:JSR Jump to subroutineOperation:SP <- SP - 4;Mem <- PCPC <- function_addresscallerCode:jsr addr # this auto magically pushes RA into the stackcalledCode:MOVE 4(SP),D1 # save RA into the RA stackMOVE D1,(RAS_P)+ # it uses D1 as temp,# RAS_P can be A0,A1,..,A6# A7 is always the SP.... do stuffMOVE (RAS_P)-,D1 # load RA from the RA stackMOVE D1,4(SP) # save RA into the stackRTS # return

author: DiTBho
author_rank: Super Contributor
timestamp: December 24, 2020, 06:38:53 pm
content: Quote from: Nominal Animal on December 24, 2020, 04:31:48 pmIt is a serious problem. At the moment I have reserved space only for a depth of 32 function calls per task.Quote from: Nominal Animal on December 24, 2020, 04:31:48 pmI am open to other solutions.Quote from: Nominal Animal on December 24, 2020, 04:31:48 pmThe problem here is the "buffer overflow"It can happens in kernel space as well as in user-space. I have here a micro-controller (without any MMU/TLB) running uc/OS2 as kernel, and a tiny Web-server as main task. Well, it's not robust code but rather weak, so technically it can for sure experiment a malicious-exploit when a function that handles an HTTP request doesn't check parameters and this is a serious vulnerability when the request is made just to deliberately cause a buffer overflow in order to cause the RA overwritten by something that will be used as Return Address when the function returns.edit:Please note, in the above hardware solution, theRAS_pushandRAS_popmechanisms add privilege levels per-instruction basis, and it's already fully implementable without requiring special memories.

author: Nominal Animal
author_rank: Super Contributor
timestamp: December 24, 2020, 09:55:43 pm
content: Quote from: DiTBho on December 24, 2020, 06:38:53 pmYup; and deliberate overwrite of the return address is a knownexploit scheme.Separating the return addresses into a separate stack that is not easily accidentally overwritten is definitely something I'd like to see (but with the constraints I mentioned above).Using a stack that grows upwards (increasing addresses) would help a lot here, because then the return address is at a lower address than the temporary variables and arrays; buffer overrun then does not overwrite the return address, but the unused area of the stack (and whatever follows the stack).A big reason for buffer overruns being so common in C code is because a lot of the C standard library functions have idiotic interfaces. Hell, standard C still doesn't havegetline(),strdup(),strndup(), etc.An embedded RTOS, or any code running in a freestanding environment (no standard C library available, only features described by <float.h>, <iso646.h>, <limits.h>, <stdalign.h>, <stdarg.h>, <stdbool.h>, <stddef.h>, <stdint.h>, and <stdnoreturn.h>; and compiler-provided C extensions), like say the Arduino environment for example, is free to implement its own.For specific things like serial input processing, a coroutine consuming input byte by byte, and parsing and processing it as it comes in, would work fine on AVRs. But, the C model really doesn't provide a good interface for those. So, buffers are usually used instead.(If you want, try writing a C function to parse input, with the function called once whenever a new byte is available, with that byte as a function parameter. So, basically like a finite state machine, where each call provides the new input value, so you can do a state transition, and optionally produce output – say, in the form of calling a function. It can be done, sure; but it's not something you'd expect a C learner to be able to do correctly.)If you want mutable strings or data buffers in C, without risking buffer overruns, I like to useCode:struct area {size_t size;size_t used;unsigned char data[];};wheredata[]contains the actual data in a C99 flexible array member;sizeis the size of that array; andusedis the number of bytes of data in the initial part of the array. This is very similar to Pascal strings, where the length of the string is just before the data; except I have size, length, and then data.For I/O buffers, I like to useCode:struct iobuffer1 {size_t size;size_t head;size_t tail;unsigned char data[];};struct iobuffer2 {size_t size;unsigned char *head;unsigned char *tail;unsigned char *data;};whereheadis the index or pointer to the first byte in the buffer,tailis the index or pointer just past the last byte in the buffer (withhead == tailindicating an empty buffer). With these, the only buffer issues are off-by-one errors, especially when parsing e.g. escape codes and the escape mark is the last byte buffered. Those I avoid by comparing the current parsing position to the buffer tail, with as many "extra" bytes allocated todata[]as the longest escape sequence is, and being able to cancel parsing and retry when there is more data available.Quote from: DiTBho on December 24, 2020, 06:38:53 pmHave you read any Apache or APR sources? Instead ofheap, it usesallocation pools. There is one "global" pool, but each request gets their own pool, and any memory that needs to be allocated, is allocated from that pool. (It's not a pool in the sense that it has limits; it's more in the sense that all allocations within that pool can be freed with a single call.) This does not help with buffer overruns, though? Well, it does, because no array-like data ever lives on stack; they're always allocated from the pool instead.Similarly, we don't really needa stackfor function-local data. It is just one of the possible ways of managing the memory needed by functions for temporary or more longer-duration data. It's just that a stack is an obvious choice when you consider the complexity of implementing an allocator.Let's say you had a very fast memory allocator, and the stack is only used for storing scalar data, no arrays.If all arrays had to be dynamically allocated, then using something likestruct iobuffer2above would require four pointers (three pointers and one unsigned size), and "standard library" functions could check for array boundaries.Thus far, such dynamic allocators tend to be considered too "slow" for general purpose stuff.(Yet, it is exactly the direction e.g. the Linux kernel is going.)See how it comes round to memory management schemes? Well, at least for me. This is why I'm looking into segmented memory models, i.e. where one has some sort of special base pointer (maybe just aligned in memory), and offsets and sizes with respect to that base pointer; and thehardwarecan detect buffer overruns (by simply making sure that the effective offset with respect to a given base pointer is within its size). Return address stack would be only one particular use case, and if it was only accessible to CALL/RET instructions, ROP exploits and buffer overruns affecting function return address would be impossible. It's just that passing three or four pointer- or pointer-size variables per address/variable is a lot of overhead; there must be a pattern that reduces the overhead, especially since typical code only juggles between a small number of separate "objects" (or variable scopes). If the segment is hardcoded to the assembly instruction, like on x86 and x86-64, you'd need a separate function for each combination of reference segments. If, however, the segment was coded to the "base pointer", then one function would suffice.

author: DiTBho
author_rank: Super Contributor
timestamp: December 24, 2020, 10:23:49 pm
content: WOW great post, a lot of info, thank you!Quote from: Nominal Animal on December 24, 2020, 09:55:43 pmNot yet, my HTTPd is very simple and primitive. I don't remember how many kbytes its code takes exactly, but the whole firmware takes less than 32Kbyte of ROM coupled with an RTOS, a TCP/IP library, and a few other tiny things. I know it because the Keil-ARM7-toolchian has this limit in evaluation mode.This is also the amount for BRAM I have on my FPGA: less than 64Kbyte for RAM and ROM.Apache is up and running on my Linux server, I have never looked at its source. I remember when I compiled it it took 2 hours, so I think it's much more advanced and reach of features and mechanisms.I will look for the "allocation pool" concept, that's very interesting!

author: Nominal Animal
author_rank: Super Contributor
timestamp: December 24, 2020, 11:17:24 pm
content: Apache Portable Runtimeis a bit bloaty, but pretty reliable set of utility functions; definitely interesting stuff to look at and consider. Thebucket conceptAPR implements is also very useful.You can do a minimal pool allocator implementation on top of standard C withCode:struct pool;struct chunk {struct pool *pool; /* Pool this belongs to */struct chunk *next; /* Next chunk in this pool */size_t size;unsigned char data[];};struct pool {struct pool *pools; /* Optional: For subpools */struct chunk *chunks;};using the following functions on top of malloc() et al:Code:struct pool *pool_new(void);struct pool *pool_subpool(struct pool *parent);void pool_free(struct pool *pool);void *pool_get(struct pool *pool, size_t size);void *pool_drop(void *mem);void *pool_resize(void *mem, size_t size);void *pool_move(struct pool *to, void *mem);void *pool_optimize(struct pool *to, void *mem, size_t size);pool_new() and pool_subpool() just allocate a new pool structure, and initialize it. pool_subpool() adds the new subpool to the parent->pools chain.pool_free() frees all pools hanging off the ->pools, and all chunks hanging off its ->chunks, as well as the ->chunks of the subpools. Simple two-loop iterative function, unless you have multiple threads, when it becomes highly complex.pool_get() is like malloc(), but reserves memory for the struct chunk just before the data also. Basically,char *ptr = malloc(size + sizeof (struct chunk));, then returnsptr + sizeof (struct chunk), i.e.((struct chunk *)ptr)->data. The chunk is added to the ->chunks chain of the specified pool.pool_drop() is like free(), but removes the chunk from the owner pool ->chunks chain. Note thatstruct chunk *ch = (struct chunk *)((char *)mem - sizeof (struct chunk));. pool_drop() is rarely used.pool_resize() first has to remove the chunk from the owner pool ->chunks chain. Then, it can use realloc() to resize the chunk. If successful, the chunk (with a possibly changed address) is re-added to the owner pool ->chunks chain. If realloc() fails, the old allocation is still valid, and the chunk should be added back to the owner pool ->chunks chain anyway. This way, the ->chunks chain never contains pointers to invalid chunks. If you only update the chain afterwards, you cannot have threadsafe structures even with locking. (As these are now, you get thread safety by adding a mutex to each pool structure, and grab it before traversing either chain.)pool_move() just removes the chunk from one ->chunks chain, and adds to another.pool_optimize() combines reallocation and moving. You remove the chunk from one ->chunks chain, then reallocate it, and if successful, add it to the to->chunks chain; if realloc fails, you again need to add the original back to its original ->chunks chain.A service can have a pool for each connected session, with each request (within that session) having their own pool. That way, if the session is destroyed, the request pools are destroyed also.In multithreaded processes, pool_free() is a bit more complicated. Usually, its chains are added to a garbage chain, and not immediately freed, because other threads may be holding a pointer to one of the objects in the pool. Because objects are not individually freed, reference counting will not help. You can only destroy the chunks in the garbage chain when you know no thread is holding a temporary pointer to its contents. If you use a worker pool of threads, then you can mark each garbage chain with a timestamp, and have each worker thread record their timestamp when they complete ongoing work and go back to the worker pool. When recording that timestamp, you can check the other threads' timestamps, and find the oldest. All garbage pools with an even older timestamp can be safely destroyed.

author: DiTBho
author_rank: Super Contributor
timestamp: January 11, 2021, 12:40:27 am
content: Today I needed to recompile gcc-4.4.3 because used in an old project and I added "Mudflap" to the building scriptQuoteseehere