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

          Line data    Source code
       1             : #include "fd_keyguard.h"
       2             : #include "../../ballet/shred/fd_shred.h"
       3             : #include "../../ballet/txn/fd_compact_u16.h"
       4             : #include "../../flamenco/gossip/fd_gossip_value.h"
       5             : #include "../../discof/repair/fd_repair.h"
       6             : 
       7             : /* fd_keyguard_match fingerprints signing requests and checks them for
       8             :    ambiguity.
       9             : 
      10             :    Supported message types are as follows:
      11             : 
      12             :    - Legacy transaction messages
      13             :    - Version 0 transaction messages
      14             :    - Legacy shred signed payloads
      15             :    - Merkle shred roots
      16             :    - TLS CertificateVerify challenges
      17             :    - Gossip message signed payloads (CrdsData)
      18             : 
      19             :    ### Fake Signing Attacks
      20             : 
      21             :    The main goal of fd_keyguard_match is to defeat "fake signing"
      22             :    attacks.  These are attacks in which the keyguard signs a request for
      23             :    which the client is not authorized.  Such attacks use a combination
      24             :    of vulnerabilities:  Key reuse, and type confusion.
      25             : 
      26             :    Key reuse is particularly prevalent with the validator identity key,
      27             :    the hot Ed25519 key that a validator uses in almost all protocols
      28             :    that it actively participates in.
      29             : 
      30             :    Type confusion occurs when the message payload being signed can be
      31             :    interpreted as multiple different message types.  Usually, this is
      32             :    categorically prevented by using "signing domains".
      33             : 
      34             :    Such attacks are particularly dangerous to validators because their
      35             :    validator identity key holds an amount of native tokens to
      36             :    participate in Tower BFT voting.  In the worst case, an attacker
      37             :    could trick a validator into signing an innocuous message (e.g. a
      38             :    gossip message) that can also be interpreted as a transaction
      39             :    withdrawing these tokens.
      40             : 
      41             :    ### Code Verification
      42             : 
      43             :    The safety of this module can be verified using a number of CBMC
      44             :    proofs composed via deductive reasoning.
      45             : 
      46             :    - fd_txn_minsz_proof verifies the constant FD_TXN_MIN_SERIALIZED_SZ.
      47             :    - fd_txn_ambiguity_gossip_proof verifies that gossip messages cannot
      48             :      be parsed as transactions.
      49             :    - fd_keyguard_match_txn_harness verifies that the txn fingerprinting
      50             :      logic is free of false negatives.
      51             :    - fd_keyguard_ambiguity_proof verifies that any input up to 2048 byte
      52             :      size are unambiguous, i.e. either detected by one or none of the
      53             :      fingerprinting functions.
      54             : 
      55             :    Under the hood, CBMC executes the keyguard logic with all possible
      56             :    inputs (>=2^16384 unique inputs) via symbolic execution.  The CBMC
      57             :    machine model also verifies that the code is free of common
      58             :    vulnerability classes (memory unsoundness, undefined behavior, …).
      59             : 
      60             :    As a result, we know with a high degree of certainty that type
      61             :    detection logic is free of false negatives.  For example, when
      62             :    fd_keyguard_match sees a transaction, it will always reliably detect
      63             :    it as one.  (fd_keyguard_match might also wrongly fingerprint
      64             :    arbitrary other inputs as, e.g. transactions.  But this is not a
      65             :    problem, as strict checks follow later on in fd_keyguard_authorize.)
      66             : 
      67             :    ### Deployment Context
      68             : 
      69             :    fd_keyguard_match is exposed to untrusted "signing request" inputs
      70             :    and implements the first line of authorization checks in the
      71             :    keyguard.  It is thus a critical component for securing the identity
      72             :    key.
      73             : 
      74             :    ### Implementation Approach
      75             : 
      76             :    This code looks awful and scary, but is carefully crafted to meet the
      77             :    aforementioned high assurance and formal verification requirements.
      78             : 
      79             :    Although parsers for the supported message types are available
      80             :    elsewhere in the codebase, they were not used here due to their time
      81             :    complexity exceeding the capabilities of CBMC.  The time complexity
      82             :    of all parsers in this compile unit is O(1), which allowed for
      83             :    complete CBMC coverage.
      84             : 
      85             :    TLDR:  The following code implements the least possible logic
      86             :           required to reliably detect types of identity key signing
      87             :           payloads without false negatives. */
      88             : 
      89             : FD_FN_PURE static int
      90             : fd_keyguard_payload_matches_txn_msg( uchar const * data,
      91             :                                      ulong         sz,
      92           0 :                                      int           sign_type ) {
      93             : 
      94           0 :   uchar const * end = data + sz;
      95             : 
      96           0 :   if( sign_type != FD_KEYGUARD_SIGN_TYPE_ED25519 ) return 0;
      97             : 
      98             :   /* txn_msg_min_sz is the smallest valid size of a transaction msg. A
      99             :      transaction is the concatenation of (signature count, signatures,
     100             :      msg).  The smallest size of a txn is FD_TXN_MIN_SERIALIZED_SZ
     101             :      (formally proven with CBMC in fd_txn_minsz_proof.c).  We know the
     102             :      smallest sizes of "signature count" and "signatures", thus we can
     103             :      derive the smallest size of "msg". */
     104             : 
     105           0 :   ulong const txn_msg_min_sz =
     106           0 :       FD_TXN_MIN_SERIALIZED_SZ
     107           0 :     -  1UL   /* min sz of signature count (compact_u16 encoding) */
     108           0 :     - 64UL;  /* min sz of signature list (array of Ed25519 sigs) */
     109           0 :   if( sz<txn_msg_min_sz ) return 0;
     110             : 
     111             :   /* Message type check.
     112             : 
     113             :      Bit patterns of first bytes are as follows
     114             : 
     115             :      - 0aaaaaaa bbbbbbbb cccccccc           (Legacy txns)
     116             :      - 10000000 aaaaaaaa bbbbbbbb cccccccc  (v0     txns)
     117             : 
     118             :      Where 'a' are the bits that make up the 'required signature count'
     119             :        ... 'b'         ....                  'readonly signed count'
     120             :        ... 'c'         ....                  'readonly unsigned count' */
     121             : 
     122           0 :   uchar const * cursor    = data;
     123           0 :   uint          header_b0 = *cursor;
     124           0 :   cursor++;
     125           0 :   uint          sig_cnt;  /* sig count (ignoring compact_u16 encoding) */
     126           0 :   if( header_b0 & 0x80UL ) {
     127             :     /* Versioned message, only v0 recognized so far */
     128           0 :     if( (header_b0&0x7F)!=FD_TXN_V0 ) return 0;
     129           0 :     sig_cnt = *cursor;
     130           0 :     cursor++;
     131           0 :   } else {
     132             :     /* Legacy message */
     133           0 :     sig_cnt = header_b0;
     134           0 :   }
     135             : 
     136             :   /* There must be at least one signature. */
     137           0 :   if( sig_cnt==0U ) return 0;
     138             : 
     139             :   /* Check if signatures exceed txn size limit */
     140           0 :   ulong sig_sz;
     141           0 :   if( __builtin_umull_overflow( sig_cnt, 64UL, &sig_sz ) ) return 0;
     142           0 :   if( sig_sz > (FD_TXN_MTU-txn_msg_min_sz) ) return 0;
     143             : 
     144             :   /* Skip other fields */
     145             :   //uint ro_signed_cnt   = *cursor;
     146           0 :   cursor++;
     147             :   //uint ro_unsigned_cnt = *cursor;
     148           0 :   cursor++;
     149             : 
     150           0 :   if( cursor + 3 > end ) return 0;
     151           0 :   ulong addr_cnt_sz = fd_cu16_dec_sz( cursor, 3UL );
     152           0 :   if( !addr_cnt_sz ) return 0;
     153           0 :   ulong addr_cnt    = fd_cu16_dec_fixed( cursor, addr_cnt_sz );
     154           0 :   cursor += addr_cnt_sz;
     155             : 
     156           0 :   if( sig_cnt>addr_cnt ) return 0;
     157             : 
     158           0 :   return 1;
     159           0 : }
     160             : 
     161             : FD_FN_PURE static int
     162             : fd_keyguard_payload_matches_ping_msg( uchar const * data,
     163             :                                       ulong         sz,
     164           0 :                                       int           sign_type ) {
     165           0 :   return sign_type==FD_KEYGUARD_SIGN_TYPE_ED25519 &&
     166           0 :          sz==32UL &&
     167           0 :          (memcmp( data, "SOLANA_PING_PONG", 16UL ) == 0);
     168           0 : }
     169             : 
     170             : FD_FN_PURE static int
     171             : fd_keyguard_payload_matches_pong_msg( uchar const * data,
     172             :                                       ulong         sz,
     173           0 :                                       int           sign_type ) {
     174           0 :   return sign_type==FD_KEYGUARD_SIGN_TYPE_SHA256_ED25519 &&
     175           0 :          sz==48UL &&
     176           0 :          (memcmp( data, "SOLANA_PING_PONG", 16UL ) == 0);
     177           0 : }
     178             : 
     179             : FD_FN_PURE static int
     180             : fd_keyguard_payload_matches_prune_data( uchar const * data,
     181             :                                         ulong         sz,
     182           0 :                                         int           sign_type ) {
     183           0 :   if( sign_type != FD_KEYGUARD_SIGN_TYPE_ED25519 ) return 0;
     184             : 
     185           0 :   ulong const static_sz = 106UL;
     186           0 :   if( sz < static_sz ) return 0;
     187             : 
     188           0 :   if( FD_LOAD( ulong, data )!=18UL ) return 0;
     189           0 :   if(  memcmp( data+8UL, "\xffSOLANA_PRUNE_DATA", 18UL ) ) return 0;
     190             : 
     191           0 :   ulong prune_cnt = FD_LOAD( ulong, data+58UL );
     192           0 :   ulong expected_sz;
     193           0 :   if( __builtin_umull_overflow( prune_cnt,   32UL,      &expected_sz ) ) return 0;
     194           0 :   if( __builtin_uaddl_overflow( expected_sz, static_sz, &expected_sz ) ) return 0;
     195           0 :   if( sz != expected_sz ) return 0;
     196             : 
     197           0 :   return 1;
     198           0 : }
     199             : 
     200             : FD_FN_PURE static int
     201             : fd_keyguard_payload_matches_gossip( uchar const * data,
     202             :                                     ulong         sz,
     203           0 :                                     int           sign_type ) {
     204             : 
     205             :   /* All gossip messages except pings use raw signing */
     206           0 :   if( sign_type != FD_KEYGUARD_SIGN_TYPE_ED25519 ) return 0;
     207             : 
     208             :   /* Every gossip message contains a 4 byte enum variant tag (at the
     209             :      beginning of the message) and a 32 byte public key (at an arbitrary
     210             :      location). */
     211           0 :   if( sz<36UL ) return 0;
     212             : 
     213           0 :   uint tag = FD_LOAD( uint, data );
     214             : 
     215           0 :   return tag<FD_GOSSIP_VALUE_CNT;
     216           0 : }
     217             : 
     218             : FD_FN_PURE static int
     219             : fd_keyguard_payload_matches_repair( uchar const * data,
     220             :                                     ulong         sz,
     221           0 :                                     int           sign_type ) {
     222             : 
     223             :   /* All repair messages except pings use raw signing */
     224           0 :   if( sign_type != FD_KEYGUARD_SIGN_TYPE_ED25519 ) return 0;
     225             : 
     226             :   /* Every repair message contains a 4 byte enum variant tag (at the
     227             :      beginning of the message) and a 32 byte public key (at an arbitrary
     228             :      location). */
     229           0 :   if( sz<36UL ) return 0;
     230             : 
     231             :   /* Ensure that the kind matches a possible repair request. */
     232           0 :   uint kind = FD_LOAD( uint, data );
     233           0 :   if( (kind==FD_REPAIR_KIND_SHRED)
     234           0 :     | (kind==FD_REPAIR_KIND_HIGHEST_SHRED)
     235           0 :     | (kind==FD_REPAIR_KIND_ORPHAN) )
     236           0 :     return 1;
     237             : 
     238           0 :   return 0;
     239           0 : }
     240             : 
     241             : FD_FN_PURE int
     242             : fd_keyguard_payload_matches_shred( uchar const * data,
     243             :                                    ulong         sz,
     244           0 :                                    int           sign_type ) {
     245           0 :   (void)data;
     246             : 
     247             :   /* Note: Legacy shreds no longer relevant (drop_legacy_shreds) */
     248             : 
     249             :   /* FIXME: Sign Merkle shreds using SIGN_TYPE_SHA256_ED25519 (!!!) */
     250           0 :   if( sign_type != FD_KEYGUARD_SIGN_TYPE_ED25519 ) return 0;
     251           0 :   if( sz != 32 ) return 0;
     252             : 
     253           0 :   return 1;
     254           0 : }
     255             : 
     256             : FD_FN_PURE int
     257             : fd_keyguard_payload_matches_tls_cv( 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             : 
     263             :   /* TLS CertificateVerify signing payload one of 3 sizes
     264             :      depending on hash function chosen */
     265           0 :   switch( sz ) {
     266           0 :   case 130UL: break;  /* Prefix + 32 byte hash */
     267           0 :   case 146UL: break;  /* Prefix + 48 byte hash */
     268           0 :   case 162UL: break;  /* Prefix + 64 byte hash */
     269           0 :   default:
     270           0 :     return 0;
     271           0 :   }
     272             : 
     273             :   /* Always prefixed with client or server pattern */
     274           0 :   static char const client_prefix[ 98 ] =
     275           0 :     "                                "  /* 32 spaces */
     276           0 :     "                                "  /* 32 spaces */
     277           0 :     "TLS 1.3, client CertificateVerify";
     278             : 
     279           0 :   static char const server_prefix[ 98 ] =
     280           0 :     "                                "  /* 32 spaces */
     281           0 :     "                                "  /* 32 spaces */
     282           0 :     "TLS 1.3, server CertificateVerify";
     283           0 :   int is_client = 0==memcmp( data, client_prefix, 98UL );
     284           0 :   int is_server = 0==memcmp( data, server_prefix, 98UL );
     285           0 :   return (is_client)|(is_server);
     286           0 : }
     287             : 
     288             : FD_FN_PURE int
     289             : fd_keyguard_payload_matches_bundle( uchar const * data,
     290             :                                     ulong         sz,
     291           0 :                                     int           sign_type ) {
     292           0 :   (void)data;
     293             : 
     294           0 :   if( sign_type != FD_KEYGUARD_SIGN_TYPE_PUBKEY_CONCAT_ED25519 ) return 0;
     295           0 :   if( sz!=9UL ) return 0;
     296             : 
     297           0 :   return 1;
     298           0 : }
     299             : 
     300             : FD_FN_PURE int
     301             : fd_keyguard_payload_matches_event( uchar const * data,
     302             :                                    ulong         sz,
     303           0 :                                    int           sign_type ) {
     304           0 :   (void)data;
     305             : 
     306           0 :   if( sign_type != FD_KEYGUARD_SIGN_TYPE_FD_EVENTS_AUTH_CONCAT_ED25519 ) return 0;
     307           0 :   if( sz!=32UL ) return 0;
     308             : 
     309           0 :   return 1;
     310           0 : }
     311             : 
     312             : FD_FN_PURE ulong
     313             : fd_keyguard_payload_match( uchar const * data,
     314             :                            ulong         sz,
     315           0 :                            int           sign_type ) {
     316           0 :   ulong res = 0UL;
     317           0 :   res |= fd_ulong_if( fd_keyguard_payload_matches_txn_msg   ( data, sz, sign_type ), FD_KEYGUARD_PAYLOAD_TXN,    0 );
     318           0 :   res |= fd_ulong_if( fd_keyguard_payload_matches_gossip    ( data, sz, sign_type ), FD_KEYGUARD_PAYLOAD_GOSSIP, 0 );
     319           0 :   res |= fd_ulong_if( fd_keyguard_payload_matches_repair    ( data, sz, sign_type ), FD_KEYGUARD_PAYLOAD_REPAIR, 0 );
     320           0 :   res |= fd_ulong_if( fd_keyguard_payload_matches_prune_data( data, sz, sign_type ), FD_KEYGUARD_PAYLOAD_PRUNE,  0 );
     321           0 :   res |= fd_ulong_if( fd_keyguard_payload_matches_shred     ( data, sz, sign_type ), FD_KEYGUARD_PAYLOAD_SHRED,  0 );
     322           0 :   res |= fd_ulong_if( fd_keyguard_payload_matches_tls_cv    ( data, sz, sign_type ), FD_KEYGUARD_PAYLOAD_TLS_CV, 0 );
     323           0 :   res |= fd_ulong_if( fd_keyguard_payload_matches_ping_msg  ( data, sz, sign_type ), FD_KEYGUARD_PAYLOAD_PING,   0 );
     324           0 :   res |= fd_ulong_if( fd_keyguard_payload_matches_pong_msg  ( data, sz, sign_type ), FD_KEYGUARD_PAYLOAD_PONG,   0 );
     325           0 :   res |= fd_ulong_if( fd_keyguard_payload_matches_bundle    ( data, sz, sign_type ), FD_KEYGUARD_PAYLOAD_BUNDLE, 0 );
     326           0 :   res |= fd_ulong_if( fd_keyguard_payload_matches_event     ( data, sz, sign_type ), FD_KEYGUARD_PAYLOAD_EVENT,  0 );
     327           0 :   return res;
     328           0 : }

Generated by: LCOV version 1.14