Skip to content

Commit bbd5caf

Browse files
wrwgvgao1996rahxephon89
authored
[security] Cherry-picking recent security fixes from Aptos (move-language#950)
* [verifier] limit the number of back edges * [verifier] fix incorrect error code for per-module back edge limit check * copyloc-pop test (move-language#54) * [gas] allow natives to read the gas balance * [bytecode-verifier] Add metering logic and apply to absint based analysis (move-language#58) This adds a simple meter to the bytecode verifier which counts the number of abstract operations performed and can enforce a limit. The meter is for now only connected to locals and reference analysis, but plumped to all phases of the verifier so they can easily make use of it. A set of test cases have been added which exercise the new meter for a number of known pathological cases. PR history: - Add metering in type safety, to capture cost of very large types. This reduces timing of large_type_test to 1/4 - Adjusting max metering units upwards and adding a new sample which needs it - Addressing reviewer comments - Add links to security advisories, and verify that all are covered. - Switching metering granularity from function to module. - Adding non-linear growing penalty to using input refs x output refs relations (bicycles), for dealing better with `test_bicliques`. Adding printing size in benchmarks. * [bytecode verifer] Adjust metering to decrease runtime of some tests. (move-language#62) Specifically the test below now runs in 1/2 of the time. This adjustment appeard useful because the overall time allocation had to be increased to 8000 million units in production. Adjusted this as the default here too. ``` --> test_merge_state: verification time: 59.414ms, result: CONSTRAINT_NOT_SATISFIED, size: 63kb ``` Also adjusts the default to what aptos uses now in production. * [bytecode verifier] Meter type instantiations (move-language#64) Instead of just metering size of types on the operand stack, also meter size of type instantiations in calls and other places. This e.g. capture the size of types in calls like `f<T>()`, where the type does not appear on the operand stack. --------- Co-authored-by: Victor Gao <[email protected]> Co-authored-by: Teng Zhang <[email protected]>
1 parent fc1871b commit bbd5caf

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

45 files changed

+2050
-184
lines changed

Cargo.lock

+1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

language/move-binary-format/src/control_flow_graph.rs

+9
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,9 @@ pub trait ControlFlowGraph {
4545
/// Checks if the the edge from cur->next is a back edge
4646
/// returns false if the edge is not in the cfg
4747
fn is_back_edge(&self, cur: BlockId, next: BlockId) -> bool;
48+
49+
/// Return the number of back edges in the cfg
50+
fn num_back_edges(&self) -> usize;
4851
}
4952

5053
struct BasicBlock {
@@ -325,4 +328,10 @@ impl ControlFlowGraph for VMControlFlowGraph {
325328
.get(&next)
326329
.map_or(false, |back_edges| back_edges.contains(&cur))
327330
}
331+
332+
fn num_back_edges(&self) -> usize {
333+
self.loop_heads
334+
.iter()
335+
.fold(0, |acc, (_, edges)| acc + edges.len())
336+
}
328337
}

language/move-borrow-graph/src/graph.rs

+8
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,14 @@ impl<Loc: Copy, Lbl: Clone + Ord> BorrowGraph<Loc, Lbl> {
2626
Self(BTreeMap::new())
2727
}
2828

29+
/// Returns the graph size, that is the number of nodes + number of edges
30+
pub fn graph_size(&self) -> usize {
31+
self.0
32+
.values()
33+
.map(|r| 1 + r.borrowed_by.0.values().map(|e| e.len()).sum::<usize>())
34+
.sum()
35+
}
36+
2937
/// checks if the given reference is mutable or not
3038
pub fn is_mutable(&self, id: RefID) -> bool {
3139
self.0.get(&id).unwrap().mutable

language/move-bytecode-verifier/bytecode-verifier-tests/Cargo.toml

+3-2
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,12 @@ edition = "2021"
1313
petgraph = "0.5.1"
1414
proptest = "1.0.0"
1515
fail = { version = "0.4.0", features = ['failpoints']}
16-
16+
hex = "0.4.3"
1717
move-bytecode-verifier = { path = "../" }
1818
invalid-mutations = { path = "../invalid-mutations" }
1919
move-core-types = { path = "../../move-core/types" }
20-
move-binary-format = { path = "../../move-binary-format", features = ["fuzzing"] }
20+
move-binary-format = { path = "../../move-binary-format", features = ["fuzzing" ] }
2121

2222
[features]
2323
fuzzing = ["move-binary-format/fuzzing"]
24+
address32 = ["move-core-types/address32"]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
This testsuite can be run in a specific way to print the time until a 'complex' program is detected or accepted. Call as in:
2+
3+
```
4+
cargo test --release --features=address32 -- --nocapture 1>/dev/null
5+
```

language/move-bytecode-verifier/bytecode-verifier-tests/src/unit_tests/binary_samples.rs

+83
Large diffs are not rendered by default.

language/move-bytecode-verifier/bytecode-verifier-tests/src/unit_tests/catch_unwind.rs

+4-1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@ use move_core_types::{
99
};
1010
use std::panic::{self, PanicInfo};
1111

12+
// TODO: this tests must run in its own process since otherwise any crashing test here
13+
// secondary-crashes in the panic handler.
14+
#[ignore]
1215
#[test]
1316
fn test_unwind() {
1417
let scenario = FailScenario::setup();
@@ -19,7 +22,7 @@ fn test_unwind() {
1922
}));
2023

2124
let m = empty_module();
22-
let res = move_bytecode_verifier::verify_module_with_config(&VerifierConfig::default(), &m)
25+
let res = move_bytecode_verifier::verify_module_with_config(&VerifierConfig::unbounded(), &m)
2326
.unwrap_err();
2427
assert_eq!(res.major_status(), StatusCode::VERIFIER_INVARIANT_VIOLATION);
2528
scenario.teardown();

language/move-bytecode-verifier/bytecode-verifier-tests/src/unit_tests/code_unit_tests.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ fn test_max_number_of_bytecode() {
6868
nops.push(Bytecode::Ret);
6969
let module = dummy_procedure_module(nops);
7070

71-
let result = CodeUnitVerifier::verify_module(&Default::default(), &module);
71+
let result = CodeUnitVerifier::verify_module(&VerifierConfig::unbounded(), &module);
7272
assert!(result.is_ok());
7373
}
7474

language/move-bytecode-verifier/bytecode-verifier-tests/src/unit_tests/constants_tests.rs

+5
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ proptest! {
1414
}
1515

1616
#[test]
17+
#[cfg(not(feature = "address32"))]
1718
fn valid_primitives() {
1819
let mut module = empty_module();
1920
module.constant_pool = vec![
@@ -57,6 +58,7 @@ fn valid_primitives() {
5758
}
5859

5960
#[test]
61+
#[cfg(not(feature = "address32"))]
6062
fn invalid_primitives() {
6163
malformed(SignatureToken::U8, vec![0, 0]);
6264
malformed(SignatureToken::U16, vec![0, 0, 0, 0]);
@@ -72,6 +74,7 @@ fn invalid_primitives() {
7274
}
7375

7476
#[test]
77+
#[cfg(not(feature = "address32"))]
7578
fn valid_vectors() {
7679
let double_vec = |item: Vec<u8>| -> Vec<u8> {
7780
let mut items = vec![2];
@@ -193,6 +196,7 @@ fn valid_vectors() {
193196
}
194197

195198
#[test]
199+
#[cfg(not(feature = "address32"))]
196200
fn invalid_vectors() {
197201
let double_vec = |item: Vec<u8>| -> Vec<u8> {
198202
let mut items = vec![2];
@@ -244,6 +248,7 @@ fn tvec(s: SignatureToken) -> SignatureToken {
244248
SignatureToken::Vector(Box::new(s))
245249
}
246250

251+
#[allow(unused)]
247252
fn malformed(type_: SignatureToken, data: Vec<u8>) {
248253
error(type_, data, StatusCode::MALFORMED_CONSTANT_DATA)
249254
}

language/move-bytecode-verifier/bytecode-verifier-tests/src/unit_tests/control_flow_tests.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ use move_binary_format::{
88
errors::PartialVMResult,
99
file_format::{Bytecode, CompiledModule, FunctionDefinitionIndex, TableIndex},
1010
};
11-
use move_bytecode_verifier::{control_flow, VerifierConfig};
11+
use move_bytecode_verifier::{control_flow, meter::DummyMeter, VerifierConfig};
1212
use move_core_types::vm_status::StatusCode;
1313

1414
fn verify_module(verifier_config: &VerifierConfig, module: &CompiledModule) -> PartialVMResult<()> {
@@ -30,6 +30,7 @@ fn verify_module(verifier_config: &VerifierConfig, module: &CompiledModule) -> P
3030
current_function,
3131
function_definition,
3232
code,
33+
&mut DummyMeter,
3334
)?;
3435
}
3536
Ok(())
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
// Copyright (c) The Move Contributors
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
use crate::unit_tests::production_config;
5+
use move_binary_format::file_format::{
6+
empty_module, Bytecode, CodeUnit, FunctionDefinition, FunctionHandle, FunctionHandleIndex,
7+
IdentifierIndex, ModuleHandleIndex, Signature, SignatureIndex, SignatureToken,
8+
Visibility::Public,
9+
};
10+
use move_core_types::{identifier::Identifier, vm_status::StatusCode};
11+
12+
const NUM_LOCALS: u8 = 64;
13+
const NUM_CALLS: u16 = 77;
14+
const NUM_FUNCTIONS: u16 = 177;
15+
16+
fn get_nested_vec_type(len: usize) -> SignatureToken {
17+
let mut ret = SignatureToken::Bool;
18+
for _ in 0..len {
19+
ret = SignatureToken::Vector(Box::new(ret));
20+
}
21+
ret
22+
}
23+
24+
#[test]
25+
fn test_large_types() {
26+
// See also: github.com/aptos-labs/aptos-core/security/advisories/GHSA-37qw-jfpw-8899
27+
let mut m = empty_module();
28+
29+
m.signatures.push(Signature(
30+
std::iter::repeat(SignatureToken::Reference(Box::new(get_nested_vec_type(64))))
31+
.take(NUM_LOCALS as usize)
32+
.collect(),
33+
));
34+
35+
m.function_handles.push(FunctionHandle {
36+
module: ModuleHandleIndex(0),
37+
name: IdentifierIndex(0),
38+
parameters: SignatureIndex(0),
39+
return_: SignatureIndex(0),
40+
type_parameters: vec![],
41+
});
42+
m.function_defs.push(FunctionDefinition {
43+
function: FunctionHandleIndex(0),
44+
visibility: Public,
45+
is_entry: false,
46+
acquires_global_resources: vec![],
47+
code: Some(CodeUnit {
48+
locals: SignatureIndex(0),
49+
code: vec![Bytecode::Call(FunctionHandleIndex(0)), Bytecode::Ret],
50+
}),
51+
});
52+
53+
// returns_vecs
54+
m.identifiers.push(Identifier::new("returns_vecs").unwrap());
55+
m.function_handles.push(FunctionHandle {
56+
module: ModuleHandleIndex(0),
57+
name: IdentifierIndex(1),
58+
parameters: SignatureIndex(0),
59+
return_: SignatureIndex(1),
60+
type_parameters: vec![],
61+
});
62+
m.function_defs.push(FunctionDefinition {
63+
function: FunctionHandleIndex(1),
64+
visibility: Public,
65+
is_entry: false,
66+
acquires_global_resources: vec![],
67+
code: Some(CodeUnit {
68+
locals: SignatureIndex(0),
69+
code: vec![Bytecode::Call(FunctionHandleIndex(1)), Bytecode::Ret],
70+
}),
71+
});
72+
73+
// takes_and_returns_vecs
74+
m.identifiers
75+
.push(Identifier::new("takes_and_returns_vecs").unwrap());
76+
m.function_handles.push(FunctionHandle {
77+
module: ModuleHandleIndex(0),
78+
name: IdentifierIndex(2),
79+
parameters: SignatureIndex(1),
80+
return_: SignatureIndex(1),
81+
type_parameters: vec![],
82+
});
83+
m.function_defs.push(FunctionDefinition {
84+
function: FunctionHandleIndex(2),
85+
visibility: Public,
86+
is_entry: false,
87+
acquires_global_resources: vec![],
88+
code: Some(CodeUnit {
89+
locals: SignatureIndex(0),
90+
code: vec![Bytecode::Call(FunctionHandleIndex(1)), Bytecode::Ret],
91+
}),
92+
});
93+
94+
// takes_vecs
95+
m.identifiers.push(Identifier::new("takes_vecs").unwrap());
96+
m.function_handles.push(FunctionHandle {
97+
module: ModuleHandleIndex(0),
98+
name: IdentifierIndex(3),
99+
parameters: SignatureIndex(1),
100+
return_: SignatureIndex(0),
101+
type_parameters: vec![],
102+
});
103+
m.function_defs.push(FunctionDefinition {
104+
function: FunctionHandleIndex(3),
105+
visibility: Public,
106+
is_entry: false,
107+
acquires_global_resources: vec![],
108+
code: Some(CodeUnit {
109+
locals: SignatureIndex(0),
110+
code: vec![Bytecode::Ret],
111+
}),
112+
});
113+
114+
// other fcts
115+
for i in 0..NUM_FUNCTIONS {
116+
m.identifiers
117+
.push(Identifier::new(format!("f{}", i)).unwrap());
118+
m.function_handles.push(FunctionHandle {
119+
module: ModuleHandleIndex(0),
120+
name: IdentifierIndex(i + 4),
121+
parameters: SignatureIndex(0),
122+
return_: SignatureIndex(0),
123+
type_parameters: vec![],
124+
});
125+
m.function_defs.push(FunctionDefinition {
126+
function: FunctionHandleIndex(i + 4),
127+
visibility: Public,
128+
is_entry: false,
129+
acquires_global_resources: vec![],
130+
code: Some(CodeUnit {
131+
locals: SignatureIndex(0),
132+
code: vec![],
133+
}),
134+
});
135+
136+
let code = &mut m.function_defs[i as usize + 4].code.as_mut().unwrap().code;
137+
code.clear();
138+
code.push(Bytecode::Call(FunctionHandleIndex(1)));
139+
for _ in 0..NUM_CALLS {
140+
code.push(Bytecode::Call(FunctionHandleIndex(2)));
141+
}
142+
code.push(Bytecode::Call(FunctionHandleIndex(3)));
143+
code.push(Bytecode::Ret);
144+
}
145+
146+
let result = move_bytecode_verifier::verify_module_with_config_for_test(
147+
"test_large_types",
148+
&production_config(),
149+
&m,
150+
);
151+
assert_eq!(
152+
result.unwrap_err().major_status(),
153+
StatusCode::CONSTRAINT_NOT_SATISFIED,
154+
);
155+
}

language/move-bytecode-verifier/bytecode-verifier-tests/src/unit_tests/limit_tests.rs

+5-3
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@
22
// SPDX-License-Identifier: Apache-2.0
33

44
use move_binary_format::file_format::*;
5-
use move_bytecode_verifier::{limits::LimitsVerifier, verify_module_with_config, VerifierConfig};
5+
use move_bytecode_verifier::{
6+
limits::LimitsVerifier, verify_module_with_config_for_test, VerifierConfig,
7+
};
68
use move_core_types::{
79
account_address::AccountAddress, identifier::Identifier, vm_status::StatusCode,
810
};
@@ -243,8 +245,8 @@ fn big_vec_unpacks() {
243245
module.serialize(&mut mvbytes).unwrap();
244246
let module = CompiledModule::deserialize(&mvbytes).unwrap();
245247

246-
// run with mainnet aptos config
247-
let res = verify_module_with_config(
248+
let res = verify_module_with_config_for_test(
249+
"big_vec_unpacks",
248250
&VerifierConfig {
249251
max_loop_depth: Some(5),
250252
max_generic_instantiation_length: Some(32),

0 commit comments

Comments
 (0)