Edit

kc3-lang/libffi/include/ffi_common.h

Branch :

  • Show log

    Commit

  • Author : Madhavan T. Venkataraman
    Date : 2021-03-05 10:07:30
    Hash : 9ba55921
    Message : Static tramp v5 (#624) * Static Trampolines Closure Trampoline Security Issue ================================= Currently, the trampoline code used in libffi is not statically defined in a source file (except for MACH). The trampoline is either pre-defined machine code in a data buffer. Or, it is generated at runtime. In order to execute a trampoline, it needs to be placed in a page with executable permissions. Executable data pages are attack surfaces for attackers who may try to inject their own code into the page and contrive to have it executed. The security settings in a system may prevent various tricks used in user land to write code into a page and to have it executed somehow. On such systems, libffi trampolines would not be able to run. Static Trampoline ================= To solve this problem, the trampoline code needs to be defined statically in a source file, compiled and placed in the text segment so it can be mapped and executed naturally without any tricks. However, the trampoline needs to be able to access the closure pointer at runtime. PC-relative data referencing ============================ The solution implemented in this patch set uses PC-relative data references. The trampoline is mapped in a code page. Adjacent to the code page, a data page is mapped that contains the parameters of the trampoline: - the closure pointer - pointer to the ABI handler to jump to The trampoline code uses an offset relative to its current PC to access its data. Some architectures support PC-relative data references in the ISA itself. E.g., X64 supports RIP-relative references. For others, the PC has to somehow be loaded into a general purpose register to do PC-relative data referencing. To do this, we need to define a get_pc() kind of function and call it to load the PC in a desired register. There are two cases: 1. The call instruction pushes the return address on the stack. In this case, get_pc() will extract the return address from the stack and load it in the desired register and return. 2. The call instruction stores the return address in a designated register. In this case, get_pc() will copy the return address to the desired register and return. Either way, the PC next to the call instruction is obtained. Scratch register ================ In order to do its job, the trampoline code would need to use a scratch register. Depending on the ABI, there may not be a register available for scratch. This problem needs to be solved so that all ABIs will work. The trampoline will save two values on the stack: - the closure pointer - the original value of the scratch register This is what the stack will look like: sp before trampoline ------> -------------------- | closure pointer | -------------------- | scratch register | sp after trampoline -------> -------------------- The ABI handler can do the following as needed by the ABI: - the closure pointer can be loaded in a desired register - the scratch register can be restored to its original value - the stack pointer can be restored to its original value (the value when the trampoline was invoked) To do this, I have defined prolog code for each ABI handler. The legacy trampoline jumps to the ABI handler directly. But the static trampoline defined in this patch jumps tp the prolog code which performs the above actions before jumping to the ABI handler. Trampoline Table ================ In order to reduce the trampoline memory footprint, the trampoline code would be defined as a code array in the text segment. This array would be mapped into the address space of the caller. The mapping would, therefore, contain a trampoline table. Adjacent to the trampoline table mapping, there will be a data mapping that contains a parameter table, one parameter block for each trampoline. The parameter block will contain: - a pointer to the closure - a pointer to the ABI handler The static trampoline code would finally look like this: - Make space on the stack for the closure and the scratch register by moving the stack pointer down - Store the original value of the scratch register on the stack - Using PC-relative reference, get the closure pointer - Store the closure pointer on the stack - Using PC-relative reference, get the ABI handler pointer - Jump to the ABI handler Mapping size ============ The size of the code mapping that contains the trampoline table needs to be determined on a per architecture basis. If a particular architecture supports multiple base page sizes, then the largest supported base page size needs to be chosen. E.g., we choose 16K for ARM64. Trampoline allocation and free ============================== Static trampolines are allocated in ffi_closure_alloc() and freed in ffi_closure_free(). Normally, applications use these functions. But there are some cases out there where the user of libffi allocates and manages its own closure memory. In such cases, static trampolines cannot be used. These will fall back to using legacy trampolines. The user has to make sure that the memory is executable. ffi_closure structure ===================== I did not want to make any changes to the size of the closure structure for this feature to guarantee compatibility. But the opaque static trampoline handle needs to be stored in the closure. I have defined it as follows: - char tramp[FFI_TRAMPOLINE_SIZE]; + union { + char tramp[FFI_TRAMPOLINE_SIZE]; + void *ftramp; + }; If static trampolines are used, then tramp[] is not needed to store a dynamic trampoline. That space can be reused to store the handle. Hence, the union. Architecture Support ==================== Support has been added for x64, i386, aarch64 and arm. Support for other architectures can be added very easily in the future. OS Support ========== Support has been added for Linux. Support for other OSes can be added very easily. Signed-off-by: Madhavan T. Venkataraman <madvenka@linux.microsoft.com> * x86: Support for Static Trampolines - Define the arch-specific initialization function ffi_tramp_arch () that returns trampoline size information to common code. - Define the trampoline code mapping and data mapping sizes. - Define the trampoline code table statically. Define two tables, actually, one with CET and one without. - Introduce a tiny prolog for each ABI handling function. The ABI handlers addressed are: - ffi_closure_unix64 - ffi_closure_unix64_sse - ffi_closure_win64 The prolog functions are called: - ffi_closure_unix64_alt - ffi_closure_unix64_sse_alt - ffi_closure_win64_alt The legacy trampoline jumps to the ABI handler. The static trampoline jumps to the prolog function. The prolog function uses the information provided by the static trampoline, sets things up for the ABI handler and then jumps to the ABI handler. - Call ffi_tramp_set_parms () in ffi_prep_closure_loc () to initialize static trampoline parameters. Signed-off-by: Madhavan T. Venkataraman <madvenka@linux.microsoft.com> * i386: Support for Static Trampolines - Define the arch-specific initialization function ffi_tramp_arch () that returns trampoline size information to common code. - Define the trampoline code table statically. Define two tables, actually, one with CET and one without. - Define the trampoline code table statically. - Introduce a tiny prolog for each ABI handling function. The ABI handlers addressed are: - ffi_closure_i386 - ffi_closure_STDCALL - ffi_closure_REGISTER The prolog functions are called: - ffi_closure_i386_alt - ffi_closure_STDCALL_alt - ffi_closure_REGISTER_alt The legacy trampoline jumps to the ABI handler. The static trampoline jumps to the prolog function. The prolog function uses the information provided by the static trampoline, sets things up for the ABI handler and then jumps to the ABI handler. - Call ffi_tramp_set_parms () in ffi_prep_closure_loc () to initialize static trampoline parameters. Signed-off-by: Madhavan T. Venkataraman <madvenka@linux.microsoft.com> * arm64: Support for Static Trampolines - Define the arch-specific initialization function ffi_tramp_arch () that returns trampoline size information to common code. - Define the trampoline code mapping and data mapping sizes. - Define the trampoline code table statically. - Introduce a tiny prolog for each ABI handling function. The ABI handlers addressed are: - ffi_closure_SYSV - ffi_closure_SYSV_V The prolog functions are called: - ffi_closure_SYSV_alt - ffi_closure_SYSV_V_alt The legacy trampoline jumps to the ABI handler. The static trampoline jumps to the prolog function. The prolog function uses the information provided by the static trampoline, sets things up for the ABI handler and then jumps to the ABI handler. - Call ffi_tramp_set_parms () in ffi_prep_closure_loc () to initialize static trampoline parameters. Signed-off-by: Madhavan T. Venkataraman <madvenka@linux.microsoft.com> * arm: Support for Static Trampolines - Define the arch-specific initialization function ffi_tramp_arch () that returns trampoline size information to common code. - Define the trampoline code mapping and data mapping sizes. - Define the trampoline code table statically. - Introduce a tiny prolog for each ABI handling function. The ABI handlers addressed are: - ffi_closure_SYSV - ffi_closure_VFP The prolog functions are called: - ffi_closure_SYSV_alt - ffi_closure_VFP_alt The legacy trampoline jumps to the ABI handler. The static trampoline jumps to the prolog function. The prolog function uses the information provided by the static trampoline, sets things up for the ABI handler and then jumps to the ABI handler. - Call ffi_tramp_set_parms () in ffi_prep_closure_loc () to initialize static trampoline parameters. Signed-off-by: Madhavan T. Venkataraman <madvenka@linux.microsoft.com>

  • include/ffi_common.h
  • /* -----------------------------------------------------------------------
       ffi_common.h - Copyright (C) 2011, 2012, 2013  Anthony Green
                      Copyright (C) 2007  Free Software Foundation, Inc
                      Copyright (c) 1996  Red Hat, Inc.
                      
       Common internal definitions and macros. Only necessary for building
       libffi.
       ----------------------------------------------------------------------- */
    
    #ifndef FFI_COMMON_H
    #define FFI_COMMON_H
    
    #ifdef __cplusplus
    extern "C" {
    #endif
    
    #include <fficonfig.h>
    
    /* Do not move this. Some versions of AIX are very picky about where
       this is positioned. */
    #ifdef __GNUC__
    # if HAVE_ALLOCA_H
    #  include <alloca.h>
    # else
      /* mingw64 defines this already in malloc.h. */
    #  ifndef alloca
    #    define alloca __builtin_alloca
    #  endif
    # endif
    # define MAYBE_UNUSED __attribute__((__unused__))
    #else
    # define MAYBE_UNUSED
    # if HAVE_ALLOCA_H
    #  include <alloca.h>
    # else
    #  ifdef _AIX
    #   pragma alloca
    #  else
    #   ifndef alloca /* predefined by HP cc +Olibcalls */
    #    ifdef _MSC_VER
    #     define alloca _alloca
    #    else
    char *alloca ();
    #   endif
    #  endif
    # endif
    # endif
    #endif
    
    /* Check for the existence of memcpy. */
    #if STDC_HEADERS
    # include <string.h>
    #else
    # ifndef HAVE_MEMCPY
    #  define memcpy(d, s, n) bcopy ((s), (d), (n))
    # endif
    #endif
    
    #if defined(FFI_DEBUG)
    #include <stdio.h>
    #endif
    
    #ifdef FFI_DEBUG
    void ffi_assert(char *expr, char *file, int line);
    void ffi_stop_here(void);
    void ffi_type_test(ffi_type *a, char *file, int line);
    
    #define FFI_ASSERT(x) ((x) ? (void)0 : ffi_assert(#x, __FILE__,__LINE__))
    #define FFI_ASSERT_AT(x, f, l) ((x) ? 0 : ffi_assert(#x, (f), (l)))
    #define FFI_ASSERT_VALID_TYPE(x) ffi_type_test (x, __FILE__, __LINE__)
    #else
    #define FFI_ASSERT(x)
    #define FFI_ASSERT_AT(x, f, l)
    #define FFI_ASSERT_VALID_TYPE(x)
    #endif
    
    /* v cast to size_t and aligned up to a multiple of a */
    #define FFI_ALIGN(v, a)  (((((size_t) (v))-1) | ((a)-1))+1)
    /* v cast to size_t and aligned down to a multiple of a */
    #define FFI_ALIGN_DOWN(v, a) (((size_t) (v)) & -a)
    
    /* Perform machine dependent cif processing */
    ffi_status ffi_prep_cif_machdep(ffi_cif *cif);
    ffi_status ffi_prep_cif_machdep_var(ffi_cif *cif,
    	 unsigned int nfixedargs, unsigned int ntotalargs);
    
    
    #if HAVE_LONG_DOUBLE_VARIANT
    /* Used to adjust size/alignment of ffi types.  */
    void ffi_prep_types (ffi_abi abi);
    #endif
    
    /* Used internally, but overridden by some architectures */
    ffi_status ffi_prep_cif_core(ffi_cif *cif,
    			     ffi_abi abi,
    			     unsigned int isvariadic,
    			     unsigned int nfixedargs,
    			     unsigned int ntotalargs,
    			     ffi_type *rtype,
    			     ffi_type **atypes);
    
    /* Translate a data pointer to a code pointer.  Needed for closures on
       some targets.  */
    void *ffi_data_to_code_pointer (void *data) FFI_HIDDEN;
    
    /* The arch code calls this to determine if a given closure has a
       static trampoline. */
    int ffi_tramp_is_present (void *closure);
    
    /* Extended cif, used in callback from assembly routine */
    typedef struct
    {
      ffi_cif *cif;
      void *rvalue;
      void **avalue;
    } extended_cif;
    
    /* Terse sized type definitions.  */
    #if defined(_MSC_VER) || defined(__sgi) || defined(__SUNPRO_C)
    typedef unsigned char UINT8;
    typedef signed char   SINT8;
    typedef unsigned short UINT16;
    typedef signed short   SINT16;
    typedef unsigned int UINT32;
    typedef signed int   SINT32;
    # ifdef _MSC_VER
    typedef unsigned __int64 UINT64;
    typedef signed __int64   SINT64;
    # else
    # include <inttypes.h>
    typedef uint64_t UINT64;
    typedef int64_t  SINT64;
    # endif
    #else
    typedef unsigned int UINT8  __attribute__((__mode__(__QI__)));
    typedef signed int   SINT8  __attribute__((__mode__(__QI__)));
    typedef unsigned int UINT16 __attribute__((__mode__(__HI__)));
    typedef signed int   SINT16 __attribute__((__mode__(__HI__)));
    typedef unsigned int UINT32 __attribute__((__mode__(__SI__)));
    typedef signed int   SINT32 __attribute__((__mode__(__SI__)));
    typedef unsigned int UINT64 __attribute__((__mode__(__DI__)));
    typedef signed int   SINT64 __attribute__((__mode__(__DI__)));
    #endif
    
    typedef float FLOAT32;
    
    #ifndef __GNUC__
    #define __builtin_expect(x, expected_value) (x)
    #endif
    #define LIKELY(x)    __builtin_expect(!!(x),1)
    #define UNLIKELY(x)  __builtin_expect((x)!=0,0)
    
    #ifdef __cplusplus
    }
    #endif
    
    #endif