LCOV - code coverage report
Current view: top level - disco/keyguard - fd_keyload.c (source / functions) Hit Total Coverage
Test: cov.lcov Lines: 0 79 0.0 %
Date: 2024-11-13 11:58:15 Functions: 0 4 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             : /* Expects that key[i] is writable for i in [0, 1600). */
      13             : static inline uchar * FD_FN_SENSITIVE
      14             : read_key( char const * key_path,
      15           0 :           uchar      * key       ) {
      16           0 :   int key_fd = open( key_path, O_RDONLY );
      17           0 :   if( FD_UNLIKELY( key_fd==-1 ) ) {
      18           0 :     if( FD_UNLIKELY( errno==ENOENT ) ) {
      19           0 :       FD_LOG_ERR((
      20           0 :           "The [consensus.identity_path] in your configuration expects a "
      21           0 :           "keyfile at `%s` but there is no such file. Either update the "
      22           0 :           "configuration file to point to your validator identity "
      23           0 :           "keypair, or generate a new validator identity key by running "
      24           0 :           "`fdctl keys new identity`", key_path ));
      25           0 :     } else
      26           0 :       FD_LOG_ERR(( "Opening key file (%s) failed (%i-%s)", key_path,  errno, fd_io_strerror( errno ) ));
      27           0 :   }
      28           0 : #define KEY_PARSE_ERR( ... ) \
      29           0 :   FD_LOG_ERR(( "Error while parsing the validator identity key at path " \
      30           0 :                "`%s` specified by [consensus.identity_path] in the "     \
      31           0 :                "configuration TOML file. Solana key files are "         \
      32           0 :                "formatted as a 64-element JSON array. " __VA_ARGS__ ))
      33           0 : #define KEY_SZ 64UL
      34             :   /* at least one digit per byte, commas in between each byte, opening and closing brackets */
      35           0 : #define MIN_KEY_FILE_SZ ((ssize_t)(KEY_SZ + KEY_SZ-1UL + 2UL))
      36           0 : #define MAX_KEY_FILE_SZ     1023UL /* Unless it has extraneous whitespace, max is 64*4+1 */
      37             : 
      38             : 
      39           0 :   char * json_key_file = (char *)key+KEY_SZ;
      40           0 :   ssize_t bytes_read = read( key_fd, key+KEY_SZ, MAX_KEY_FILE_SZ );
      41           0 :   if( FD_UNLIKELY( bytes_read==-1  ) ) FD_LOG_ERR(( "reading key file (%s) failed (%i-%s)", key_path, errno, fd_io_strerror( errno ) ));
      42           0 :   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           0 :   if( bytes_read<MIN_KEY_FILE_SZ    ) FD_LOG_ERR(( "the specified key file (%s) was too short", key_path ));
      45           0 :   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           0 :   char ** tok = (char **)(key+KEY_SZ+1024UL);
      51           0 :   if( FD_UNLIKELY( fd_cstr_tokenize( tok, KEY_SZ, json_key_file, ',' ) != KEY_SZ ) ) KEY_PARSE_ERR( "", key_path );
      52             : 
      53           0 :   if( FD_UNLIKELY( 1!=sscanf( tok[ 0 ], "[ %hhu", &key[ 0 ] ) ) )
      54           0 :     KEY_PARSE_ERR( "The file should start with an opening `[` followed by a decimal integer.", key_path );
      55           0 :   for( ulong i=1UL; i<63UL; i++ ) {
      56           0 :     if( FD_UNLIKELY( 1!=sscanf( tok[ i ], "%hhu", &key[ i ] ) ) )
      57           0 :       KEY_PARSE_ERR( "Parsing failed near the %luth value.", key_path, i );
      58           0 :   }
      59           0 :   if( FD_UNLIKELY( 1!=sscanf( tok[ 63 ], "%hhu ]", &key[ 63 ] ) ) )
      60           0 :     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           0 :   explicit_bzero( json_key_file, MAX_KEY_FILE_SZ       );
      65           0 :   explicit_bzero( tok,           KEY_SZ*sizeof(char *) );
      66           0 : #undef MAX_KEY_FILE_SZ
      67           0 : #undef MIN_KEY_FILE_SZ
      68           0 : #undef KEY_SZ
      69           0 : #undef KEY_PARSE_ERR
      70             : 
      71           0 :   return key;
      72           0 : }
      73             : 
      74             : uchar const * FD_FN_SENSITIVE
      75             : fd_keyload_load( char const * key_path,
      76           0 :                  int          public_key_only ) {
      77             :   /* Load the signing key. Since this is key material, we load it into
      78             :      its own page that's non-dumpable, readonly, and protected by guard
      79             :      pages. */
      80           0 :   uchar * key_page = fd_keyload_alloc_protected_pages( 1UL, 2UL );
      81             : 
      82           0 :   read_key( key_path, key_page );
      83             : 
      84           0 :   if( public_key_only ) explicit_bzero( key_page, 32UL );
      85             : 
      86             :   /* For good measure, make the key page read-only */
      87           0 :   if( FD_UNLIKELY( mprotect( key_page, 4096UL, PROT_READ ) ) )
      88           0 :     FD_LOG_ERR(( "mprotect failed (%i-%s)", errno, fd_io_strerror( errno ) ));
      89             : 
      90           0 :   if( public_key_only ) return key_page+32UL;
      91           0 :   else                  return key_page;
      92           0 : }
      93             : 
      94             : void FD_FN_SENSITIVE
      95             : fd_keyload_unload( uchar const * key,
      96           0 :                    int           public_key_only ) {
      97           0 :   void * key_page = public_key_only ? (uchar *)key-32UL : (uchar *)key;
      98           0 :   ulong sz = (2UL*1UL+2UL)*4096UL;
      99             : 
     100           0 :   if( FD_UNLIKELY( mprotect( key_page, 4096UL, PROT_READ | PROT_WRITE ) ) )
     101           0 :     FD_LOG_ERR(( "mprotect failed (%i-%s)", errno, fd_io_strerror( errno ) ));
     102           0 :   explicit_bzero( key_page, 4096UL );
     103             : 
     104           0 :   if( FD_UNLIKELY( -1==munmap( (uchar*)key_page - 2UL*4096UL, sz ) ) )
     105           0 :     FD_LOG_ERR(( "munmap failed (%i-%s)", errno, fd_io_strerror( errno ) ));
     106           0 : }
     107             : 
     108             : void * FD_FN_SENSITIVE
     109             : fd_keyload_alloc_protected_pages( ulong page_cnt,
     110           0 :                                   ulong guard_page_cnt ) {
     111           0 : #define PAGE_SZ (4096UL)
     112           0 :   void * pages = mmap( NULL, (2UL*guard_page_cnt+page_cnt)*PAGE_SZ, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0UL );
     113           0 :   if( FD_UNLIKELY( pages==MAP_FAILED ) ) FD_LOG_ERR(( "mmap failed (%i-%s)", errno, fd_io_strerror( errno ) ));
     114             : 
     115           0 :   uchar * middle_pages = (uchar *)( (ulong)pages + guard_page_cnt*PAGE_SZ );
     116             : 
     117             :   /* Make the guard pages untouchable */
     118           0 :   if( FD_UNLIKELY( mprotect( pages, guard_page_cnt*PAGE_SZ, PROT_NONE ) ) )
     119           0 :     FD_LOG_ERR(( "mprotect failed (%i-%s)", errno, fd_io_strerror( errno ) ));
     120             : 
     121           0 :   if( FD_UNLIKELY( mprotect( middle_pages+page_cnt*PAGE_SZ, guard_page_cnt*PAGE_SZ, PROT_NONE ) ) )
     122           0 :     FD_LOG_ERR(( "mprotect failed (%i-%s)", errno, fd_io_strerror( errno ) ));
     123             : 
     124             :   /* Lock the key page so that it doesn't page to disk */
     125           0 :   if( FD_UNLIKELY( mlock( middle_pages, page_cnt*PAGE_SZ ) ) )
     126           0 :     FD_LOG_ERR(( "mlock failed (%i-%s)", errno, fd_io_strerror( errno ) ));
     127             : 
     128             :   /* Prevent the key page from showing up in core dumps. It shouldn't be
     129             :      possible to fork this process typically, but we also prevent any
     130             :      forked child from having this page. */
     131           0 :   if( FD_UNLIKELY( madvise( middle_pages, page_cnt*PAGE_SZ, MADV_WIPEONFORK | MADV_DONTDUMP ) ) )
     132           0 :     FD_LOG_ERR(( "madvise failed (%i-%s)", errno, fd_io_strerror( errno ) ));
     133             : 
     134           0 :   return middle_pages;
     135           0 : #undef PAGE_SZ
     136           0 : }

Generated by: LCOV version 1.14