sui_verifier_latest/
entry_points_verifier.rs

1// Copyright (c) Mysten Labs, Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4use move_binary_format::{
5    CompiledModule,
6    file_format::{AbilitySet, Bytecode, FunctionDefinition, SignatureToken, Visibility},
7};
8use move_bytecode_utils::format_signature_token;
9use move_vm_config::verifier::VerifierConfig;
10use sui_types::randomness_state::is_mutable_random;
11use sui_types::{
12    SUI_FRAMEWORK_ADDRESS,
13    base_types::{TX_CONTEXT_MODULE_NAME, TX_CONTEXT_STRUCT_NAME, TxContext, TxContextKind},
14    clock::Clock,
15    error::ExecutionError,
16    is_object, is_object_vector, is_primitive,
17    move_package::{FnInfoMap, is_test_fun},
18    transfer::Receiving,
19};
20
21use crate::{INIT_FN_NAME, verification_failure};
22
23/// Checks valid rules for entry points, both for module initialization and transactions
24///
25/// For module initialization
26/// - The existence of the function is optional
27/// - The function must have the name specified by `INIT_FN_NAME`
28/// - The function must have `Visibility::Private`
29/// - The function can have at most two parameters:
30///   - mandatory &mut TxContext or &TxContext (see `is_tx_context`) in the last position
31///   - optional one-time witness type (see one_time_witness verifier pass) passed by value in the
32///     first position
33///
34/// For transaction entry points
35/// - The function must have `is_entry` true
36/// - The function may have a &mut TxContext or &TxContext (see `is_tx_context`) parameter
37///   - The transaction context parameter must be the last parameter
38/// - The function cannot have any return values
39pub fn verify_module(
40    module: &CompiledModule,
41    fn_info_map: &FnInfoMap,
42    verifier_config: &VerifierConfig,
43) -> Result<(), ExecutionError> {
44    // When verifying test functions, a check preventing explicit calls to init functions is
45    // disabled.
46
47    for func_def in &module.function_defs {
48        let handle = module.function_handle_at(func_def.function);
49        let name = module.identifier_at(handle.name);
50
51        // allow calling init function in the test code
52        if !is_test_fun(name, module, fn_info_map) {
53            verify_init_not_called(module, func_def).map_err(verification_failure)?;
54        }
55
56        if name == INIT_FN_NAME {
57            verify_init_function(module, func_def).map_err(verification_failure)?;
58            continue;
59        }
60
61        // find candidate entry functions and check their parameters
62        // (ignore other functions)
63        if !func_def.is_entry {
64            // it's not an entry function
65            continue;
66        }
67        verify_entry_function_impl(module, func_def, verifier_config)
68            .map_err(verification_failure)?;
69    }
70    Ok(())
71}
72
73fn verify_init_not_called(
74    module: &CompiledModule,
75    fdef: &FunctionDefinition,
76) -> Result<(), String> {
77    let code = match &fdef.code {
78        None => return Ok(()),
79        Some(code) => code,
80    };
81    code.code
82        .iter()
83        .enumerate()
84        .filter_map(|(idx, instr)| match instr {
85            Bytecode::Call(fhandle_idx) => Some((idx, module.function_handle_at(*fhandle_idx))),
86            Bytecode::CallGeneric(finst_idx) => {
87                let finst = module.function_instantiation_at(*finst_idx);
88                Some((idx, module.function_handle_at(finst.handle)))
89            }
90            _ => None,
91        })
92        .try_for_each(|(idx, fhandle)| {
93            let name = module.identifier_at(fhandle.name);
94            if name == INIT_FN_NAME {
95                Err(format!(
96                    "{}::{} at offset {}. Cannot call a module's '{}' function from another Move function",
97                    module.self_id(),
98                    name,
99                    idx,
100                    INIT_FN_NAME
101                ))
102            } else {
103                Ok(())
104            }
105        })
106}
107
108/// Checks if this module has a conformant `init`
109fn verify_init_function(module: &CompiledModule, fdef: &FunctionDefinition) -> Result<(), String> {
110    if fdef.visibility != Visibility::Private {
111        return Err(format!(
112            "{}. '{}' function must be private",
113            module.self_id(),
114            INIT_FN_NAME
115        ));
116    }
117
118    if fdef.is_entry {
119        return Err(format!(
120            "{}. '{}' cannot be 'entry'",
121            module.self_id(),
122            INIT_FN_NAME
123        ));
124    }
125
126    let fhandle = module.function_handle_at(fdef.function);
127    if !fhandle.type_parameters.is_empty() {
128        return Err(format!(
129            "{}. '{}' function cannot have type parameters",
130            module.self_id(),
131            INIT_FN_NAME
132        ));
133    }
134
135    if !module.signature_at(fhandle.return_).is_empty() {
136        return Err(format!(
137            "{}, '{}' function cannot have return values",
138            module.self_id(),
139            INIT_FN_NAME
140        ));
141    }
142
143    let parameters = &module.signature_at(fhandle.parameters).0;
144    if parameters.is_empty() || parameters.len() > 2 {
145        return Err(format!(
146            "Expected at least one and at most two parameters for {}::{}",
147            module.self_id(),
148            INIT_FN_NAME,
149        ));
150    }
151
152    // Checking only the last (and possibly the only) parameter here. If there are two parameters,
153    // then the first parameter must be of a one-time witness type and must be passed by value. This
154    // is checked by the verifier for pass one-time witness value (one_time_witness_verifier) -
155    // please see the description of this pass for additional details.
156    if TxContext::kind(module, &parameters[parameters.len() - 1]) != TxContextKind::None {
157        Ok(())
158    } else {
159        Err(format!(
160            "Expected last parameter for {0}::{1} to be &mut {2}::{3}::{4} or &{2}::{3}::{4}, \
161            but found {5}",
162            module.self_id(),
163            INIT_FN_NAME,
164            SUI_FRAMEWORK_ADDRESS,
165            TX_CONTEXT_MODULE_NAME,
166            TX_CONTEXT_STRUCT_NAME,
167            format_signature_token(module, &parameters[0]),
168        ))
169    }
170}
171
172fn verify_entry_function_impl(
173    view: &CompiledModule,
174    func_def: &FunctionDefinition,
175    verifier_config: &VerifierConfig,
176) -> Result<(), String> {
177    let handle = view.function_handle_at(func_def.function);
178    let params = view.signature_at(handle.parameters);
179
180    let all_non_ctx_params = match params.0.last() {
181        Some(last_param) if TxContext::kind(view, last_param) != TxContextKind::None => {
182            &params.0[0..params.0.len() - 1]
183        }
184        _ => &params.0,
185    };
186    for param in all_non_ctx_params {
187        verify_param_type(view, &handle.type_parameters, param, verifier_config)?;
188    }
189
190    for return_ty in &view.signature_at(handle.return_).0 {
191        verify_return_type(view, &handle.type_parameters, return_ty)?;
192    }
193
194    Ok(())
195}
196
197fn verify_return_type(
198    view: &CompiledModule,
199    type_parameters: &[AbilitySet],
200    return_ty: &SignatureToken,
201) -> Result<(), String> {
202    if matches!(
203        return_ty,
204        SignatureToken::Reference(_) | SignatureToken::MutableReference(_)
205    ) {
206        return Err("Invalid entry point return type. Expected a non reference type.".to_owned());
207    }
208    let abilities = view
209        .abilities(return_ty, type_parameters)
210        .map_err(|e| format!("Unexpected CompiledModule error: {}", e))?;
211    if abilities.has_drop() {
212        Ok(())
213    } else {
214        Err(format!(
215            "Invalid entry point return type. \
216            The specified return type does not have the 'drop' ability: {}",
217            format_signature_token(view, return_ty),
218        ))
219    }
220}
221
222fn verify_param_type(
223    view: &CompiledModule,
224    function_type_args: &[AbilitySet],
225    param: &SignatureToken,
226    verifier_config: &VerifierConfig,
227) -> Result<(), String> {
228    // Only `sui::sui_system` is allowed to expose entry functions that accept a mutable clock
229    // parameter.
230    if Clock::is_mutable(view, param) {
231        return Err(format!(
232            "Invalid entry point parameter type. Clock must be passed by immutable reference. got: \
233             {}",
234            format_signature_token(view, param),
235        ));
236    }
237
238    // Only `sui::sui_system` is allowed to expose entry functions that accept a mutable Random
239    // parameter.
240    if verifier_config.reject_mutable_random_on_entry_functions && is_mutable_random(view, param) {
241        return Err(format!(
242            "Invalid entry point parameter type. Random must be passed by immutable reference. got: \
243             {}",
244            format_signature_token(view, param),
245        ));
246    }
247
248    if is_primitive(view, function_type_args, param)
249        || is_object(view, function_type_args, param)?
250        || is_object_vector(view, function_type_args, param)?
251        || Receiving::is_receiving(view, param)
252    {
253        Ok(())
254    } else {
255        Err(format!(
256            "Invalid entry point parameter type. Expected primitive or object type. Got: {}",
257            format_signature_token(view, param)
258        ))
259    }
260}