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