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 ? account->acct->const_data : NULL;
83 0 : ulong dlen = account ? account->acct->const_meta->dlen : 0UL;
84 :
85 0 : if( copy_account_data ) {
86 : /* Copy the account data into input region buffer */
87 0 : fd_memcpy( *serialized_params, data, dlen );
88 0 : *serialized_params += dlen;
89 :
90 0 : if( FD_LIKELY( is_aligned ) ) {
91 : /* Zero out padding bytes and max permitted data increase */
92 0 : ulong align_offset = fd_ulong_align_up( dlen, FD_BPF_ALIGN_OF_U128 ) - dlen;
93 0 : fd_memset( *serialized_params, 0, MAX_PERMITTED_DATA_INCREASE + align_offset );
94 0 : *serialized_params += MAX_PERMITTED_DATA_INCREASE + align_offset;
95 0 : }
96 0 : } else { /* direct_mapping == true */
97 : /* First, push on the region for the metadata that has just been serialized.
98 : This function will push the metadata in the serialized_params from
99 : serialized_params_start to serialized_params as a region to the input
100 : memory regions array. */
101 : /* TODO: This region always has length of 96 and this can be set as a constant. */
102 :
103 0 : ulong region_sz = (ulong)(*serialized_params) - (ulong)(*serialized_params_start);
104 0 : new_input_mem_region( input_mem_regions, input_mem_regions_cnt, *serialized_params_start, region_sz, 1U, 0U );
105 :
106 : /* Next, push the region for the account data if there is account data. We
107 : intentionally omit copy on write as a region type. */
108 0 : int err = 0;
109 0 : uchar is_writable = !!(fd_borrowed_account_can_data_be_changed( account, &err ) && !err);
110 :
111 : /* Update the mapping from instruction account index to memory region index.
112 : This is an optimization to avoid redundant lookups to find accounts. */
113 0 : acc_region_metas[instr_acc_idx].region_idx = *input_mem_regions_cnt;
114 0 : acc_region_metas[instr_acc_idx].has_data_region = !!dlen;
115 0 : acc_region_metas[instr_acc_idx].has_resizing_region = (uchar)is_aligned;
116 :
117 0 : if( dlen ) {
118 0 : new_input_mem_region( input_mem_regions, input_mem_regions_cnt, data, dlen, is_writable, 1U );
119 0 : }
120 :
121 0 : if( FD_LIKELY( is_aligned ) ) {
122 : /* Finally, push a third region for the max resizing data region. This is
123 : done even if there is no account data. This must be aligned so padding
124 : bytes must be inserted. This resizing region is also padded to result
125 : in 8 byte alignment for the combination of the account data region with
126 : the resizing region.
127 :
128 : We add the max permitted resizing limit along with 8 bytes of padding
129 : to the serialization buffer. However, the padding bytes are used to
130 : maintain alignment in the VM virtual address space. */
131 0 : ulong align_offset = fd_ulong_align_up( dlen, FD_BPF_ALIGN_OF_U128 ) - dlen;
132 :
133 0 : fd_memset( *serialized_params, 0, MAX_PERMITTED_DATA_INCREASE + FD_BPF_ALIGN_OF_U128 );
134 :
135 : /* Leave a gap for alignment */
136 0 : uchar * region_buffer = *serialized_params + (FD_BPF_ALIGN_OF_U128 - align_offset);
137 0 : ulong region_sz = MAX_PERMITTED_DATA_INCREASE + align_offset;
138 0 : new_input_mem_region( input_mem_regions, input_mem_regions_cnt, region_buffer, region_sz, is_writable, 1U );
139 :
140 0 : *serialized_params += MAX_PERMITTED_DATA_INCREASE + FD_BPF_ALIGN_OF_U128;
141 0 : }
142 0 : *serialized_params_start = *serialized_params;
143 0 : }
144 0 : }
145 :
146 : static uchar *
147 : fd_bpf_loader_input_serialize_aligned( fd_exec_instr_ctx_t * ctx,
148 : ulong * sz,
149 : ulong * pre_lens,
150 : fd_vm_input_region_t * input_mem_regions,
151 : uint * input_mem_regions_cnt,
152 : fd_vm_acc_region_meta_t * acc_region_metas,
153 0 : int copy_account_data ) {
154 0 : uchar const * instr_acc_idxs = ctx->instr->acct_txn_idxs;
155 0 : fd_pubkey_t * txn_accs = ctx->txn_ctx->account_keys;
156 :
157 0 : uchar acc_idx_seen[256] = {0};
158 0 : ushort dup_acc_idx[256] = {0};
159 :
160 : /* https://github.com/anza-xyz/agave/blob/b5f5c3cdd3f9a5859c49ebc27221dc27e143d760/programs/bpf_loader/src/serialization.rs#L429-L459 */
161 0 : ulong serialized_size = 0UL;
162 0 : serialized_size += sizeof(ulong); // acct_cnt
163 : /* First pass is to calculate size of buffer to allocate */
164 0 : for( ushort i=0; i<ctx->instr->acct_cnt; i++ ) {
165 0 : uchar acc_idx = instr_acc_idxs[i];
166 :
167 0 : serialized_size++; // dup byte
168 0 : if( FD_UNLIKELY( acc_idx_seen[acc_idx] ) ) {
169 0 : serialized_size += 7UL; // pad to 64-bit alignment
170 0 : } else {
171 0 : acc_idx_seen[acc_idx] = 1;
172 0 : dup_acc_idx[acc_idx] = i;
173 :
174 : /* Borrow the account without checking the error, as it is guaranteed to exist
175 : https://github.com/anza-xyz/agave/blob/v2.1.4/programs/bpf_loader/src/serialization.rs#L225 */
176 0 : fd_guarded_borrowed_account_t view_acc;
177 0 : fd_exec_instr_ctx_try_borrow_account( ctx, i, &view_acc );
178 :
179 0 : ulong acc_data_len = view_acc.acct->const_meta->dlen;
180 :
181 0 : serialized_size += sizeof(uchar) // is_signer
182 0 : + sizeof(uchar) // is_writable
183 0 : + sizeof(uchar) // executable
184 0 : + sizeof(uint) // original_data_len
185 0 : + sizeof(fd_pubkey_t) // key
186 0 : + sizeof(fd_pubkey_t) // owner
187 0 : + sizeof(ulong) // lamports
188 0 : + sizeof(ulong) // data len
189 0 : + MAX_PERMITTED_DATA_INCREASE
190 0 : + sizeof(ulong); // rent_epoch
191 0 : if( copy_account_data ) {
192 0 : serialized_size += fd_ulong_align_up( acc_data_len, FD_BPF_ALIGN_OF_U128 );
193 0 : } else {
194 0 : serialized_size += FD_BPF_ALIGN_OF_U128;
195 0 : }
196 0 : }
197 0 : }
198 :
199 0 : serialized_size += sizeof(ulong) // data len
200 0 : + ctx->instr->data_sz
201 0 : + sizeof(fd_pubkey_t); // program id
202 :
203 0 : uchar * serialized_params = fd_spad_alloc( ctx->txn_ctx->spad, FD_BPF_ALIGN_OF_U128, fd_ulong_align_up( serialized_size, FD_RUNTIME_INPUT_REGION_ALLOC_ALIGN_UP ) );
204 0 : uchar * serialized_params_start = serialized_params;
205 0 : uchar * curr_serialized_params_start = serialized_params;
206 :
207 0 : FD_STORE( ulong, serialized_params, ctx->instr->acct_cnt );
208 0 : serialized_params += sizeof(ulong);
209 :
210 : /* Second pass over the account is to serialize into the buffer. */
211 0 : for( ushort i=0; i<ctx->instr->acct_cnt; i++ ) {
212 0 : uchar acc_idx = instr_acc_idxs[i];
213 0 : fd_pubkey_t * acc = &txn_accs[acc_idx];
214 :
215 0 : if( FD_UNLIKELY( acc_idx_seen[acc_idx] && dup_acc_idx[acc_idx] != i ) ) {
216 : /* Duplicate. Store 8 byte buffer to maintain alignment but store the
217 : account index in the first byte.*/
218 0 : FD_STORE( ulong, serialized_params, 0UL );
219 0 : FD_STORE( uchar, serialized_params, (uchar)dup_acc_idx[acc_idx] );
220 0 : serialized_params += sizeof(ulong);
221 0 : } else {
222 : /* Calculate and store the start of the actual metadata region for this account,
223 : excluding any duplicate account markers at the beginning.
224 :
225 : We use this later for retrieving the serialized values later in the CPI security checks. */
226 0 : ulong metadata_region_offset_with_dups = *input_mem_regions_cnt==0UL ? 0UL :
227 0 : input_mem_regions[ *input_mem_regions_cnt-1U ].vaddr_offset +
228 0 : input_mem_regions[ *input_mem_regions_cnt-1U ].region_sz;
229 :
230 0 : acc_region_metas[i].metadata_region_offset = metadata_region_offset_with_dups +
231 0 : (ulong)(serialized_params - curr_serialized_params_start);
232 :
233 0 : FD_STORE( uchar, serialized_params, FD_NON_DUP_MARKER );
234 0 : serialized_params += sizeof(uchar);
235 :
236 : /* Borrow the account without checking the error, as it is guaranteed to exist
237 : https://github.com/anza-xyz/agave/blob/v2.1.4/programs/bpf_loader/src/serialization.rs#L225 */
238 0 : fd_guarded_borrowed_account_t view_acc;
239 0 : fd_exec_instr_ctx_try_borrow_account( ctx, i, &view_acc );
240 :
241 : /* https://github.com/anza-xyz/agave/blob/b5f5c3cdd3f9a5859c49ebc27221dc27e143d760/programs/bpf_loader/src/serialization.rs#L465 */
242 0 : fd_account_meta_t const * metadata = view_acc.acct->const_meta;
243 :
244 0 : uchar is_signer = (uchar)fd_instr_acc_is_signer_idx( ctx->instr, (uchar)i );
245 0 : FD_STORE( uchar, serialized_params, is_signer );
246 0 : serialized_params += sizeof(uchar);
247 :
248 0 : uchar is_writable = (uchar)fd_instr_acc_is_writable_idx( ctx->instr, (uchar)i );
249 0 : FD_STORE( uchar, serialized_params, is_writable );
250 0 : serialized_params += sizeof(uchar);
251 :
252 0 : uchar is_executable = (uchar)metadata->info.executable;
253 0 : FD_STORE( uchar, serialized_params, is_executable );
254 0 : serialized_params += sizeof(uchar);
255 :
256 : /* The original data len field is intentionally NOT populated. */
257 0 : uint padding_0 = 0U;
258 0 : FD_STORE( uint, serialized_params, padding_0 );
259 0 : serialized_params += sizeof(uint);
260 :
261 0 : fd_pubkey_t key = *acc;
262 0 : FD_STORE( fd_pubkey_t, serialized_params, key );
263 0 : serialized_params += sizeof(fd_pubkey_t);
264 :
265 0 : fd_pubkey_t owner = *(fd_pubkey_t *)&metadata->info.owner;
266 0 : FD_STORE( fd_pubkey_t, serialized_params, owner );
267 0 : serialized_params += sizeof(fd_pubkey_t);
268 :
269 0 : ulong lamports = metadata->info.lamports;
270 0 : FD_STORE( ulong, serialized_params, lamports );
271 0 : serialized_params += sizeof(ulong);
272 :
273 0 : ulong acc_data_len = metadata->dlen;
274 0 : pre_lens[i] = acc_data_len;
275 :
276 0 : ulong data_len = acc_data_len;
277 0 : FD_STORE( ulong, serialized_params, data_len );
278 0 : serialized_params += sizeof(ulong);
279 :
280 0 : write_account( &view_acc, (uchar)i, &serialized_params, &curr_serialized_params_start,
281 0 : input_mem_regions, input_mem_regions_cnt, acc_region_metas, 1, copy_account_data );
282 :
283 0 : ulong rent_epoch = metadata->info.rent_epoch;
284 0 : FD_STORE( ulong, serialized_params, rent_epoch );
285 0 : serialized_params += sizeof(ulong);
286 0 : }
287 :
288 0 : }
289 :
290 0 : ulong instr_data_len = ctx->instr->data_sz;
291 0 : FD_STORE( ulong, serialized_params, instr_data_len );
292 0 : serialized_params += sizeof(ulong);
293 :
294 0 : uchar * instr_data = ctx->instr->data;
295 0 : fd_memcpy( serialized_params, instr_data, instr_data_len );
296 0 : serialized_params += instr_data_len;
297 :
298 0 : FD_STORE( fd_pubkey_t, serialized_params, txn_accs[ctx->instr->program_id] );
299 0 : serialized_params += sizeof(fd_pubkey_t);
300 :
301 0 : if( FD_UNLIKELY( serialized_params!=serialized_params_start+serialized_size ) ) {
302 0 : FD_LOG_ERR(( "Serializing error" )); /* TODO: we can likely get rid of this check altogether */
303 0 : }
304 :
305 : /* Write out the final region. */
306 0 : new_input_mem_region( input_mem_regions, input_mem_regions_cnt, curr_serialized_params_start,
307 0 : (ulong)(serialized_params - curr_serialized_params_start), 1U, 0U );
308 :
309 0 : *sz = serialized_size;
310 :
311 0 : return serialized_params_start;
312 0 : }
313 :
314 : /* https://github.com/anza-xyz/agave/blob/b5f5c3cdd3f9a5859c49ebc27221dc27e143d760/programs/bpf_loader/src/serialization.rs#L500-L603 */
315 : static int
316 : fd_bpf_loader_input_deserialize_aligned( fd_exec_instr_ctx_t * ctx,
317 : ulong const * pre_lens,
318 : uchar * buffer,
319 : ulong FD_FN_UNUSED buffer_sz,
320 0 : int copy_account_data ) {
321 : /* TODO: An optimization would be to skip ahead through non-writable accounts */
322 : /* https://github.com/anza-xyz/agave/blob/b5f5c3cdd3f9a5859c49ebc27221dc27e143d760/programs/bpf_loader/src/serialization.rs#L507 */
323 0 : ulong start = 0UL;
324 :
325 0 : uchar acc_idx_seen[256] = {0};
326 :
327 0 : uchar const * instr_acc_idxs = ctx->instr->acct_txn_idxs;
328 :
329 0 : start += sizeof(ulong); // number of accounts
330 : /* https://github.com/anza-xyz/agave/blob/b5f5c3cdd3f9a5859c49ebc27221dc27e143d760/programs/bpf_loader/src/serialization.rs#L508-L600 */
331 0 : for( ulong i=0UL; i<ctx->instr->acct_cnt; i++ ) {
332 0 : uchar acc_idx = instr_acc_idxs[i];
333 :
334 0 : start++; // position
335 :
336 : /* get the borrowed account
337 : https://github.com/anza-xyz/agave/blob/v2.1.4/programs/bpf_loader/src/serialization.rs#L519 */
338 0 : fd_guarded_borrowed_account_t view_acc;
339 0 : FD_TRY_BORROW_INSTR_ACCOUNT_DEFAULT_ERR_CHECK( ctx, i, &view_acc );
340 :
341 0 : if( FD_UNLIKELY( acc_idx_seen[acc_idx] ) ) {
342 : /* https://github.com/anza-xyz/agave/blob/b5f5c3cdd3f9a5859c49ebc27221dc27e143d760/programs/bpf_loader/src/serialization.rs#L515-517 */
343 0 : start += 7UL;
344 0 : } else {
345 : /* https://github.com/anza-xyz/agave/blob/b5f5c3cdd3f9a5859c49ebc27221dc27e143d760/programs/bpf_loader/src/serialization.rs#L518-524 */
346 0 : acc_idx_seen[acc_idx] = 1;
347 0 : start += sizeof(uchar) // is_signer
348 0 : + sizeof(uchar) // is_writable
349 0 : + sizeof(uchar) // executable
350 0 : + sizeof(uint) // original_data_len
351 0 : + sizeof(fd_pubkey_t); // key
352 :
353 : /* https://github.com/anza-xyz/agave/blob/b5f5c3cdd3f9a5859c49ebc27221dc27e143d760/programs/bpf_loader/src/serialization.rs#L525-548 */
354 :
355 0 : fd_pubkey_t * owner = (fd_pubkey_t *)(buffer+start);
356 0 : start += sizeof(fd_pubkey_t); // owner
357 :
358 0 : ulong lamports = FD_LOAD( ulong, buffer+start );
359 0 : if( lamports!=view_acc.acct->const_meta->info.lamports ) {
360 0 : int err = fd_borrowed_account_set_lamports( &view_acc, lamports );
361 0 : if( FD_UNLIKELY( err ) ) {
362 0 : return err;
363 0 : }
364 0 : }
365 0 : start += sizeof(ulong); // lamports
366 :
367 0 : ulong post_len = FD_LOAD( ulong, buffer+start );
368 0 : start += sizeof(ulong); // data length
369 :
370 0 : ulong pre_len = pre_lens[i];
371 0 : ulong alignment_offset = fd_ulong_align_up( pre_len, FD_BPF_ALIGN_OF_U128 ) - pre_len;
372 :
373 0 : uchar * post_data = buffer+start;
374 :
375 0 : fd_account_meta_t const * metadata_check = view_acc.acct->const_meta;
376 0 : if( FD_UNLIKELY( fd_ulong_sat_sub( post_len, metadata_check->dlen )>MAX_PERMITTED_DATA_INCREASE ||
377 0 : post_len>MAX_PERMITTED_DATA_LENGTH ) ) {
378 0 : return FD_EXECUTOR_INSTR_ERR_INVALID_REALLOC;
379 0 : }
380 :
381 0 : if( copy_account_data ) {
382 : /* https://github.com/anza-xyz/agave/blob/b5f5c3cdd3f9a5859c49ebc27221dc27e143d760/programs/bpf_loader/src/serialization.rs#L551-563 */
383 0 : int err = 0;
384 0 : if( fd_borrowed_account_can_data_be_resized( &view_acc, post_len, &err ) &&
385 0 : fd_borrowed_account_can_data_be_changed( &view_acc, &err ) ) {
386 :
387 0 : int err = fd_borrowed_account_set_data_from_slice( &view_acc, post_data, post_len );
388 0 : if( FD_UNLIKELY( err ) ) {
389 0 : return err;
390 0 : }
391 :
392 0 : } else if( FD_UNLIKELY( view_acc.acct->const_meta->dlen!=post_len ||
393 0 : memcmp( view_acc.acct->const_data, post_data, post_len ) ) ) {
394 0 : return err;
395 0 : }
396 0 : start += pre_len;
397 0 : } else { /* If direct mapping is enabled */
398 : /* https://github.com/anza-xyz/agave/blob/b5f5c3cdd3f9a5859c49ebc27221dc27e143d760/programs/bpf_loader/src/serialization.rs#L564-587 */
399 0 : start += FD_BPF_ALIGN_OF_U128 - alignment_offset;
400 0 : int err = 0;
401 0 : if( fd_borrowed_account_can_data_be_resized( &view_acc, post_len, &err ) &&
402 0 : fd_borrowed_account_can_data_be_changed( &view_acc, &err ) ) {
403 :
404 0 : err = fd_borrowed_account_set_data_length( &view_acc, post_len );
405 0 : if( FD_UNLIKELY( err ) ) {
406 0 : return err;
407 0 : }
408 :
409 0 : ulong allocated_bytes = fd_ulong_sat_sub( post_len, pre_len );
410 0 : if( allocated_bytes ) {
411 0 : uchar * acc_data = NULL;
412 0 : ulong acc_dlen = 0UL;
413 0 : err = fd_borrowed_account_get_data_mut( &view_acc, &acc_data, &acc_dlen );
414 0 : if( FD_UNLIKELY( err ) ) {
415 0 : return err;
416 0 : }
417 0 : if( FD_UNLIKELY( pre_len+allocated_bytes>acc_dlen ) ) {
418 0 : return FD_EXECUTOR_INSTR_ERR_INVALID_ARG;
419 0 : }
420 : /* We want to copy in the reallocated bytes from the input
421 : buffer directly into the borrowed account data buffer
422 : which has now been extended. */
423 0 : memcpy( acc_data+pre_len, buffer+start, allocated_bytes );
424 0 : }
425 0 : } else if( FD_UNLIKELY( view_acc.acct->const_meta->dlen!=post_len ) ) {
426 0 : return err;
427 0 : }
428 0 : }
429 :
430 : /* https://github.com/anza-xyz/agave/blob/b5f5c3cdd3f9a5859c49ebc27221dc27e143d760/programs/bpf_loader/src/serialization.rs#L593-598 */
431 0 : start += MAX_PERMITTED_DATA_INCREASE;
432 0 : start += alignment_offset;
433 0 : start += sizeof(ulong); // rent epoch
434 0 : if( memcmp( view_acc.acct->const_meta->info.owner, owner, sizeof(fd_pubkey_t) ) ) {
435 0 : int err = fd_borrowed_account_set_owner( &view_acc, owner );
436 0 : if( FD_UNLIKELY( err ) ) {
437 0 : return err;
438 0 : }
439 0 : }
440 0 : }
441 0 : }
442 :
443 0 : return FD_EXECUTOR_INSTR_SUCCESS;
444 0 : }
445 :
446 : static uchar *
447 : fd_bpf_loader_input_serialize_unaligned( fd_exec_instr_ctx_t * ctx,
448 : ulong * sz,
449 : ulong * pre_lens,
450 : fd_vm_input_region_t * input_mem_regions,
451 : uint * input_mem_regions_cnt,
452 : fd_vm_acc_region_meta_t * acc_region_metas,
453 0 : int copy_account_data ) {
454 0 : ulong serialized_size = 0UL;
455 0 : uchar const * instr_acc_idxs = ctx->instr->acct_txn_idxs;
456 0 : fd_pubkey_t const * txn_accs = ctx->txn_ctx->account_keys;
457 :
458 0 : uchar acc_idx_seen[256] = {0};
459 0 : ushort dup_acc_idx[256] = {0};
460 :
461 0 : serialized_size += sizeof(ulong);
462 0 : for( ushort i=0; i<ctx->instr->acct_cnt; i++ ) {
463 0 : uchar acc_idx = instr_acc_idxs[i];
464 :
465 0 : serialized_size++; // dup
466 0 : if( FD_UNLIKELY( acc_idx_seen[acc_idx] ) ) {
467 0 : continue;
468 0 : }
469 :
470 0 : acc_idx_seen[acc_idx] = 1;
471 0 : dup_acc_idx[acc_idx] = i;
472 :
473 : /* Borrow the account without checking the error, as it is guaranteed to exist
474 : https://github.com/anza-xyz/agave/blob/v2.1.4/programs/bpf_loader/src/serialization.rs#L225 */
475 0 : fd_guarded_borrowed_account_t view_acc;
476 0 : fd_exec_instr_ctx_try_borrow_account( ctx, i, &view_acc );
477 :
478 0 : ulong acc_data_len = view_acc.acct->const_meta->dlen;
479 :
480 0 : pre_lens[i] = acc_data_len;
481 :
482 0 : serialized_size += sizeof(uchar) // is_signer
483 0 : + sizeof(uchar) // is_writable
484 0 : + sizeof(fd_pubkey_t) // key
485 0 : + sizeof(ulong) // lamports
486 0 : + sizeof(ulong) // data_len
487 0 : + sizeof(fd_pubkey_t) // owner
488 0 : + sizeof(uchar) // executable
489 0 : + sizeof(ulong); // rent_epoch
490 0 : if( copy_account_data ) {
491 0 : serialized_size += acc_data_len;
492 0 : }
493 0 : }
494 :
495 0 : serialized_size += sizeof(ulong) // instruction data len
496 0 : + ctx->instr->data_sz // instruction data
497 0 : + sizeof(fd_pubkey_t); // program id
498 :
499 0 : uchar * serialized_params = fd_spad_alloc( ctx->txn_ctx->spad, 1UL, serialized_size );
500 0 : uchar * serialized_params_start = serialized_params;
501 0 : uchar * curr_serialized_params_start = serialized_params;
502 :
503 0 : FD_STORE( ulong, serialized_params, ctx->instr->acct_cnt );
504 0 : serialized_params += sizeof(ulong);
505 :
506 0 : for( ushort i=0; i<ctx->instr->acct_cnt; i++ ) {
507 0 : uchar acc_idx = instr_acc_idxs[i];
508 0 : fd_pubkey_t const * acc = &txn_accs[acc_idx];
509 :
510 0 : if( FD_UNLIKELY( acc_idx_seen[acc_idx] && dup_acc_idx[acc_idx] != i ) ) {
511 : // Duplicate
512 0 : FD_STORE( uchar, serialized_params, (uchar)dup_acc_idx[acc_idx] );
513 0 : serialized_params += sizeof(uchar);
514 0 : } else {
515 : /* Calculate and store the start of the actual metadata region for this account,
516 : excluding any duplicate account markers at the beginning.
517 :
518 : We use this later for retrieving the serialized values later in the CPI security checks. */
519 0 : ulong metadata_region_offset_with_dups = *input_mem_regions_cnt==0UL ? 0UL :
520 0 : input_mem_regions[ *input_mem_regions_cnt-1U ].vaddr_offset +
521 0 : input_mem_regions[ *input_mem_regions_cnt-1U ].region_sz;
522 :
523 0 : acc_region_metas[i].metadata_region_offset = metadata_region_offset_with_dups +
524 0 : (ulong)(serialized_params - curr_serialized_params_start);
525 :
526 0 : FD_STORE( uchar, serialized_params, FD_NON_DUP_MARKER );
527 0 : serialized_params += sizeof(uchar);
528 :
529 : /* Borrow the account without checking the error, as it is guaranteed to exist
530 : https://github.com/anza-xyz/agave/blob/v2.1.4/programs/bpf_loader/src/serialization.rs#L225 */
531 0 : fd_guarded_borrowed_account_t view_acc;
532 0 : fd_exec_instr_ctx_try_borrow_account( ctx, i, &view_acc );
533 :
534 0 : fd_account_meta_t const * metadata = view_acc.acct->const_meta;
535 :
536 0 : uchar is_signer = (uchar)fd_instr_acc_is_signer_idx( ctx->instr, (uchar)i );
537 0 : FD_STORE( uchar, serialized_params, is_signer );
538 0 : serialized_params += sizeof(uchar);
539 :
540 0 : uchar is_writable = (uchar)fd_instr_acc_is_writable_idx( ctx->instr, (uchar)i );
541 0 : FD_STORE( uchar, serialized_params, is_writable );
542 0 : serialized_params += sizeof(uchar);
543 :
544 0 : fd_pubkey_t key = *acc;
545 0 : FD_STORE( fd_pubkey_t, serialized_params, key );
546 0 : serialized_params += sizeof(fd_pubkey_t);
547 :
548 0 : ulong lamports = metadata->info.lamports;
549 0 : FD_STORE( ulong, serialized_params, lamports );
550 0 : serialized_params += sizeof(ulong);
551 :
552 0 : ulong acc_data_len = metadata->dlen;
553 0 : FD_STORE( ulong, serialized_params, acc_data_len );
554 0 : serialized_params += sizeof(ulong);
555 :
556 0 : write_account( &view_acc, (uchar)i, &serialized_params, &curr_serialized_params_start,
557 0 : input_mem_regions, input_mem_regions_cnt, acc_region_metas, 0, copy_account_data );
558 :
559 0 : fd_pubkey_t owner = *(fd_pubkey_t *)&metadata->info.owner;
560 0 : FD_STORE( fd_pubkey_t, serialized_params, owner );
561 0 : serialized_params += sizeof(fd_pubkey_t);
562 :
563 0 : uchar is_executable = (uchar)metadata->info.executable;
564 0 : FD_STORE( uchar, serialized_params, is_executable );
565 0 : serialized_params += sizeof(uchar);
566 :
567 0 : ulong rent_epoch = metadata->info.rent_epoch;
568 0 : FD_STORE( ulong, serialized_params, rent_epoch );
569 0 : serialized_params += sizeof(ulong);
570 0 : }
571 0 : }
572 :
573 0 : ulong instr_data_len = ctx->instr->data_sz;
574 0 : FD_STORE( ulong, serialized_params, instr_data_len );
575 0 : serialized_params += sizeof(ulong);
576 :
577 0 : uchar * instr_data = (uchar *)ctx->instr->data;
578 0 : fd_memcpy( serialized_params, instr_data, instr_data_len );
579 0 : serialized_params += instr_data_len;
580 :
581 0 : FD_STORE( fd_pubkey_t, serialized_params, txn_accs[ctx->instr->program_id] );
582 0 : serialized_params += sizeof(fd_pubkey_t);
583 :
584 0 : FD_TEST( serialized_params == serialized_params_start + serialized_size );
585 0 : *sz = serialized_size;
586 :
587 0 : new_input_mem_region( input_mem_regions, input_mem_regions_cnt, curr_serialized_params_start,
588 0 : (ulong)(serialized_params - curr_serialized_params_start), 1U, 0U );
589 :
590 0 : return serialized_params_start;
591 0 : }
592 :
593 : static int
594 : fd_bpf_loader_input_deserialize_unaligned( fd_exec_instr_ctx_t * ctx,
595 : ulong const * pre_lens,
596 : uchar * input,
597 : ulong input_sz,
598 0 : int copy_account_data ) {
599 0 : uchar * input_cursor = input;
600 0 : uchar acc_idx_seen[256] = {0};
601 0 : uchar const * instr_acc_idxs = ctx->instr->acct_txn_idxs;
602 :
603 0 : input_cursor += sizeof(ulong);
604 :
605 0 : for( ulong i=0UL; i<ctx->instr->acct_cnt; i++ ) {
606 0 : uchar acc_idx = instr_acc_idxs[i];
607 :
608 0 : input_cursor++; /* is_dup */
609 0 : if( FD_UNLIKELY( acc_idx_seen[acc_idx] ) ) {
610 : /* no-op */
611 0 : } else {
612 0 : acc_idx_seen[acc_idx] = 1;
613 0 : input_cursor += sizeof(uchar) + /* is_signer */
614 0 : sizeof(uchar) + /* is_writable */
615 0 : sizeof(fd_pubkey_t); /* key */
616 :
617 : /* https://github.com/anza-xyz/agave/blob/v2.1.4/programs/bpf_loader/src/serialization.rs#L378 */
618 0 : fd_guarded_borrowed_account_t view_acc;
619 0 : FD_TRY_BORROW_INSTR_ACCOUNT_DEFAULT_ERR_CHECK( ctx, i, &view_acc );
620 :
621 0 : ulong lamports = FD_LOAD( ulong, input_cursor );
622 0 : if( view_acc.acct->const_meta && view_acc.acct->const_meta->info.lamports!=lamports ) {
623 0 : int err = fd_borrowed_account_set_lamports( &view_acc, lamports );
624 0 : if( FD_UNLIKELY( err ) ) {
625 0 : return err;
626 0 : }
627 0 : }
628 :
629 0 : input_cursor += sizeof(ulong); /* lamports */
630 0 : input_cursor += sizeof(ulong); /* data length */
631 :
632 0 : if( copy_account_data ) {
633 0 : ulong pre_len = pre_lens[i];
634 0 : uchar * post_data = input_cursor;
635 0 : if( view_acc.acct->const_meta ) {
636 0 : int err = 0;
637 0 : if( fd_borrowed_account_can_data_be_resized( &view_acc, pre_len, &err ) &&
638 0 : fd_borrowed_account_can_data_be_changed( &view_acc, &err ) ) {
639 0 : err = fd_borrowed_account_set_data_from_slice( &view_acc, post_data, pre_len );
640 0 : if( FD_UNLIKELY( err ) ) {
641 0 : return err;
642 0 : }
643 0 : } else if( view_acc.acct->const_meta->dlen != pre_len ||
644 0 : memcmp( post_data, view_acc.acct->const_data, pre_len ) ) {
645 0 : return err;
646 0 : }
647 0 : }
648 0 : input_cursor += pre_len;
649 0 : }
650 0 : input_cursor += sizeof(fd_pubkey_t) + /* owner */
651 0 : sizeof(uchar) + /* executable */
652 0 : sizeof(ulong); /* rent_epoch*/
653 0 : }
654 0 : }
655 :
656 0 : if( FD_UNLIKELY( input_cursor>input+input_sz ) ) {
657 0 : return FD_EXECUTOR_INSTR_ERR_INVALID_ARG;
658 0 : }
659 :
660 0 : return 0;
661 0 : }
662 :
663 : /* https://github.com/anza-xyz/agave/blob/v2.1.11/programs/bpf_loader/src/serialization.rs#L191-L252 */
664 : int
665 : fd_bpf_loader_input_serialize_parameters( fd_exec_instr_ctx_t * instr_ctx,
666 : ulong * sz,
667 : ulong * pre_lens,
668 : fd_vm_input_region_t * input_mem_regions,
669 : uint * input_mem_regions_cnt,
670 : fd_vm_acc_region_meta_t * acc_region_metas,
671 : int direct_mapping,
672 : uchar is_deprecated,
673 0 : uchar ** out /* output */ ) {
674 :
675 : /* https://github.com/anza-xyz/agave/blob/v2.1.11/programs/bpf_loader/src/serialization.rs#L203-L206 */
676 0 : ulong num_ix_accounts = instr_ctx->instr->acct_cnt;
677 0 : if( FD_UNLIKELY( num_ix_accounts>=FD_INSTR_ACCT_MAX ) ) {
678 0 : return FD_EXECUTOR_INSTR_ERR_MAX_ACCS_EXCEEDED;
679 0 : }
680 :
681 : /* TODO: Like Agave's serialization functions, ours should probably return error codes
682 :
683 : https://github.com/anza-xyz/agave/blob/v2.1.11/programs/bpf_loader/src/serialization.rs#L237-L251 */
684 0 : if( FD_UNLIKELY( is_deprecated ) ) {
685 0 : *out = fd_bpf_loader_input_serialize_unaligned( instr_ctx, sz, pre_lens,
686 0 : input_mem_regions, input_mem_regions_cnt,
687 0 : acc_region_metas, !direct_mapping );
688 0 : } else {
689 0 : *out = fd_bpf_loader_input_serialize_aligned( instr_ctx, sz, pre_lens,
690 0 : input_mem_regions, input_mem_regions_cnt,
691 0 : acc_region_metas, !direct_mapping );
692 0 : }
693 :
694 0 : return FD_EXECUTOR_INSTR_SUCCESS;
695 0 : }
696 :
697 : /* https://github.com/anza-xyz/agave/blob/v2.1.11/programs/bpf_loader/src/serialization.rs#L254-L283 */
698 : int
699 : fd_bpf_loader_input_deserialize_parameters( fd_exec_instr_ctx_t * ctx,
700 : ulong const * pre_lens,
701 : uchar * input,
702 : ulong input_sz,
703 : int direct_mapping,
704 0 : uchar is_deprecated ) {
705 0 : if( FD_UNLIKELY( is_deprecated ) ) {
706 0 : return fd_bpf_loader_input_deserialize_unaligned( ctx, pre_lens, input, input_sz, !direct_mapping );
707 0 : } else {
708 0 : return fd_bpf_loader_input_deserialize_aligned( ctx, pre_lens, input, input_sz, !direct_mapping );
709 0 : }
710 0 : }
|