LCOV - code coverage report
Current view: top level - disco/keyguard - fd_keyload.c (source / functions) Hit Total Coverage
Test: cov.lcov Lines: 0 83 0.0 %
Date: 2025-03-20 12:08:36 Functions: 0 5 0.0 %

          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           0 :                  uchar *      keypair ) {
      16           0 : #define KEY_PARSE_ERR( ... ) \
      17           0 :   FD_LOG_ERR(( "Error while parsing the validator identity key at path " \
      18           0 :                "`%s` specified by [consensus.identity_path] in the "     \
      19           0 :                "configuration TOML file. Solana key files are "         \
      20           0 :                "formatted as a 64-element JSON array. " __VA_ARGS__ ))
      21           0 : #define KEY_SZ 64UL
      22             :   /* at least one digit per byte, commas in between each byte, opening and closing brackets */
      23           0 : #define MIN_KEY_FILE_SZ ((ssize_t)(KEY_SZ + KEY_SZ-1UL + 2UL))
      24           0 : #define MAX_KEY_FILE_SZ     1023UL /* Unless it has extraneous whitespace, max is 64*4+1 */
      25             : 
      26             : 
      27           0 :   char * json_key_file = (char *)keypair+KEY_SZ;
      28           0 :   long bytes_read = read( key_fd, keypair+KEY_SZ, MAX_KEY_FILE_SZ );
      29           0 :   if( FD_UNLIKELY( bytes_read==-1  ) ) FD_LOG_ERR(( "reading key file (%s) failed (%i-%s)", key_path, errno, fd_io_strerror( errno ) ));
      30           0 :   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           0 :   if( bytes_read<MIN_KEY_FILE_SZ     ) FD_LOG_ERR(( "the specified key file (%s) was too short", key_path ));
      33           0 :   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           0 :   char ** tok = (char **)(keypair+KEY_SZ+1024UL);
      39           0 :   if( FD_UNLIKELY( fd_cstr_tokenize( tok, KEY_SZ, json_key_file, ',' ) != KEY_SZ ) ) KEY_PARSE_ERR( "", key_path );
      40             : 
      41           0 :   if( FD_UNLIKELY( 1!=sscanf( tok[ 0 ], "[ %hhu", &keypair[ 0 ] ) ) )
      42           0 :     KEY_PARSE_ERR( "The file should start with an opening `[` followed by a decimal integer.", key_path );
      43           0 :   for( ulong i=1UL; i<63UL; i++ ) {
      44           0 :     if( FD_UNLIKELY( 1!=sscanf( tok[ i ], "%hhu", &keypair[ i ] ) ) )
      45           0 :       KEY_PARSE_ERR( "Parsing failed near the %luth value.", key_path, i );
      46           0 :   }
      47           0 :   if( FD_UNLIKELY( 1!=sscanf( tok[ 63 ], "%hhu ]", &keypair[ 63 ] ) ) )
      48           0 :     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           0 :   explicit_bzero( json_key_file, MAX_KEY_FILE_SZ       );
      53           0 :   explicit_bzero( tok,           KEY_SZ*sizeof(char *) );
      54           0 : #undef MAX_KEY_FILE_SZ
      55           0 : #undef MIN_KEY_FILE_SZ
      56           0 : #undef KEY_SZ
      57           0 : #undef KEY_PARSE_ERR
      58             : 
      59           0 :   return keypair;
      60           0 : }
      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           0 :           uchar      * key       ) {
      66           0 :   int key_fd = open( key_path, O_RDONLY );
      67           0 :   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 identity`", 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           0 :   return fd_keyload_read( key_fd, key_path, key );
      80           0 : }
      81             : 
      82             : uchar * FD_FN_SENSITIVE
      83             : fd_keyload_load( char const * key_path,
      84           0 :                  int          public_key_only ) {
      85           0 :   if( FD_UNLIKELY( !key_path || !key_path[0] ) ) {
      86           0 :     FD_LOG_ERR(( "Invalid 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           0 :   uchar * key_page = fd_keyload_alloc_protected_pages( 1UL, 2UL );
      93             : 
      94           0 :   read_key( key_path, key_page );
      95             : 
      96           0 :   if( public_key_only ) explicit_bzero( key_page, 32UL );
      97             : 
      98           0 :   if( public_key_only ) return key_page+32UL;
      99           0 :   else                  return key_page;
     100           0 : }
     101             : 
     102             : void FD_FN_SENSITIVE
     103             : fd_keyload_unload( uchar const * key,
     104           0 :                    int           public_key_only ) {
     105           0 :   void * key_page = public_key_only ? (uchar *)key-32UL : (uchar *)key;
     106           0 :   ulong sz = (2UL*1UL+2UL)*4096UL;
     107             : 
     108           0 :   if( FD_UNLIKELY( mprotect( key_page, 4096UL, PROT_READ | PROT_WRITE ) ) )
     109           0 :     FD_LOG_ERR(( "mprotect failed (%i-%s)", errno, fd_io_strerror( errno ) ));
     110           0 :   explicit_bzero( key_page, 4096UL );
     111             : 
     112           0 :   if( FD_UNLIKELY( -1==munmap( (uchar*)key_page - 2UL*4096UL, sz ) ) )
     113           0 :     FD_LOG_ERR(( "munmap failed (%i-%s)", errno, fd_io_strerror( errno ) ));
     114           0 : }
     115             : 
     116             : void * FD_FN_SENSITIVE
     117             : fd_keyload_alloc_protected_pages( ulong page_cnt,
     118           0 :                                   ulong guard_page_cnt ) {
     119           0 : #define PAGE_SZ (4096UL)
     120           0 :   void * pages = mmap( NULL, (2UL*guard_page_cnt+page_cnt)*PAGE_SZ, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0UL );
     121           0 :   if( FD_UNLIKELY( pages==MAP_FAILED ) ) FD_LOG_ERR(( "mmap failed (%i-%s)", errno, fd_io_strerror( errno ) ));
     122             : 
     123           0 :   uchar * middle_pages = (uchar *)( (ulong)pages + guard_page_cnt*PAGE_SZ );
     124             : 
     125             :   /* Make the guard pages untouchable */
     126           0 :   if( FD_UNLIKELY( mprotect( pages, guard_page_cnt*PAGE_SZ, PROT_NONE ) ) )
     127           0 :     FD_LOG_ERR(( "mprotect failed (%i-%s)", errno, fd_io_strerror( errno ) ));
     128             : 
     129           0 :   if( FD_UNLIKELY( mprotect( middle_pages+page_cnt*PAGE_SZ, guard_page_cnt*PAGE_SZ, PROT_NONE ) ) )
     130           0 :     FD_LOG_ERR(( "mprotect failed (%i-%s)", errno, fd_io_strerror( errno ) ));
     131             : 
     132             :   /* Lock the key page so that it doesn't page to disk */
     133           0 :   if( FD_UNLIKELY( mlock( middle_pages, page_cnt*PAGE_SZ ) ) )
     134           0 :     FD_LOG_ERR(( "mlock failed (%i-%s)", errno, fd_io_strerror( errno ) ));
     135             : 
     136             :   /* Prevent the key page from showing up in core dumps. It shouldn't be
     137             :      possible to fork this process typically, but we also prevent any
     138             :      forked child from having this page. */
     139           0 :   if( FD_UNLIKELY( madvise( middle_pages, page_cnt*PAGE_SZ, MADV_WIPEONFORK | MADV_DONTDUMP ) ) )
     140           0 :     FD_LOG_ERR(( "madvise failed (%i-%s)", errno, fd_io_strerror( errno ) ));
     141             : 
     142           0 :   return middle_pages;
     143           0 : #undef PAGE_SZ
     144           0 : }

Generated by: LCOV version 1.14