-
Notifications
You must be signed in to change notification settings - Fork 23
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
Refactor calculateClockOffset to fix overflow issues #17
Refactor calculateClockOffset to fix overflow issues #17
Conversation
3770a6d
to
f4723d9
Compare
d1b7183
to
773d71f
Compare
source/core_sntp_serializer.c
Outdated
static bool isEligibileForClockOffsetCalculation( uint32_t firstOrderDiff ) | ||
{ | ||
return( ( ( firstOrderDiff & CLOCK_OFFSET_FIRST_ORDER_DIFF_OVERFLOW_BITS_MASK ) == 0U ) || | ||
( ( ( 0U - firstOrderDiff ) & CLOCK_OFFSET_FIRST_ORDER_DIFF_OVERFLOW_BITS_MASK ) == 0U ) ); |
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 think CBMC would flag the 0U - firstOrderDiff
as a subtraction that can potentially cause an unsigned integer overflow case. It wouldn't complain if we apply a unary negation though -firstOrderDiff
.
I think the intuition here is that a subtraction sometimes introduces a wrap around so that could be unintentional, but a unary negation always introduces a wrap around (except on 0) so CBMC treats it as intentional. Of course, 0U - x
is the same as -x
but CBMC currently doesn't simplify this binary subtraction to unary negation.
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.
As unary minus operator with unsigned integer isn't allowed by MISRA Rule 10.1, we decided to use literal 2's complement operation (i.e. ~value + 1U) to perform negation
source/core_sntp_serializer.c
Outdated
/* Perform ( T2 - T1 ) offset calculation of SNTP Request packet path. */ | ||
firstOrderDiffSend = pServerRxTime->seconds - pClientTxTime->seconds; | ||
{ | ||
firstOrderDiffSend = 0U - firstOrderDiffSend; |
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.
Same comment regarding binary subtraction vs unary negation applies here.
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.
As unary minus operator with unsigned integer isn't allowed by MISRA Rule 10.1, we decided to use literal 2's complement operation (i.e. ~value + 1U) to perform negation.
source/core_sntp_serializer.c
Outdated
== UINT32_MOST_SIGNIFICANT_BIT_BIT_MASK ) | ||
|
||
{ | ||
firstOrderDiffRecv = 0U - firstOrderDiffRecv; |
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.
Same comment regarding binary subtraction vs unary negation applies here.
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.
As unary minus operator with unsigned integer isn't allowed by MISRA Rule 10.1, we decided to use literal 2's complement operation (i.e. ~value + 1U) to perform negation.
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.
LGTM. Thank you for incorporating all the feedback!
The 2's complement part is somewhat unsatisfactory. I would bring it up in our CBMC discussions. CBMC shouldn't be directly contradicting a MISRA rule.
EDIT: We had a discussion regarding this. The consensus was that 2's complement is indeed the right thing to do here. An "arithmetic negation" on a value that has no sign (so no notion of positive or negative) isn't really intuitive -- in the end it's just an unsigned bit pattern.
Instead, it's better to be explicit that we are changing the bit pattern to represent a value that can be interpreted as the negated value in 2's-complement representation for signed integers.
I'd like to keep this PR open for now so we can solicit further feedback. |
eceb9cc
to
d0d8f2b
Compare
…ers in internal function
258757b
to
52c0399
Compare
2d8f016
to
0c777b4
Compare
source/core_sntp_serializer.c
Outdated
/* Check whether the difference value has the most significant bit set. | ||
* An unsigned integer with the most significant bit set cannot be safely assigned | ||
* to a signed integer.*/ | ||
if( ( positiveDiff & UINT32_MOST_SIGNIFICANT_BIT_BIT_MASK ) == UINT32_MOST_SIGNIFICANT_BIT_BIT_MASK ) |
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.
[Nitpick]
Would it be cleaner to check positiveDiff > INT32_MAX
instead? Basically, we would be explicit about the overflow; we can mention that in the comment too -- that the reason we have this if
branch is to handle overflows correctly.
If we choose to make this change, we can also remove the definition for UINT32_MOST_SIGNIFICANT_BIT_BIT_MASK
.
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.
Yes, done that!
source/core_sntp_serializer.c
Outdated
* | ||
* @return The calculated signed subtraction value between the unsigned integers. | ||
*/ | ||
static int32_t safeSubtraction( uint32_t minuend, |
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.
[Nitpick]
The function is doing a subtraction and returning a signed value, so do you think we could call it safeSignedSubtraction
?
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.
Changed
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.
LGTM. Thanks again for making this function bullet proof!
104ae37
to
ceaf23b
Compare
The current subtraction routine is a big improvement over the previous iteration, but I think you could probably take it a bit farther. E.g. something like
This gets rid of all the polarity and 2's complement stuff and is IMO easier to understand. IDK if this code makes MISRA happy, but I verified with clang's undefined behavior checker that it doesn't cause any overflows: https://paste.amazon.com/show/mcourage/1620948125. Since the subtraction routine checks for overflow, you could also get rid of the bit twiddling in isEligibleForClockOffsetCalculation. |
@courage Thank you for taking a look. I agree that extending the integer width to safely accommodate overflows would also solve this issue. But as you said, this would be much cleaner and we could simplify the |
Yes, my concern also rests for use of "However, the offset and delay are computed as sums and differences of these values, which contain 62 significant bits and two sign bits, so they can represent unambiguous values from 34 years in the past to 34 years in the future. In other words, the time of the client must be set within 34 years of the server before the service is started. This is a fundamental limitation with 64-bit integer arithmetic." The |
The purpose of the code in Though I haven't read through the NTP spec on this subject, I'd be kind of surprised if this "fail when the difference is greater than 34 years" is intended. Is that really what's supposed to happen? I would expect that if my two timestamps are Regarding efficiency of
You'd really have to be doing a tremendous amount of subtraction for me to worry about the efficiency of this code, and even if you were, I'd tell you to favor simpler, clearer code until you had benchmark data showing a material difference. |
Yes, the The restriction of 34 years is applied when, for example, the server is in 2036 but the client time is more than 34 years behind sometime in 2002. In this example, the server time would have overflow (i.e. be represented in NTP era 1) but the client time would be represented as a large value in NTP era 0. The |
a7fb95c
to
0dfa01d
Compare
b84f41e
to
c06675b
Compare
54a76dd
to
96e663d
Compare
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.
LGTM.
No more bit magic and CBMC is also happy.
Thanks for all the changes, @aggarw13!
d3632fe
to
bcb4dfc
Compare
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.
The discussion of this PR yielded a compelling suggestion that may result in simpler code. I'm going to approve this now to unblock concurrent efforts, but I sincerely hope that we explore this option forthwith.
CBMC proof run for
Sntp_DeserializeResponse
PR #15 flags overflow issue in thecalculateClockOffset
function when assignining negative value to an unsigned integer. This PR refactors the implementation to avoid overflow flagging by:This PR also adds resiliency to the clock-offset calculation by adding check against overflow (i.e. when server and client times are more than 34 years apart) on the Client -> Server SNTP request send network path.