Commit message: Throw on bad types during skipping data

Summary:
The current code silently returns on bad types. In case when we have an invalid data, we may get a container of a large size with a bad type, this would lead to us running long loop doing nothing (though we already can say that the data is invalid).

The new code would throw an exception as soon as we try to skip a value of invalid type.

Fixes CVE-2019-3552

Reviewed By: yfeldblum, stevegury

Differential Revision: D8344920

fbshipit-source-id: f12e8f3442f7ad5e1a81d822380701e929b80f0d
CVE: CVE-2019-3558
CWE: CWE-703
Vulnerability description: Python Facebook Thrift servers would not error upon receiving messages with containers of fields of unknown type. As a result, malicious clients could send short messages which would take a long time for the server to parse, potentially leading to denial of service. This issue affects Facebook Thrift prior to v2019.02.18.00.

Project: fbthrift

  static bool isEqual(const StrType& lhs, const StrType& rhs) {
    return lhs == rhs;
  }

Project: fbthrift

  static bool isLess(
      const std::unique_ptr<folly::IOBuf>& lhs,
      const std::unique_ptr<folly::IOBuf>& rhs) {
    return folly::IOBufLess{}(lhs, rhs);
  }

Project: fbthrift

  static std::unique_ptr<folly::IOBuf> fromStringLiteral(const char* str) {
    return (
        str[0] != '\0' ? folly::IOBuf::wrapBuffer(str, strlen(str)) : nullptr);
  }

Project: fbthrift

  static bool isEqual(const folly::IOBuf& lhs, const folly::IOBuf& rhs) {
    return folly::IOBufEqualTo{}(lhs, rhs);
  }

Project: fbthrift

[[noreturn]] void TProtocolException::throwReportedTypeMismatch() {
  throw TProtocolException(
      TProtocolException::INVALID_DATA,
      "The reported type of thrift element does not match the serialized type");
}

Project: fbthrift

  static bool isEmpty(const StrType& str) {
    return str.empty();
  }

Project: fbthrift

[[noreturn]] void TProtocolException::throwMissingRequiredField(
    folly::StringPiece field,
    folly::StringPiece type) {
  constexpr auto fmt =
      "Required field '{}' was not found in serialized data! Struct: {}";
  throw TProtocolException(
      TProtocolException::MISSING_REQUIRED_FIELD,
      folly::sformat(fmt, field, type));
}

Project: fbthrift

  static bool isEmpty(const folly::IOBuf& str) {
    return str.empty();
  }

Project: fbthrift

[[noreturn]] void TProtocolException::throwInvalidSkipType(TType type) {
  throw TProtocolException(
      TProtocolException::INVALID_DATA,
      folly::sformat(
          "Encountered invalid field/element type ({}) during skipping",
          static_cast<uint8_t>(type)));
}

Project: fbthrift

[[noreturn]] void TProtocolException::throwNegativeSize() {
  throw TProtocolException(TProtocolException::NEGATIVE_SIZE);
}

Project: fbthrift

  static bool isLess(const StrType& lhs, const StrType& rhs) {
    return lhs < rhs;
  }

Project: fbthrift

void skip(Protocol_& prot, TType arg_type) {
  switch (arg_type) {
    case TType::T_BOOL: {
      bool boolv;
      prot.readBool(boolv);
      return;
    }
    case TType::T_BYTE: {
      int8_t bytev;
      prot.readByte(bytev);
      return;
    }
    case TType::T_I16: {
      int16_t i16;
      prot.readI16(i16);
      return;
    }
    case TType::T_I32: {
      int32_t i32;
      prot.readI32(i32);
      return;
    }
    case TType::T_I64: {
      int64_t i64;
      prot.readI64(i64);
      return;
    }
    case TType::T_DOUBLE: {
      double dub;
      prot.readDouble(dub);
      return;
    }
    case TType::T_FLOAT: {
      float flt;
      prot.readFloat(flt);
      return;
    }
    case TType::T_STRING: {
      std::string str;
      prot.readBinary(str);
      return;
    }
    case TType::T_STRUCT: {
      std::string name;
      int16_t fid;
      TType ftype;
      prot.readStructBegin(name);
      while (true) {
        prot.readFieldBegin(name, ftype, fid);
        if (ftype == TType::T_STOP) {
          break;
        }
        apache::thrift::skip(prot, ftype);
        prot.readFieldEnd();
      }
      prot.readStructEnd();
      return;
    }
    case TType::T_MAP: {
      TType keyType;
      TType valType;
      uint32_t i, size;
      prot.readMapBegin(keyType, valType, size);
      for (i = 0; i < size; i++) {
        apache::thrift::skip(prot, keyType);
        apache::thrift::skip(prot, valType);
      }
      prot.readMapEnd();
      return;
    }
    case TType::T_SET: {
      TType elemType;
      uint32_t i, size;
      prot.readSetBegin(elemType, size);
      for (i = 0; i < size; i++) {
        apache::thrift::skip(prot, elemType);
      }
      prot.readSetEnd();
      return;
    }
    case TType::T_LIST: {
      TType elemType;
      uint32_t i, size;
      prot.readListBegin(elemType, size);
      for (i = 0; i < size; i++) {
        apache::thrift::skip(prot, elemType);
      }
      prot.readListEnd();
      return;
    }
    default: {
      TProtocolException::throwInvalidSkipType(arg_type);
    }
  }
}

Project: fbthrift

[[noreturn]] void TProtocolException::throwUnionMissingStop() {
  throw TProtocolException(
      TProtocolException::INVALID_DATA,
      "missing stop marker to terminate a union");
}

Project: fbthrift

[[noreturn]] void TProtocolException::throwBoolValueOutOfRange(uint8_t value) {
  throw TProtocolException(
      TProtocolException::INVALID_DATA,
      folly::sformat(
          "Attempt to interpret value {} as bool, probably the data is corrupted",
          value));
}

Project: fbthrift

[[noreturn]] void TProtocolException::throwExceededSizeLimit() {
  throw TProtocolException(TProtocolException::SIZE_LIMIT);
}

Project: fbthrift

  static folly::IOBuf fromStringLiteral(const char* str) {
    return folly::IOBuf::wrapBufferAsValue(str, strlen(str));
  }

Project: fbthrift

TEST(ProtocolSkipTest, SkipInt) {
  IOBufQueue queue;
  CompactProtocolWriter writer;
  writer.setOutput(&queue);
  writer.writeI32(123);
  auto buf = queue.move();
  CompactProtocolReader reader;
  reader.setInput(buf.get());
  reader.skip(TType::T_I32);
}

Project: fbthrift

  static StrType fromStringLiteral(const char* str) {
    return StrType(str);
  }

Project: fbthrift

  static bool isLess(const folly::IOBuf& lhs, const folly::IOBuf& rhs) {
    return folly::IOBufLess{}(lhs, rhs);
  }

Project: fbthrift

  static bool isEqual(
      const std::unique_ptr<folly::IOBuf>& lhs,
      const std::unique_ptr<folly::IOBuf>& rhs) {
    return folly::IOBufEqualTo{}(lhs, rhs);
  }

Project: fbthrift

  static bool isEmpty(const std::unique_ptr<folly::IOBuf>& str) {
    return !str || str->empty();
  }

Project: fbthrift

TEST(ProtocolSkipTest, SkipStopInContainer) {
  IOBufQueue queue;
  CompactProtocolWriter writer;
  writer.setOutput(&queue);
  writer.writeListBegin(TType::T_STOP, 1u << 30);
  auto buf = queue.move();
  CompactProtocolReader reader;
  reader.setInput(buf.get());
  bool thrown = false;
  try {
    reader.skip(TType::T_LIST);
  } catch (const TProtocolException& ex) {
    EXPECT_EQ(TProtocolException::INVALID_DATA, ex.getType());
    thrown = true;
  }
  EXPECT_TRUE(thrown);
}

Project: fbthrift

TEST(ProtocolSkipTest, SkipStop) {
  IOBufQueue queue;
  CompactProtocolWriter writer;
  writer.setOutput(&queue);
  writer.writeFieldStop();
  auto buf = queue.move();
  CompactProtocolReader reader;
  reader.setInput(buf.get());
  bool thrown = false;
  try {
    reader.skip(TType::T_STOP);
  } catch (const TProtocolException& ex) {
    EXPECT_EQ(TProtocolException::INVALID_DATA, ex.getType());
    thrown = true;
  }
  EXPECT_TRUE(thrown);
}