LCOV - code coverage report
Current view: top level - disco/keyguard - fd_keyload.c (source / functions) Hit Total Coverage
Test: cov.lcov Lines: 67 100 67.0 %
Date: 2026-09-17 04:28:31 Functions: 5 7 71.4 %

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

Generated by: LCOV version 1.14