[Fix] Bound Iota storage, relay and transport resources
This commit is contained in:
parent
46078cbc4a
commit
e19c3c3d12
19 changed files with 609 additions and 48 deletions
|
|
@ -1,4 +1,5 @@
|
|||
use crate::message_common::*;
|
||||
use iota_logger::log;
|
||||
use iota_storage::util::chat_files::{self, MessageState};
|
||||
use iota_storage::util::chats_util::{self, get_user, has_user, mod_user};
|
||||
use iota_storage::util::communities_util::CommunitiesUtil;
|
||||
|
|
@ -2084,8 +2085,50 @@ fn upload_response(
|
|||
}
|
||||
|
||||
fn asset_error(cv: &CommunicationValue, error: StorageError) -> CommunicationValue {
|
||||
error_response(cv, CommunicationType::ErrorInternal)
|
||||
.add_typed_default(DataType::ErrorType, DataValue::Str(error.to_string()))
|
||||
log!(
|
||||
"Asset request rejected sender={:?} request_id={:?}: {error}",
|
||||
cv.sender(),
|
||||
cv.id()
|
||||
);
|
||||
let (kind, category) = match error {
|
||||
StorageError::AssetResourceLimit(_) => {
|
||||
(CommunicationType::ErrorInvalidData, "asset_resource_limit")
|
||||
}
|
||||
_ => (CommunicationType::ErrorInternal, "asset_request_failed"),
|
||||
};
|
||||
error_response(cv, kind).add_typed_default(DataType::ErrorType, DataValue::Str(category.into()))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod asset_error_tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn storage_errors_do_not_expose_internal_paths() {
|
||||
let request = CommunicationValue::new(CommunicationType::UserAssetUploadStart).with_id(7);
|
||||
let internal = asset_error(
|
||||
&request,
|
||||
StorageError::Other("private path /srv/assets/key".into()),
|
||||
);
|
||||
assert!(internal.is_type(CommunicationType::ErrorInternal));
|
||||
assert_eq!(
|
||||
internal
|
||||
.get_data(DataType::ErrorType)
|
||||
.and_then(DataValue::as_str),
|
||||
Some("asset_request_failed")
|
||||
);
|
||||
let limit = asset_error(
|
||||
&request,
|
||||
StorageError::AssetResourceLimit("max_asset_bytes"),
|
||||
);
|
||||
assert!(limit.is_type(CommunicationType::ErrorInvalidData));
|
||||
assert_eq!(
|
||||
limit
|
||||
.get_data(DataType::ErrorType)
|
||||
.and_then(DataValue::as_str),
|
||||
Some("asset_resource_limit")
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn upload_request(cv: &CommunicationValue) -> Result<(i64, String), CommunicationValue> {
|
||||
|
|
@ -2404,10 +2447,21 @@ pub fn handle_user_blob_list(cv: &CommunicationValue) -> CommunicationValue {
|
|||
if user_id <= 0 {
|
||||
return error_response(cv, CommunicationType::ErrorInvalidData);
|
||||
}
|
||||
match user_blobs::list_metadata(user_id) {
|
||||
Ok(blobs) => CommunicationValue::new(CommunicationType::UserBlobList)
|
||||
let after_id = match cv.get_data(DataType::Offset).and_then(DataValue::as_number) {
|
||||
Some(raw) => match i64::try_from(raw) {
|
||||
Ok(id) if id > 0 => Some(id),
|
||||
_ => return error_response(cv, CommunicationType::ErrorInvalidData),
|
||||
},
|
||||
None => None,
|
||||
};
|
||||
match user_blobs::list_metadata_page(user_id, after_id) {
|
||||
Ok((blobs, next)) => CommunicationValue::new(CommunicationType::UserBlobList)
|
||||
.with_request_id(cv)
|
||||
.with_receiver(sender_wire_id(user_id))
|
||||
.add_typed_default(
|
||||
DataType::Offset,
|
||||
DataValue::SignedNumber(next.unwrap_or(0).into()),
|
||||
)
|
||||
.add_typed_default(
|
||||
DataType::Blobs,
|
||||
DataValue::Array(blobs.iter().map(blob_metadata_value).collect()),
|
||||
|
|
@ -2542,10 +2596,21 @@ pub fn handle_user_asset_list(cv: &CommunicationValue) -> CommunicationValue {
|
|||
Ok(id) if id > 0 => id,
|
||||
_ => return error_response(cv, CommunicationType::ErrorInvalidData),
|
||||
};
|
||||
match user_assets::list(user_id) {
|
||||
Ok(assets) => CommunicationValue::new(CommunicationType::UserAssetList)
|
||||
let after_id = match cv.get_data(DataType::Offset).and_then(DataValue::as_number) {
|
||||
Some(raw) => match i64::try_from(raw) {
|
||||
Ok(id) if id > 0 => Some(id),
|
||||
_ => return error_response(cv, CommunicationType::ErrorInvalidData),
|
||||
},
|
||||
None => None,
|
||||
};
|
||||
match user_assets::list_page(user_id, after_id) {
|
||||
Ok((assets, next)) => CommunicationValue::new(CommunicationType::UserAssetList)
|
||||
.with_request_id(cv)
|
||||
.with_receiver(sender_wire_id(user_id))
|
||||
.add_typed_default(
|
||||
DataType::Offset,
|
||||
DataValue::SignedNumber(next.unwrap_or(0).into()),
|
||||
)
|
||||
.add_typed_default(
|
||||
DataType::Assets,
|
||||
DataValue::Array(assets.iter().map(asset_metadata_value).collect()),
|
||||
|
|
|
|||
|
|
@ -1,3 +1,7 @@
|
|||
use iota_storage::util::{
|
||||
config_util::CONFIG,
|
||||
relay_replay::{MAX_RELAY_FUTURE_SKEW_MILLIS, RELAY_RETENTION_MILLIS},
|
||||
};
|
||||
use iota_util::route_target::RouteTarget;
|
||||
use mtp::codec::{
|
||||
CommunicationValue, ProtectionPolicy, RelayError, RelayOpenOptions, SignaturePolicy, TypeMap,
|
||||
|
|
@ -107,6 +111,9 @@ pub enum RelayValidationError {
|
|||
MissingTypeMap,
|
||||
InvalidRouteTarget(u64),
|
||||
KeyLookup(String),
|
||||
Clock(String),
|
||||
StaleRelay { created_at: u64, now: u64 },
|
||||
RelayFromFuture { created_at: u64, now: u64 },
|
||||
Relay(RelayError),
|
||||
}
|
||||
|
||||
|
|
@ -129,6 +136,14 @@ impl fmt::Display for RelayValidationError {
|
|||
write!(formatter, "relay has invalid route target {target}")
|
||||
}
|
||||
Self::KeyLookup(error) => write!(formatter, "trusted signer lookup failed: {error}"),
|
||||
Self::Clock(error) => write!(formatter, "relay clock failed: {error}"),
|
||||
Self::StaleRelay { created_at, now } => {
|
||||
write!(formatter, "relay timestamp {created_at} is stale at {now}")
|
||||
}
|
||||
Self::RelayFromFuture { created_at, now } => write!(
|
||||
formatter,
|
||||
"relay timestamp {created_at} is in the future at {now}"
|
||||
),
|
||||
Self::Relay(error) => error.fmt(formatter),
|
||||
}
|
||||
}
|
||||
|
|
@ -142,6 +157,23 @@ impl From<RelayError> for RelayValidationError {
|
|||
}
|
||||
}
|
||||
|
||||
fn validate_created_at(
|
||||
created_at: u64,
|
||||
now: u64,
|
||||
max_future_skew_millis: u64,
|
||||
) -> Result<(), RelayValidationError> {
|
||||
if created_at > now.saturating_add(max_future_skew_millis) {
|
||||
return Err(RelayValidationError::RelayFromFuture { created_at, now });
|
||||
}
|
||||
let max_age = u64::try_from(RELAY_RETENTION_MILLIS)
|
||||
.unwrap_or(0)
|
||||
.saturating_sub(MAX_RELAY_FUTURE_SKEW_MILLIS);
|
||||
if now.saturating_sub(created_at) > max_age {
|
||||
return Err(RelayValidationError::StaleRelay { created_at, now });
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/*
|
||||
* Relay metadata is opened only after the claimed signer selects trusted key
|
||||
* history. Replay reservation happens after verification and durable
|
||||
|
|
@ -195,6 +227,14 @@ where
|
|||
RelayOpenOptions::new(RELAY_PROTECTION_POLICY),
|
||||
)?;
|
||||
|
||||
let now = mtp::common::unix_time_millis()
|
||||
.map_err(|error| RelayValidationError::Clock(error.to_string()))?;
|
||||
validate_created_at(
|
||||
metadata.created_at(),
|
||||
now,
|
||||
CONFIG.load().max_relay_future_skew_millis,
|
||||
)?;
|
||||
|
||||
let context = VerifiedRelayContext {
|
||||
signer_id: metadata.signer_id(),
|
||||
final_recipient_id: metadata.final_recipient_id(),
|
||||
|
|
@ -244,6 +284,23 @@ mod tests {
|
|||
use mtp::codec::SealedRelayBuilder;
|
||||
use mtp::crypto::{DualSigner, Ed25519Signer, Keyring};
|
||||
|
||||
#[test]
|
||||
fn relay_freshness_has_bounded_future_and_retention() {
|
||||
let now = 1_000_000_000_000_u64;
|
||||
let max_age = u64::try_from(RELAY_RETENTION_MILLIS).unwrap_or(0) - 300_000;
|
||||
assert!(validate_created_at(now, now, 300_000).is_ok());
|
||||
assert!(validate_created_at(now - max_age, now, 300_000).is_ok());
|
||||
assert!(matches!(
|
||||
validate_created_at(now - max_age - 1, now, 300_000),
|
||||
Err(RelayValidationError::StaleRelay { .. })
|
||||
));
|
||||
assert!(validate_created_at(now + 300_000, now, 300_000).is_ok());
|
||||
assert!(matches!(
|
||||
validate_created_at(now + 300_001, now, 300_000),
|
||||
Err(RelayValidationError::RelayFromFuture { .. })
|
||||
));
|
||||
}
|
||||
|
||||
fn relay(message_id: u128) -> Result<(Keyring, Keyring, CommunicationValue), String> {
|
||||
let signer_keyring = Keyring::generate();
|
||||
let recipient_keyring = Keyring::generate();
|
||||
|
|
@ -264,7 +321,7 @@ mod tests {
|
|||
&signer,
|
||||
)
|
||||
.message_id(message_id)
|
||||
.created_at(123)
|
||||
.created_at(mtp::common::unix_time_millis().map_err(|error| error.to_string())?)
|
||||
.metadata_recipients(vec![recipient_keyring.public_key_bundle()])
|
||||
.content_recipients(vec![recipient_keyring.public_key_bundle()])
|
||||
.build()
|
||||
|
|
@ -339,7 +396,7 @@ mod tests {
|
|||
&signer,
|
||||
)
|
||||
.message_id(7_u128)
|
||||
.created_at(123)
|
||||
.created_at(mtp::common::unix_time_millis().map_err(|error| error.to_string())?)
|
||||
.metadata_recipients(vec![recipient_keyring.public_key_bundle()])
|
||||
.content_recipients(vec![recipient_keyring.public_key_bundle()])
|
||||
.build()
|
||||
|
|
|
|||
Loading…
Reference in a new issue