-
Notifications
You must be signed in to change notification settings - Fork 38
fix: initialize strongest with null #145
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
Conversation
index.bs
Outdated
|
||
1. Let |result| be the empty set and |strongest| be the empty | ||
string. | ||
1. Let |result| be the empty set and |strongest| be empty. |
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 no strong feelings rather than doing what's common in other specs. My quick skim of of other specs makes me think that variables which are supposed to hold strings are initialized to "empty string" rather than "null" or "empty".
Given this is just a variable initialize that is immediately overwritten, I don't think this should make a difference, right?
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.
Yeah, this should be reverted.
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.
Well, the problem does not arise in programming languages where you dont have to initialize a variable with a datatype like php or javascript. But if you are e.g. programming c++ you declare strongest
as string and set it as empty String and that you dont even need that string, suddenly you need to assign a struct, well how you gonna implement this?. I am not a c++ dev, but i think every c++ dev would just decide to deviate from the spec, and declare strongest
to be struct and let it be empty.
In nodejs it can be a small performance issue. V8 engine could potentially not optimize the function due to the variable strongest
not to be monomorphic, because the datatype changes. If we keep it to be undefined or null, which is in js our empty value, v8 has atleast a chance to optimize 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.
Thanks. Let's keep the empty string then.
(Edit: I raced with your comment. Please ignore)
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.
Then we should set it to null. Why shouldnt it be possible?
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.
Yeah, that makes sense to me given what 2.2.2 does.
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 misunderstood it to be a string in the end. You're right, it should be null initially and then eventually assigned a struct. Can you rewrite your patch to say null
rather than empty
?
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.
done
index.bs
Outdated
7. Otherwise, |newAlgorithmIndex| and |currentAlgorithmIndex| are the | ||
7. Otherwise, if |newAlgorithmIndex| and |currentAlgorithmIndex| are the |
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.
Thanks, looks like this was a missing word indeed.
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.
Are you sure? How else can they compare besides greater than, less than, and equal to?
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.
Sure you could also read that as a statement "Otherwise they are the same, so append". Either way, this shouldn't change the logic.
Though, I am not sure what the test case difference @Uzlopak meant. Maybe he can help me understand.
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.
In the wording of the spec it is not clear if you want that i implement an if condition to check that both variables are equal or if you just state in step 7 that we are left with the case that both variables are equal and we have to process as defined in 7.
When i implemented it on the weekend i assessed that the "if" is missing.
Imho: In a Security spec it should be not so much wiggle room in interpreting an Instruction.
Especially as there can arise a false implementation, which could be undetected even if you write a test, see initial post.
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 adding an "if" is a big change as it implies it was not a statement of fact.
I don't think there's any wiggle room here, though it could be modernized to use Assert instead. There might be a bug, but that's still unclear.
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.
So if it is a statement of fact that at step 7. |newAlgorithmIndex| and |currentAlgorithmIndex| are the same
, then the implementation is as follows:
/**
* @param {MetadataList} metadataList
* @returns {MetadataList} The strongest hash algorithm from the metadata list.
*/
function getStrongestMetadata (metadataList) {
// 1. Let result be the empty set and strongest be the empty string.
const result = []
/** @type {Metadata|string} */
let strongest = ''
// 2. For each item in set:
for (const item of metadataList) {
// 1. Assert: item["alg"] is a valid SRI hash algorithm token.
assert(isValidSRIHashAlgorithm(item.alg), `Invalid SRI hash algorithm: ${item.alg}`)
// 2. If result is the empty set, then:
if (result.length === 0) {
// 1. Append item to result.
result.push(item)
// 2. Set strongest to item.
strongest = item
// 3. Continue.
continue
}
// 3. Let currentAlgorithm be strongest["alg"], and currentAlgorithmIndex be
// the index of currentAlgorithm in the valid SRI hash algorithm token set.
const currentAlgorithm = /** @type {Metadata} */ (strongest).alg
const currentAlgorithmIndex = validSRIHashAlgorithmTokenSet.indexOf(currentAlgorithm)
// 4. Let newAlgorithm be the item["alg"], and newAlgorithmIndex be the
// index of newAlgorithm in the valid SRI hash algorithm token set.
const newAlgorithm = item.alg
const newAlgorithmIndex = validSRIHashAlgorithmTokenSet.indexOf(newAlgorithm)
// 5. If newAlgorithmIndex is less than 0, then continue.
if (newAlgorithmIndex < 0) {
continue
// 6. Otherwise, if newAlgorithmIndex is greater than
// currentAlgorithmIndex:
} else if (newAlgorithmIndex > currentAlgorithmIndex) {
// 1. Set strongest to item.
strongest = item
// 2. Set result to « item ».
result[0] = item
result.length = 1
// 7. Otherwise, newAlgorithmIndex and currentAlgorithmIndex are the same
// value. Append item to result.
} else {
result.push(item)
}
}
// 3. Return result.
return result
}
If we say, that we are NOT sure, that `|newAlgorithmIndex| and |currentAlgorithmIndex| are the same and we have to check with an if-condition we get following implementation.
/**
* @param {MetadataList} metadataList
* @returns {MetadataList} The strongest hash algorithm from the metadata list.
*/
function getStrongestMetadata (metadataList) {
// 1. Let result be the empty set and strongest be the empty string.
const result = []
/** @type {Metadata|string} */
let strongest = ''
// 2. For each item in set:
for (const item of metadataList) {
// 1. Assert: item["alg"] is a valid SRI hash algorithm token.
assert(isValidSRIHashAlgorithm(item.alg), `Invalid SRI hash algorithm: ${item.alg}`)
// 2. If result is the empty set, then:
if (result.length === 0) {
// 1. Append item to result.
result.push(item)
// 2. Set strongest to item.
strongest = item
// 3. Continue.
continue
}
// 3. Let currentAlgorithm be strongest["alg"], and currentAlgorithmIndex be
// the index of currentAlgorithm in the valid SRI hash algorithm token set.
const currentAlgorithm = /** @type {Metadata} */ (strongest).alg
const currentAlgorithmIndex = validSRIHashAlgorithmTokenSet.indexOf(currentAlgorithm)
// 4. Let newAlgorithm be the item["alg"], and newAlgorithmIndex be the
// index of newAlgorithm in the valid SRI hash algorithm token set.
const newAlgorithm = item.alg
const newAlgorithmIndex = validSRIHashAlgorithmTokenSet.indexOf(newAlgorithm)
// 5. If newAlgorithmIndex is less than 0, then continue.
if (newAlgorithmIndex < 0) {
continue
// 6. Otherwise, if newAlgorithmIndex is greater than
// currentAlgorithmIndex:
} else if (newAlgorithmIndex > currentAlgorithmIndex) {
// 1. Set strongest to item.
strongest = item
// 2. Set result to « item ».
result[0] = item
result.length = 1
// 7. Otherwise, newAlgorithmIndex and currentAlgorithmIndex are the same
// value. Append item to result.
} else if (newAlgorithmIndex === currentAlgorithmIndex) {
result.push(item)
}
}
// 3. Return result.
return result
}
And then we get back to the original post of mine.
If we pass
[
{ alg: 'sha256', val: 'sha256-abc' },
{ alg: 'sha384', val: 'sha384-def' },
{ alg: 'sha512', val: 'sha512-ghi' }
]
as payload for getStrongestMetaaata, then both implementation return [{ alg: 'sha512', val: 'sha512-ghi' }]
as result.
But if we pass
[
{ alg: 'sha512', val: 'sha512-ghi' },
{ alg: 'sha256', val: 'sha256-abc' },
{ alg: 'sha384', val: 'sha384-def' }
]
as payload for getStrongMetadata then the original spec will return
[
{ alg: 'sha512', val: 'sha512-ghi' },
{ alg: 'sha256', val: 'sha256-abc' },
{ alg: 'sha384', val: 'sha384-def' }
]
In step 5 we check if the newIndex is lower than 0, which means, that the algorithm isnt supported
In step 6 we check if the newIndex is greater than currentIndex, OK, makes sense
and in step 7 we expect it to be equal, but we never checked if newIndex is lower than currentIndex.
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.
In step 5 you do if (newAlgorithmIndex < 0) { continue
, I think you'd need to do if (newAlgorithmIndex < currentAlgorithmIndex) { continue
.
If you check for newAlgorithmIndex < currentAlgorithmIndex
, then newAlgorithmIndex > currentAlgorithmIndex
, the only remaining obvious case is equality.
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.
@mozfreddyb
yeah, also in 2.1. you assert, that the algorithm is valid. So currentAlgorithmIndex can never be < 0, so 2.5 is never happening.
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.
OK! I guess I have here a discrepancy between the draft from april and from may, as it is clearly fixed already in .2.5.
I didnt see that.
I revert that if.
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.
Done :)
@annevk Can you briefly chime in on the empty string / null string question above? I don't have a strong opinion but couldn't find an authoritative answer |
I connected my account to w3c. |
I think I can waive the commit as non-substantive, as this clarifies the algorithm but does not change the logical behavior |
mozfreddyb marked as non substantive for IPR from ash-nazg. |
Thank you @Uzlopak. |
SHA: 679d685 Reason: push, by mozfreddyb Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
While implementing the latest draft to undici, nodejs/undici#4307, I found following issues:
Why is strongest initialized with an empty string, just to later contain an object? Why cant we just initialize it with null?
I think the word
if
was missing inOtherwise, |newAlgorithmIndex| and |currentAlgorithmIndex| are the
The point is, that we search for the strongest hash methods and the corresponding hashes. Without the
if
we get also based on the input we keep also weaker hashes, just based on the order of the input for the integrity string.WIthout the if the second test would fail, just because the order is different.
Also
Preview | Diff