Line data Source code
1 : #include "fd_bpf_loader_serialization.h"
2 : #include "../fd_borrowed_account.h"
3 : #include "../fd_runtime.h"
4 :
5 : /* As a general note, copy_account_data implies that direct mapping is not being
6 : used/is inactive. This file is responsible for serializing and deserializing
7 : the input region of the BPF virtual machine. The input region contains
8 : instruction information, account metadata, and account data. The high level
9 : format is as follows:
10 :
11 : [ account 1 metadata, account 1 data, account 2 metadata, account 2 data, ...,
12 : account N metadata, account N data, instruction info. ]
13 :
14 : This format by no means comprehensive, but it should give an idea of how
15 : the input region is laid out. When direct mapping is not enabled, the input
16 : region is stored as a single contiguous buffer. This buffer in the host
17 : address space is then mapped to the VM virtual address space (the range
18 : starting with 0x400...). This means to serialize into the input region, we
19 : need to copy in the account metadata and account data into the buffer for
20 : each account. Everything must get copied out after execution is complete.
21 : A consequence of this is that a memcpy for the account data is required
22 : for each serialize and deserialize operation: this can potentially become
23 : expensive if there are many accounts and many nested CPI calls. Also, the
24 : entire memory region is treated as writable even though many accounts are
25 : read-only. This means that for all read-only accounts, a memcmp must be done
26 : while deserializing to make sure that the account (meta)data has not changed.
27 :
28 : Direct mapping offers a solution to this by introducing a more sophisticated
29 : memory translation protocol. Now the account data is not copied into a single
30 : contiguous buffer, but instead a borrowed account's data is directly mapped
31 : into the VM's virtual address space. The host memory for the input region is
32 : now represented by a list of fragmented memory regions. These sub regions
33 : also have different write permissions. This should solve the problem of
34 : having to memcpy/memcmp account data regions (which can be up to 10MiB each).
35 : There is some nuance to this, as the account data can be resized. This means
36 : that memcpys for account data regions can't totally be avoided. */
37 :
38 : /* Add a new memory region to represent the input region. All of the memory
39 : regions here have sorted virtual addresses. These regions may or may not
40 : correspond to an account's data region. If it corresponds to metadata,
41 : the pubkey for the region will be NULL. */
42 : static void
43 : new_input_mem_region( fd_vm_input_region_t * input_mem_regions,
44 : uint * input_mem_regions_cnt,
45 : const uchar * buffer,
46 : ulong region_sz,
47 : uchar is_writable,
48 0 : uchar is_acct_data ) {
49 :
50 : /* The start vaddr of the new region should be equal to start of the previous
51 : region added to its size. */
52 0 : ulong vaddr_offset = *input_mem_regions_cnt==0UL ? 0UL : input_mem_regions[ *input_mem_regions_cnt-1U ].vaddr_offset +
53 0 : input_mem_regions[ *input_mem_regions_cnt-1U ].region_sz;
54 0 : input_mem_regions[ *input_mem_regions_cnt ].is_writable = is_writable;
55 0 : input_mem_regions[ *input_mem_regions_cnt ].haddr = (ulong)buffer;
56 0 : input_mem_regions[ *input_mem_regions_cnt ].region_sz = (uint)region_sz;
57 0 : input_mem_regions[ *input_mem_regions_cnt ].vaddr_offset = vaddr_offset;
58 0 : input_mem_regions[ *input_mem_regions_cnt ].is_acct_data = is_acct_data;
59 0 : (*input_mem_regions_cnt)++;
60 0 : }
61 :
62 : /* https://github.com/anza-xyz/agave/blob/b5f5c3cdd3f9a5859c49ebc27221dc27e143d760/programs/bpf_loader/src/serialization.rs#L93-L130 */
63 : /* This function handles casing for direct mapping being enabled as well as if
64 : the alignment is being stored. In the case where direct mapping is not
65 : enabled, we copy in the account data and a 10KiB buffer into the input region.
66 : These both go into the same memory buffer. However, when direct mapping is
67 : enabled, the account data and resizing buffers are represented by two
68 : different memory regions. In both cases, padding is used to maintain 8 byte
69 : alignment. If alignment is not required, then a resizing buffer is not used
70 : as the deprecated loader doesn't allow for resizing accounts. */
71 : static void
72 : write_account( fd_borrowed_account_t * account,
73 : uchar instr_acc_idx,
74 : uchar * * serialized_params,
75 : uchar * * serialized_params_start,
76 : fd_vm_input_region_t * input_mem_regions,
77 : uint * input_mem_regions_cnt,
78 : fd_vm_acc_region_meta_t * acc_region_metas,
79 : int is_aligned,
80 0 : int copy_account_data ) {
81 :
82 0 : uchar const * data = account ? fd_borrowed_account_get_data( account ) : NULL;
83 0 : ulong dlen = account ? fd_borrowed_account_get_data_len( account ) : 0UL;
84 :
85 0 : acc_region_metas[instr_acc_idx].original_data_len = dlen;
86 0 : if( copy_account_data ) {
87 : /* Copy the account data into input region buffer */
88 0 : fd_memcpy( *serialized_params, data, dlen );
89 0 : *serialized_params += dlen;
90 :
91 0 : if( FD_LIKELY( is_aligned ) ) {
92 : /* Zero out padding bytes and max permitted data increase */
93 0 : ulong align_offset = fd_ulong_align_up( dlen, FD_BPF_ALIGN_OF_U128 ) - dlen;
94 0 : fd_memset( *serialized_params, 0, MAX_PERMITTED_DATA_INCREASE + align_offset );
95 0 : *serialized_params += MAX_PERMITTED_DATA_INCREASE + align_offset;
96 0 : }
97 : /* In the non-DM case, we don't bother with setting up mem regions.
98 : So has_data_region and has_resizing_region are set to 0. */
99 0 : acc_region_metas[instr_acc_idx].region_idx = UINT_MAX;
100 0 : acc_region_metas[instr_acc_idx].has_data_region = 0U;
101 0 : acc_region_metas[instr_acc_idx].has_resizing_region = 0U;
102 0 : } else { /* direct_mapping == true */
103 : /* First, push on the region for the metadata that has just been serialized.
104 : This function will push the metadata in the serialized_params from
105 : serialized_params_start to serialized_params as a region to the input
106 : memory regions array. */
107 : /* TODO: This region always has length of 96 and this can be set as a constant. */
108 :
109 0 : ulong region_sz = (ulong)(*serialized_params) - (ulong)(*serialized_params_start);
110 0 : new_input_mem_region( input_mem_regions, input_mem_regions_cnt, *serialized_params_start, region_sz, 1U, 0U );
111 :
112 : /* Next, push the region for the account data if there is account data. We
113 : intentionally omit copy on write as a region type. */
114 0 : int err = 0;
115 0 : uchar is_writable = !!(fd_borrowed_account_can_data_be_changed( account, &err ) && !err);
116 :
117 : /* Update the mapping from instruction account index to memory region index.
118 : This is an optimization to avoid redundant lookups to find accounts. */
119 0 : acc_region_metas[instr_acc_idx].region_idx = *input_mem_regions_cnt;
120 0 : acc_region_metas[instr_acc_idx].has_data_region = !!dlen;
121 0 : acc_region_metas[instr_acc_idx].has_resizing_region = (uchar)is_aligned;
122 :
123 0 : if( dlen ) {
124 0 : new_input_mem_region( input_mem_regions, input_mem_regions_cnt, data, dlen, is_writable, 1U );
125 0 : }
126 :
127 0 : if( FD_LIKELY( is_aligned ) ) {
128 : /* Finally, push a third region for the max resizing data region. This is
129 : done even if there is no account data. This must be aligned so padding
130 : bytes must be inserted. This resizing region is also padded to result
131 : in 8 byte alignment for the combination of the account data region with
132 : the resizing region.
133 :
134 : We add the max permitted resizing limit along with 8 bytes of padding
135 : to the serialization buffer. However, the padding bytes are used to
136 : maintain alignment in the VM virtual address space. */
137 0 : ulong align_offset = fd_ulong_align_up( dlen, FD_BPF_ALIGN_OF_U128 ) - dlen;
138 :
139 0 : fd_memset( *serialized_params, 0, MAX_PERMITTED_DATA_INCREASE + FD_BPF_ALIGN_OF_U128 );
140 :
141 : /* Leave a gap for alignment */
142 0 : uchar * region_buffer = *serialized_params + (FD_BPF_ALIGN_OF_U128 - align_offset);
143 0 : ulong region_sz = MAX_PERMITTED_DATA_INCREASE + align_offset;
144 0 : new_input_mem_region( input_mem_regions, input_mem_regions_cnt, region_buffer, region_sz, is_writable, 1U );
145 :
146 0 : *serialized_params += MAX_PERMITTED_DATA_INCREASE + FD_BPF_ALIGN_OF_U128;
147 0 : }
148 0 : *serialized_params_start = *serialized_params;
149 0 : }
150 0 : }
151 :
152 : static uchar *
153 : fd_bpf_loader_input_serialize_aligned( fd_exec_instr_ctx_t * ctx,
154 : ulong * sz,
155 : ulong * pre_lens,
156 : fd_vm_input_region_t * input_mem_regions,
157 : uint * input_mem_regions_cnt,
158 : fd_vm_acc_region_meta_t * acc_region_metas,
159 : int copy_account_data,
160 0 : int mask_out_rent_epoch_in_vm_serialization ) {
161 0 : fd_pubkey_t * txn_accs = ctx->txn_ctx->account_keys;
162 :
163 0 : uchar acc_idx_seen[256] = {0};
164 0 : ushort dup_acc_idx[256] = {0};
165 :
166 : /* https://github.com/anza-xyz/agave/blob/b5f5c3cdd3f9a5859c49ebc27221dc27e143d760/programs/bpf_loader/src/serialization.rs#L429-L459 */
167 0 : ulong serialized_size = 0UL;
168 0 : serialized_size += sizeof(ulong); // acct_cnt
169 : /* First pass is to calculate size of buffer to allocate */
170 0 : for( ushort i=0; i<ctx->instr->acct_cnt; i++ ) {
171 0 : uchar acc_idx = (uchar)ctx->instr->accounts[i].index_in_transaction;
172 :
173 0 : serialized_size++; // dup byte
174 0 : if( FD_UNLIKELY( acc_idx_seen[acc_idx] ) ) {
175 0 : serialized_size += 7UL; // pad to 64-bit alignment
176 0 : } else {
177 0 : acc_idx_seen[acc_idx] = 1;
178 0 : dup_acc_idx[acc_idx] = i;
179 :
180 : /* Borrow the account without checking the error, as it is guaranteed to exist
181 : https://github.com/anza-xyz/agave/blob/v2.1.4/programs/bpf_loader/src/serialization.rs#L225 */
182 0 : fd_guarded_borrowed_account_t view_acc;
183 0 : fd_exec_instr_ctx_try_borrow_instr_account( ctx, i, &view_acc );
184 :
185 0 : ulong acc_data_len = fd_borrowed_account_get_data_len( &view_acc );
186 :
187 0 : serialized_size += sizeof(uchar) // is_signer
188 0 : + sizeof(uchar) // is_writable
189 0 : + sizeof(uchar) // executable
190 0 : + sizeof(uint) // original_data_len
191 0 : + sizeof(fd_pubkey_t) // key
192 0 : + sizeof(fd_pubkey_t) // owner
193 0 : + sizeof(ulong) // lamports
194 0 : + sizeof(ulong) // data len
195 0 : + MAX_PERMITTED_DATA_INCREASE
196 0 : + sizeof(ulong); // rent_epoch
197 0 : if( copy_account_data ) {
198 0 : serialized_size += fd_ulong_align_up( acc_data_len, FD_BPF_ALIGN_OF_U128 );
199 0 : } else {
200 0 : serialized_size += FD_BPF_ALIGN_OF_U128;
201 0 : }
202 0 : }
203 0 : }
204 :
205 0 : serialized_size += sizeof(ulong) // data len
206 0 : + ctx->instr->data_sz
207 0 : + sizeof(fd_pubkey_t); // program id
208 :
209 : /* 16-byte aligned buffer:
210 : https://github.com/anza-xyz/agave/blob/v2.2.13/programs/bpf_loader/src/serialization.rs#L32
211 : */
212 0 : uchar * serialized_params = fd_spad_alloc( ctx->txn_ctx->spad, FD_RUNTIME_INPUT_REGION_ALLOC_ALIGN_UP, fd_ulong_align_up( serialized_size, FD_RUNTIME_INPUT_REGION_ALLOC_ALIGN_UP ) );
213 0 : uchar * serialized_params_start = serialized_params;
214 0 : uchar * curr_serialized_params_start = serialized_params;
215 :
216 0 : FD_STORE( ulong, serialized_params, ctx->instr->acct_cnt );
217 0 : serialized_params += sizeof(ulong);
218 :
219 : /* Second pass over the account is to serialize into the buffer. */
220 0 : for( ushort i=0; i<ctx->instr->acct_cnt; i++ ) {
221 0 : uchar acc_idx = (uchar)ctx->instr->accounts[i].index_in_transaction;
222 0 : fd_pubkey_t * acc = &txn_accs[acc_idx];
223 :
224 0 : if( FD_UNLIKELY( acc_idx_seen[acc_idx] && dup_acc_idx[acc_idx] != i ) ) {
225 : /* Duplicate. Store 8 byte buffer to maintain alignment but store the
226 : account index in the first byte.*/
227 0 : FD_STORE( ulong, serialized_params, 0UL );
228 0 : FD_STORE( uchar, serialized_params, (uchar)dup_acc_idx[acc_idx] );
229 0 : serialized_params += sizeof(ulong);
230 0 : } else {
231 : /* Calculate and store the start of the actual metadata region for this account,
232 : excluding any duplicate account markers at the beginning.
233 :
234 : We use this later for retrieving the serialized values later in the CPI security checks. */
235 0 : ulong metadata_region_offset_with_dups = *input_mem_regions_cnt==0UL ? 0UL :
236 0 : input_mem_regions[ *input_mem_regions_cnt-1U ].vaddr_offset +
237 0 : input_mem_regions[ *input_mem_regions_cnt-1U ].region_sz;
238 :
239 0 : acc_region_metas[i].metadata_region_offset = metadata_region_offset_with_dups +
240 0 : (ulong)(serialized_params - curr_serialized_params_start);
241 :
242 0 : FD_STORE( uchar, serialized_params, FD_NON_DUP_MARKER );
243 0 : serialized_params += sizeof(uchar);
244 :
245 : /* Borrow the account without checking the error, as it is guaranteed to exist
246 : https://github.com/anza-xyz/agave/blob/v2.1.4/programs/bpf_loader/src/serialization.rs#L225 */
247 0 : fd_guarded_borrowed_account_t view_acc;
248 0 : fd_exec_instr_ctx_try_borrow_instr_account( ctx, i, &view_acc );
249 :
250 : /* https://github.com/anza-xyz/agave/blob/b5f5c3cdd3f9a5859c49ebc27221dc27e143d760/programs/bpf_loader/src/serialization.rs#L465 */
251 0 : fd_account_meta_t const * metadata = fd_borrowed_account_get_acc_meta( &view_acc );
252 :
253 0 : uchar is_signer = (uchar)fd_instr_acc_is_signer_idx( ctx->instr, (uchar)i );
254 0 : FD_STORE( uchar, serialized_params, is_signer );
255 0 : serialized_params += sizeof(uchar);
256 :
257 0 : uchar is_writable = (uchar)fd_instr_acc_is_writable_idx( ctx->instr, (uchar)i );
258 0 : FD_STORE( uchar, serialized_params, is_writable );
259 0 : serialized_params += sizeof(uchar);
260 :
261 0 : uchar is_executable = (uchar)metadata->info.executable;
262 0 : FD_STORE( uchar, serialized_params, is_executable );
263 0 : serialized_params += sizeof(uchar);
264 :
265 : /* The original data len field is intentionally NOT populated. */
266 0 : uint padding_0 = 0U;
267 0 : FD_STORE( uint, serialized_params, padding_0 );
268 0 : serialized_params += sizeof(uint);
269 :
270 0 : fd_pubkey_t key = *acc;
271 0 : FD_STORE( fd_pubkey_t, serialized_params, key );
272 0 : serialized_params += sizeof(fd_pubkey_t);
273 :
274 0 : fd_pubkey_t owner = *(fd_pubkey_t *)&metadata->info.owner;
275 0 : FD_STORE( fd_pubkey_t, serialized_params, owner );
276 0 : serialized_params += sizeof(fd_pubkey_t);
277 :
278 0 : ulong lamports = metadata->info.lamports;
279 0 : FD_STORE( ulong, serialized_params, lamports );
280 0 : serialized_params += sizeof(ulong);
281 :
282 0 : ulong acc_data_len = metadata->dlen;
283 0 : pre_lens[i] = acc_data_len;
284 :
285 0 : ulong data_len = acc_data_len;
286 0 : FD_STORE( ulong, serialized_params, data_len );
287 0 : serialized_params += sizeof(ulong);
288 :
289 0 : write_account( &view_acc, (uchar)i, &serialized_params, &curr_serialized_params_start,
290 0 : input_mem_regions, input_mem_regions_cnt, acc_region_metas, 1, copy_account_data );
291 :
292 0 : ulong rent_epoch = mask_out_rent_epoch_in_vm_serialization ? ULONG_MAX : metadata->info.rent_epoch;
293 0 : FD_STORE( ulong, serialized_params, rent_epoch );
294 0 : serialized_params += sizeof(ulong);
295 0 : }
296 :
297 0 : }
298 :
299 0 : ulong instr_data_len = ctx->instr->data_sz;
300 0 : FD_STORE( ulong, serialized_params, instr_data_len );
301 0 : serialized_params += sizeof(ulong);
302 :
303 0 : uchar * instr_data = ctx->instr->data;
304 0 : fd_memcpy( serialized_params, instr_data, instr_data_len );
305 0 : serialized_params += instr_data_len;
306 :
307 0 : FD_STORE( fd_pubkey_t, serialized_params, txn_accs[ctx->instr->program_id] );
308 0 : serialized_params += sizeof(fd_pubkey_t);
309 :
310 0 : if( FD_UNLIKELY( serialized_params!=serialized_params_start+serialized_size ) ) {
311 0 : FD_LOG_ERR(( "Serializing error" )); /* TODO: we can likely get rid of this check altogether */
312 0 : }
313 :
314 : /* Write out the final region. */
315 0 : new_input_mem_region( input_mem_regions, input_mem_regions_cnt, curr_serialized_params_start,
316 0 : (ulong)(serialized_params - curr_serialized_params_start), 1U, 0U );
317 :
318 0 : *sz = serialized_size;
319 :
320 0 : return serialized_params_start;
321 0 : }
322 :
323 : /* https://github.com/anza-xyz/agave/blob/b5f5c3cdd3f9a5859c49ebc27221dc27e143d760/programs/bpf_loader/src/serialization.rs#L500-L603 */
324 : static int
325 : fd_bpf_loader_input_deserialize_aligned( fd_exec_instr_ctx_t * ctx,
326 : ulong const * pre_lens,
327 : uchar * buffer,
328 : ulong FD_FN_UNUSED buffer_sz,
329 0 : int copy_account_data ) {
330 : /* TODO: An optimization would be to skip ahead through non-writable accounts */
331 : /* https://github.com/anza-xyz/agave/blob/b5f5c3cdd3f9a5859c49ebc27221dc27e143d760/programs/bpf_loader/src/serialization.rs#L507 */
332 0 : ulong start = 0UL;
333 :
334 0 : uchar acc_idx_seen[256] = {0};
335 :
336 0 : start += sizeof(ulong); // number of accounts
337 : /* https://github.com/anza-xyz/agave/blob/b5f5c3cdd3f9a5859c49ebc27221dc27e143d760/programs/bpf_loader/src/serialization.rs#L508-L600 */
338 0 : for( ushort i=0; i<ctx->instr->acct_cnt; i++ ) {
339 0 : uchar acc_idx = (uchar)ctx->instr->accounts[i].index_in_transaction;
340 :
341 0 : start++; // position
342 :
343 : /* get the borrowed account
344 : https://github.com/anza-xyz/agave/blob/v2.1.4/programs/bpf_loader/src/serialization.rs#L519 */
345 0 : fd_guarded_borrowed_account_t view_acc;
346 0 : FD_TRY_BORROW_INSTR_ACCOUNT_DEFAULT_ERR_CHECK( ctx, i, &view_acc );
347 :
348 0 : if( FD_UNLIKELY( acc_idx_seen[acc_idx] ) ) {
349 : /* https://github.com/anza-xyz/agave/blob/b5f5c3cdd3f9a5859c49ebc27221dc27e143d760/programs/bpf_loader/src/serialization.rs#L515-517 */
350 0 : start += 7UL;
351 0 : } else {
352 : /* https://github.com/anza-xyz/agave/blob/b5f5c3cdd3f9a5859c49ebc27221dc27e143d760/programs/bpf_loader/src/serialization.rs#L518-524 */
353 0 : acc_idx_seen[acc_idx] = 1;
354 0 : start += sizeof(uchar) // is_signer
355 0 : + sizeof(uchar) // is_writable
356 0 : + sizeof(uchar) // executable
357 0 : + sizeof(uint) // original_data_len
358 0 : + sizeof(fd_pubkey_t); // key
359 :
360 : /* https://github.com/anza-xyz/agave/blob/b5f5c3cdd3f9a5859c49ebc27221dc27e143d760/programs/bpf_loader/src/serialization.rs#L525-548 */
361 :
362 0 : fd_pubkey_t * owner = (fd_pubkey_t *)(buffer+start);
363 0 : start += sizeof(fd_pubkey_t); // owner
364 :
365 0 : ulong lamports = FD_LOAD( ulong, buffer+start );
366 0 : if( lamports!=fd_borrowed_account_get_lamports( &view_acc ) ) {
367 0 : int err = fd_borrowed_account_set_lamports( &view_acc, lamports );
368 0 : if( FD_UNLIKELY( err ) ) {
369 0 : return err;
370 0 : }
371 0 : }
372 0 : start += sizeof(ulong); // lamports
373 :
374 0 : ulong post_len = FD_LOAD( ulong, buffer+start );
375 0 : start += sizeof(ulong); // data length
376 :
377 0 : ulong pre_len = pre_lens[i];
378 0 : ulong alignment_offset = fd_ulong_align_up( pre_len, FD_BPF_ALIGN_OF_U128 ) - pre_len;
379 :
380 0 : uchar * post_data = buffer+start;
381 :
382 0 : fd_account_meta_t const * metadata_check = fd_borrowed_account_get_acc_meta( &view_acc );
383 0 : if( FD_UNLIKELY( fd_ulong_sat_sub( post_len, metadata_check->dlen )>MAX_PERMITTED_DATA_INCREASE ||
384 0 : post_len>MAX_PERMITTED_DATA_LENGTH ) ) {
385 0 : return FD_EXECUTOR_INSTR_ERR_INVALID_REALLOC;
386 0 : }
387 :
388 0 : if( copy_account_data ) {
389 : /* https://github.com/anza-xyz/agave/blob/b5f5c3cdd3f9a5859c49ebc27221dc27e143d760/programs/bpf_loader/src/serialization.rs#L551-563 */
390 0 : int err = 0;
391 0 : if( fd_borrowed_account_can_data_be_resized( &view_acc, post_len, &err ) &&
392 0 : fd_borrowed_account_can_data_be_changed( &view_acc, &err ) ) {
393 :
394 0 : int err = fd_borrowed_account_set_data_from_slice( &view_acc, post_data, post_len );
395 0 : if( FD_UNLIKELY( err ) ) {
396 0 : return err;
397 0 : }
398 :
399 0 : } else if( FD_UNLIKELY( fd_borrowed_account_get_data_len( &view_acc )!=post_len ||
400 0 : memcmp( fd_borrowed_account_get_data( &view_acc ), post_data, post_len ) ) ) {
401 0 : return err;
402 0 : }
403 0 : start += pre_len;
404 0 : } else { /* If direct mapping is enabled */
405 : /* https://github.com/anza-xyz/agave/blob/b5f5c3cdd3f9a5859c49ebc27221dc27e143d760/programs/bpf_loader/src/serialization.rs#L564-587 */
406 0 : start += FD_BPF_ALIGN_OF_U128 - alignment_offset;
407 0 : int err = 0;
408 0 : if( fd_borrowed_account_can_data_be_resized( &view_acc, post_len, &err ) &&
409 0 : fd_borrowed_account_can_data_be_changed( &view_acc, &err ) ) {
410 :
411 0 : err = fd_borrowed_account_set_data_length( &view_acc, post_len );
412 0 : if( FD_UNLIKELY( err ) ) {
413 0 : return err;
414 0 : }
415 :
416 0 : ulong allocated_bytes = fd_ulong_sat_sub( post_len, pre_len );
417 0 : if( allocated_bytes ) {
418 0 : uchar * acc_data = NULL;
419 0 : ulong acc_dlen = 0UL;
420 0 : err = fd_borrowed_account_get_data_mut( &view_acc, &acc_data, &acc_dlen );
421 0 : if( FD_UNLIKELY( err ) ) {
422 0 : return err;
423 0 : }
424 0 : if( FD_UNLIKELY( pre_len+allocated_bytes>acc_dlen ) ) {
425 0 : return FD_EXECUTOR_INSTR_ERR_INVALID_ARG;
426 0 : }
427 : /* We want to copy in the reallocated bytes from the input
428 : buffer directly into the borrowed account data buffer
429 : which has now been extended. */
430 0 : memcpy( acc_data+pre_len, buffer+start, allocated_bytes );
431 0 : }
432 0 : } else if( FD_UNLIKELY( fd_borrowed_account_get_data_len( &view_acc )!=post_len ) ) {
433 0 : return err;
434 0 : }
435 0 : }
436 :
437 : /* https://github.com/anza-xyz/agave/blob/b5f5c3cdd3f9a5859c49ebc27221dc27e143d760/programs/bpf_loader/src/serialization.rs#L593-598 */
438 0 : start += MAX_PERMITTED_DATA_INCREASE;
439 0 : start += alignment_offset;
440 0 : start += sizeof(ulong); // rent epoch
441 0 : if( memcmp( fd_borrowed_account_get_owner( &view_acc ), owner, sizeof(fd_pubkey_t) ) ) {
442 0 : int err = fd_borrowed_account_set_owner( &view_acc, owner );
443 0 : if( FD_UNLIKELY( err ) ) {
444 0 : return err;
445 0 : }
446 0 : }
447 0 : }
448 0 : }
449 :
450 0 : return FD_EXECUTOR_INSTR_SUCCESS;
451 0 : }
452 :
453 : static uchar *
454 : fd_bpf_loader_input_serialize_unaligned( fd_exec_instr_ctx_t * ctx,
455 : ulong * sz,
456 : ulong * pre_lens,
457 : fd_vm_input_region_t * input_mem_regions,
458 : uint * input_mem_regions_cnt,
459 : fd_vm_acc_region_meta_t * acc_region_metas,
460 : int copy_account_data,
461 0 : int mask_out_rent_epoch_in_vm_serialization ) {
462 0 : ulong serialized_size = 0UL;
463 0 : fd_pubkey_t const * txn_accs = ctx->txn_ctx->account_keys;
464 :
465 0 : uchar acc_idx_seen[256] = {0};
466 0 : ushort dup_acc_idx[256] = {0};
467 :
468 0 : serialized_size += sizeof(ulong);
469 0 : for( ushort i=0; i<ctx->instr->acct_cnt; i++ ) {
470 0 : uchar acc_idx = (uchar)ctx->instr->accounts[i].index_in_transaction;
471 :
472 0 : serialized_size++; // dup
473 0 : if( FD_UNLIKELY( acc_idx_seen[acc_idx] ) ) {
474 0 : continue;
475 0 : }
476 :
477 0 : acc_idx_seen[acc_idx] = 1;
478 0 : dup_acc_idx[acc_idx] = i;
479 :
480 : /* Borrow the account without checking the error, as it is guaranteed to exist
481 : https://github.com/anza-xyz/agave/blob/v2.1.4/programs/bpf_loader/src/serialization.rs#L225 */
482 0 : fd_guarded_borrowed_account_t view_acc;
483 0 : fd_exec_instr_ctx_try_borrow_instr_account( ctx, i, &view_acc );
484 :
485 0 : ulong acc_data_len = fd_borrowed_account_get_data_len( &view_acc );
486 :
487 0 : pre_lens[i] = acc_data_len;
488 :
489 0 : serialized_size += sizeof(uchar) // is_signer
490 0 : + sizeof(uchar) // is_writable
491 0 : + sizeof(fd_pubkey_t) // key
492 0 : + sizeof(ulong) // lamports
493 0 : + sizeof(ulong) // data_len
494 0 : + sizeof(fd_pubkey_t) // owner
495 0 : + sizeof(uchar) // executable
496 0 : + sizeof(ulong); // rent_epoch
497 0 : if( copy_account_data ) {
498 0 : serialized_size += acc_data_len;
499 0 : }
500 0 : }
501 :
502 0 : serialized_size += sizeof(ulong) // instruction data len
503 0 : + ctx->instr->data_sz // instruction data
504 0 : + sizeof(fd_pubkey_t); // program id
505 :
506 : /* 16-byte aligned buffer:
507 : https://github.com/anza-xyz/agave/blob/v2.2.13/programs/bpf_loader/src/serialization.rs#L32
508 : */
509 0 : uchar * serialized_params = fd_spad_alloc( ctx->txn_ctx->spad, FD_RUNTIME_INPUT_REGION_ALLOC_ALIGN_UP, serialized_size );
510 0 : uchar * serialized_params_start = serialized_params;
511 0 : uchar * curr_serialized_params_start = serialized_params;
512 :
513 0 : FD_STORE( ulong, serialized_params, ctx->instr->acct_cnt );
514 0 : serialized_params += sizeof(ulong);
515 :
516 0 : for( ushort i=0; i<ctx->instr->acct_cnt; i++ ) {
517 0 : uchar acc_idx = (uchar)ctx->instr->accounts[i].index_in_transaction;
518 0 : fd_pubkey_t const * acc = &txn_accs[acc_idx];
519 :
520 0 : if( FD_UNLIKELY( acc_idx_seen[acc_idx] && dup_acc_idx[acc_idx] != i ) ) {
521 : // Duplicate
522 0 : FD_STORE( uchar, serialized_params, (uchar)dup_acc_idx[acc_idx] );
523 0 : serialized_params += sizeof(uchar);
524 0 : } else {
525 : /* Calculate and store the start of the actual metadata region for this account,
526 : excluding any duplicate account markers at the beginning.
527 :
528 : We use this later for retrieving the serialized values later in the CPI security checks. */
529 0 : ulong metadata_region_offset_with_dups = *input_mem_regions_cnt==0UL ? 0UL :
530 0 : input_mem_regions[ *input_mem_regions_cnt-1U ].vaddr_offset +
531 0 : input_mem_regions[ *input_mem_regions_cnt-1U ].region_sz;
532 :
533 0 : acc_region_metas[i].metadata_region_offset = metadata_region_offset_with_dups +
534 0 : (ulong)(serialized_params - curr_serialized_params_start);
535 :
536 0 : FD_STORE( uchar, serialized_params, FD_NON_DUP_MARKER );
537 0 : serialized_params += sizeof(uchar);
538 :
539 : /* Borrow the account without checking the error, as it is guaranteed to exist
540 : https://github.com/anza-xyz/agave/blob/v2.1.4/programs/bpf_loader/src/serialization.rs#L225 */
541 0 : fd_guarded_borrowed_account_t view_acc;
542 0 : fd_exec_instr_ctx_try_borrow_instr_account( ctx, i, &view_acc );
543 :
544 0 : fd_account_meta_t const * metadata = fd_borrowed_account_get_acc_meta( &view_acc );
545 :
546 0 : uchar is_signer = (uchar)fd_instr_acc_is_signer_idx( ctx->instr, (uchar)i );
547 0 : FD_STORE( uchar, serialized_params, is_signer );
548 0 : serialized_params += sizeof(uchar);
549 :
550 0 : uchar is_writable = (uchar)fd_instr_acc_is_writable_idx( ctx->instr, (uchar)i );
551 0 : FD_STORE( uchar, serialized_params, is_writable );
552 0 : serialized_params += sizeof(uchar);
553 :
554 0 : fd_pubkey_t key = *acc;
555 0 : FD_STORE( fd_pubkey_t, serialized_params, key );
556 0 : serialized_params += sizeof(fd_pubkey_t);
557 :
558 0 : ulong lamports = metadata->info.lamports;
559 0 : FD_STORE( ulong, serialized_params, lamports );
560 0 : serialized_params += sizeof(ulong);
561 :
562 0 : ulong acc_data_len = metadata->dlen;
563 0 : FD_STORE( ulong, serialized_params, acc_data_len );
564 0 : serialized_params += sizeof(ulong);
565 :
566 0 : write_account( &view_acc, (uchar)i, &serialized_params, &curr_serialized_params_start,
567 0 : input_mem_regions, input_mem_regions_cnt, acc_region_metas, 0, copy_account_data );
568 :
569 0 : fd_pubkey_t owner = *(fd_pubkey_t *)&metadata->info.owner;
570 0 : FD_STORE( fd_pubkey_t, serialized_params, owner );
571 0 : serialized_params += sizeof(fd_pubkey_t);
572 :
573 0 : uchar is_executable = (uchar)metadata->info.executable;
574 0 : FD_STORE( uchar, serialized_params, is_executable );
575 0 : serialized_params += sizeof(uchar);
576 :
577 0 : ulong rent_epoch = mask_out_rent_epoch_in_vm_serialization ? ULONG_MAX : metadata->info.rent_epoch;
578 0 : FD_STORE( ulong, serialized_params, rent_epoch );
579 0 : serialized_params += sizeof(ulong);
580 0 : }
581 0 : }
582 :
583 0 : ulong instr_data_len = ctx->instr->data_sz;
584 0 : FD_STORE( ulong, serialized_params, instr_data_len );
585 0 : serialized_params += sizeof(ulong);
586 :
587 0 : uchar * instr_data = (uchar *)ctx->instr->data;
588 0 : fd_memcpy( serialized_params, instr_data, instr_data_len );
589 0 : serialized_params += instr_data_len;
590 :
591 0 : FD_STORE( fd_pubkey_t, serialized_params, txn_accs[ctx->instr->program_id] );
592 0 : serialized_params += sizeof(fd_pubkey_t);
593 :
594 0 : FD_TEST( serialized_params == serialized_params_start + serialized_size );
595 0 : *sz = serialized_size;
596 :
597 0 : new_input_mem_region( input_mem_regions, input_mem_regions_cnt, curr_serialized_params_start,
598 0 : (ulong)(serialized_params - curr_serialized_params_start), 1U, 0U );
599 :
600 0 : return serialized_params_start;
601 0 : }
602 :
603 : static int
604 : fd_bpf_loader_input_deserialize_unaligned( fd_exec_instr_ctx_t * ctx,
605 : ulong const * pre_lens,
606 : uchar * input,
607 : ulong input_sz,
608 0 : int copy_account_data ) {
609 0 : uchar * input_cursor = input;
610 0 : uchar acc_idx_seen[256] = {0};
611 :
612 0 : input_cursor += sizeof(ulong);
613 :
614 0 : for( ushort i=0; i<ctx->instr->acct_cnt; i++ ) {
615 0 : uchar acc_idx = (uchar)ctx->instr->accounts[i].index_in_transaction;
616 :
617 0 : input_cursor++; /* is_dup */
618 0 : if( FD_UNLIKELY( acc_idx_seen[acc_idx] ) ) {
619 : /* no-op */
620 0 : } else {
621 0 : acc_idx_seen[acc_idx] = 1;
622 0 : input_cursor += sizeof(uchar) + /* is_signer */
623 0 : sizeof(uchar) + /* is_writable */
624 0 : sizeof(fd_pubkey_t); /* key */
625 :
626 : /* https://github.com/anza-xyz/agave/blob/v2.1.4/programs/bpf_loader/src/serialization.rs#L378 */
627 0 : fd_guarded_borrowed_account_t view_acc;
628 0 : FD_TRY_BORROW_INSTR_ACCOUNT_DEFAULT_ERR_CHECK( ctx, i, &view_acc );
629 :
630 0 : ulong lamports = FD_LOAD( ulong, input_cursor );
631 0 : if( fd_borrowed_account_get_acc_meta( &view_acc ) && fd_borrowed_account_get_lamports( &view_acc )!=lamports ) {
632 0 : int err = fd_borrowed_account_set_lamports( &view_acc, lamports );
633 0 : if( FD_UNLIKELY( err ) ) {
634 0 : return err;
635 0 : }
636 0 : }
637 :
638 0 : input_cursor += sizeof(ulong); /* lamports */
639 0 : input_cursor += sizeof(ulong); /* data length */
640 :
641 0 : if( copy_account_data ) {
642 0 : ulong pre_len = pre_lens[i];
643 0 : uchar * post_data = input_cursor;
644 0 : if( fd_borrowed_account_get_acc_meta( &view_acc ) ) {
645 0 : int err = 0;
646 0 : if( fd_borrowed_account_can_data_be_resized( &view_acc, pre_len, &err ) &&
647 0 : fd_borrowed_account_can_data_be_changed( &view_acc, &err ) ) {
648 0 : err = fd_borrowed_account_set_data_from_slice( &view_acc, post_data, pre_len );
649 0 : if( FD_UNLIKELY( err ) ) {
650 0 : return err;
651 0 : }
652 0 : } else if( fd_borrowed_account_get_data_len( &view_acc ) != pre_len ||
653 0 : memcmp( post_data, fd_borrowed_account_get_data( &view_acc ), pre_len ) ) {
654 0 : return err;
655 0 : }
656 0 : }
657 0 : input_cursor += pre_len;
658 0 : }
659 0 : input_cursor += sizeof(fd_pubkey_t) + /* owner */
660 0 : sizeof(uchar) + /* executable */
661 0 : sizeof(ulong); /* rent_epoch*/
662 0 : }
663 0 : }
664 :
665 0 : if( FD_UNLIKELY( input_cursor>input+input_sz ) ) {
666 0 : return FD_EXECUTOR_INSTR_ERR_INVALID_ARG;
667 0 : }
668 :
669 0 : return 0;
670 0 : }
671 :
672 : /* https://github.com/anza-xyz/agave/blob/v2.1.11/programs/bpf_loader/src/serialization.rs#L191-L252 */
673 : int
674 : fd_bpf_loader_input_serialize_parameters( fd_exec_instr_ctx_t * instr_ctx,
675 : ulong * sz,
676 : ulong * pre_lens,
677 : fd_vm_input_region_t * input_mem_regions,
678 : uint * input_mem_regions_cnt,
679 : fd_vm_acc_region_meta_t * acc_region_metas,
680 : int direct_mapping,
681 : int mask_out_rent_epoch_in_vm_serialization,
682 : uchar is_deprecated,
683 0 : uchar ** out /* output */ ) {
684 :
685 : /* https://github.com/anza-xyz/agave/blob/v2.1.11/programs/bpf_loader/src/serialization.rs#L203-L206 */
686 0 : ulong num_ix_accounts = instr_ctx->instr->acct_cnt;
687 0 : if( FD_UNLIKELY( num_ix_accounts>=FD_INSTR_ACCT_MAX ) ) {
688 0 : return FD_EXECUTOR_INSTR_ERR_MAX_ACCS_EXCEEDED;
689 0 : }
690 :
691 : /* TODO: Like Agave's serialization functions, ours should probably return error codes
692 :
693 : https://github.com/anza-xyz/agave/blob/v2.1.11/programs/bpf_loader/src/serialization.rs#L237-L251 */
694 0 : if( FD_UNLIKELY( is_deprecated ) ) {
695 0 : *out = fd_bpf_loader_input_serialize_unaligned( instr_ctx, sz, pre_lens,
696 0 : input_mem_regions, input_mem_regions_cnt,
697 0 : acc_region_metas, !direct_mapping,
698 0 : mask_out_rent_epoch_in_vm_serialization );
699 0 : } else {
700 0 : *out = fd_bpf_loader_input_serialize_aligned( instr_ctx, sz, pre_lens,
701 0 : input_mem_regions, input_mem_regions_cnt,
702 0 : acc_region_metas, !direct_mapping,
703 0 : mask_out_rent_epoch_in_vm_serialization );
704 0 : }
705 :
706 0 : return FD_EXECUTOR_INSTR_SUCCESS;
707 0 : }
708 :
709 : /* https://github.com/anza-xyz/agave/blob/v2.1.11/programs/bpf_loader/src/serialization.rs#L254-L283 */
710 : int
711 : fd_bpf_loader_input_deserialize_parameters( fd_exec_instr_ctx_t * ctx,
712 : ulong const * pre_lens,
713 : uchar * input,
714 : ulong input_sz,
715 : int direct_mapping,
716 0 : uchar is_deprecated ) {
717 0 : if( FD_UNLIKELY( is_deprecated ) ) {
718 0 : return fd_bpf_loader_input_deserialize_unaligned( ctx, pre_lens, input, input_sz, !direct_mapping );
719 0 : } else {
720 0 : return fd_bpf_loader_input_deserialize_aligned( ctx, pre_lens, input, input_sz, !direct_mapping );
721 0 : }
722 0 : }
|