LCOV - code coverage report
Current view: top level - flamenco/vm/syscall - fd_vm_syscall_macros.h (source / functions) Hit Total Coverage
Test: cov.lcov Lines: 83 110 75.5 %
Date: 2025-10-27 04:40:00 Functions: 2 15 13.3 %

          Line data    Source code
       1             : #ifndef HEADER_fd_src_flamenco_vm_syscall_fd_vm_syscall_macros_h
       2             : #define HEADER_fd_src_flamenco_vm_syscall_fd_vm_syscall_macros_h
       3             : #include "../fd_vm_private.h"
       4             : 
       5             : /* fd_vm_cu API *******************************************************/
       6             : 
       7             : /* FD_VM_CU_UPDATE charges the vm cost compute units.
       8             : 
       9             :    If the vm does not have more than cost cu available, this will cause
      10             :    the caller to zero out the vm->cu and return with FD_VM_SYSCALL_ERR_COMPUTE_BUDGET_EXCEEDED.
      11             :    This macro is robust.
      12             :    This is meant to be used by syscall implementations and strictly
      13             :    conforms with the vm-syscall ABI interface.
      14             : 
      15             :    Note: in Agave a syscall can return success leaving 0 available CUs.
      16             :    The instruction will fail at the next instruction (e.g., exit).
      17             :    To reproduce the same behavior, we do not return FD_VM_SYSCALL_ERR_COMPUTE_BUDGET_EXCEEDED
      18             :    when cu == 0.
      19             : 
      20             :    FD_VM_CU_MEM_UPDATE charges the vm the equivalent of sz bytes of
      21             :    compute units.  Behavior is otherwise identical to FD_VM_CU_UPDATE.
      22             :    FIXME: THIS API PROBABLY BELONGS IN SYSCALL CPI LAND. */
      23             : 
      24         180 : #define FD_VM_CU_UPDATE( vm, cost ) (__extension__({ \
      25         180 :     fd_vm_t * _vm   = (vm);                          \
      26         180 :     ulong     _cost = (cost);                        \
      27         180 :     ulong     _cu   = _vm->cu;                       \
      28         180 :     if( FD_UNLIKELY( _cost>_cu ) ) {                 \
      29           0 :       _vm->cu = 0UL;                                 \
      30           0 :       FD_VM_ERR_FOR_LOG_INSTR( vm, FD_EXECUTOR_INSTR_ERR_COMPUTE_BUDGET_EXCEEDED ); \
      31           0 :       return FD_VM_SYSCALL_ERR_COMPUTE_BUDGET_EXCEEDED; \
      32           0 :     }                                                \
      33         180 :     _vm->cu = _cu - _cost;                           \
      34         180 :   }))
      35             : 
      36             : /* https://github.com/anza-xyz/agave/blob/5263c9d61f3af060ac995956120bef11c1bbf182/programs/bpf_loader/src/syscalls/mem_ops.rs#L7 */
      37             : #define FD_VM_CU_MEM_OP_UPDATE( vm, sz ) \
      38         126 :   FD_VM_CU_UPDATE( vm, fd_ulong_max( FD_VM_MEM_OP_BASE_COST, sz / FD_VM_CPI_BYTES_PER_UNIT ) )
      39             : 
      40             : 
      41             : /* fd_vm_mem API *****************************************************/
      42             : 
      43             : /* fd_vm_haddr_query is a struct that contains information about a vaddr, align, sz, and whether it is a slice.
      44             :    The translated haddr is written into the `haddr` field of the struct on success. This struct is primarily used
      45             :    by the FD_VM_TRANSLATE_MUT macro. See more details in the macro's documentation. */
      46             : struct fd_vm_haddr_query {
      47             :   ulong vaddr;
      48             :   ulong align;
      49             :   ulong sz;
      50             :   uchar is_slice;
      51             :   void * haddr; /* out field */
      52             : };
      53             : typedef struct fd_vm_haddr_query fd_vm_haddr_query_t;
      54             : 
      55             : /* FD_VM_MEM_HADDR_LD returns a read only pointer to the first byte
      56             :    in the host address space corresponding to vm's virtual address range
      57             :    [vaddr,vaddr+sz).  If the vm has check_align enabled, the vaddr
      58             :    should be aligned to align and the returned pointer will be similarly
      59             :    aligned.  Align is assumed to be a power of two <= 8 (FIXME: CHECK
      60             :    THIS LIMIT).
      61             : 
      62             :    If the virtual address range cannot be mapped to the host address
      63             :    space completely and/or (when applicable) vaddr is not appropriately
      64             :    aligned, this will cause the caller to return FD_VM_SYSCALL_ERR_SEGFAULT.
      65             :    This macro is robust.  This is meant to be used by syscall
      66             :    implementations and strictly conforms with the vm-syscall ABI
      67             :    interface.
      68             : 
      69             :    FD_VM_MEM_HADDR_ST returns a read-write pointer but is otherwise
      70             :    identical to FD_VM_MEM_HADDR_LD.
      71             : 
      72             :    FD_VM_MEM_HADDR_LD_FAST and FD_VM_HADDR_ST_FAST are for use when the
      73             :    corresponding vaddr region it known to correctly resolve (e.g.  a
      74             :    syscall has already done preflight checks on them).
      75             : 
      76             :    These macros intentionally don't support multi region loads/stores.
      77             :    The load/store macros are used by vm syscalls and mirror the use
      78             :    of translate_slice{_mut}. However, this check does not allow for
      79             :    multi region accesses. So if there is an attempt at a multi region
      80             :    translation, an error will be returned.
      81             : 
      82             :    FD_VM_MEM_HADDR_ST_UNCHECKED has all of the checks of a load or a
      83             :    store, but intentionally omits the is_writable checks for the
      84             :    input region that are done during memory translation.
      85             : 
      86             :    FD_VM_MEM_HADDR_ST_NO_SZ_CHECK does all of the checks of a load,
      87             :    except for a check on the validity of the size of a load. It only
      88             :    checks that the specific vaddr that is being translated is valid. */
      89             : 
      90         180 : #define FD_VM_MEM_HADDR_LD( vm, vaddr, align, sz ) (__extension__({                                          \
      91         180 :     fd_vm_t * _vm       = (vm);                                                                              \
      92         180 :     ulong     _vaddr    = (vaddr);                                                                           \
      93         180 :     ulong     _haddr    = fd_vm_mem_haddr( vm, _vaddr, (sz), _vm->region_haddr, _vm->region_ld_sz, 0, 0UL ); \
      94         180 :     int       _sigbus   = fd_vm_is_check_align_enabled( vm ) & (!fd_ulong_is_aligned( _haddr, (align) ));    \
      95         180 :     if ( FD_UNLIKELY( sz > LONG_MAX ) ) {                                                                    \
      96           0 :       FD_VM_ERR_FOR_LOG_SYSCALL( _vm, FD_VM_SYSCALL_ERR_INVALID_LENGTH );                                    \
      97           0 :       return FD_VM_SYSCALL_ERR_SEGFAULT;                                                                     \
      98           0 :     }                                                                                                        \
      99         180 :     if( FD_UNLIKELY( (!_haddr) ) ) {                                                                         \
     100          15 :       _vm->segv_vaddr       = _vaddr;                                                                        \
     101          15 :       _vm->segv_access_len  = (sz);                                                                          \
     102          15 :       _vm->segv_access_type = FD_VM_ACCESS_TYPE_LD;                                                          \
     103          15 :       FD_VM_ERR_FOR_LOG_EBPF( _vm, fd_vm_generate_access_violation( _vaddr, _vm->sbpf_version ) );           \
     104          15 :       return FD_VM_SYSCALL_ERR_SEGFAULT;                                                                     \
     105          15 :     }                                                                                                        \
     106         180 :     if ( FD_UNLIKELY( _sigbus ) ) {                                                                          \
     107           0 :       FD_VM_ERR_FOR_LOG_SYSCALL( _vm, FD_VM_SYSCALL_ERR_UNALIGNED_POINTER );                                 \
     108           0 :       return FD_VM_SYSCALL_ERR_SEGFAULT;                                                                     \
     109           0 :     }                                                                                                        \
     110         165 :     (void const *)_haddr;                                                                                    \
     111         165 :   }))
     112             : 
     113             : #define FD_VM_MEM_HADDR_LD_UNCHECKED( vm, vaddr, align, sz ) (__extension__({                                      \
     114             :     fd_vm_t const * _vm       = (vm);                                                                              \
     115             :     ulong           _vaddr    = (vaddr);                                                                           \
     116             :     ulong           _haddr    = fd_vm_mem_haddr( vm, _vaddr, (sz), _vm->region_haddr, _vm->region_ld_sz, 0, 0UL ); \
     117             :     (void const *)_haddr;                                                                                          \
     118             :   }))
     119             : 
     120             : 
     121             : #define FD_VM_MEM_HADDR_LD_NO_SZ_CHECK( vm, vaddr, align ) (__extension__({ \
     122             :   FD_VM_MEM_HADDR_LD( vm, vaddr, align, 1UL );                              \
     123             :   }))
     124             : 
     125             : static inline void *
     126         129 : FD_VM_MEM_HADDR_ST_( fd_vm_t *vm, ulong vaddr, ulong align, ulong sz, int *err ) {
     127         129 :   fd_vm_t * _vm       = (vm);
     128         129 :   ulong     _vaddr    = (vaddr);
     129         129 :   ulong     _haddr    = fd_vm_mem_haddr( vm, _vaddr, (sz), _vm->region_haddr, _vm->region_st_sz, 1, 0UL );
     130         129 :   int       _sigbus   = fd_vm_is_check_align_enabled( vm ) & (!fd_ulong_is_aligned( _haddr, (align) ));
     131         129 :   if ( FD_UNLIKELY( sz > LONG_MAX ) ) {
     132           0 :     FD_VM_ERR_FOR_LOG_SYSCALL( _vm, FD_VM_SYSCALL_ERR_INVALID_LENGTH );
     133           0 :     *err = FD_VM_SYSCALL_ERR_SEGFAULT;
     134           0 :     return 0;
     135           0 :   }
     136         129 :   if( FD_UNLIKELY( (!_haddr) ) ) {
     137          42 :     vm->segv_vaddr       = vaddr;
     138          42 :     vm->segv_access_len  = (sz);
     139          42 :     vm->segv_access_type = FD_VM_ACCESS_TYPE_ST;
     140          42 :     FD_VM_ERR_FOR_LOG_EBPF( _vm, fd_vm_generate_access_violation( _vaddr, _vm->sbpf_version ) );
     141          42 :     *err = FD_VM_SYSCALL_ERR_SEGFAULT;
     142          42 :     return 0;
     143          42 :   }
     144          87 :   if ( FD_UNLIKELY( _sigbus ) ) {
     145           0 :     FD_VM_ERR_FOR_LOG_SYSCALL( _vm, FD_VM_SYSCALL_ERR_UNALIGNED_POINTER );
     146           0 :     *err = FD_VM_SYSCALL_ERR_SEGFAULT;
     147           0 :     return 0;
     148           0 :   }
     149          87 :   return (void *)_haddr;
     150          87 : }
     151             : 
     152         129 : #define FD_VM_MEM_HADDR_ST( vm, vaddr, align, sz ) (__extension__({                                         \
     153         129 :     int _err = 0;                                                                                           \
     154         129 :     void * ret = FD_VM_MEM_HADDR_ST_( vm, vaddr, align, sz, &_err );                                        \
     155         129 :     if ( FD_UNLIKELY( 0 != _err ))                                                                          \
     156         129 :       return _err;                                                                                          \
     157         129 :     ret;                                                                                                    \
     158          87 : }))
     159             : 
     160             : #define FD_VM_MEM_HADDR_ST_UNCHECKED( vm, vaddr, align, sz ) (__extension__({                             \
     161             :     fd_vm_t * _vm    = (vm);                                                                              \
     162             :     ulong     _vaddr = (vaddr);                                                                           \
     163             :     ulong     _haddr = fd_vm_mem_haddr( vm, _vaddr, (sz), _vm->region_haddr, _vm->region_st_sz, 1, 0UL ); \
     164             :     (void const *)_haddr;                                                                                 \
     165             :   }))
     166             : 
     167             : #define FD_VM_MEM_HADDR_ST_WRITE_UNCHECKED( vm, vaddr, align, sz ) (__extension__({                          \
     168             :     fd_vm_t * _vm       = (vm);                                                                              \
     169             :     ulong     _vaddr    = (vaddr);                                                                           \
     170             :     ulong     _haddr    = fd_vm_mem_haddr( vm, _vaddr, (sz), _vm->region_haddr, _vm->region_st_sz, 0, 0UL ); \
     171             :     int       _sigbus   = fd_vm_is_check_align_enabled( vm ) & (!fd_ulong_is_aligned( _haddr, (align) ));    \
     172             :     if ( FD_UNLIKELY( sz > LONG_MAX ) ) {                                                                    \
     173             :       FD_VM_ERR_FOR_LOG_SYSCALL( _vm, FD_VM_SYSCALL_ERR_INVALID_LENGTH );                                    \
     174             :       return FD_VM_SYSCALL_ERR_SEGFAULT;                                                                     \
     175             :     }                                                                                                        \
     176             :     if( FD_UNLIKELY( !_haddr ) ) {                                                                           \
     177             :       _vm->segv_vaddr       = _vaddr;                                                                        \
     178             :       _vm->segv_access_len  = (sz);                                                                          \
     179             :       _vm->segv_access_type = FD_VM_ACCESS_TYPE_ST;                                                          \
     180             :       FD_VM_ERR_FOR_LOG_EBPF( _vm, fd_vm_generate_access_violation( _vaddr, _vm->sbpf_version ) );           \
     181             :       return FD_VM_SYSCALL_ERR_SEGFAULT;                                                                     \
     182             :     }                                                                                                        \
     183             :     if ( FD_UNLIKELY( _sigbus ) ) {                                                                          \
     184             :       FD_VM_ERR_FOR_LOG_SYSCALL( _vm, FD_VM_SYSCALL_ERR_UNALIGNED_POINTER );                                 \
     185             :       return FD_VM_SYSCALL_ERR_SEGFAULT;                                                                     \
     186             :     }                                                                                                        \
     187             :     (void *)_haddr;                                                                                          \
     188             :   }))
     189             : 
     190             : 
     191             : #define FD_VM_MEM_HADDR_ST_NO_SZ_CHECK( vm, vaddr, align ) (__extension__({                                 \
     192             :     int _err = 0;                                                                                           \
     193             :     void * ret = FD_VM_MEM_HADDR_ST_( vm, vaddr, align, 1UL, &_err );                                       \
     194             :     if ( FD_UNLIKELY( 0 != _err ))                                                                          \
     195             :       return _err;                                                                                          \
     196             :     ret;                                                                                                    \
     197             : }))
     198             : 
     199             : 
     200             : #define FD_VM_MEM_HADDR_LD_FAST( vm, vaddr ) ((void const *)fd_vm_mem_haddr_fast( (vm), (vaddr), (vm)->region_haddr ))
     201             : #define FD_VM_MEM_HADDR_ST_FAST( vm, vaddr ) ((void       *)fd_vm_mem_haddr_fast( (vm), (vaddr), (vm)->region_haddr ))
     202             : 
     203             : /* FD_VM_MEM_HADDR_AND_REGION_IDX_FROM_INPUT_REGION_CHECKED simply converts a vaddr within the input memory region
     204             :    into an haddr. The sets the region_idx and haddr. */
     205             : #define FD_VM_MEM_HADDR_AND_REGION_IDX_FROM_INPUT_REGION_CHECKED( _vm, _offset, _out_region_idx, _out_haddr ) (__extension__({                  \
     206             :   _out_region_idx = fd_vm_get_input_mem_region_idx( _vm, _offset );                                                                             \
     207             :   if( FD_UNLIKELY( _offset>=vm->input_mem_regions[ _out_region_idx ].vaddr_offset+vm->input_mem_regions[ _out_region_idx ].region_sz ) ) {      \
     208             :     FD_VM_ERR_FOR_LOG_EBPF( vm, FD_VM_ERR_EBPF_ACCESS_VIOLATION );                                                                              \
     209             :     return FD_VM_SYSCALL_ERR_SEGFAULT;                                                                                                          \
     210             :   }                                                                                                                                             \
     211             :   _out_haddr      = (uchar*)_vm->input_mem_regions[ _out_region_idx ].haddr + _offset - _vm->input_mem_regions[ _out_region_idx ].vaddr_offset; \
     212             : }))
     213             : 
     214             : /* FD_VM_MEM_SLICE_HADDR_[LD, ST] macros return an arbitrary value if sz == 0. This is because
     215             :    Agave's translate_slice function returns an empty array if the sz == 0.
     216             : 
     217             :    Users of this macro should be aware that they should never access the returned value if sz==0.
     218             : 
     219             :    https://github.com/solana-labs/solana/blob/767d24e5c10123c079e656cdcf9aeb8a5dae17db/programs/bpf_loader/src/syscalls/mod.rs#L560
     220             : 
     221             :    LONG_MAX check: https://github.com/anza-xyz/agave/blob/dc4b9dcbbf859ff48f40d00db824bde063fdafcc/programs/bpf_loader/src/syscalls/mod.rs#L580
     222             :    Technically, the check in Agave is against
     223             :    "pointer-sized signed integer type ... The size of this primitive is
     224             :     how many bytes it takes to reference any location in memory. For
     225             :     example, on a 32 bit target, this is 4 bytes and on a 64 bit target,
     226             :     this is 8 bytes."
     227             :    Realistically, given the amount of memory that a validator consumes,
     228             :    no one is going to be running on a 32 bit target. So, we don't bother
     229             :    with conditionally compiling in an INT_MAX check. We just assume
     230             :    LONG_MAX. */
     231          72 : #define FD_VM_MEM_SLICE_HADDR_LD( vm, vaddr, align, sz ) (__extension__({                                       \
     232          72 :     if ( FD_UNLIKELY( sz > LONG_MAX ) ) {                                                                       \
     233           0 :       FD_VM_ERR_FOR_LOG_SYSCALL( vm, FD_VM_SYSCALL_ERR_INVALID_LENGTH );                                        \
     234           0 :       return FD_VM_SYSCALL_ERR_INVALID_LENGTH;                                                                  \
     235           0 :     }                                                                                                           \
     236          72 :     void const * haddr = 0UL;                                                                                   \
     237          72 :     if ( FD_LIKELY( (ulong)sz > 0UL ) ) {                                                                       \
     238         216 :       haddr = FD_VM_MEM_HADDR_LD( vm, vaddr, align, sz );                                                       \
     239         216 :     }                                                                                                           \
     240          72 :     haddr;                                                                                                      \
     241          72 : }))
     242             : 
     243             : 
     244             : /* This is the same as the above function but passes in a size of 1 to support
     245             :    loads with no size bounding support. */
     246             : #define FD_VM_MEM_SLICE_HADDR_LD_SZ_UNCHECKED( vm, vaddr, align ) (__extension__({                              \
     247             :     if ( FD_UNLIKELY( sz > LONG_MAX ) ) {                                                                       \
     248             :       FD_VM_ERR_FOR_LOG_SYSCALL( vm, FD_VM_SYSCALL_ERR_INVALID_LENGTH );                                        \
     249             :       return FD_VM_SYSCALL_ERR_INVALID_LENGTH;                                                                  \
     250             :     }                                                                                                           \
     251             :     void const * haddr = 0UL;                                                                                   \
     252             :     if ( FD_LIKELY( (ulong)sz > 0UL ) ) {                                                                       \
     253             :       haddr = FD_VM_MEM_HADDR_LD( vm, vaddr, align, 1UL );                                                      \
     254             :     }                                                                                                           \
     255             :     haddr;                                                                                                      \
     256             : }))
     257             : 
     258          93 : #define FD_VM_MEM_SLICE_HADDR_ST( vm, vaddr, align, sz ) (__extension__({                                       \
     259          93 :     if ( FD_UNLIKELY( sz > LONG_MAX ) ) {                                                                       \
     260           0 :       FD_VM_ERR_FOR_LOG_SYSCALL( vm, FD_VM_SYSCALL_ERR_INVALID_LENGTH );                                        \
     261           0 :       return FD_VM_SYSCALL_ERR_INVALID_LENGTH;                                                                  \
     262           0 :     }                                                                                                           \
     263          93 :     void * haddr = 0UL;                                                                                         \
     264          93 :     if ( FD_LIKELY( (ulong)sz > 0UL ) ) {                                                                       \
     265          93 :       haddr = FD_VM_MEM_HADDR_ST( vm, vaddr, align, sz );                                                       \
     266          57 :     }                                                                                                           \
     267          93 :     haddr;                                                                                                      \
     268          57 : }))
     269             : 
     270             : /* FIXME: use overlap logic from runtime? */
     271          48 : #define FD_VM_MEM_CHECK_NON_OVERLAPPING( vm, addr0, sz0, addr1, sz1 ) do {                                      \
     272          48 :   if( FD_UNLIKELY(( ((addr0> addr1) && (fd_ulong_sat_sub(addr0, addr1) < sz1)) ) ||                             \
     273          48 :                   ( ((addr1>=addr0) && (fd_ulong_sat_sub(addr1, addr0) < sz0)) ) )) {                           \
     274          18 :     FD_VM_ERR_FOR_LOG_SYSCALL( vm, FD_VM_SYSCALL_ERR_COPY_OVERLAPPING );                                        \
     275          18 :     return FD_VM_SYSCALL_ERR_COPY_OVERLAPPING;                                                                  \
     276          18 :   }                                                                                                             \
     277          48 : } while(0)
     278             : 
     279             : /* Mimics Agave's `translate_mut!` macro by taking in a variable number
     280             :    of (vaddr, align, sz) entries and translates each of them, failing
     281             :    if any one of the translations fail, or if any of the vaddrs have
     282             :    overlapping haddr regions. The caller is responsible for creating
     283             :    each `fd_vm_haddr_query_t` object containing information about the
     284             :    vaddr, align, sz, and whether it is a slice. Takes in any number
     285             :    of queries, provided into the input as an array of pointers to each
     286             :    query. The translated haddr is written into the `haddr` field
     287             :    of each of the `fd_vm_haddr_query_t` objects on success.
     288             : 
     289             :    https://github.com/anza-xyz/agave/blob/v2.3.1/programs/bpf_loader/src/syscalls/mod.rs#L701-L738 */
     290         126 : #define FD_VM_TRANSLATE_MUT( _vm, _queries ) do { \
     291         126 :   ulong _n = sizeof(_queries)/sizeof(fd_vm_haddr_query_t *); \
     292         213 :   for( ulong i=0UL; i<(_n); i++ ) { \
     293         126 :     fd_vm_haddr_query_t * query = _queries[i]; \
     294         126 :     if( query->is_slice ) { \
     295         150 :       query->haddr = FD_VM_MEM_SLICE_HADDR_ST( _vm, query->vaddr, query->align, query->sz ); \
     296         150 :     } else { \
     297          33 :       query->haddr = FD_VM_MEM_HADDR_ST( _vm, query->vaddr, query->align, query->sz ); \
     298          30 :     } \
     299         126 :     for( ulong j=0UL; j<i; j++ ) { \
     300           0 :       fd_vm_haddr_query_t * other_query = queries[j]; \
     301           0 :       FD_VM_MEM_CHECK_NON_OVERLAPPING( _vm, (ulong)query->haddr, query->sz, (ulong)other_query->haddr, other_query->sz ); \
     302           0 :     } \
     303          87 :   } \
     304         126 : } while(0)
     305             : 
     306             : #endif /* HEADER_fd_src_flamenco_vm_syscall_fd_vm_syscall_macros_h */

Generated by: LCOV version 1.14