Skip to content

Commit 01eb5b2

Browse files
authored
Merge pull request #2227 from zhangsoledad/zhangsoledad/tests
Add test cases for security GHSA-q73f-w3h7-7wcc
2 parents 7c5a00a + 9fe418f commit 01eb5b2

8 files changed

Lines changed: 512 additions & 2 deletions

File tree

chain/src/tests/cell.rs

Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
use crate::cell::{attach_block_cell, detach_block_cell};
2+
use crate::tests::util::{calculate_reward, create_always_success_tx, start_chain, MockStore};
3+
use ckb_chain_spec::consensus::{Consensus, ConsensusBuilder};
4+
use ckb_dao_utils::genesis_dao_data;
5+
use ckb_shared::shared::Shared;
6+
use ckb_store::ChainStore;
7+
use ckb_test_chain_utils::always_success_cell;
8+
use ckb_types::prelude::*;
9+
use ckb_types::{
10+
bytes::Bytes,
11+
core::{
12+
capacity_bytes,
13+
cell::{CellProvider, CellStatus},
14+
BlockBuilder, BlockView, Capacity, EpochNumberWithFraction, HeaderView, TransactionBuilder,
15+
TransactionView,
16+
},
17+
packed::{CellInput, CellOutputBuilder, OutPoint},
18+
utilities::DIFF_TWO,
19+
};
20+
21+
const TX_FEE: Capacity = capacity_bytes!(10);
22+
23+
#[allow(clippy::int_plus_one)]
24+
pub(crate) fn create_cellbase(
25+
parent: &HeaderView,
26+
store: &MockStore,
27+
consensus: &Consensus,
28+
) -> TransactionView {
29+
let number = parent.number() + 1;
30+
let capacity = calculate_reward(store, consensus, parent);
31+
let builder = TransactionBuilder::default().input(CellInput::new_cellbase_input(number));
32+
33+
if (parent.number() + 1) <= consensus.finalization_delay_length() {
34+
builder.build()
35+
} else {
36+
builder
37+
.output(
38+
CellOutputBuilder::default()
39+
.capacity(capacity.pack())
40+
.build(),
41+
)
42+
.output_data(Bytes::new().pack())
43+
.build()
44+
}
45+
}
46+
47+
#[allow(clippy::too_many_arguments)]
48+
pub(crate) fn gen_block(
49+
parent_header: &HeaderView,
50+
transactions: Vec<TransactionView>,
51+
shared: &Shared,
52+
store: &MockStore,
53+
) -> BlockView {
54+
let number = parent_header.number() + 1;
55+
let consensus = shared.consensus();
56+
let cellbase = create_cellbase(parent_header, store, consensus);
57+
let mut txs = vec![cellbase];
58+
txs.extend_from_slice(&transactions);
59+
60+
let last_epoch = store
61+
.0
62+
.get_block_epoch_index(&parent_header.hash())
63+
.and_then(|index| store.0.get_epoch_ext(&index))
64+
.unwrap();
65+
let epoch = store
66+
.0
67+
.next_epoch_ext(shared.consensus(), &last_epoch, &parent_header)
68+
.unwrap_or(last_epoch);
69+
70+
let block = BlockBuilder::default()
71+
.parent_hash(parent_header.hash())
72+
.timestamp((parent_header.timestamp() + 20_000).pack())
73+
.number(number.pack())
74+
.compact_target(epoch.compact_target().pack())
75+
.epoch(epoch.number_with_fraction(number).pack())
76+
.transactions(txs)
77+
.build();
78+
79+
store.insert_block(&block, consensus.genesis_epoch_ext());
80+
block
81+
}
82+
83+
pub(crate) fn create_transaction(parent: &TransactionView, index: u32) -> TransactionView {
84+
let input_cap: Capacity = parent
85+
.outputs()
86+
.get(0)
87+
.expect("get output index 0")
88+
.capacity()
89+
.unpack();
90+
91+
TransactionBuilder::default()
92+
.output(
93+
CellOutputBuilder::default()
94+
.capacity(input_cap.safe_sub(TX_FEE).unwrap().pack())
95+
.build(),
96+
)
97+
.input(CellInput::new(OutPoint::new(parent.hash(), index), 0))
98+
.output_data(Bytes::new().pack())
99+
.build()
100+
}
101+
102+
#[test]
103+
fn test_block_cells_update() {
104+
let (_, _, always_success_script) = always_success_cell();
105+
let always_success_tx = create_always_success_tx();
106+
let issue_tx = TransactionBuilder::default()
107+
.input(CellInput::new(OutPoint::null(), 0))
108+
.output(
109+
CellOutputBuilder::default()
110+
.capacity(capacity_bytes!(5_000).pack())
111+
.lock(always_success_script.clone())
112+
.build(),
113+
)
114+
.output_data(Bytes::new().pack())
115+
.build();
116+
117+
let dao = genesis_dao_data(vec![&always_success_tx, &issue_tx]).unwrap();
118+
119+
let genesis_block = BlockBuilder::default()
120+
.transaction(always_success_tx)
121+
.transaction(issue_tx.clone())
122+
.compact_target(DIFF_TWO.pack())
123+
.dao(dao)
124+
.build();
125+
126+
let consensus = ConsensusBuilder::default()
127+
.cellbase_maturity(EpochNumberWithFraction::new(0, 0, 1))
128+
.genesis_block(genesis_block)
129+
.build();
130+
131+
let (_chain_controller, shared, parent) = start_chain(Some(consensus));
132+
let mock_store = MockStore::new(&parent, shared.store());
133+
134+
let tx0 = create_transaction(&issue_tx, 0);
135+
let tx1 = create_transaction(&tx0, 0);
136+
let tx2 = create_transaction(&tx1, 0);
137+
let tx3 = create_transaction(&tx2, 0);
138+
139+
let block = gen_block(&parent, vec![tx0, tx1, tx2, tx3], &shared, &mock_store);
140+
141+
let db_txn = shared.store().begin_transaction();
142+
db_txn.insert_block(&block).unwrap();
143+
db_txn.attach_block(&block).unwrap();
144+
145+
attach_block_cell(&db_txn, &block).unwrap();
146+
let txn_cell_provider = db_txn.cell_provider();
147+
148+
// ensure tx0-2 outputs is spent after attach_block_cell
149+
for tx in block.transactions()[1..4].iter() {
150+
for pt in tx.output_pts() {
151+
// full spent
152+
assert_eq!(txn_cell_provider.cell(&pt, false), CellStatus::Unknown);
153+
}
154+
}
155+
156+
// ensure tx3 outputs is unspent after attach_block_cell
157+
for pt in block.transactions()[4].output_pts() {
158+
assert!(txn_cell_provider.cell(&pt, false).is_live());
159+
}
160+
161+
// ensure issue_tx outputs is spent after attach_block_cell
162+
assert_eq!(
163+
txn_cell_provider.cell(&issue_tx.output_pts()[0], false),
164+
CellStatus::Unknown
165+
);
166+
167+
detach_block_cell(&db_txn, &block).unwrap();
168+
169+
// ensure tx0-3 outputs is unknown after detach_block_cell
170+
for tx in block.transactions()[1..=4].iter() {
171+
for pt in tx.output_pts() {
172+
assert_eq!(txn_cell_provider.cell(&pt, false), CellStatus::Unknown);
173+
}
174+
}
175+
176+
// ensure issue_tx outputs is back to live after detach_block_cell
177+
assert!(txn_cell_provider
178+
.cell(&issue_tx.output_pts()[0], false)
179+
.is_live());
180+
}
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
use crate::tests::util::{
2+
create_load_input_data_hash_cell_out_point, create_load_input_data_hash_cell_tx, start_chain,
3+
};
4+
use ckb_chain_spec::consensus::ConsensusBuilder;
5+
use ckb_dao_utils::genesis_dao_data;
6+
use ckb_test_chain_utils::load_input_data_hash_cell;
7+
use ckb_tx_pool::{PlugTarget, TxEntry};
8+
use ckb_types::prelude::*;
9+
use ckb_types::{
10+
bytes::Bytes,
11+
core::{
12+
capacity_bytes, BlockBuilder, Capacity, EpochNumberWithFraction, TransactionBuilder,
13+
TransactionView,
14+
},
15+
packed::{CellDep, CellInput, CellOutputBuilder, OutPoint},
16+
utilities::DIFF_TWO,
17+
};
18+
19+
const TX_FEE: Capacity = capacity_bytes!(10);
20+
21+
pub(crate) fn create_load_input_data_hash_transaction(
22+
parent: &TransactionView,
23+
index: u32,
24+
) -> TransactionView {
25+
let (_, _, load_input_data_hash_script) = load_input_data_hash_cell();
26+
let load_input_data_hash_out_point = create_load_input_data_hash_cell_out_point();
27+
28+
let input_cap: Capacity = parent
29+
.outputs()
30+
.get(0)
31+
.expect("get output index 0")
32+
.capacity()
33+
.unpack();
34+
35+
TransactionBuilder::default()
36+
.output(
37+
CellOutputBuilder::default()
38+
.capacity(input_cap.safe_sub(TX_FEE).unwrap().pack())
39+
.lock(load_input_data_hash_script.clone())
40+
.build(),
41+
)
42+
.output_data(Bytes::new().pack())
43+
.input(CellInput::new(OutPoint::new(parent.hash(), index), 0))
44+
.cell_dep(
45+
CellDep::new_builder()
46+
.out_point(load_input_data_hash_out_point)
47+
.build(),
48+
)
49+
.build()
50+
}
51+
52+
// Ensure tx-pool reject tx which calls syscall load_cell_data_hash from input
53+
#[test]
54+
fn test_load_input_data_hash_cell() {
55+
let (_, _, load_input_data_hash_script) = load_input_data_hash_cell();
56+
let load_input_data_hash_cell_tx = create_load_input_data_hash_cell_tx();
57+
58+
let issue_tx = TransactionBuilder::default()
59+
.input(CellInput::new(OutPoint::null(), 0))
60+
.output(
61+
CellOutputBuilder::default()
62+
.capacity(capacity_bytes!(5_000).pack())
63+
.lock(load_input_data_hash_script.clone())
64+
.build(),
65+
)
66+
.output_data(Bytes::new().pack())
67+
.build();
68+
69+
let dao = genesis_dao_data(vec![&load_input_data_hash_cell_tx, &issue_tx]).unwrap();
70+
71+
let genesis_block = BlockBuilder::default()
72+
.transaction(load_input_data_hash_cell_tx)
73+
.transaction(issue_tx.clone())
74+
.compact_target(DIFF_TWO.pack())
75+
.dao(dao)
76+
.build();
77+
78+
let consensus = ConsensusBuilder::default()
79+
.cellbase_maturity(EpochNumberWithFraction::new(0, 0, 1))
80+
.genesis_block(genesis_block)
81+
.build();
82+
83+
let (_chain_controller, shared, _parent) = start_chain(Some(consensus));
84+
85+
let tx0 = create_load_input_data_hash_transaction(&issue_tx, 0);
86+
let tx1 = create_load_input_data_hash_transaction(&tx0, 0);
87+
88+
let tx_pool = shared.tx_pool_controller();
89+
let ret = tx_pool.submit_txs(vec![tx0.clone()]).unwrap();
90+
assert!(ret.is_err());
91+
//ValidationFailure(2) missing item
92+
assert!(format!("{}", ret.err().unwrap()).contains("ValidationFailure(2)"));
93+
94+
let entry0 = vec![TxEntry::new(tx0, 0, Capacity::shannons(0), 100, vec![])];
95+
tx_pool.plug_entry(entry0, PlugTarget::Proposed).unwrap();
96+
97+
// Ensure tx which calls syscall load_cell_data_hash will got reject even previous tx is already in tx-pool
98+
let ret = tx_pool.submit_txs(vec![tx1]).unwrap();
99+
assert!(ret.is_err());
100+
assert!(format!("{}", ret.err().unwrap()).contains("ValidationFailure(2)"));
101+
}

chain/src/tests/mod.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
mod basic;
22
mod block_assembler;
3+
mod cell;
34
mod delay_verify;
45
mod find_fork;
6+
mod load_input_data_hash_cell;
7+
mod non_contextual_block_txs_verify;
58
mod reward;
69
mod truncate;
710
mod uncle;

0 commit comments

Comments
 (0)