Commit 3ab3e21a authored by John Williams's avatar John Williams Committed by Commit Bot

[Cast Channel] Added more fuzz testing.

Fuzz testing now covers all of cast_framer.cc and
cast_message_util.cc, and part of cast_auth_until.cc.

Additional work is needed for functions in cast_auth_util.cc.

Bug: 796717
Change-Id: I732175645feae2ccd5c9d0c8e0f19f287eb1d6d9
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1680610Reviewed-by: default avatarmark a. foltz <mfoltz@chromium.org>
Commit-Queue: John Williams <jrw@chromium.org>
Cr-Commit-Position: refs/heads/master@{#772356}
parent f067925c
......@@ -101,8 +101,11 @@ source_set("unit_tests") {
]
}
# TODO(jrw): Rename target to cast_framer_ingest_fuzzer. The name
# is left unchanged for now to avoid the need to get reviews for
# various files that include it.
fuzzer_test("cast_message_fuzzer") {
sources = [ "cast_message_fuzzer.cc" ]
sources = [ "cast_framer_ingest_fuzzer.cc" ]
deps = [
":test_support",
"//base",
......@@ -115,3 +118,36 @@ fuzzer_test("cast_message_fuzzer") {
# See MessageFramer::MessageHeader::max_message_size()
libfuzzer_options = [ "max_len=65535" ]
}
fuzzer_test("cast_auth_util_fuzzer") {
sources = [ "cast_auth_util_fuzzer.cc" ]
dict = "fuzz.dict"
deps = [
":cast_channel",
"//components/cast_channel/proto:cast_channel_fuzzer_inputs_proto",
"//net/data/ssl/certificates:generate_fuzzer_cert_includes",
"//third_party/libprotobuf-mutator",
"//third_party/openscreen/src/cast/common/channel/proto:channel_proto",
]
}
fuzzer_test("cast_framer_serialize_fuzzer") {
sources = [ "cast_framer_serialize_fuzzer.cc" ]
deps = [
":cast_channel",
"//components/cast_channel/proto:cast_channel_fuzzer_inputs_proto",
"//third_party/libprotobuf-mutator",
"//third_party/openscreen/src/cast/common/channel/proto:channel_proto",
]
}
fuzzer_test("cast_message_util_fuzzer") {
sources = [ "cast_message_util_fuzzer.cc" ]
dict = "fuzz.dict"
deps = [
":cast_channel",
"//components/cast_channel/proto:cast_channel_fuzzer_inputs_proto",
"//third_party/libprotobuf-mutator",
"//third_party/openscreen/src/cast/common/channel/proto:channel_proto",
]
}
# How to Run a Fuzz Test
Create an appropriate build config:
```shell
% tools/mb/mb.py gen -m chromium.fuzz -b 'Libfuzzer Upload Linux ASan' out/libfuzzer
% gn gen out/libfuzzer
```
Build the fuzz target:
```shell
% ninja -C out/libfuzzer $TEST_NAME
```
Create an empty corpus directory:
```shell
% mkdir ${TEST_NAME}_corpus
```
Run the fuzz target, turning off detection of ODR violations that occur in
component builds:
```shell
% export ASAN_OPTIONS=detect_odr_violation=0
% ./out/libfuzzer/$TEST_NAME ${TEST_NAME}_corpus
```
For more details, refer to https://chromium.googlesource.com/chromium/src/testing/libfuzzer/+/refs/heads/master/getting_started.md
......@@ -259,9 +259,26 @@ AuthContext AuthContext::Create() {
return AuthContext(CastNonce::Get());
}
// static
AuthContext AuthContext::CreateForTest(const std::string& nonce_data) {
// Given some garbage data, try to turn it into a string that at least has the
// right length.
std::string nonce;
if (nonce_data.empty()) {
nonce = std::string(kNonceSizeInBytes, '0');
} else {
while (nonce.size() < kNonceSizeInBytes) {
nonce += nonce_data;
}
nonce.erase(kNonceSizeInBytes);
}
DCHECK(nonce.size() == kNonceSizeInBytes);
return AuthContext(nonce);
}
AuthContext::AuthContext(const std::string& nonce) : nonce_(nonce) {}
AuthContext::~AuthContext() {}
AuthContext::~AuthContext() = default;
AuthResult AuthContext::VerifySenderNonce(
const std::string& nonce_response) const {
......
......@@ -77,6 +77,8 @@ class AuthContext {
// The same context must be used in the challenge and reply.
static AuthContext Create();
static AuthContext CreateForTest(const std::string& nonce);
// Verifies the nonce received in the response is equivalent to the one sent.
// Returns success if |nonce_response| matches nonce_
AuthResult VerifySenderNonce(const std::string& nonce_response) const;
......
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <cstdlib>
#include <iostream>
#include "components/cast_channel/cast_auth_util.h"
#include "components/cast_channel/fuzz_proto/fuzzer_inputs.pb.h"
#include "net/cert/x509_certificate.h"
#include "testing/libfuzzer/proto/lpm_interface.h"
namespace cast_channel {
namespace fuzz {
namespace {
const char kCertData[] = {
#include "net/data/ssl/certificates/wildcard.inc"
};
} // namespace
DEFINE_PROTO_FUZZER(const CastAuthUtilInputs& input_union) {
// TODO(crbug.com/796717): Add tests for AuthenticateChallengeReply and
// VerifyTLSCertificate if necessary. Refer to updates on the bug, and check
// to see if there is already coverage through BoringSSL
switch (input_union.input_case()) {
case CastAuthUtilInputs::kAuthenticateChallengeReplyInput: {
const auto& input = input_union.authenticate_challenge_reply_input();
cast::channel::DeviceAuthMessage auth_message = input.auth_message();
AuthContext context = AuthContext::CreateForTest(input.nonce());
scoped_refptr<net::X509Certificate> peer_cert =
net::X509Certificate::CreateFromBytes(kCertData,
base::size(kCertData));
AuthenticateChallengeReply(input.cast_message(), *peer_cert, context);
break;
}
default:
NOTREACHED();
}
}
} // namespace fuzz
} // namespace cast_channel
......@@ -12,7 +12,6 @@
#include "components/cast_channel/cast_framer.h"
#include "net/base/io_buffer.h"
#include "third_party/openscreen/src/cast/common/channel/proto/cast_channel.pb.h"
#include "third_party/protobuf/src/google/protobuf/stubs/logging.h"
// Silence logging from the protobuf library.
google::protobuf::LogSilencer log_silencer;
......
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <cstdlib>
#include <iostream>
#include <string>
#include "components/cast_channel/cast_framer.h"
#include "testing/libfuzzer/proto/lpm_interface.h"
#include "third_party/openscreen/src/cast/common/channel/proto/cast_channel.pb.h"
namespace cast_channel {
namespace fuzz {
DEFINE_PROTO_FUZZER(const cast::channel::CastMessage& input) {
std::string native_input;
MessageFramer::Serialize(input, &native_input);
if (::getenv("LPM_DUMP_NATIVE_INPUT"))
std::cout << native_input << std::endl;
}
} // namespace fuzz
} // namespace cast_channel
......@@ -174,9 +174,6 @@ CastMessageType CastMessageTypeFromString(const std::string& type);
// correspond to a known type.
V2MessageType V2MessageTypeFromString(const std::string& type);
// Returns a human readable string for |message_proto|.
std::string CastMessageToString(const CastMessage& message_proto);
// Returns a human readable string for |message|.
std::string AuthMessageToString(const DeviceAuthMessage& message);
......
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <string>
#include <utility>
#include <vector>
#include "base/values.h"
#include "components/cast_channel/cast_message_util.h"
#include "components/cast_channel/enum_table.h"
#include "components/cast_channel/fuzz_proto/fuzzer_inputs.pb.h"
#include "testing/libfuzzer/proto/lpm_interface.h"
using cast_util::EnumToString;
namespace cast_channel {
namespace fuzz {
namespace {
base::Value MakeValue(const JunkValue& junk) {
base::Value result(base::Value::Type::DICTIONARY);
for (int i = 0; i < junk.field_size(); i++) {
const auto& field = junk.field(i);
base::Value field_value = field.has_int_value()
? base::Value(field.int_value())
: field.has_string_value()
? base::Value(field.string_value())
: field.has_float_value()
? base::Value(field.float_value())
: base::Value(field.bool_value());
result.SetKey(field.name(), std::move(field_value));
}
return result;
}
template <typename Field, typename T = typename Field::value_type>
std::vector<T> MakeVector(const Field& field) {
return std::vector<T>(field.cbegin(), field.cend());
}
} // namespace
DEFINE_PROTO_FUZZER(const CastMessageUtilInputs& input_union) {
// TODO(crbug.com/796717): Add test for CreateAuthChallengeMessage()
switch (input_union.input_case()) {
case CastMessageUtilInputs::kCreateBroadcastRequestInput: {
const auto& input = input_union.create_broadcast_request_input();
CreateBroadcastRequest(input.source_id(), input.request_id(),
MakeVector(input.app_id()),
BroadcastRequest(input.broadcast_namespace(),
input.broadcast_message()));
break;
}
case CastMessageUtilInputs::kCreateLaunchRequestInput: {
const auto& input = input_union.create_launch_request_input();
base::Optional<base::Value> app_params;
if (input.has_app_params())
app_params = MakeValue(input.app_params());
CreateLaunchRequest(input.source_id(), input.request_id(), input.app_id(),
input.locale(),
MakeVector(input.supported_app_types()), app_params);
break;
}
case CastMessageUtilInputs::kCreateStopRequestInput: {
const auto& input = input_union.create_stop_request_input();
CreateStopRequest(input.source_id(), input.request_id(),
input.session_id());
break;
}
case CastMessageUtilInputs::kCreateCastMessageInput: {
const auto& input = input_union.create_cast_message_input();
base::Value body = MakeValue(input.body());
CreateCastMessage(input.message_namespace(), body, input.source_id(),
input.destination_id());
break;
}
case CastMessageUtilInputs::kCreateMediaRequestInput: {
const auto& input = input_union.create_media_request_input();
auto type = static_cast<V2MessageType>(input.type());
if (IsMediaRequestMessageType(type)) {
base::Value body = MakeValue(input.body());
body.SetKey("type", base::Value(*EnumToString(type)));
CreateMediaRequest(body, input.request_id(), input.source_id(),
input.destination_id());
}
break;
}
case CastMessageUtilInputs::kCreateSetVolumeRequestInput: {
const auto& input = input_union.create_set_volume_request_input();
base::Value body = MakeValue(input.body());
body.SetKey(
"type",
base::Value(
EnumToString<V2MessageType, V2MessageType::kSetVolume>()));
CreateSetVolumeRequest(body, input.request_id(), input.source_id());
break;
}
case CastMessageUtilInputs::kIntInput: {
IsMediaRequestMessageType(
static_cast<V2MessageType>(input_union.int_input()));
ToString(static_cast<GetAppAvailabilityResult>(input_union.int_input()));
ToString(static_cast<CastMessageType>(input_union.int_input()));
ToString(static_cast<V2MessageType>(input_union.int_input()));
break;
}
case CastMessageUtilInputs::kStringInput: {
IsCastInternalNamespace(input_union.string_input());
break;
}
case CastMessageUtilInputs::kCastMessage: {
const auto& message = input_union.cast_message();
IsCastMessageValid(message);
IsAuthMessage(message);
IsReceiverMessage(message);
IsPlatformSenderMessage(message);
break;
}
case CastMessageUtilInputs::kCreateVirtualConnectionRequestInput: {
const auto& input = input_union.create_virtual_connection_request_input();
CreateVirtualConnectionRequest(
input.source_id(), input.destination_id(),
static_cast<VirtualConnectionType>(input.connection_type()),
input.user_agent(), input.browser_version());
break;
}
case CastMessageUtilInputs::kCreateGetAppAvailabilityRequestInput: {
const auto& input =
input_union.create_get_app_availability_request_input();
CreateGetAppAvailabilityRequest(input.source_id(), input.request_id(),
input.app_id());
break;
}
case CastMessageUtilInputs::kGetRequestIdFromResponseInput: {
const auto& input = input_union.get_request_id_from_response_input();
base::Value payload = MakeValue(input.payload());
if (input.has_request_id())
payload.SetKey("requestId", base::Value(input.request_id()));
GetRequestIdFromResponse(payload);
break;
}
case CastMessageUtilInputs::kGetLaunchSessionResponseInput: {
const auto& input = input_union.get_launch_session_response_input();
base::Value payload = MakeValue(input.payload());
GetLaunchSessionResponse(payload);
break;
}
case CastMessageUtilInputs::kParseMessageTypeFromPayloadInput: {
const auto& input = input_union.parse_message_type_from_payload_input();
base::Value payload = MakeValue(input.payload());
if (input.has_type())
payload.SetKey("type", base::Value(input.type()));
ParseMessageTypeFromPayload(payload);
break;
}
case CastMessageUtilInputs::kCreateReceiverStatusRequestInput: {
const auto& input = input_union.create_receiver_status_request_input();
CreateReceiverStatusRequest(input.source_id(), input.request_id());
break;
}
default:
NOTREACHED();
}
}
} // namespace fuzz
} // namespace cast_channel
# Enum values
"ANSWER"
"APP_AVAILABLE"
"APPLICATION_BROADCAST"
"APP_UNAVAILABLE"
"CLOSE"
"CONNECT"
"EDIT_TRACKS_INFO"
"GET_APP_AVAILABILITY"
"GET_STATUS"
"LAUNCH"
"LAUNCH_ERROR"
"LOAD"
"MEDIA_GET_STATUS"
"MEDIA_SET_VOLUME"
"MEDIA_STATUS"
"OFFER"
"PAUSE"
"PING"
"PLAY"
"PONG"
"PRECACHE"
"QUEUE_INSERT"
"QUEUE_LOAD"
"QUEUE_REMOVE"
"QUEUE_REORDER"
"QUEUE_UPDATE"
"RECEIVER_STATUS"
"SEEK"
"SET_VOLUME"
"STOP"
"STOP_MEDIA"
# Constants form cast_message_util.h
"urn:x-cast:com.google.cast."
"urn:x-cast:com.google.cast.tp.deviceauth"
"urn:x-cast:com.google.cast.tp.heartbeat"
"urn:x-cast:com.google.cast.tp.connection"
"urn:x-cast:com.google.cast.receiver"
"urn:x-cast:com.google.cast.broadcast"
"urn:x-cast:com.google.cast.media"
"sender-0"
"receiver-0"
# JSON message fields
"appId"
"appIds"
"availability"
"browserVersion"
"connectionType"
"connType"
"language"
"message"
"namespace"
"origin"
"platform"
"requestId"
"sdkType"
"senderInfo"
"sessionId"
"status"
"systemVersion"
"type"
"userAgent"
"version"
......@@ -2,11 +2,11 @@
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
import("//third_party/libprotobuf-mutator/fuzzable_proto_library.gni")
import("//third_party/protobuf/proto_library.gni")
proto_library("cast_channel_proto") {
sources = [
"authority_keys.proto",
"cast_channel.proto",
]
fuzzable_proto_library("cast_channel_fuzzer_inputs_proto") {
sources = [ "fuzzer_inputs.proto" ]
import_dirs = [ "//third_party/openscreen/src/cast/common/channel/proto" ]
proto_out_dir = "components/cast_channel/fuzz_proto"
}
// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
syntax = "proto2";
option optimize_for = LITE_RUNTIME;
package cast_channel.proto;
message AuthorityKeys {
message Key {
required bytes fingerprint = 1;
required bytes public_key = 2;
}
repeated Key keys = 1;
}
// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
syntax = "proto2";
option optimize_for = LITE_RUNTIME;
package cast_channel;
message CastMessage {
// Always pass a version of the protocol for future compatibility
// requirements.
enum ProtocolVersion { CASTV2_1_0 = 0; }
required ProtocolVersion protocol_version = 1;
// source and destination ids identify the origin and destination of the
// message. They are used to route messages between endpoints that share a
// device-to-device channel.
//
// For messages between applications:
// - The sender application id is a unique identifier generated on behalf of
// the sender application.
// - The receiver id is always the the session id for the application.
//
// For messages to or from the sender or receiver platform, the special ids
// 'sender-0' and 'receiver-0' can be used.
//
// For messages intended for all endpoints using a given channel, the
// wildcard destination_id '*' can be used.
required string source_id = 2;
required string destination_id = 3;
// This is the core multiplexing key. All messages are sent on a namespace
// and endpoints sharing a channel listen on one or more namespaces. The
// namespace defines the protocol and semantics of the message.
required string namespace = 4;
// Encoding and payload info follows.
// What type of data do we have in this message.
enum PayloadType {
STRING = 0;
BINARY = 1;
}
required PayloadType payload_type = 5;
// Depending on payload_type, exactly one of the following optional fields
// will always be set.
optional string payload_utf8 = 6;
optional bytes payload_binary = 7;
}
enum SignatureAlgorithm {
UNSPECIFIED = 0;
RSASSA_PKCS1v15 = 1;
RSASSA_PSS = 2;
}
enum HashAlgorithm {
SHA1 = 0;
SHA256 = 1;
}
// Messages for authentication protocol between a sender and a receiver.
message AuthChallenge {
optional SignatureAlgorithm signature_algorithm = 1
[default = RSASSA_PKCS1v15];
optional bytes sender_nonce = 2;
optional HashAlgorithm hash_algorithm = 3 [default = SHA1];
}
message AuthResponse {
required bytes signature = 1;
required bytes client_auth_certificate = 2;
repeated bytes intermediate_certificate = 3;
optional SignatureAlgorithm signature_algorithm = 4
[default = RSASSA_PKCS1v15];
optional bytes sender_nonce = 5;
optional HashAlgorithm hash_algorithm = 6 [default = SHA1];
optional bytes crl = 7;
}
message AuthError {
enum ErrorType {
INTERNAL_ERROR = 0;
NO_TLS = 1; // The underlying connection is not TLS
SIGNATURE_ALGORITHM_UNAVAILABLE = 2;
}
required ErrorType error_type = 1;
}
message DeviceAuthMessage {
// Request fields
optional AuthChallenge challenge = 1;
// Response fields
optional AuthResponse response = 2;
optional AuthError error = 3;
}
// Copyright 2020 The Chromium Authors. All rights reserved. Use of
// this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Inputs for various fuzz tests
// NOTE(crbug.com/796717): As for this writing, the tests are
// incomplete, and its not entirely clear what additional tests need
// to be written. The general priority for tests is:
//
// 1. Messages that contain any data passed through the SDK/renderer
// process.
// 2. Messages from Cast devices.
// 3. Messages generated entirely by the native Cast MRP, since they
// are already from privileged code (at least for now).
//
// See also
// https://chromium-coverage.appspot.com/reports/763373_fuzzers_only/linux/chromium/src/components/cast_channel/report.html
// for fuzz test coverage.
syntax = "proto2";
import "cast_channel.proto";
option optimize_for = LITE_RUNTIME;
package cast_channel.fuzz;
// Inputs for functions in cast_auth_utils.cc
message CastAuthUtilInputs {
message AuthenticateChallengeReplyInput {
required cast.channel.DeviceAuthMessage auth_message = 1;
required cast.channel.CastMessage cast_message = 2;
required string nonce = 3;
}
oneof input {
AuthenticateChallengeReplyInput authenticate_challenge_reply_input = 1;
// TODO(crbug.com/796717): Add inputs for other functions to test:
// - VerifyTLSCertificate
// - VerifyCredentials
}
}
// Inputs for functions in cast_message_utils.cc
message CastMessageUtilInputs {
message CreateBroadcastRequestInput {
required string source_id = 1;
required int32 request_id = 2;
repeated string app_id = 3;
required string broadcast_namespace = 4;
required string broadcast_message = 5;
}
message CreateLaunchRequestInput {
required string source_id = 1;
required int32 request_id = 2;
required string app_id = 3;
required string locale = 4;
repeated string supported_app_types = 5;
optional JunkValue app_params = 6;
}
message CreateStopRequestInput {
required string source_id = 1;
required int32 request_id = 2;
required string session_id = 3;
}
message CreateCastMessageInput {
required string message_namespace = 1;
required string source_id = 2;
required string destination_id = 3;
required JunkValue body = 4;
}
message CreateMediaRequestInput {
required int32 request_id = 1;
required string source_id = 2;
required string destination_id = 3;
required int32 type = 4;
required JunkValue body = 5;
}
message CreateSetVolumeRequestInput {
required int32 request_id = 1;
required string source_id = 2;
required JunkValue body = 3;
}
message CreateVirtualConnectionRequestInput {
required string source_id = 1;
required string destination_id = 2;
required int32 connection_type = 3;
required string user_agent = 4;
required string browser_version = 5;
}
message CreateGetAppAvailabilityRequestInput {
required string source_id = 1;
required int32 request_id = 2;
required string app_id = 3;
}
message GetRequestIdFromResponseInput {
optional int32 request_id = 1;
required JunkValue payload = 2;
}
message GetAppAvailabilityResultFromResponseInput {
required JunkValue payload = 1;
required string app_id = 2;
}
message GetLaunchSessionResponseInput { required JunkValue payload = 1; }
message IsCastInternalNamespaceInput {
required string message_namespace = 1;
}
message ParseMessageTypeFromPayloadInput {
optional string type = 1;
required JunkValue payload = 2;
}
message CreateReceiverStatusRequestInput {
required string source_id = 1;
required int32 request_id = 2;
}
oneof input {
CreateBroadcastRequestInput create_broadcast_request_input = 1;
CreateLaunchRequestInput create_launch_request_input = 2;
CreateStopRequestInput create_stop_request_input = 3;
CreateCastMessageInput create_cast_message_input = 4;
CreateMediaRequestInput create_media_request_input = 5;
CreateSetVolumeRequestInput create_set_volume_request_input = 6;
int32 int_input = 7;
string string_input = 8;
cast.channel.CastMessage cast_message = 9;
CreateVirtualConnectionRequestInput
create_virtual_connection_request_input = 10;
CreateGetAppAvailabilityRequestInput
create_get_app_availability_request_input = 11;
GetRequestIdFromResponseInput get_request_id_from_response_input = 12;
GetLaunchSessionResponseInput get_launch_session_response_input = 13;
ParseMessageTypeFromPayloadInput parse_message_type_from_payload_input = 14;
CreateReceiverStatusRequestInput create_receiver_status_request_input = 15;
}
}
// Message used to generate a plausible but meaningless instance of
// base::Value.
message JunkValue {
message Field {
required string name = 1;
oneof value {
int32 int_value = 2;
string string_value = 3;
float float_value = 4;
bool bool_value = 5;
}
}
repeated Field field = 1;
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment