Commit message: Avoid arithmetic operation on uint16 read from the wire.

Summary:
This could overflow previously.

CVE-2019-3560

Reviewed By: yfeldblum

Differential Revision: D14152362

fbshipit-source-id: c0ebb3fc59b49c7c23e6bcb90458c19cd891be65
CVE: CVE-2019-3560
CWE: CWE-787
Vulnerability description: An improperly performed length calculation on a buffer in PlaintextRecordLayer could lead to an infinite loop and denial-of-service based on user input. This issue affected versions of fizz prior to v2019.03.04.00.

Project: fizz
File: PlaintextRecordTest.cpp

TEST_F(PlaintextRecordTest, TestWriteHandshake) {
  TLSMessage msg{ContentType::handshake, getBuf("1234567890")};
  auto buf = write_.write(std::move(msg));
  expectSame(buf.data, "16030300051234567890");
}

Project: fizz
File: PlaintextRecordTest.cpp

TEST_F(PlaintextRecordTest, TestWaitForHeader) {
  addToQueue("16030102");
  EXPECT_FALSE(read_.read(queue_).hasValue());
  EXPECT_EQ(queue_.chainLength(), 4);
}

Project: fizz
File: PlaintextRecordTest.cpp

TEST_F(PlaintextRecordTest, TestSkipAndRead) {
  read_.setSkipEncryptedRecords(true);
  addToQueue("170301000501234567891703010005012345678916030100050123456789");
  auto msg = read_.read(queue_);
  EXPECT_EQ(msg->type, ContentType::handshake);
  expectSame(msg->fragment, "0123456789");
  EXPECT_TRUE(queue_.empty());
}

Project: fizz
File: PlaintextRecordLayer.cpp

folly::Optional<TLSMessage> PlaintextReadRecordLayer::read(
    folly::IOBufQueue& buf) {
  while (true) {
    folly::io::Cursor cursor(buf.front());

    if (buf.empty() || !cursor.canAdvance(kPlaintextHeaderSize)) {
      return folly::none;
    }

    TLSMessage msg;
    msg.type = static_cast<ContentType>(cursor.readBE<ContentTypeType>());

    if (skipEncryptedRecords_) {
      if (msg.type == ContentType::application_data) {
        cursor.skip(sizeof(ProtocolVersion));
        auto length = cursor.readBE<uint16_t>();
        if (buf.chainLength() < (cursor - buf.front()) + length) {
          return folly::none;
        }
        buf.trimStart(static_cast<size_t>(kPlaintextHeaderSize) + length);
        continue;
      } else if (msg.type != ContentType::change_cipher_spec) {
        skipEncryptedRecords_ = false;
      }
    }

    switch (msg.type) {
      case ContentType::handshake:
      case ContentType::alert:
        break;
      case ContentType::change_cipher_spec:
        break;
      default:
        throw std::runtime_error(folly::to<std::string>(
            "received plaintext content type ",
            static_cast<ContentTypeType>(msg.type),
            ", header: ",
            folly::hexlify(buf.splitAtMost(10)->coalesce())));
    }

    receivedRecordVersion_ =
        static_cast<ProtocolVersion>(cursor.readBE<ProtocolVersionType>());

    auto length = cursor.readBE<uint16_t>();
    if (length > kMaxPlaintextRecordSize) {
      throw std::runtime_error("received too long plaintext record");
    }
    if (length == 0) {
      throw std::runtime_error("received empty plaintext record");
    }
    if (buf.chainLength() < (cursor - buf.front()) + length) {
      return folly::none;
    }

    cursor.clone(msg.fragment, length);

    buf.trimStart(cursor - buf.front());

    if (msg.type == ContentType::change_cipher_spec) {
      msg.fragment->coalesce();
      if (msg.fragment->length() == 1 && *msg.fragment->data() == 0x01) {
        continue;
      } else {
        throw FizzException(
            "received ccs", AlertDescription::illegal_parameter);
      }
    }

    return std::move(msg);
  }
}

Project: fizz
File: PlaintextRecordTest.cpp

TEST_F(PlaintextRecordTest, TestReadAlert) {
  addToQueue("15030100050123456789");
  auto msg = read_.read(queue_);
  EXPECT_EQ(msg->type, ContentType::alert);
  expectSame(msg->fragment, "0123456789");
  EXPECT_TRUE(queue_.empty());
}

Project: fizz
File: PlaintextRecordTest.cpp

TEST_F(PlaintextRecordTest, TestDataRemaining) {
  addToQueue("16030100050123456789160301");
  auto msg = read_.read(queue_);
  EXPECT_EQ(msg->type, ContentType::handshake);
  expectSame(msg->fragment, "0123456789");
  EXPECT_EQ(queue_.chainLength(), 3);
  expectSame(queue_.move(), "160301");
}

Project: fizz
File: PlaintextRecordTest.cpp

  Buf getBuf(const std::string& hex) {
    auto data = unhexlify(hex);
    return IOBuf::copyBuffer(data.data(), data.size());
  }

Project: fizz
File: PlaintextRecordTest.cpp

TEST_F(PlaintextRecordTest, TestReadEmpty) {
  EXPECT_FALSE(read_.read(queue_).hasValue());
}

Project: fizz
File: PlaintextRecordLayer.cpp

TLSContent PlaintextWriteRecordLayer::write(
    TLSMessage msg,
    ProtocolVersion recordVersion) const {
  if (msg.type == ContentType::application_data) {
    throw std::runtime_error("refusing to send plaintext application data");
  }

  auto fragment = std::move(msg.fragment);
  folly::io::Cursor cursor(fragment.get());
  std::unique_ptr<folly::IOBuf> data;
  while (!cursor.isAtEnd()) {
    Buf thisFragment;
    auto len = cursor.cloneAtMost(thisFragment, kMaxPlaintextRecordSize);

    auto header = folly::IOBuf::create(kPlaintextHeaderSize);
    folly::io::Appender appender(header.get(), kPlaintextHeaderSize);
    appender.writeBE(static_cast<ContentTypeType>(msg.type));
    appender.writeBE(static_cast<ProtocolVersionType>(recordVersion));
    appender.writeBE<uint16_t>(len);

    if (!data) {
      data = std::move(header);
    } else {
      data->prependChain(std::move(header));
    }
    data->prependChain(std::move(thisFragment));
  }
  TLSContent content;
  content.data = std::move(data);
  content.contentType = msg.type;
  content.encryptionLevel = EncryptionLevel::Plaintext;
  return content;
}

Project: fizz
File: PlaintextRecordTest.cpp

TEST_F(PlaintextRecordTest, TestWriteAppData) {
  TLSMessage msg{ContentType::application_data};
  EXPECT_ANY_THROW(write_.write(std::move(msg)));
}

Project: fizz
File: PlaintextRecordLayer.cpp

EncryptionLevel PlaintextReadRecordLayer::getEncryptionLevel() const {
  return EncryptionLevel::Plaintext;
}

Project: fizz
File: PlaintextRecordLayer.cpp

TLSContent PlaintextWriteRecordLayer::writeInitialClientHello(
    Buf encodedClientHello) const {
  return write(
      TLSMessage{ContentType::handshake, std::move(encodedClientHello)},
      ProtocolVersion::tls_1_0);
}

Project: fizz
File: PlaintextRecordTest.cpp

TEST_F(PlaintextRecordTest, TestSkipOversizedRecord) {
  read_.setSkipEncryptedRecords(true);
  addToQueue("170301fffb");
  auto longBuf = IOBuf::create(0xfffb);
  longBuf->append(0xfffb);
  queue_.append(std::move(longBuf));
  EXPECT_FALSE(read_.read(queue_).hasValue());
  EXPECT_TRUE(queue_.empty());
}

Project: fizz
File: PlaintextRecordTest.cpp

TEST_F(PlaintextRecordTest, TestEmpty) {
  addToQueue("1603010000aa");
  EXPECT_ANY_THROW(read_.read(queue_));
}

Project: fizz
File: PlaintextRecordLayer.cpp

EncryptionLevel PlaintextWriteRecordLayer::getEncryptionLevel() const {
  return EncryptionLevel::Plaintext;
}

Project: fizz
File: PlaintextRecordTest.cpp

TEST_F(PlaintextRecordTest, TestMaxSize) {
  addToQueue("1603014000");
  EXPECT_FALSE(read_.read(queue_).hasValue());
  EXPECT_EQ(queue_.chainLength(), 5);
}

Project: fizz
File: PlaintextRecordTest.cpp

TEST_F(PlaintextRecordTest, TestOverSize) {
  addToQueue("1603014001");
  EXPECT_ANY_THROW(read_.read(queue_));
}

Project: fizz
File: PlaintextRecordTest.cpp

TEST_F(PlaintextRecordTest, TestWaitBeforeSkip) {
  read_.setSkipEncryptedRecords(true);
  addToQueue("170301000501234567");
  EXPECT_FALSE(read_.read(queue_).hasValue());
  expectSame(queue_.move(), "170301000501234567");
}

Project: fizz
File: PlaintextRecordTest.cpp

  void expectSame(const Buf& buf, const std::string& hex) {
    auto str = buf->moveToFbString().toStdString();
    EXPECT_EQ(hexlify(str), hex);
  }

Project: fizz
File: PlaintextRecordTest.cpp

TEST_F(PlaintextRecordTest, TestReadHandshake) {
  addToQueue("16030100050123456789");
  auto msg = read_.read(queue_);
  EXPECT_EQ(msg->type, ContentType::handshake);
  expectSame(msg->fragment, "0123456789");
  EXPECT_TRUE(queue_.empty());
}

Project: fizz
File: PlaintextRecordTest.cpp

TEST_F(PlaintextRecordTest, TestWriteClientHello) {
  auto buf = write_.writeInitialClientHello(getBuf("1234567890"));
  expectSame(buf.data, "16030100051234567890");
}

Project: fizz
File: PlaintextRecordTest.cpp

TEST_F(PlaintextRecordTest, TestFragmentedWrite) {
  TLSMessage msg{ContentType::handshake, IOBuf::create(0)};
  auto buf = IOBuf::create(0x4010);
  buf->append(0x4010);
  memset(buf->writableData(), 0x1, buf->length());
  msg.fragment->prependChain(std::move(buf));
  auto write = write_.write(std::move(msg));

  TLSMessage msg1{ContentType::handshake, IOBuf::create(0)};
  buf = IOBuf::create(0x4000);
  buf->append(0x4000);
  memset(buf->writableData(), 0x1, buf->length());
  msg1.fragment->prependChain(std::move(buf));
  auto write1 = write_.write(std::move(msg1));

  TLSMessage msg2{ContentType::handshake, IOBuf::create(0)};
  buf = IOBuf::create(0x10);
  buf->append(0x10);
  memset(buf->writableData(), 0x1, buf->length());
  msg2.fragment->prependChain(std::move(buf));
  auto write2 = write_.write(std::move(msg2));

  write1.data->prependChain(std::move(write2.data));
  IOBufEqualTo eq;
  EXPECT_TRUE(eq(write.data, write1.data));
}

Project: fizz
File: PlaintextRecordTest.cpp

TEST_F(PlaintextRecordTest, TestReadAppData) {
  addToQueue("17030100050123456789");
  EXPECT_ANY_THROW(read_.read(queue_));
}

Project: fizz
File: PlaintextRecordTest.cpp

TEST_F(PlaintextRecordTest, TestWaitForData) {
  addToQueue("160301000512345678");
  EXPECT_FALSE(read_.read(queue_).hasValue());
  EXPECT_EQ(queue_.chainLength(), 9);
}

Project: fizz
File: PlaintextRecordLayer.cpp

TLSContent PlaintextWriteRecordLayer::write(TLSMessage&& msg) const {
  return write(std::move(msg), ProtocolVersion::tls_1_2);
}

Project: fizz
File: PlaintextRecordTest.cpp

TEST_F(PlaintextRecordTest, TestSkipAndWait) {
  read_.setSkipEncryptedRecords(true);
  addToQueue("17030100050123456789");
  EXPECT_FALSE(read_.read(queue_).hasValue());
  EXPECT_TRUE(queue_.empty());
}