Split the TLV payload processing into parsing and validation#3278
Conversation
a827c84 to
78ca90e
Compare
rustyrussell
left a comment
There was a problem hiding this comment.
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.
| } | ||
| /* 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.
tal_arr_expand() in utils.h does this a bit more compactly, BTW.
There was a problem hiding this comment.
Ah, was looking for that but couldn't find it so I thought I was misremembering the name xD
| *max -= field.length; | ||
| tal_expand(&record->fields, &field, 1); | ||
| } | ||
| assert(tal_count(record->fields) > 0); |
There was a problem hiding this comment.
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.
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.
| fromwire_fail(cursor, max); | ||
| return false; | ||
| } | ||
| bool ${tlv.name}_is_valid(const struct ${tlv.struct_name()} *record) |
There was a problem hiding this comment.
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.
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.
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.
| 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.
f->meta == NULL please.
There was a problem hiding this comment.
Ouch, sorry. Copy pasted it without checking.
| SUPERVERBOSE("invalid ordering"); | ||
| return false; | ||
| } | ||
| first = false; |
There was a problem hiding this comment.
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.
I'll migrate the run-tlvstream tests to use the typesafe wrapper and remove the old ones. 👍
| */ | ||
| static void route_step_decode(struct route_step *rs) | ||
| { | ||
| if (rs->type == SPHINX_V0_PAYLOAD) { |
There was a problem hiding this comment.
Make this a switch() to catch if we ever add another type?
|
|
||
| /* 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.
Please quote the BOLT directly instead, so we know when this requirement changes (which it will!).
78ca90e to
08fc431
Compare
| * - 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.
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.
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.
| 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.
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
sendonionor 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_payloadinstance, 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 iflightningdisn't explicitly told how to handle the HTLC and to forward or terminate with an internal invoice.