Skip to content

Instantly share code, notes, and snippets.

@Zoxc
Last active August 29, 2015 14:26
Show Gist options
  • Save Zoxc/338baa18a631cd730a36 to your computer and use it in GitHub Desktop.
Save Zoxc/338baa18a631cd730a36 to your computer and use it in GitHub Desktop.
All code is shared between processes. Processes can not read or write to the code segment.
The global code segment is 2 TB. It is split into 1MB blocks, giving 2M blocks in total.
A module is a executable or shared library.
Each process has an bitmap where each bit represents a block of code of the code segment.
With 1 bit per block means the size of the array is 256KB.
If a bit is 0, the process does not have access to the block in the code segment.
There is a global array of function tables each associated with a block in the code segment.
We require indirect function targets to be 16 byte aligned.
The table contains 16-bit offsets each representing a valid code target of the form: block_offset + table[index] * 16
If a table entry is invalid, it points to an address which contains a halting instruction.
The number of legal function targets in a block is 1MB / 16B = 64K.
The size of the function table for a block would be 64K * 16-bit = 128KB. A bitmap of legal targets would be 8 KB.
The global array of function tables would be 128KB * 2M = 256 GB. A bitmap would be 64 GB.
Since the number of possible function targets are way larger than actual function targets we can require the linker
to limit the number of indirect function targets per block. Say we limit each block to have 1K targets.
The function table size would now be 1K * 16-bit = 2KB. The global array 2KB * 2M = 4 GB.
The format of code pointers is this:
64-bits 32 0
mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmbbbbbbbbbbbbbbbbbbbbbiiiiiiiiiiu
The m's form a 32-bit integer referring to the data segment of the callee.
The b's form a 21-bit integer referring to the block index.
The i's form a 10-bit integer referring to the function table index for the block.
The u is an unused bit.
We multiply the data segment integer with 0x1000 to get a pointer to the actual data segments.
This limits the data segments to a 4 TB address space.
When we are making an indirect function call:
mov r15, function_pointer
call verify_call
...
verify_call:
mov ecx, r15d
shr ecx, 11 ; access the block index
mov eax, 1
mov r11, rcx
shl eax, cl
mov rcx, r11
shr rcx, 6
test qword ptr gs:[rcx], rax ; check if the correct bit is set in the process bitmap; it is located at gs:0
je failure
mov rax, r15
and rax, 2046 ; access the function table index * 2
movzx eax, word ptr [rax] ; rax = global_array[index * 2] ; The global array is at offset 0
shl r11, 20 ; multiply by block size
shr r15, 32
shl r15, 12 ; extract and align the data segment
shl rax, 4 ; 16-bit multiply of offset due to alignment
add rax, r11 ; add block base and code offset
jmp rax
failure:
jmp bad_function_call
r15 allows functions to access their data segment using [r15 + offset].
This is needed since the address space is shared between multiple processes.
In the common case of modules directly calling libraries we simply load the callee's data segment from the caller's data segment.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment