Skip to content

Conversation

@yknoya
Copy link
Contributor

@yknoya yknoya commented Sep 6, 2023

Problem

If the following conditions are met, an issue occurs where ATS sends an invalid Server Hello during the TLS handshake:

  • Next Protocol Negotiation (NPN) extension is used.
  • HTTP/2 is disabled in sni.yaml.

How to reproduce

  1. Add the following remap to the remap.config:
map https://cdn.example.com/ http://example.com
  1. Add the following configuration to the sni.yaml:
sni:
  - fqdn: cdn.example.com
    http2: off
  1. Launch Traffic Server.
  2. Execute the following command:
openssl s_client \
  -connect localhost:443 \
  -servername cdn.example.com \
  -tls1_2 \
  -nextprotoneg http/2,http/1.1 \
  -CAfile /etc/pki/tls/certs/ca-bundle.crt \
  </dev/null \
| grep error

If reproduced, the following error will be displayed:

140451424151360:error:1423506E:SSL routines:ssl_next_proto_validate:bad extension:ssl/statem/extensions_clnt.c:1565:

Cause

The issue is caused by ATS setting a length of the NPN string greater than the actual length in the NPN extension.
For example, when HTTP/2 is disabled in sni.yaml, the NPN string should be 8http/1.18http/1.0 and its length should be 18, but ATS sets the length as 21.

Here are some pointers to the relevant bits of code:

The ssl_next_protos_advertised_callback function is responsible for setting the NPN string and its length.

bool
SSLMultiCertConfigLoader::_set_npn_callback(SSL_CTX *ctx)
{
SSL_CTX_set_next_protos_advertised_cb(ctx, ssl_next_protos_advertised_callback, nullptr);
return true;
}

The NPN string is stored in the ALPNSupport::npn, and its length is stored in the ALPNSupport::npnsz.

int
ssl_next_protos_advertised_callback(SSL *ssl, const unsigned char **out, unsigned *outlen, void *)
{
ALPNSupport *alpns = ALPNSupport::getInstance(ssl);
ink_assert(alpns);
if (alpns) {
return alpns->advertise_next_protocol(ssl, out, outlen);
}
return SSL_TLSEXT_ERR_NOACK;
}

int
ALPNSupport::advertise_next_protocol(SSL *ssl, const unsigned char **out, unsigned *outlen)
{
if (this->getNPN(out, outlen)) {

bool
getNPN(const unsigned char **out, unsigned int *outlen) const
{
if (this->npn && this->npnsz) {
*out = this->npn;
*outlen = this->npnsz;

The values of ALPNSupport::npn and ALPNSupport::npnsz are set in the SSLNextProtocolSet::create_npn_advertisement function.

npnSet->create_npn_advertisement(protoenabled, &npn, &npnsz);

In SSLNextProtocolSet::create_npn_advertisement, it is checked whether each protocol is enabled when setting the value for ALPNSupport::npn.

for (ep = endpoints.head; ep != nullptr; ep = endpoints.next(ep)) {
if (enabled.contains(globalSessionProtocolNameRegistry.toIndex(swoc::TextView{ep->protocol, strlen(ep->protocol)}))) {
Debug("ssl", "advertising protocol %s, %p", ep->protocol, ep->endpoint);
advertised = append_protocol(ep->protocol, advertised);
}
}

However, when setting the value for ALPNSupport::npnsz, it isn't checked whether each protocol is enabled.

for (ep = endpoints.head; ep != nullptr; ep = endpoints.next(ep)) {
ink_release_assert((strlen(ep->protocol) > 0));
*len += (strlen(ep->protocol) + 1);
}

As a result, when HTTP/2 is disabled in sni.yaml, the value excluding HTTP/2 is set for ALPNSupport::npn, but the value including HTTP/2 is set for ALPNSupport::npnsz.
Therefore, the length of the NPN string is greater than the actual length.

@yknoya yknoya changed the title fix: check whether a protocol is enabled during the length calculatio… fix: check whether a protocol is enabled during the length calculation in create_npn_advertisement Sep 6, 2023
Copy link
Contributor

@masaori335 masaori335 left a comment

Choose a reason for hiding this comment

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

Looks reasonable. The length should be incremented only if it's appended.

@randall randall merged commit 18b3d55 into apache:master Sep 8, 2023
cmcfarlen pushed a commit to cmcfarlen/trafficserver that referenced this pull request Jun 3, 2024
* asf/master: (22 commits)
  fix: check whether a protocol is enabled during the length calculation in create_npn_advertisement (apache#10381)
  Coverity 1518612: Remove dead code (apache#10384)
  prefetch_cmcd: make autests more robust by removing need for gold file wildcard (apache#10382)
  Give a chance to send a response before receiving next request on H2 (apache#9997)
  CID 1516688: Fix uninitialized member of AcceptOptions (apache#10152)
  Fix slice head request memory issue (apache#10285)
  Fixes the TSMgmt metrics APIs for new API metrics (apache#10379)
  Minor parent.config a/an change (apache#10372)
  Allow DbgCtl tag to be set after instance construction. (apache#10375)
  Fix more build dep issues, for later PRs to work (apache#10376)
  money_trace cid 1518569: string not null terminated (apache#10373)
  Fix a couple of Coverity issues in health check plugin, around filenames (apache#10371)
  Fixes some build issues that happens with  other changes (apache#10374)
  Eliminate unreachable code covered by switch default (apache#10370)
  Add tests for disk failure (apache#10192)
  Disable copying/moving for DbgCtl. (apache#10321)
  Cmake autest (apache#10327)
  cmake: add unit tests from mgmt/rpc (apache#10366)
  Adjust CMakeLists with git worktree (apache#10298)
  Fix example plugins build (apache#10326)
  ...
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants