Skip to content
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

Fixing GRPC initial metadata validation #16414

Merged
merged 3 commits into from
May 20, 2021
Merged

Conversation

omriz
Copy link
Contributor

@omriz omriz commented May 10, 2021

Commit Message: Fixing GRPC initial metadata validation

Additional Description:
According to the GRPC spec https://github.com/grpc/grpc/blob/master/doc/PROTOCOL-HTTP2.md
The values in the initial metadata can be any valid ASCII character in
case of a non binary header type.

Risk Level: Low
Testing: Unit Tests
Docs Changes:N/A
Release Notes: Fixing GRPC initial metadata validation for ASCII characters
Platform Specific Features: None

@omriz
Copy link
Contributor Author

omriz commented May 10, 2021

@htuch for your review

// ASCII header
} else {
for (auto ch : header.value()) {
if (!(absl::ascii_isascii(ch))) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this check is sufficient: absl::ascii_isascii(ch) returns true if ch < 128 (see here: http://docs.ros.org/en/kinetic/api/abseil_cpp/html/ascii_8h_source.html). GRPC spec? (https://github.com/grpc/grpc/blob/master/doc/PROTOCOL-HTTP2.md) defines ascii char as one between %x20-%x7E.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, I can change the check

if (!validateGrpcHeaderChars(header.key()) || !validateGrpcHeaderChars(header.value())) {
throw EnvoyException("Illegal characters in gRPC initial metadata.");
// Validate key
if (!validateGrpcHeaderChars(header.key())) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I'm reading the spec right, header names can be lower-case ascii chars, numericals, _, -, and .. validateGrpcHeaderChars will return true if the parameter is an upper-case char (try running python -c 'print("A".isalnum())'. Abseil's kPropertyBits is generated using python script, according to comments in ascii.c. kPropertyBits is then used to verify if a character is an alpha-numeric one),

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So I'm not sure - but according to this: https://github.com/grpc/grpc-go/blob/master/Documentation/grpc-metadata.md#creating-a-new-metadata it sounds like you can define initial metadata to be both lower and upercase but it will be converted to lowercase. If you think it's better to have a stricter check I can do so but I'm not sure it's the correct intent in this case.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FYI, HTTP/2 always sends header names as lower-case on the wire:

https://datatracker.ietf.org/doc/html/rfc7540#section-8.1.2


// Validate value
// Binary base64 encoded
if (::absl::EndsWith(header.key(), "-bin")) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@markdroth can you also take a look at this? Thanks.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I'm understanding this code right, it looks like this is requiring that the metadata be already base64 encoded. I don't think that's necessary for GOOGLE_GRPC, because the gRPC library will automatically do base64 encoding and decoding for header values if the header name ends with "-bin".

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What happens if it is already base64 encoded?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the gRPC library automatically does encoding/decoding - what check should be done in this case? or is any value allowed?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't check to see if the value is already base64-encoded; we just unconditionally base64-encode it. So it would wind up being double-base64-encoded on the wire.

I think this will actually work, but it does add unnecessary overhead.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Then we can probably allow any value for -bin. This check was added in #14129 (CC @asraa) because fuzzers were crashing the gRPC library with inputs that looked like https://github.com/envoyproxy/envoy/pull/14129/files#diff-2a20e9888f070730d0412bf9806728cfae9f9f9269747837f68287ff4fcdb5caR1.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That seems right, any value probably goes for -bin.
Otherwise you would have seen a crash in the grpc library in the test GoogleGrpcIllegalLengthBase64 if you didn't catch with the exception below...

On the PR as a whole, thanks for correcting this check!

@omriz
Copy link
Contributor Author

omriz commented May 13, 2021

Updated the code:

  1. Removed the base64 check
  2. Added the correct ASCII range

I did not modify the lower/upper case of the header key

}
}
return true;

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: superfluous blank. If you really want you can use return std::all_of style here, but it's a bit of a wash.

if (::absl::EndsWith(header.key(), "-bin")) {
continue;
// ASCII header
} else {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: you can probably collapse this down to a single if predicate with &&

Copy link
Member

@htuch htuch left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM modulo nits.

@dmitri-d
Copy link
Contributor

I did not modify the lower/upper case of the header key

I think validateGrpcHeaderChars should enforce lower-case for header names; even the comment above states they will be lower-case. @htuch -- do you have a (strong) opinion on that?

@htuch
Copy link
Member

htuch commented May 16, 2021

@dmitri-d I think it's fine to have input being lower/upper mix; it looks like the C++ gRPC library is robust to this as this was also the previous validation. I'd suspect it just gets forced lower case when put on the wire.

@omriz this need a merge to fix format concerns (see CI). Otherwise ready to ship.

@omriz
Copy link
Contributor Author

omriz commented May 18, 2021

@htuch @dmitri-d , pushed (I think) final version according to comments. Please approve.

@omriz
Copy link
Contributor Author

omriz commented May 18, 2021

/retest

@repokitteh-read-only
Copy link

Retrying Azure Pipelines:
Retried failed jobs in: envoy-presubmit

🐱

Caused by: a #16414 (comment) was created by @omriz.

see: more, trace.

According to the GRPC spec https://github.com/grpc/grpc/blob/master/doc/PROTOCOL-HTTP2.md
The values in the initial metadata can be any valid ASCII character in
case of a non binary header type.

Signed-off-by: Omri Zohar <omriz@google.com>
@omriz
Copy link
Contributor Author

omriz commented May 18, 2021

/retest

@repokitteh-read-only
Copy link

Retrying Azure Pipelines:
Retried failed jobs in: envoy-presubmit

🐱

Caused by: a #16414 (comment) was created by @omriz.

see: more, trace.

Copy link
Member

@htuch htuch left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, thanks!

@omriz for future it is generally preferable to merge main, avoid rebase and force push. This allows faster validation of deltas between commits.

@htuch htuch merged commit d304a2f into envoyproxy:main May 20, 2021
leyao-daily pushed a commit to leyao-daily/envoy that referenced this pull request Sep 30, 2021
According to the GRPC spec https://github.com/grpc/grpc/blob/master/doc/PROTOCOL-HTTP2.md
The values in the initial metadata can be any valid ASCII character in
case of a non binary header type.

Risk Level: Low
Testing: Unit Tests
Docs Changes:N/A
Release Notes: Fixing GRPC initial metadata validation for ASCII characters

Signed-off-by: Omri Zohar <omriz@google.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants