LCOV - code coverage report
Current view: top level - disco/keyguard - fd_keyguard_authorize.c (source / functions) Hit Total Coverage
Test: cov.lcov Lines: 0 243 0.0 %
Date: 2026-03-19 18:19:27 Functions: 0 9 0.0 %

          Line data    Source code
       1             : #include "fd_keyguard.h"
       2             : #include "fd_keyguard_client.h"
       3             : #include "../bundle/fd_bundle_crank_constants.h"
       4             : #include "../../flamenco/runtime/fd_system_ids.h"
       5             : #include "../../flamenco/gossip/fd_gossip_value.h"
       6             : #include "../../ballet/txn/fd_compact_u16.h"
       7             : #include "../../waltz/tls/fd_tls.h"
       8             : /* manually include just fd_features_generated.h so we can get
       9             :    FD_FEATURE_SET_ID without anything else that we don't need. */
      10             : #define HEADER_fd_src_flamenco_features_fd_features_h
      11             : #include "../../flamenco/features/fd_features_generated.h"
      12             : #undef HEADER_fd_src_flamenco_features_fd_features_h
      13             : 
      14             : struct fd_keyguard_sign_req {
      15             :   fd_keyguard_authority_t * authority;
      16             : };
      17             : 
      18             : typedef struct fd_keyguard_sign_req fd_keyguard_sign_req_t;
      19             : 
      20             : static int
      21             : fd_keyguard_authorize_vote_txn( fd_keyguard_authority_t const * authority,
      22             :                                 uchar const *                   data,
      23             :                                 ulong                           sz,
      24           0 :                                 int                             sign_type ) {
      25           0 :   if( sign_type != FD_KEYGUARD_SIGN_TYPE_ED25519 ) return 0;
      26           0 :   if( sz > FD_TXN_MTU ) return 0;
      27             :   /* Each vote transaction may have 1 or 2 signers.  The first byte in
      28             :      the transaction message is the number of signers. */
      29           0 :   ulong off = 0UL;
      30           0 :   uchar signer_cnt = data[off];
      31           0 :   if( signer_cnt!=1 && signer_cnt!=2 ) return 0;
      32           0 :   if( signer_cnt==1 && sz<=140 ) return 0;
      33           0 :   if( signer_cnt==2 && sz<=172 ) return 0;
      34             :   /* The authority's public key will be the first listed account in the
      35             :      transaction message. */
      36             : 
      37             :   /* r/o signers = 1 when there are 2 signers and 1 otherwise. */
      38           0 :   off++;
      39           0 :   if( data[off]!=signer_cnt-1 ) return 0;
      40             : 
      41             :   /* There will always be 1 r/o unsigned account. */
      42           0 :   off++;
      43           0 :   if( data[off]!=1 ) return 0;
      44             : 
      45             :   /* The only accounts should be the 1 or 2 signers, the vote account,
      46             :      and the vote program.  The number of accounts is represented as a
      47             :      compact u16. */
      48           0 :   off++;
      49           0 :   ulong bytes = fd_cu16_dec_sz( data+off, 3UL );
      50           0 :   if( bytes!=1UL ) return 0;
      51           0 :   ulong acc_cnt = 2+signer_cnt;
      52           0 :   if( data[off]!=acc_cnt ) return 0;
      53             : 
      54             :   /* The first account should always be the authority's public key. */
      55           0 :   off++;
      56           0 :   ulong acct_off = off;
      57           0 :   if( memcmp( authority->identity_pubkey, data + acct_off, 32 ) ) return 0;
      58             : 
      59             :   /* Each transaction account key is listed out and is followed by a 32
      60             :      byte blockhash.  The instruction count is after this. */
      61           0 :   off += (acc_cnt+1) * 32;
      62           0 :   bytes = fd_cu16_dec_sz( data+off, 3UL );
      63           0 :   uchar instr_cnt = data[ off ];
      64           0 :   if( bytes!=1UL ) return 0;
      65           0 :   if( instr_cnt!=1 ) return 0;
      66             : 
      67             :   /* The program id will be the first byte of the instruction payload
      68             :      and should be the vote program. */
      69           0 :   off++;
      70           0 :   uchar program_id = data[ off ];
      71           0 :   if( program_id != acc_cnt-1 ) return 0;
      72           0 :   ulong program_acct_off = 4UL + (program_id * 32UL);
      73           0 :   if( memcmp( &fd_solana_vote_program_id, data+program_acct_off, 32 ) ) return 0;
      74             : 
      75           0 :   off++;
      76           0 :   bytes = fd_cu16_dec_sz( data+off, 3UL );
      77           0 :   if( bytes!=1UL ) return 0;
      78             : 
      79             :   /* Vote account count will always be 2.  One byte is used to list the
      80             :      account count for the transaction and 1 byte for each account. */
      81           0 :   if( data[ off ]!=2 ) return 0;
      82           0 :   off += 3UL;
      83             : 
      84             :   /* Move the cursor forward by the instruction data size.  The first
      85             :      byte of the instruction data will be the discriminant.  Only allow
      86             :      tower sync vote instructions (14). */
      87           0 :   bytes = fd_cu16_dec_sz( data+off, 3UL );
      88           0 :   off += bytes;
      89           0 :   if( data[off]!=14 ) return 0;
      90             : 
      91           0 :   return 1;
      92           0 : }
      93             : 
      94             : static int
      95             : fd_keyguard_authorize_gossip( fd_keyguard_authority_t const * authority,
      96             :                               uchar const *                   data,
      97             :                               ulong                           sz,
      98           0 :                               int                             sign_type ) {
      99           0 :   if( sign_type != FD_KEYGUARD_SIGN_TYPE_ED25519 ) return 0;
     100             : 
     101             :   /* Every gossip message contains a 4 byte enum variant tag (at the
     102             :      beginning of the message) and a 32 byte public key (at an arbitrary
     103             :      location). */
     104           0 :   if( sz<36UL        ) return 0;
     105           0 :   if( sz>1188UL-64UL ) return 0;
     106             : 
     107           0 :   uint tag = FD_LOAD( uint, data );
     108           0 :   ulong origin_off = ULONG_MAX;
     109           0 :   switch( tag ) {
     110           0 :     case FD_GOSSIP_VALUE_VOTE:
     111           0 :       origin_off = 1UL;
     112           0 :       if( sz<4UL+1UL+32UL+FD_TXN_MIN_SERIALIZED_SZ+8UL ) return 0;
     113           0 :       ulong sig_cnt = data[ 4UL+1UL+32UL ];
     114           0 :       if( (sig_cnt==0UL) | (sig_cnt>2UL) ) return 0;
     115           0 :       ulong vote_off = 4UL+1UL+32UL+1UL+64UL*sig_cnt;
     116           0 :       if( !fd_keyguard_authorize_vote_txn( authority, data+vote_off, sz-(vote_off+8UL), FD_KEYGUARD_SIGN_TYPE_ED25519 ) )
     117           0 :         return 0;
     118           0 :       break;
     119           0 :     case FD_GOSSIP_VALUE_CONTACT_INFO:
     120           0 :       origin_off = 0UL;
     121             :       /* Contact info is pretty tough to parse.  The best we can do is
     122             :          check the feature set and client ID.
     123             :          min sz
     124             :           4B      tag
     125             :          32B      origin
     126             :           1B-10B  wallclock
     127             :           8B      outset
     128             :           2B      shred version
     129             :           1B-3B   major version
     130             :           1B-3B   minor version
     131             :           1B-3B   patch version
     132             :           4B      commit
     133             :           4B      feature set
     134             :           1B-3B   client ID
     135             :           1B-3B   unique addr cnt
     136             :           ???     IP addresses
     137             :           1B-3B   socket cnt
     138             :           ???     ports
     139             :           1B-3B   extension len
     140             :           ...
     141             : 
     142             :         Total: 62B+
     143             :          */
     144           0 :       if( sz<62UL     ) return 0;
     145           0 :       ulong off = 4UL+32UL;
     146           0 :       for( ulong i=0UL; i<10UL; i++ ) if( !(data[ off++ ]&0x80) ) break;
     147           0 :       off += 8UL+2UL;
     148             :       /* off<56, so we're still safe here */
     149           0 :       if( sz<off+15UL ) return 0;
     150           0 :       for( ulong i=0UL; i<3UL;  i++ ) if( !(data[ off++ ]&0x80) ) break;
     151           0 :       for( ulong i=0UL; i<3UL;  i++ ) if( !(data[ off++ ]&0x80) ) break;
     152           0 :       for( ulong i=0UL; i<3UL;  i++ ) if( !(data[ off++ ]&0x80) ) break;
     153           0 :       if( sz<off+12UL ) return 0;
     154           0 :       uint  commit      = FD_LOAD( uint, data+off ); off += 4UL;
     155           0 :       uint  feature_set = FD_LOAD( uint, data+off ); off += 4UL;
     156           0 :       uchar client_id   = data[ off ];
     157           0 :       (void)commit; /* Checking commit introduces a circular dependency between disco and app :'( */
     158           0 :       if( feature_set!=FD_FEATURE_SET_ID ) return 0;
     159           0 :       if( client_id  !=5                 ) return 0; /* FD_GOSSIP_CONTACT_INFO_CLIENT_FIREDANCER */
     160             : 
     161           0 :       break;
     162           0 :     case FD_GOSSIP_VALUE_DUPLICATE_SHRED:
     163           0 :       origin_off = 2UL;
     164           0 :       if( sz< 4UL+65UL           ) return 0;
     165           0 :       ulong chunk_len = FD_LOAD( ulong, data+4UL+57UL );
     166           0 :       if( sz!=4UL+65UL+chunk_len ) return 0;
     167           0 :       break;
     168             : 
     169             :     /* We don't sign these yet. */
     170           0 :     case FD_GOSSIP_VALUE_NODE_INSTANCE:   /* origin_off = 0UL; break; */ return 0;
     171           0 :     case FD_GOSSIP_VALUE_SNAPSHOT_HASHES: /* origin_off = 0UL; break; */ return 0;
     172             : 
     173             :     /* We refuse to serialize these */
     174           0 :     case FD_GOSSIP_VALUE_LEGACY_CONTACT_INFO:
     175           0 :     case FD_GOSSIP_VALUE_LOWEST_SLOT:
     176           0 :     case FD_GOSSIP_VALUE_LEGACY_SNAPSHOT_HASHES:
     177           0 :     case FD_GOSSIP_VALUE_ACCOUNT_HASHES:
     178           0 :     case FD_GOSSIP_VALUE_EPOCH_SLOTS:
     179           0 :     case FD_GOSSIP_VALUE_LEGACY_VERSION:
     180           0 :     case FD_GOSSIP_VALUE_VERSION:
     181           0 :     case FD_GOSSIP_VALUE_RESTART_LAST_VOTED_FORK_SLOTS:
     182           0 :     case FD_GOSSIP_VALUE_RESTART_HEAVIEST_FORK:
     183           0 :     default:
     184           0 :                                           return 0;
     185           0 :   }
     186           0 :   if( sz<sizeof(uint)+origin_off+32UL ) return 0;
     187             : 
     188           0 :   return fd_memeq( authority->identity_pubkey, data+sizeof(uint)+origin_off, 32UL );
     189           0 : }
     190             : 
     191             : static int
     192             : fd_keyguard_authorize_bundle_crank_txn( fd_keyguard_authority_t const * authority,
     193             :                                         uchar const *                   data,
     194             :                                         ulong                           sz,
     195           0 :                                         int                             sign_type ) {
     196           0 :   static const uchar disc1[ 8 ] = { FD_BUNDLE_CRANK_DISC_INIT_TIP_DISTR };
     197           0 :   static const uchar disc2[ 8 ] = { FD_BUNDLE_CRANK_DISC_CHANGE_TIP_RCV };
     198           0 :   static const uchar disc3[ 8 ] = { FD_BUNDLE_CRANK_DISC_CHANGE_BLK_BLD };
     199             : 
     200           0 :   if( sign_type != FD_KEYGUARD_SIGN_TYPE_ED25519 ) return 0;
     201             : 
     202           0 :   (void)authority;
     203             :   /* TODO: we can check a lot more bytes */
     204           0 :   switch( sz ) {
     205           0 :     case (FD_BUNDLE_CRANK_2_SZ-65UL):
     206           0 :       return fd_memeq( data+FD_BUNDLE_CRANK_2_IX1_DISC_OFF-65UL, disc2, 8UL ) &&
     207           0 :              fd_memeq( data+FD_BUNDLE_CRANK_2_IX2_DISC_OFF-65UL, disc3, 8UL );
     208           0 :     case (FD_BUNDLE_CRANK_3_SZ-65UL):
     209           0 :       return fd_memeq( data+FD_BUNDLE_CRANK_3_IX1_DISC_OFF-65UL, disc1, 8UL ) &&
     210           0 :              fd_memeq( data+FD_BUNDLE_CRANK_3_IX2_DISC_OFF-65UL, disc2, 8UL ) &&
     211           0 :              fd_memeq( data+FD_BUNDLE_CRANK_3_IX3_DISC_OFF-65UL, disc3, 8UL );
     212           0 :     default:
     213           0 :       return 0;
     214           0 :   }
     215           0 : }
     216             : 
     217             : static int
     218             : fd_keyguard_authorize_ping( fd_keyguard_authority_t const * authority,
     219             :                             uchar const *                   data,
     220             :                             ulong                           sz,
     221           0 :                             int                             sign_type ) {
     222           0 :   (void)authority;
     223           0 :   if( sign_type != FD_KEYGUARD_SIGN_TYPE_ED25519 ) return 0;
     224           0 :   if( sz != 32 ) return 0;
     225           0 :   if( 0!=memcmp( data, "SOLANA_PING_PONG", 16 ) ) return 0;
     226           0 :   return 1;
     227           0 : }
     228             : 
     229             : static int
     230             : fd_keyguard_authorize_pong( fd_keyguard_authority_t const * authority,
     231             :                             uchar const *                   data,
     232             :                             ulong                           sz,
     233           0 :                             int                             sign_type ) {
     234           0 :   (void)authority;
     235           0 :   if( sign_type != FD_KEYGUARD_SIGN_TYPE_SHA256_ED25519 ) return 0;
     236           0 :   if( sz != 48 ) return 0;
     237           0 :   if( 0!=memcmp( data, "SOLANA_PING_PONG", 16 ) ) return 0;
     238           0 :   return 1;
     239           0 : }
     240             : 
     241             : static int
     242             : fd_keyguard_authorize_gossip_prune( fd_keyguard_authority_t const * authority,
     243             :                                     uchar const *                   data,
     244             :                                     ulong                           sz,
     245           0 :                                     int                             sign_type ) {
     246           0 :   if( FD_UNLIKELY( sign_type != FD_KEYGUARD_SIGN_TYPE_ED25519 ) ) return 0;
     247             :   /* Prune messages always start with the prefix followed by the pubkey. */
     248           0 :   if( sz<66UL ) return 0;
     249           0 :   if( FD_LOAD( ulong, data )!=18UL ) return 0;
     250           0 :   if( 0!=memcmp( data+8UL, "\xffSOLANA_PRUNE_DATA",     18 ) ) return 0;
     251           0 :   if( 0!=memcmp( authority->identity_pubkey, data+26UL, 32 ) ) return 0;
     252           0 :   return 1;
     253           0 : }
     254             : 
     255             : static int
     256             : fd_keyguard_authorize_repair( fd_keyguard_authority_t const * authority,
     257             :                               uchar const *                   data,
     258             :                               ulong                           sz,
     259           0 :                               int                             sign_type ) {
     260             : 
     261           0 :   if( sign_type != FD_KEYGUARD_SIGN_TYPE_ED25519 ) return 0;
     262           0 :   if( sz<80 ) return 0;
     263             : 
     264           0 :   uint          discriminant = fd_uint_load_4( data );
     265           0 :   uchar const * sender       = data+4;
     266             : 
     267           0 :   if( discriminant< 8 ) return 0; /* window_index is min ID */
     268           0 :   if( discriminant>11 ) return 0; /* ancestor_hashes is max ID */
     269             : 
     270           0 :   if( 0!=memcmp( authority->identity_pubkey, sender, 32 ) ) return 0;
     271             : 
     272           0 :   return 1;
     273           0 : }
     274             : 
     275             : static int
     276             : fd_keyguard_authorize_tls_cv( fd_keyguard_authority_t const * authority FD_PARAM_UNUSED,
     277             :                               uchar const *                   data,
     278             :                               ulong                           sz,
     279           0 :                               int                             sign_type ) {
     280           0 :   if( FD_UNLIKELY( sign_type != FD_KEYGUARD_SIGN_TYPE_ED25519 ) ) return 0;
     281           0 :   if( FD_UNLIKELY( sz != 130 ) ) return 0;
     282             : 
     283             :   /* validate client prefix against fd_tls */
     284           0 :   return fd_memeq( fd_tls13_cli_sign_prefix, data, sizeof(fd_tls13_cli_sign_prefix) );
     285           0 : }
     286             : 
     287             : int
     288             : fd_keyguard_payload_authorize( fd_keyguard_authority_t const * authority,
     289             :                                uchar const *                   data,
     290             :                                ulong                           sz,
     291             :                                int                             role,
     292           0 :                                int                             sign_type ) {
     293             : 
     294           0 :   if( sz > FD_KEYGUARD_SIGN_REQ_MTU ) {
     295           0 :     FD_LOG_WARNING(( "oversz signing request (role=%d sz=%lu)", role, sz ));
     296           0 :     return 0;
     297           0 :   }
     298             : 
     299             :   /* Identify payload type */
     300             : 
     301           0 :   ulong payload_mask = fd_keyguard_payload_match( data, sz, sign_type );
     302           0 :   int   match_cnt    = fd_ulong_popcnt( payload_mask );
     303           0 :   if( FD_UNLIKELY( payload_mask==0UL ) ) {
     304           0 :     FD_LOG_WARNING(( "unrecognized payload type (role=%#x)", (uint)role ));
     305           0 :   }
     306             : 
     307           0 :   int is_ambiguous = match_cnt != 1;
     308             : 
     309             :  /* We know that gossip, gossip prune, and repair messages are
     310             :     ambiguous, so allow mismatches here. */
     311           0 :   int is_gossip_repair =
     312           0 :     0==( payload_mask &
     313           0 :         (~( FD_KEYGUARD_PAYLOAD_GOSSIP |
     314           0 :             FD_KEYGUARD_PAYLOAD_REPAIR ) ) );
     315             :   /* Also allow ambiguities between shred and gossip ping messages
     316             :      until shred sign type is fixed... */
     317           0 :   int is_shred_ping =
     318           0 :     0==( payload_mask &
     319           0 :         (~( FD_KEYGUARD_PAYLOAD_SHRED |
     320           0 :             FD_KEYGUARD_PAYLOAD_PING  ) ) );
     321             : 
     322           0 :   if( FD_UNLIKELY( is_ambiguous && !is_gossip_repair && !is_shred_ping ) ) {
     323           0 :     FD_LOG_WARNING(( "ambiguous payload type (role=%#x mask=%#lx)", (uint)role, payload_mask ));
     324           0 :   }
     325             : 
     326             :   /* Authorize each role */
     327             : 
     328           0 :   switch( role ) {
     329             : 
     330           0 :   case FD_KEYGUARD_ROLE_TXSEND: {
     331           0 :     int txn_ok = (!!( payload_mask & FD_KEYGUARD_PAYLOAD_TXN )) &&
     332           0 :                  fd_keyguard_authorize_vote_txn( authority, data, sz, sign_type );
     333           0 :     int tls_ok = (!!( payload_mask & FD_KEYGUARD_PAYLOAD_TLS_CV )) &&
     334           0 :                  fd_keyguard_authorize_tls_cv( authority, data, sz, sign_type );
     335           0 :     if( FD_UNLIKELY( !txn_ok && !tls_ok ) ) {
     336           0 :       FD_LOG_WARNING(( "unauthorized payload type for send (mask=%#lx)", payload_mask ));
     337           0 :       return 0;
     338           0 :     }
     339           0 :     return 1;
     340           0 :   }
     341             : 
     342           0 :   case FD_KEYGUARD_ROLE_GOSSIP: {
     343           0 :     int ping_ok   = (!!( payload_mask & FD_KEYGUARD_PAYLOAD_PING )) &&
     344           0 :                     fd_keyguard_authorize_ping( authority, data, sz, sign_type );
     345           0 :     int pong_ok   = (!!( payload_mask & FD_KEYGUARD_PAYLOAD_PONG )) &&
     346           0 :                     fd_keyguard_authorize_pong( authority, data, sz, sign_type );
     347           0 :     int prune_ok  = (!!( payload_mask & FD_KEYGUARD_PAYLOAD_PRUNE )) &&
     348           0 :                     fd_keyguard_authorize_gossip_prune( authority, data, sz, sign_type );
     349           0 :     int gossip_ok = (!!( payload_mask & FD_KEYGUARD_PAYLOAD_GOSSIP )) &&
     350           0 :                     fd_keyguard_authorize_gossip( authority, data, sz, sign_type );
     351           0 :     if( FD_UNLIKELY( !ping_ok && !pong_ok && !prune_ok && !gossip_ok ) ) {
     352           0 :       FD_LOG_WARNING(( "unauthorized payload type for gossip (mask=%#lx)", payload_mask ));
     353           0 :       return 0;
     354           0 :     }
     355           0 :     return 1;
     356           0 :   }
     357             : 
     358           0 :   case FD_KEYGUARD_ROLE_REPAIR: {
     359           0 :     int ping_ok   = (!!( payload_mask & FD_KEYGUARD_PAYLOAD_PING )) &&
     360           0 :                     fd_keyguard_authorize_ping( authority, data, sz, sign_type );
     361           0 :     int pong_ok   = (!!( payload_mask & FD_KEYGUARD_PAYLOAD_PONG )) &&
     362           0 :                     fd_keyguard_authorize_pong( authority, data, sz, sign_type );
     363           0 :     int repair_ok = (!!( payload_mask & FD_KEYGUARD_PAYLOAD_REPAIR )) &&
     364           0 :                     fd_keyguard_authorize_repair( authority, data, sz, sign_type );
     365           0 :     if( FD_UNLIKELY( !ping_ok && !pong_ok && !repair_ok ) ) {
     366           0 :       FD_LOG_WARNING(( "unauthorized payload type for repair (mask=%#lx)", payload_mask ));
     367           0 :       return 0;
     368           0 :     }
     369           0 :     return 1;
     370           0 :   }
     371             : 
     372           0 :   case FD_KEYGUARD_ROLE_LEADER:
     373           0 :     if( FD_UNLIKELY( payload_mask != FD_KEYGUARD_PAYLOAD_SHRED ) ) {
     374           0 :       FD_LOG_WARNING(( "unauthorized payload type for leader (mask=%#lx)", payload_mask ));
     375           0 :       return 0;
     376           0 :     }
     377             :     /* no further restrictions on shred */
     378           0 :     return 1;
     379             : 
     380           0 :   case FD_KEYGUARD_ROLE_BUNDLE:
     381           0 :     if( FD_UNLIKELY( payload_mask != FD_KEYGUARD_PAYLOAD_BUNDLE ) ) {
     382           0 :       FD_LOG_WARNING(( "unauthorized payload type for bundle (mask=%#lx)", payload_mask ));
     383           0 :       return 0;
     384           0 :     }
     385             :     /* no further restrictions on bundle */
     386           0 :     return 1;
     387             : 
     388           0 :   case FD_KEYGUARD_ROLE_EVENT:
     389           0 :     if( FD_UNLIKELY( payload_mask != FD_KEYGUARD_PAYLOAD_EVENT ) ) {
     390           0 :       FD_LOG_WARNING(( "unauthorized payload type for event (mask=%#lx)", payload_mask ));
     391           0 :       return 0;
     392           0 :     }
     393             :     /* no further restrictions on event */
     394           0 :     return 1;
     395             : 
     396           0 :   case FD_KEYGUARD_ROLE_BUNDLE_CRANK:
     397           0 :     if( FD_UNLIKELY( payload_mask != FD_KEYGUARD_PAYLOAD_TXN ) ) {
     398           0 :       FD_LOG_WARNING(( "unauthorized payload type for event (mask=%#lx)", payload_mask ));
     399           0 :       return 0;
     400           0 :     }
     401           0 :     return fd_keyguard_authorize_bundle_crank_txn( authority, data, sz, sign_type );
     402             : 
     403           0 :   default:
     404           0 :     FD_LOG_WARNING(( "unsupported role=%#x", (uint)role ));
     405           0 :     return 0;
     406           0 :   }
     407           0 : }

Generated by: LCOV version 1.14