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 : }
|