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