LCOV - code coverage report
Current view: top level - disco/keyguard - fd_keyload.c (source / functions) Hit Total Coverage
Test: cov.lcov Lines: 70 143 49.0 %
Date: 2026-11-12 04:25:03 Functions: 5 10 50.0 %

          Line data    Source code
       1             : #define _GNU_SOURCE
       2             : #include "fd_keyload.h"
       3             : #include "../../ballet/ed25519/fd_ed25519.h"
       4             : 
       5             : #include <errno.h>
       6             : #include <string.h>
       7             : #include <fcntl.h>
       8             : #include <unistd.h>
       9             : #include <string.h>
      10             : #include <stdio.h>
      11             : #include <sys/mman.h>
      12             : #include <sys/stat.h>
      13             : 
      14             : static FD_TL uchar junk_public_key[ 32 ];
      15             : static FD_TL int   junk_key_fd = -1;
      16             : 
      17             : void
      18             : fd_keyload_set_failover_junk( uchar const public_key[ 32 ],
      19           0 :                               int         key_fd ) {
      20           0 :   fd_memcpy( junk_public_key, public_key, 32UL );
      21           0 :   junk_key_fd = key_fd;
      22           0 : }
      23             : 
      24             : uchar * FD_FN_SENSITIVE
      25             : fd_keyload_read( int          key_fd,
      26             :                  char const * key_path,
      27          12 :                  uchar *      keypair ) {
      28          12 : #define KEY_PARSE_ERR( ... ) \
      29          12 :   FD_LOG_ERR(( "Error while parsing the validator identity key at path " \
      30         780 :                "`%s` specified by [consensus.{identity|vote_account}_path] in the "     \
      31         780 :                "configuration TOML file. Solana key files are "         \
      32         780 :                "formatted as a 64-element JSON array. " __VA_ARGS__ ))
      33          72 : #define KEY_SZ 64UL
      34             :   /* at least one digit per byte, commas in between each byte, opening and closing brackets */
      35          12 : #define MIN_KEY_FILE_SZ ((ssize_t)(KEY_SZ + KEY_SZ-1UL + 2UL))
      36          24 : #define MAX_KEY_FILE_SZ     1023UL /* Unless it has extraneous whitespace, max is 64*4+1 */
      37             : 
      38             : 
      39          12 :   char * json_key_file = (char *)keypair+KEY_SZ;
      40          12 :   long bytes_read = read( key_fd, keypair+KEY_SZ, MAX_KEY_FILE_SZ );
      41          12 :   if( FD_UNLIKELY( bytes_read==-1  ) ) FD_LOG_ERR(( "reading key file (%s) failed (%i-%s)", key_path, errno, fd_io_strerror( errno ) ));
      42          12 :   if( FD_UNLIKELY( close( key_fd ) ) ) FD_LOG_ERR(( "closing key file (%s) failed (%i-%s)", key_path, errno, fd_io_strerror( errno ) ));
      43             : 
      44          12 :   if( bytes_read<MIN_KEY_FILE_SZ     ) FD_LOG_ERR(( "the specified key file (%s) was too short", key_path ));
      45          12 :   json_key_file[ bytes_read ] = '\0';
      46             : 
      47             : 
      48             :   /* These pointers reveal information about the key, so store them in
      49             :      the protected page temporarily as well. */
      50          12 :   char ** tok = (char **)(keypair+KEY_SZ+1024UL);
      51          12 :   if( FD_UNLIKELY( fd_cstr_tokenize( tok, KEY_SZ, json_key_file, ',' ) != KEY_SZ ) ) KEY_PARSE_ERR( "", key_path );
      52             : 
      53          12 :   if( FD_UNLIKELY( 1!=sscanf( tok[ 0 ], "[ %hhu", &keypair[ 0 ] ) ) )
      54          12 :     KEY_PARSE_ERR( "The file should start with an opening `[` followed by a decimal integer.", key_path );
      55         756 :   for( ulong i=1UL; i<63UL; i++ ) {
      56         744 :     if( FD_UNLIKELY( 1!=sscanf( tok[ i ], "%hhu", &keypair[ i ] ) ) )
      57         744 :       KEY_PARSE_ERR( "Parsing failed near the %luth value.", key_path, i );
      58         744 :   }
      59          12 :   if( FD_UNLIKELY( 1!=sscanf( tok[ 63 ], "%hhu ]", &keypair[ 63 ] ) ) )
      60          12 :     KEY_PARSE_ERR( "Parsing failed near the 63rd value. Perhaps the file is missing a closing `]`", key_path );
      61             : 
      62             : 
      63             :   /* Clear out the buffer just in case it was actually used */
      64          12 :   fd_memzero_explicit( json_key_file, MAX_KEY_FILE_SZ       );
      65          12 :   fd_memzero_explicit( tok,           KEY_SZ*sizeof(char *) );
      66          12 : #undef MAX_KEY_FILE_SZ
      67          12 : #undef MIN_KEY_FILE_SZ
      68          12 : #undef KEY_SZ
      69          12 : #undef KEY_PARSE_ERR
      70             : 
      71          12 :   return keypair;
      72          12 : }
      73             : 
      74             : /* Expects that key[i] is writable for i in [0, 1600). */
      75             : static inline uchar * FD_FN_SENSITIVE
      76             : read_key( char const * key_path,
      77          12 :           uchar      * key       ) {
      78          12 :   int key_fd = open( key_path, O_RDONLY );
      79          12 :   if( FD_UNLIKELY( key_fd==-1 ) ) {
      80           0 :     if( FD_UNLIKELY( errno==ENOENT ) ) {
      81           0 :       FD_LOG_ERR((
      82           0 :           "The [consensus.identity_path] in your configuration expects a "
      83           0 :           "keyfile at `%s` but there is no such file. Either update the "
      84           0 :           "configuration file to point to your validator identity "
      85           0 :           "keypair, or generate a new validator identity key by running "
      86           0 :           "`fdctl keys new %s`", key_path, key_path ));
      87           0 :     } else
      88           0 :       FD_LOG_ERR(( "Opening key file (%s) failed (%i-%s)", key_path,  errno, fd_io_strerror( errno ) ));
      89           0 :   }
      90             : 
      91          12 :   return fd_keyload_read( key_fd, key_path, key );
      92          12 : }
      93             : 
      94             : static void FD_FN_SENSITIVE
      95           0 : read_junk_key( uchar * key ) {
      96           0 :   int key_fd = junk_key_fd;
      97           0 :   junk_key_fd = -1;
      98           0 :   struct stat st;
      99           0 :   if( FD_UNLIKELY( fstat( key_fd, &st ) || !S_ISFIFO( st.st_mode ) ) )
     100           0 :     FD_LOG_ERR(( "the failover junk key requires a startup pipe" ));
     101           0 :   for( ulong off=0UL; off<64UL; ) {
     102           0 :     long n = read( key_fd, key+off, 64UL-off );
     103           0 :     if( FD_UNLIKELY( n==-1L && errno==EINTR ) ) continue;
     104           0 :     if( FD_UNLIKELY( n<=0L ) ) FD_LOG_ERR(( "could not read the failover junk key" ));
     105           0 :     off += (ulong)n;
     106           0 :   }
     107           0 :   long n;
     108           0 :   do n = read( key_fd, key+64UL, 1UL ); while( FD_UNLIKELY( n==-1L && errno==EINTR ) );
     109           0 :   if( FD_UNLIKELY( n ) ) FD_LOG_ERR(( "unexpected data after the failover junk key" ));
     110           0 :   if( FD_UNLIKELY( close( key_fd ) ) ) FD_LOG_ERR(( "closing the failover junk key pipe failed (%i-%s)", errno, fd_io_strerror( errno ) ));
     111           0 :   fd_sha512_t * sha = fd_sha512_join( fd_sha512_new( key+FD_SHA512_ALIGN ) );
     112           0 :   FD_TEST( sha );
     113           0 :   fd_ed25519_public_from_private( key+64UL, key, sha );
     114           0 :   if( FD_UNLIKELY( !fd_memeq( key+32UL, key+64UL, 32UL ) ||
     115           0 :                    !fd_memeq( key+32UL, junk_public_key, 32UL ) ) )
     116           0 :     FD_LOG_ERR(( "the failover junk key does not match its public key" ));
     117           0 :   fd_memzero_explicit( key+64UL, 4096UL-64UL );
     118           0 : }
     119             : 
     120             : uchar const * FD_FN_SENSITIVE
     121             : fd_keyload_load( char const * key_path,
     122          12 :                  int          public_key_only ) {
     123          12 :   if( FD_UNLIKELY( !key_path || !key_path[0] ) ) {
     124           0 :     FD_LOG_ERR(( "Missing key_path" ));
     125           0 :   }
     126             : 
     127             :   /* Load the signing key. Since this is key material, we load it into
     128             :      its own page that's non-dumpable, readonly, and protected by guard
     129             :      pages. */
     130          12 :   uchar * key_page = fd_keyload_alloc_protected_pages( 1UL, 2UL );
     131             : 
     132          12 :   if( FD_UNLIKELY( !strcmp( key_path, FD_KEYLOAD_FAILOVER_JUNK ) ) ) {
     133           0 :     if( FD_UNLIKELY( fd_memeq( junk_public_key, (uchar[ 32 ]){0}, 32UL ) ) ) FD_LOG_ERR(( "missing failover junk key" ));
     134           0 :     if( public_key_only ) fd_memcpy( key_page+32UL, junk_public_key, 32UL );
     135           0 :     else                  read_junk_key( key_page );
     136          12 :   } else {
     137          12 :     read_key( key_path, key_page );
     138          12 :   }
     139             : 
     140          12 :   if( public_key_only ) fd_memzero_explicit( key_page, 32UL );
     141             : 
     142             :   /* Make the key page read-only now that the key has been loaded. */
     143          12 :   if( FD_UNLIKELY( mprotect( key_page, 4096UL, PROT_READ ) ) )
     144           0 :     FD_LOG_ERR(( "mprotect failed (%i-%s)", errno, fd_io_strerror( errno ) ));
     145             : 
     146          12 :   if( public_key_only ) return key_page+32UL;
     147           6 :   else                  return key_page;
     148          12 : }
     149             : 
     150             : uchar const * FD_FN_SENSITIVE
     151           0 : fd_keyload_generate( void ) {
     152           0 :   uchar * key = fd_keyload_alloc_protected_pages( 1UL, 2UL );
     153           0 :   if( FD_UNLIKELY( !fd_rng_secure( key, 32UL ) ) ) FD_LOG_ERR(( "could not generate a keypair" ));
     154           0 :   fd_sha512_t * sha = fd_sha512_join( fd_sha512_new( key+FD_SHA512_ALIGN ) );
     155           0 :   FD_TEST( sha );
     156           0 :   fd_ed25519_public_from_private( key+32UL, key, sha );
     157           0 :   fd_memzero_explicit( key+FD_SHA512_ALIGN, sizeof(fd_sha512_t) );
     158           0 :   return fd_keyload_mprotect_ro( key, 0 );
     159           0 : }
     160             : 
     161             : uchar * FD_FN_SENSITIVE
     162             : fd_keyload_mprotect_wr( uchar const * key,
     163           0 :                         int           public_key_only ) {
     164           0 :   uchar * key_mut = (uchar *)key;
     165           0 :   void * key_page = public_key_only ? key_mut-32UL : key_mut;
     166           0 :   if( FD_UNLIKELY( mprotect( key_page, 4096UL, PROT_READ | PROT_WRITE ) ) )
     167           0 :     FD_LOG_ERR(( "mprotect failed (%i-%s)", errno, fd_io_strerror( errno ) ));
     168           0 :   return key_mut;
     169           0 : }
     170             : 
     171             : uchar const * FD_FN_SENSITIVE
     172             : fd_keyload_mprotect_ro( uchar * key,
     173           0 :                         int     public_key_only ) {
     174           0 :   void * key_page = public_key_only ? key-32UL : key;
     175           0 :   if( FD_UNLIKELY( mprotect( key_page, 4096UL, PROT_READ ) ) )
     176           0 :     FD_LOG_ERR(( "mprotect failed (%i-%s)", errno, fd_io_strerror( errno ) ));
     177           0 :   return key;
     178           0 : }
     179             : 
     180             : void FD_FN_SENSITIVE
     181             : fd_keyload_unload( uchar const * key,
     182           6 :                    int           public_key_only ) {
     183           6 :   void * key_page = public_key_only ? (uchar *)key-32UL : (uchar *)key;
     184           6 :   ulong sz = (2UL*1UL+2UL)*4096UL;
     185             : 
     186           6 :   if( FD_UNLIKELY( mprotect( key_page, 4096UL, PROT_READ | PROT_WRITE ) ) )
     187           0 :     FD_LOG_ERR(( "mprotect failed (%i-%s)", errno, fd_io_strerror( errno ) ));
     188           6 :   fd_memzero_explicit( key_page, 4096UL );
     189             : 
     190           6 :   if( FD_UNLIKELY( -1==munmap( (uchar*)key_page - 2UL*4096UL, sz ) ) )
     191           0 :     FD_LOG_ERR(( "munmap failed (%i-%s)", errno, fd_io_strerror( errno ) ));
     192           6 : }
     193             : 
     194             : void * FD_FN_SENSITIVE
     195             : fd_keyload_alloc_protected_pages( ulong page_cnt,
     196          18 :                                   ulong guard_page_cnt ) {
     197          36 : #define PAGE_SZ (4096UL)
     198          18 :   void * pages = mmap( NULL, (2UL*guard_page_cnt+page_cnt)*PAGE_SZ, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0UL );
     199          18 :   if( FD_UNLIKELY( pages==MAP_FAILED ) ) FD_LOG_ERR(( "mmap failed (%i-%s)", errno, fd_io_strerror( errno ) ));
     200             : 
     201          18 :   uchar * middle_pages = (uchar *)( (ulong)pages + guard_page_cnt*PAGE_SZ );
     202             : 
     203             :   /* Make the guard pages untouchable */
     204          18 :   if( FD_UNLIKELY( mprotect( pages, guard_page_cnt*PAGE_SZ, PROT_NONE ) ) )
     205           0 :     FD_LOG_ERR(( "mprotect failed (%i-%s)", errno, fd_io_strerror( errno ) ));
     206             : 
     207          18 :   if( FD_UNLIKELY( mprotect( middle_pages+page_cnt*PAGE_SZ, guard_page_cnt*PAGE_SZ, PROT_NONE ) ) )
     208           0 :     FD_LOG_ERR(( "mprotect failed (%i-%s)", errno, fd_io_strerror( errno ) ));
     209             : 
     210             :   /* Lock the key page so that it doesn't page to disk */
     211          18 :   if( FD_UNLIKELY( mlock( middle_pages, page_cnt*PAGE_SZ ) ) )
     212           0 :     FD_LOG_ERR(( "mlock failed (%i-%s)", errno, fd_io_strerror( errno ) ));
     213             : 
     214             :   /* Prevent the key page from showing up in core dumps. It shouldn't be
     215             :      possible to fork this process typically, but we also prevent any
     216             :      forked child from having this page. */
     217             :   /* `madvise` supports only a single `advice` per call, so we cannot combine via bitwise or. */
     218          18 :   if( FD_UNLIKELY( madvise( middle_pages, page_cnt*PAGE_SZ, MADV_WIPEONFORK ) ) )
     219           0 :     FD_LOG_ERR(( "madvise failed (%i-%s)", errno, fd_io_strerror( errno ) ));
     220          18 :   if( FD_UNLIKELY( madvise( middle_pages, page_cnt*PAGE_SZ, MADV_DONTDUMP ) ) )
     221           0 :     FD_LOG_ERR(( "madvise failed (%i-%s)", errno, fd_io_strerror( errno ) ));
     222             : 
     223          18 :   return middle_pages;
     224          18 : #undef PAGE_SZ
     225          18 : }

Generated by: LCOV version 1.14