-
Notifications
You must be signed in to change notification settings - Fork 225
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
Use core
and alloc
crates for no_std
compatibility
#980
Conversation
Codecov Report
@@ Coverage Diff @@
## master #980 +/- ##
========================================
+ Coverage 72.5% 72.7% +0.2%
========================================
Files 203 203
Lines 16595 16585 -10
========================================
+ Hits 12032 12062 +30
+ Misses 4563 4523 -40
Continue to review full report at Codecov.
|
@@ -107,9 +117,15 @@ pub mod serializers; | |||
/// // We expect a validation error here | |||
/// assert!(MyDomainType::decode(invalid_raw_bytes.as_ref()).is_err()); | |||
/// ``` | |||
pub trait Protobuf<T: Message + From<Self> + Default> | |||
pub trait Protobuf<T> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have modified the Protobuf
trait such that it allows the message type to implement TryFrom
instead of From
. This allows safe parsing of the TimeStamp
type, to prevent undefined behavior caused by integer overflow in DOS attack.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Originally the idea here was that we wanted conversions from SerializationType -> DomainType
to be fallible to allow for validation, and then from DomainType -> SerializationType
to be infallible because DomainType
should be a subset of SerializationType
.
Changing this would imply that we could possibly have DomainType
s that possibly do not have valid serialized representations, which implies to me that those DomainType
s are incorrectly modeled, does it not?
So then the practical consequences of this architectural change would be:
- All
From<DomainType> for SerializationType
impls now need to becomeTryFrom<DomainType> for SerializationType
, touching all domain types - We need custom serde
Serialize
trait impls, as per https://github.com/informalsystems/tendermint-rs/pull/980/files#r717530415
This seems like a lot of work/code just to be able to cater for the DomainTimestamp -> SerializedTimestamp
conversion, does it not? Is there no way to just implement a better DomainTimestamp
whose conversion to SerializedTimestamp
is infallible (as it should actually be)?
|
||
SystemTime::from(prost_value).into() | ||
fn try_from(value: Timestamp) -> Result<Self, Error> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I implement TryFrom
instead of From
to allow safe conversion of the raw protobuf Timestamp
to the Time
type. This is necessary as one is using i32
while the other use u32
. Although the overflow is unlikely to occur, for security purpose it is still possible that an attacker may try to provide overflow time values to cause undefined behavior on the relayer or the chain. So it is better to return an error in case that happens.
As a result of this change, a number of From
instances for various data structures that use Time
have to be updated to implement TryFrom
instead.
fn from(value: Time) -> Self { | ||
// prost_types::Timestamp has a SystemTime converter but | ||
// tendermint_proto::Timestamp can be JSON-encoded | ||
let prost_value = prost_types::Timestamp::from(SystemTime::from(value)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I remove the use of SystemTime
here, as this is the only place where the tendermint
crate is still using std
.
} | ||
} | ||
|
||
impl Serialize for Block { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
By changing from TryFrom
to Try
, it is not possible to auto derive the Serialize
trait through type attributes. (serde-rs/serde#1524) So I just implement the trait manually.
I'm not sure why there is a need to auto derive the Serialize
trait other than for use in testing. In any case, I see that there are many custom impl
blocks for Serialize
in tendermint
, so it should be fine to add a few more.
// TODO: Either add this check in verification or remove this test because otherwise there's no | ||
// point of it | ||
// TODO: Either add this check in verification or remove this test because otherwise there's | ||
// no point of it |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It seems like there are some changes the Rust nightly that cause comments to be formatted slightly differently by cargo fmt
.
|
||
fn add(self, rhs: Duration) -> Self::Output { | ||
let st: SystemTime = self.into(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I remove the use of SystemTime
for addition/substration here to support no_std. To perform safe addition in chrono
, we have to take care of integer overflows by returning error. This is important as the overflow result may cause computation such as trusted period to return incorrect result.
@@ -91,7 +93,7 @@ hyper = { version = "0.14", optional = true, features = ["client", "http1", "htt | |||
hyper-proxy = { version = "0.9", optional = true } | |||
hyper-rustls = { version = "0.22.1", optional = true } | |||
structopt = { version = "0.3", optional = true } | |||
tokio = { version = "1.0", optional = true } | |||
tokio = { version = "1.0", optional = true, features = ["rt-multi-thread"] } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This feature's already specified for the websocket-client
feature, which I think is the only one that needs it. #990 fixes the mock client's (incorrect) dependency on this Tokio feature.
@@ -107,9 +117,15 @@ pub mod serializers; | |||
/// // We expect a validation error here | |||
/// assert!(MyDomainType::decode(invalid_raw_bytes.as_ref()).is_err()); | |||
/// ``` | |||
pub trait Protobuf<T: Message + From<Self> + Default> | |||
pub trait Protobuf<T> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Originally the idea here was that we wanted conversions from SerializationType -> DomainType
to be fallible to allow for validation, and then from DomainType -> SerializationType
to be infallible because DomainType
should be a subset of SerializationType
.
Changing this would imply that we could possibly have DomainType
s that possibly do not have valid serialized representations, which implies to me that those DomainType
s are incorrectly modeled, does it not?
So then the practical consequences of this architectural change would be:
- All
From<DomainType> for SerializationType
impls now need to becomeTryFrom<DomainType> for SerializationType
, touching all domain types - We need custom serde
Serialize
trait impls, as per https://github.com/informalsystems/tendermint-rs/pull/980/files#r717530415
This seems like a lot of work/code just to be able to cater for the DomainTimestamp -> SerializedTimestamp
conversion, does it not? Is there no way to just implement a better DomainTimestamp
whose conversion to SerializedTimestamp
is infallible (as it should actually be)?
Just because the type signature of a function does not return an error, does not mean that there isn't actually an error. The purpose of defining type signatures is to reflect the actual behavior of the implementation as accurate as possible. In the case of the timestamp conversion, defining a If we really want to ignore the failure and define a
I originally chose option 3 as the solution. But given that there aren't too many code changes, I do think that it is worth handling the error properly. I also had similar issue in Btw note that all types implementing |
Is this a real possibility? How would an attacker trigger an incorrect conversion from The only way I could see that being a possibility is if we somehow constructed an incorrect Here's my reasoning:
Hence if we want to prevent invalid nanosecond values, we should check that the If we do somehow end up with a |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There is a bit of a pattern here where change-sets are proposed which often sneak in changes not directly pertaining to the set goal of the change-set. If we want to change the semantics of the Prototype trait this should be reasoned about and proposed in isolation instead of in the context of switching to core
and alloc
. It would be warranted to preface such a change with a small ADR so the design and rational can be discussed instead of it being intermingled with the review of the implementation.
Yeah, in that particular case it shouldn't happen unless there is a bug in For the case of I'll check whether it is fine to have
Yeah the changes get a bit large as I tried to get rid of the use of |
Closing this PR in favor of #993 and #994. It seems like we do able to avoid modifying the |
Part of informalsystems/hermes#1158
This follows similar work in informalsystems/hermes#1156 to move closer to supporting no_std in tendermint-rs.
.changelog/