blob: bd403db6fd55df6aa158af610d5c27d177dd793c [file] [edit]
// Copyright 2016 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <stddef.h>
#include <stdint.h>
#include <algorithm>
#include <cassert>
#include <vector>
#include "base/compiler_specific.h"
#include "base/containers/span.h"
#include "base/containers/span_reader.h"
#include "testing/libfuzzer/libfuzzer_base_wrappers.h"
#include "third_party/libsrtp/include/srtp.h"
#include "third_party/libsrtp/include/srtp_priv.h"
#include "third_party/libsrtp/test/rtp.h"
// TODO(katrielc) Also test the authenticated path, which is what
// WebRTC uses. This is nontrivial because you need to bypass the MAC
// check. Two options: add a UNSAFE_FUZZER_MODE flag to libsrtp (or
// the chromium fork of it), or compute the HMAC of whatever gibberish
// the fuzzer produces and write it into the packet manually.
namespace LibSrtpFuzzer {
enum CryptoPolicy {
NONE,
LIKE_WEBRTC,
LIKE_WEBRTC_SHORT_AUTH,
LIKE_WEBRTC_WITHOUT_AUTH,
AES_128_GCM,
AES_256_GCM,
NUMBER_OF_POLICIES,
};
}
static size_t GetKeyLength(LibSrtpFuzzer::CryptoPolicy crypto_policy) {
switch (crypto_policy) {
case LibSrtpFuzzer::NUMBER_OF_POLICIES:
return SRTP_AES_ICM_128_KEY_LEN_WSALT;
case LibSrtpFuzzer::NONE:
return 0; // Null-auth.
case LibSrtpFuzzer::LIKE_WEBRTC:
case LibSrtpFuzzer::LIKE_WEBRTC_SHORT_AUTH:
case LibSrtpFuzzer::LIKE_WEBRTC_WITHOUT_AUTH:
return SRTP_AES_ICM_128_KEY_LEN_WSALT;
case LibSrtpFuzzer::AES_128_GCM:
return SRTP_AES_GCM_128_KEY_LEN_WSALT;
case LibSrtpFuzzer::AES_256_GCM:
return SRTP_AES_GCM_256_KEY_LEN_WSALT;
}
}
struct Environment {
srtp_policy_t GetCryptoPolicy(LibSrtpFuzzer::CryptoPolicy crypto_policy,
base::span<const uint8_t> replacement_key) {
switch (crypto_policy) {
case LibSrtpFuzzer::NUMBER_OF_POLICIES:
case LibSrtpFuzzer::NONE:
srtp_crypto_policy_set_null_cipher_hmac_null(&policy_.rtp);
srtp_crypto_policy_set_null_cipher_hmac_null(&policy_.rtcp);
break;
case LibSrtpFuzzer::LIKE_WEBRTC:
srtp_crypto_policy_set_aes_cm_128_hmac_sha1_80(&policy_.rtp);
srtp_crypto_policy_set_aes_cm_128_hmac_sha1_80(&policy_.rtcp);
break;
case LibSrtpFuzzer::LIKE_WEBRTC_SHORT_AUTH:
srtp_crypto_policy_set_aes_cm_128_hmac_sha1_32(&policy_.rtp);
srtp_crypto_policy_set_aes_cm_128_hmac_sha1_32(&policy_.rtcp);
break;
case LibSrtpFuzzer::LIKE_WEBRTC_WITHOUT_AUTH:
srtp_crypto_policy_set_aes_cm_128_null_auth(&policy_.rtp);
srtp_crypto_policy_set_aes_cm_128_null_auth(&policy_.rtcp);
break;
case LibSrtpFuzzer::AES_128_GCM:
// There was a security bug in the GCM mode in libsrtp 1.5.2.
srtp_crypto_policy_set_aes_gcm_128_16_auth(&policy_.rtp);
srtp_crypto_policy_set_aes_gcm_128_16_auth(&policy_.rtcp);
break;
case LibSrtpFuzzer::AES_256_GCM:
// WebRTC uses AES-256-GCM by default if GCM ciphers are enabled.
srtp_crypto_policy_set_aes_gcm_256_16_auth(&policy_.rtp);
srtp_crypto_policy_set_aes_gcm_256_16_auth(&policy_.rtcp);
break;
}
assert(static_cast<size_t>(policy_.rtp.cipher_key_len) ==
replacement_key.size());
assert(static_cast<size_t>(policy_.rtcp.cipher_key_len) ==
replacement_key.size());
std::ranges::fill(base::span(key_), 0);
base::span(key_).copy_prefix_from(replacement_key);
return policy_;
}
Environment() {
srtp_init();
policy_.allow_repeat_tx = 1;
policy_.key = key_;
policy_.ssrc.type = ssrc_any_inbound;
policy_.ssrc.value = 0xdeadbeef;
policy_.window_size = 1024;
}
private:
srtp_policy_t policy_ = {};
unsigned char key_[SRTP_MAX_KEY_LEN] = {};
};
DEFINE_LLVM_FUZZER_TEST_ONE_INPUT_SPAN(base::span<const uint8_t> data) {
static Environment env;
base::SpanReader reader(data);
// Read one byte and use it to choose a crypto policy.
uint8_t policy_byte;
if (!reader.ReadU8BigEndian(policy_byte)) {
return 0;
}
LibSrtpFuzzer::CryptoPolicy policy = static_cast<LibSrtpFuzzer::CryptoPolicy>(
policy_byte % LibSrtpFuzzer::NUMBER_OF_POLICIES);
// Read some more bytes to use as a key.
size_t key_length = GetKeyLength(policy);
auto key_bytes = reader.Read<SRTP_MAX_KEY_LEN>();
if (!key_bytes) {
return 0;
}
srtp_policy_t srtp_policy =
env.GetCryptoPolicy(policy, key_bytes->first(key_length));
// Read one byte and use as number of encrypted header extensions.
uint8_t num_encrypted_headers;
if (!reader.ReadU8BigEndian(num_encrypted_headers)) {
return 0;
}
std::vector<int> enc_xtn_hdrs;
if (num_encrypted_headers > 0) {
// Use next bytes as extension ids.
auto headers_bytes = reader.Read(num_encrypted_headers);
if (!headers_bytes) {
return 0;
}
enc_xtn_hdrs.assign(headers_bytes->begin(), headers_bytes->end());
srtp_policy.enc_xtn_hdr = enc_xtn_hdrs.data();
srtp_policy.enc_xtn_hdr_count = static_cast<int>(num_encrypted_headers);
}
srtp_t session;
srtp_err_status_t error = srtp_create(&session, &srtp_policy);
if (error != srtp_err_status_ok) {
return 0;
}
// Read one byte as a packet length N, then feed the next N bytes
// into srtp_unprotect. Keep doing until we run out of data.
while (true) {
uint8_t packet_size_byte;
if (!reader.ReadU8BigEndian(packet_size_byte)) {
break;
}
size_t packet_size =
std::min(static_cast<size_t>(packet_size_byte), reader.remaining());
if (packet_size == 0) {
break;
}
auto packet_span = reader.Read(packet_size);
if (!packet_span) {
break;
}
size_t header_size = std::min(sizeof(srtp_hdr_t), packet_size);
// We deliberately do not initialise this struct. MSAN will catch
// usage of the uninitialised memory.
rtp_msg_t message;
auto [header_part, body_part] = packet_span->split_at(header_size);
base::byte_span_from_ref(message.header).copy_prefix_from(header_part);
base::byte_span_from_ref(message.body).copy_prefix_from(body_part);
int out_len = static_cast<int>(packet_size);
srtp_unprotect(session, &message, &out_len);
}
srtp_dealloc(session);
return 0;
}