Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

test: did precompile DOS attack #158

Merged
merged 2 commits into from
Sep 28, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 6 additions & 9 deletions precompiles/did/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -184,15 +184,14 @@ where
let origin = Some(R::AddressMapping::into_account_id(handle.context().caller));
let did = R::AddressMapping::into_account_id(input.read::<Address>()?.into());
let service_details = input.read::<Vec<Bytes>>()?;
let mut services: BoundedVec<<R as frame_system::Config>::Hash, R::MaxServices> =
BoundedVec::with_bounded_capacity(service_details.len());
let mut services = BoundedVec::with_bounded_capacity(service_details.len());
Moliholy marked this conversation as resolved.
Show resolved Hide resolved
for service in service_details.into_iter() {
if service.0.len() != H256::len_bytes() {
return Err(revert("Service length different than 32 bytes"));
}
let hash = H256::from_slice(service.0.as_slice());
let endpoint = <R as frame_system::Config>::Hash::from(hash);
services.try_push(endpoint).map_err(|_| revert("failed to parse to service"))?;
services.try_push(endpoint).map_err(|_| revert("failed to parse service"))?;
}
RuntimeHelper::<R>::try_dispatch(
handle,
Expand All @@ -215,7 +214,7 @@ where
.map_err(|_| revert("Credential too long"))?;
credentials
.try_push(credential)
.map_err(|_| revert("failed to parse to credential"))?;
.map_err(|_| revert("failed to parse credential"))?;
}
Ok(credentials)
}
Expand Down Expand Up @@ -272,10 +271,8 @@ where
fn parse_services(
raw_services: Vec<(u8, Bytes)>,
) -> Result<BoundedVec<ServiceInfo<R>, R::MaxServices>, PrecompileFailure> {
let mut services: BoundedVec<ServiceInfo<R>, R::MaxServices> =
BoundedVec::with_bounded_capacity(raw_services.len());
let s = raw_services.iter();
for service in s {
let mut services = BoundedVec::with_bounded_capacity(raw_services.len());
peterwht marked this conversation as resolved.
Show resolved Hide resolved
for service in raw_services {
let service_type: ServiceType = match service.0 {
0u8 => ServiceType::VerifiableCredentialFileStorage,
_ => ServiceType::default(),
Expand All @@ -285,7 +282,7 @@ where
service.1.clone().0.try_into().map_err(|_| revert("Services string too long"))?;
services
.try_push(ServiceInfo { type_id: service_type, service_endpoint: endpoint })
.map_err(|_| revert("failed to parse to service"))?;
.map_err(|_| revert("failed to parse service"))?;
}
Ok(services)
}
Expand Down
4 changes: 2 additions & 2 deletions precompiles/did/src/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,9 @@ parameter_types! {
pub const SS58Prefix: u8 = 19;
pub const DidDeposit: u64 = 5;
pub const MaxString: u8 = 100;
pub const MaxCredentialsTypes: u8 = 50;
pub const MaxCredentialsTypes: u8 = 5;
pub const MaxCredentialTypeLength: u32 = 32;
pub const MaxServices: u8 = 10;
pub const MaxServices: u8 = 5;
pub const MaxHash: u32 = 512;
}

Expand Down
218 changes: 210 additions & 8 deletions precompiles/did/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ use pallet_did::{
};
use precompile_utils::testing::PrecompileTesterExt;
use sp_core::{bounded_vec, H160};
use sp_std::vec::Vec;

use super::*;
use crate::mock::*;
Expand All @@ -40,9 +41,7 @@ fn events() -> Vec<pallet_did::Event<Test>> {
result
}

fn hash_services(
services: &BoundedVec<ServiceInfo<Test>, <mock::Test as pallet_did::Config>::MaxServices>,
) -> ServiceKeysOf<Test> {
fn hash_services(services: &Vec<ServiceInfo<Test>>) -> ServiceKeysOf<Test> {
let mut services_keys: ServiceKeysOf<Test> = BoundedVec::default();
for service in services {
let _ = services_keys
Expand Down Expand Up @@ -109,7 +108,7 @@ fn default_services(
bounded_vec![ServiceInfo {
type_id: pallet_did::types::ServiceType::VerifiableCredentialFileStorage,
service_endpoint: bounded_vec![b's', b'0']
},]
}]
}

#[test]
Expand All @@ -128,8 +127,7 @@ fn it_creates_did_without_assertion() {
.build(),
)
.execute_returns(EvmDataWriter::new().write(true).build());
let events = events();
assert!(events.contains(&pallet_did::Event::<Test>::DidCreated {
assert!(events().contains(&pallet_did::Event::<Test>::DidCreated {
did: TestAccount::Alice,
document: expected_document,
}));
Expand Down Expand Up @@ -159,6 +157,36 @@ fn it_creates_did_with_assertion() {
});
}

#[test]
fn reverts_creates_did_if_too_many_services() {
new_test_ext().execute_with(|| {
precompiles()
.prepare_test(
TestAccount::Alice,
PRECOMPILE_ADDRESS,
EvmDataWriter::new_with_selector(Action::CreateDID)
.write(Address(TestAccount::Alice.into()))
.write(Address(H160::from([0u8; 20])))
.write((false, Address(H160::from([0u8; 20]))))
.write(vec![
(1u8, Bytes(default_services()[0].service_endpoint.to_vec())),
(2u8, Bytes(default_services()[0].service_endpoint.to_vec())),
(3u8, Bytes(default_services()[0].service_endpoint.to_vec())),
(4u8, Bytes(default_services()[0].service_endpoint.to_vec())),
(5u8, Bytes(default_services()[0].service_endpoint.to_vec())),
// the 6th should fail
(6u8, Bytes(default_services()[0].service_endpoint.to_vec())),
])
.build(),
)
.execute_reverts(|err| {
let reason = sp_std::str::from_utf8(err).unwrap();
assert_eq!(reason, "failed to parse service");
true
});
});
}

#[test]
fn it_updates_nothing_from_did() {
new_test_ext().execute_with(|| {
Expand Down Expand Up @@ -187,6 +215,40 @@ fn it_updates_nothing_from_did() {
});
}

#[test]
fn reverts_updates_did_if_too_many_services() {
new_test_ext().execute_with(|| {
insert_default_did(TestAccount::Alice);
precompiles()
.prepare_test(
TestAccount::Alice,
PRECOMPILE_ADDRESS,
EvmDataWriter::new_with_selector(Action::UpdateDID)
.write(Address(TestAccount::Alice.into()))
.write((false, Address(TestAccount::Alice.into())))
.write((false, Address(H160::from([0u8; 20]))))
.write((false, Address(H160::from([0u8; 20]))))
.write((
true,
vec![
(2u8, Bytes(default_services()[0].service_endpoint.to_vec())),
(3u8, Bytes(default_services()[0].service_endpoint.to_vec())),
(4u8, Bytes(default_services()[0].service_endpoint.to_vec())),
(5u8, Bytes(default_services()[0].service_endpoint.to_vec())),
(6u8, Bytes(default_services()[0].service_endpoint.to_vec())),
(7u8, Bytes(default_services()[0].service_endpoint.to_vec())),
],
))
.build(),
)
.execute_reverts(|err| {
let reason = sp_std::str::from_utf8(err).unwrap();
assert_eq!(reason, "failed to parse service");
true
});
});
}

#[test]
fn it_updates_all_from_did() {
new_test_ext().execute_with(|| {
Expand Down Expand Up @@ -278,7 +340,7 @@ fn can_add_did_services() {
service_keys.sort();
let mut raw_services: Vec<(u8, Bytes)> = Vec::with_capacity(services.len());

for service in services.iter() {
for service in services {
match service.type_id {
ServiceType::VerifiableCredentialFileStorage => {
raw_services.push((0u8, Bytes(service.service_endpoint.to_vec())))
Expand All @@ -292,7 +354,7 @@ fn can_add_did_services() {
PRECOMPILE_ADDRESS,
EvmDataWriter::new_with_selector(Action::AddDIDServices)
.write(Address(TestAccount::Alice.into()))
.write::<Vec<(u8, Bytes)>>(raw_services)
.write(raw_services)
.build(),
)
.execute_returns(EvmDataWriter::new().write(true).build());
Expand All @@ -303,6 +365,66 @@ fn can_add_did_services() {
});
}

#[test]
fn reverts_add_did_services_if_too_many() {
new_test_ext().execute_with(|| {
let services = vec![
ServiceInfo {
type_id: pallet_did::types::ServiceType::VerifiableCredentialFileStorage,
service_endpoint: bounded_vec![b's', b'1'],
},
ServiceInfo {
type_id: pallet_did::types::ServiceType::VerifiableCredentialFileStorage,
service_endpoint: bounded_vec![b's', b'2'],
},
ServiceInfo {
type_id: pallet_did::types::ServiceType::VerifiableCredentialFileStorage,
service_endpoint: bounded_vec![b's', b'3'],
},
ServiceInfo {
type_id: pallet_did::types::ServiceType::VerifiableCredentialFileStorage,
service_endpoint: bounded_vec![b's', b'4'],
},
ServiceInfo {
type_id: pallet_did::types::ServiceType::VerifiableCredentialFileStorage,
service_endpoint: bounded_vec![b's', b'5'],
},
// the 6th one should fail
ServiceInfo {
type_id: pallet_did::types::ServiceType::VerifiableCredentialFileStorage,
service_endpoint: bounded_vec![b's', b'6'],
},
];
insert_default_did(TestAccount::Alice);
let mut service_keys = hash_services(&services);
service_keys.sort();
let mut raw_services: Vec<(u8, Bytes)> = Vec::with_capacity(services.len());

for service in services {
match service.type_id {
ServiceType::VerifiableCredentialFileStorage => {
raw_services.push((0u8, Bytes(service.service_endpoint.to_vec())))
},
}
}

precompiles()
.prepare_test(
TestAccount::Alice,
PRECOMPILE_ADDRESS,
EvmDataWriter::new_with_selector(Action::AddDIDServices)
.write(Address(TestAccount::Alice.into()))
.write(raw_services)
.build(),
)
.execute_reverts(|err| {
let reason = sp_std::str::from_utf8(err).unwrap();
assert_eq!(reason, "failed to parse service");
true
});
});
}

#[test]
fn can_remove_did_services() {
new_test_ext().execute_with(|| {
Expand Down Expand Up @@ -362,6 +484,43 @@ fn it_issues_credentials() {
});
}

#[test]
fn revert_issues_credentials_if_too_many() {
new_test_ext().execute_with(|| {
let credentials: BoundedVec<
BoundedVec<u8, <mock::Test as pallet_did::Config>::MaxCredentialTypeLength>,
<mock::Test as pallet_did::Config>::MaxCredentialsTypes,
> = bounded_vec![bounded_vec![1u8; 32]];
insert_default_credential_types(credentials.clone());
insert_default_did(TestAccount::Alice);
insert_default_issuer(TestAccount::Alice);
precompiles()
.prepare_test(
TestAccount::Alice,
PRECOMPILE_ADDRESS,
EvmDataWriter::new_with_selector(Action::IssueCredentials)
.write(Address(TestAccount::Alice.into()))
.write(Address(TestAccount::Alice.into()))
.write(vec![
Bytes(vec![1u8; 32]),
Bytes(vec![2u8; 32]),
Bytes(vec![3u8; 32]),
Bytes(vec![4u8; 32]),
Bytes(vec![5u8; 32]),
// the 6th one should fail
Bytes(vec![6u8; 32]),
])
.write(Bytes(vec![5u8; 32]))
.build(),
)
.execute_reverts(|err| {
let reason = sp_std::str::from_utf8(err).unwrap();
assert_eq!(reason, "failed to parse credential");
true
});
});
}

#[test]
fn it_revokes_credentials() {
new_test_ext().execute_with(|| {
Expand Down Expand Up @@ -397,3 +556,46 @@ fn it_revokes_credentials() {
}));
});
}

#[test]
fn revert_revoke_credentials_if_too_many() {
new_test_ext().execute_with(|| {
let credentials: BoundedVec<
BoundedVec<u8, <mock::Test as pallet_did::Config>::MaxCredentialTypeLength>,
<mock::Test as pallet_did::Config>::MaxCredentialsTypes,
> = bounded_vec![bounded_vec![1u8; 32]];
insert_default_credential_types(credentials.clone());
insert_default_did(TestAccount::Alice);
insert_default_issuer(TestAccount::Alice);
assert_ok!(DID::issue_credentials(
RuntimeOrigin::signed(TestAccount::Alice),
TestAccount::Alice,
TestAccount::Alice,
bounded_vec![bounded_vec![1u8; 32]],
bounded_vec![5u8; 32],
));
precompiles()
.prepare_test(
TestAccount::Alice,
PRECOMPILE_ADDRESS,
EvmDataWriter::new_with_selector(Action::RevokeCredentials)
.write(Address(TestAccount::Alice.into()))
.write(Address(TestAccount::Alice.into()))
.write(vec![
Bytes(vec![1u8; 32]),
Bytes(vec![2u8; 32]),
Bytes(vec![3u8; 32]),
Bytes(vec![4u8; 32]),
Bytes(vec![5u8; 32]),
// if we insert a 6th it should crash
Bytes(vec![6u8; 32]),
])
.build(),
)
.execute_reverts(|err| {
let reason = sp_std::str::from_utf8(err).unwrap();
assert_eq!(reason, "failed to parse credential");
true
});
});
}
Loading