-
Notifications
You must be signed in to change notification settings - Fork 912
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
Split the TLV payload processing into parsing and validation #3278
Split the TLV payload processing into parsing and validation #3278
Conversation
a827c84
to
78ca90e
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.
Nice cleanup, minor nits mainly, but please convert the TLV parsing tests in wire/test-run-tlvstream.c to ensure these new functions are correct.
tools/gen/impl_template
Outdated
} | ||
/* We've read bytes in ->fromwire, so update max */ | ||
*max -= field.length; | ||
tal_expand(&record->fields, &field, 1); |
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.
tal_arr_expand() in utils.h does this a bit more compactly, BTW.
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.
Ah, was looking for that but couldn't find it so I thought I was misremembering the name xD
tools/gen/impl_template
Outdated
*max -= field.length; | ||
tal_expand(&record->fields, &field, 1); | ||
} | ||
assert(tal_count(record->fields) > 0); |
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.
If we ever hand a zero-length field in here, this will assert, right? *max == 0? That seems wrong.
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 one slipped in, it checks whether we had any TLV fields,not whether one of the fields had zero-length. However the check is wrong since an empty TLV is valid per spec, even though not very sensible.
tools/gen/impl_template
Outdated
@@ -305,6 +305,39 @@ fail: | |||
fromwire_fail(cursor, max); | |||
return false; | |||
} | |||
bool ${tlv.name}_is_valid(const struct ${tlv.struct_name()} *record) |
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.
Please have this return the problematic offset within the TLV? There's a FIXME for this requirement in the onion parsing code, where we're supposed to return the offset which was the problem in the error msg.
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.
We can return the exact offset of the offending field in fromwire_tlv_payload
which has the locations, while here we would have to track the offset by accumulating the varint
lengths and field lengths while verifying. I can add the offset in the fromwire
code and just add the field index that we get upset about if you'd like :-)
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.
Might make more sense anyway, since we can then say things like "Unknown even type x in field y" instead of saying I got upset somewhere at byte z
.
tools/gen/impl_template
Outdated
u64 prev_type = 0; | ||
for (size_t i=0; i<numfields; i++) { | ||
struct tlv_field *f = &record->fields[i]; | ||
if (f->numtype % 2 == 0 && f->meta == 0) { |
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.
f->meta == NULL please.
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.
Ouch, sorry. Copy pasted it without checking.
SUPERVERBOSE("invalid ordering"); | ||
return false; | ||
} | ||
first = false; |
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 code doesn't actually work; we don't update prev_type!
We had tests for the generic code which I think we should replicate here which should have caught this?
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'll migrate the run-tlvstream
tests to use the typesafe wrapper and remove the old ones. 👍
common/sphinx.c
Outdated
*/ | ||
static void route_step_decode(struct route_step *rs) | ||
{ | ||
if (rs->type == SPHINX_V0_PAYLOAD) { |
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.
Make this a switch() to catch if we ever add another type?
lightningd/peer_htlcs.c
Outdated
result = htlc_accepted_hook_deserialize(buffer, toks, &payment_preimage, &failure_code, &channel_update); | ||
|
||
/* In order to handle it internally the payload needs to have at least | ||
* the `outgoing_cltv_value`, the `amt_to_forward` and the | ||
* `short_channel_id` if we are forwarding. */ |
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.
Please quote the BOLT directly instead, so we know when this requirement changes (which it will!).
78ca90e
to
08fc431
Compare
lightningd/peer_htlcs.c
Outdated
* - No unknown even types | ||
* - Types in monotonical non-repeating order | ||
*/ | ||
valid = valid && (rs->type == SPHINX_V0_PAYLOAD || tlv_payload_is_valid(rs->payload.tlv)); |
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.
seems like it'd be nicer to have a separate method for validity check
, instead of making it all inline 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.
Added a htlc_accepted_can_continue
function which tells us whether we can continue with the route-step if we're told to by the hook.
lightningd/peer_htlcs.c
Outdated
switch (result) { | ||
case htlc_accepted_continue: | ||
if (rs->type == SPHINX_TLV_PAYLOAD && !tlv_payload_is_valid(rs->payload.tlv)) { | ||
log_debug(channel->log, "Failing HTLC because of an invalid TLV payload"); | ||
if (!valid) { |
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.
you could just call the valid()
method here
a8fbe3e
to
aecdf78
Compare
We were weaving in and out of generic code through `fromwire_tlvs` with custom parameters defining the types in that namespace. This hard-wires the parser with the namespace's types. Slowly trying to deprecate `fromwire_tlvs` in favor of this typesafe variant.
Since the parser itself just parses and doesn't include validation anymore we need to put that functionality somewhere. The validation consists of enforcing that the types are in monotonically increasing order without duplicates and that for the even types we know how to handle it.
We wire in the code-generated function, which removes the upfront validation and add the validation back after the `htlc_accepted` hook returns. If a plugin wanted to handle the onion in a special way it'll not have told us to just continue.
We'll need to pass them around anyway, so just make them easier to access by doing a bit more to `process_onionpacket`.
We make the fields in `htlc_accepted_payload` optional (NULL if not present in the payload) and defer validation till after the hook call.
This now enforces all rules for validity, both for the TLV format and checking that the required fields have been provided.
We have consolidated the two functions into a single `route_step_decode` function, and made it static since we call it in the `process_onionpacket` function. We remove the two exposed functions since they're no longer useful.
aecdf78
to
df4c187
Compare
Updated the PR to address the feedback:
Notice that there are two new commit that will not be shown in the range-diff: 02fb929 and df4c187 😉 |
In order to be useful #3260 requires that we allow TLV payloads with types that we may not know how to handle. In this case the plugin needs to terminate the onion and handle it by either forwarding manually using
sendonion
or terminate it either successfully or fail it.This PR splits the parsing of the TLV payload from the validation. Parsing just stashes the entire payload in a
tlv_payload
instance, with typed pointers to fields that we know how to parse and handle. Validation then goes through and checks that there are no TLV fields that we don't know how to handle, that the types are in the correct order and that we have all the fields that we need in order to forward. The validity is then only used iflightningd
isn't explicitly told how to handle the HTLC and to forward or terminate with an internal invoice.