Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 21110c50 authored by Hui Peng's avatar Hui Peng Committed by Gerrit Code Review
Browse files

Merge changes Ie624b4cb,I0a87bdb4,Id12dd5d9

* changes:
  Adding more bluetooth stack fuzzers.
  Updating Bluetooth stack fuzzer configurations
  Adding a Bluetooth SDP stack fuzzer
parents 44fb69b1 bfebdebe
Loading
Loading
Loading
Loading
+295 −0
Original line number Diff line number Diff line
@@ -297,6 +297,301 @@ cc_library_static {
    min_sdk_version: "Tiramisu",
}

cc_defaults {
    name: "btstack_fuzzer_default",
    host_supported: true,
    cflags: [
        "-Wall",
        "-Wextra",
        "-Werror",
        // Mocked components have too many unused parameters
        "-Wno-unused-parameter",
        "-DHAS_NO_BDROID_BUILDCFG",
    ],
    include_dirs: [
        "packages/modules/Bluetooth/system",
        "packages/modules/Bluetooth/system/gd",
        "packages/modules/Bluetooth/system/include",
        "packages/modules/Bluetooth/system/internal_include",
        "packages/modules/Bluetooth/system/stack/include",
        "packages/modules/Bluetooth/system/test/common",
        "packages/modules/Bluetooth/system/types",
    ],
    static_libs: [
        "libchrome",
    ],
    target: {
        darwin: {
            enabled: false,
        },
        android: {
            shared_libs: [
                "libcutils",
                "libutils",
            ],
        },
        host: {
            cflags: [
                "-DOS_GENERIC",
            ],
        },
    },
    fuzz_config: {
        // Options for performance improvement
        libfuzzer_options: [
            // This disables the stdout and stderr
            "close_fd_mask=3",
            // This limits the maximum corpus size to 4KB
            "max_len=4096",
            // TODO: b/280300628 for fixing memory leaks. Until it's fixed leak
            // detection needs to be turned off to unblock fuzzing.
            "detect_leaks=0",
        ],
        cc: [
            "android-bluetooth-security@google.com",
            "android-security-assurance-redteam@google.com",
        ],
        componentid: 27441, // Android > Android OS & Apps > Systems > bluetooth
        hotlists: [
            "4810445", // ASA Red Team: Bluetooth Engagement Issues
            "3705175", // ASA Red Team Discovered Issues
        ],
        acknowledgement: [
            "Android Red Team of Google",
            "Android Bluetooth Team of Google",
        ],
    },
}

cc_fuzz {
    name: "sdp-fuzzer",
    defaults: [
        "btstack_fuzzer_default",
        "fluoride_defaults_fuzzable",
    ],
    include_dirs: [
        "packages/modules/Bluetooth/system/stack/btm",
    ],
    srcs: [
        ":LegacyStackSdp",
        ":TestCommonLogMsg",
        ":TestCommonMockFunctions",
        ":TestFakeOsi",
        ":TestMockBtif",
        ":TestMockDevice",
        ":TestMockGdOsLoggingLogRedaction",
        ":TestMockStackL2cap",
        ":TestMockStackMetrics",
        ":TestMockStackBtm",
        "fuzzers/sdp_fuzzer.cc",
    ],
}

cc_fuzz {
    name: "gatt-fuzzer",
    defaults: [
        "btstack_fuzzer_default",
        "fluoride_defaults_fuzzable",
    ],
    include_dirs: [
        "packages/modules/Bluetooth/system/stack/btm",
        "external/flatbuffers/include",
        "external/rust/crates/quiche/deps/boringssl/src/include",
    ],
    generated_headers: [
        "BluetoothGeneratedDumpsysDataSchema_h",
        "BluetoothGeneratedPackets_h",
    ],
    srcs: crypto_toolbox_srcs + [
        "gatt/*.cc",
        "eatt/*.cc",
        ":TestCommonLogMsg",
        ":TestCommonMockFunctions",
        ":TestCommonStackConfig",
        ":TestFakeOsi",
        ":TestMockBtif",
        ":TestMockDevice",
        ":TestMockGdOsLoggingLogRedaction",
        ":TestMockSrvcDis",
        ":TestMockStackL2cap",
        ":TestMockStackMetrics",
        ":TestMockStackBtm",
        ":TestMockStackAcl",
        ":TestMockStackHcic",
        ":TestMockMainShim",
        ":TestMockStackSdp",
        ":TestMockStackArbiter",
        ":TestMockRustFfi",
        "fuzzers/gatt_fuzzer.cc",
    ],
    static_libs: [
        "libgmock",
    ],
}

cc_fuzz {
    name: "smp-fuzzer",
    defaults: [
        "btstack_fuzzer_default",
        "fluoride_defaults_fuzzable",
    ],
    include_dirs: [
        "packages/modules/Bluetooth/system/stack/btm",
        "external/flatbuffers/include",
        "external/rust/crates/quiche/deps/boringssl/src/include",
    ],
    generated_headers: [
        "BluetoothGeneratedDumpsysDataSchema_h",
        "BluetoothGeneratedPackets_h",
    ],
    srcs: crypto_toolbox_srcs + [
        "smp/smp_*.cc",
        ":TestCommonLogMsg",
        ":TestCommonMockFunctions",
        ":TestCommonStackConfig",
        ":TestFakeOsi",
        ":TestMockBtif",
        ":TestMockDevice",
        ":TestMockGdOsLoggingLogRedaction",
        ":TestMockStackL2cap",
        ":TestMockStackMetrics",
        ":TestMockStackBtm",
        ":TestMockStackAcl",
        ":TestMockStackHcic",
        ":TestMockMainShim",
        "fuzzers/smp_fuzzer.cc",
    ],
    static_libs: [
        "libgmock",
    ],
}

cc_fuzz {
    name: "bnep-fuzzer",
    defaults: [
        "btstack_fuzzer_default",
        "fluoride_defaults_fuzzable",
    ],
    include_dirs: [
        "packages/modules/Bluetooth/system/stack/btm",
        "external/flatbuffers/include",
        "external/rust/crates/quiche/deps/boringssl/src/include",
    ],
    generated_headers: [
        "BluetoothGeneratedDumpsysDataSchema_h",
        "BluetoothGeneratedPackets_h",
    ],
    srcs: [
        "bnep/*.cc",
        ":TestCommonLogMsg",
        ":TestCommonMockFunctions",
        ":TestCommonStackConfig",
        ":TestFakeOsi",
        ":TestMockBtif",
        ":TestMockDevice",
        ":TestMockGdOsLoggingLogRedaction",
        ":TestMockStackL2cap",
        ":TestMockStackMetrics",
        ":TestMockStackBtm",
        ":TestMockStackAcl",
        ":TestMockStackHcic",
        ":TestMockMainShim",
        "fuzzers/bnep_fuzzer.cc",
    ],
    static_libs: [
        "libgmock",
    ],
}

cc_fuzz {
    name: "avrc-fuzzer",
    defaults: [
        "btstack_fuzzer_default",
        "fluoride_defaults_fuzzable",
    ],
    include_dirs: [
        "packages/modules/Bluetooth/system/stack/btm",
        "external/flatbuffers/include",
        "external/rust/crates/quiche/deps/boringssl/src/include",
    ],
    generated_headers: [
        "BluetoothGeneratedDumpsysDataSchema_h",
        "BluetoothGeneratedPackets_h",
    ],
    srcs: [
        "avrc/*.cc",
        "avct/*.cc",
        ":TestCommonLogMsg",
        ":TestCommonMockFunctions",
        ":TestCommonStackConfig",
        ":TestFakeOsi",
        ":TestMockBtif",
        ":TestMockDevice",
        ":TestMockGdOsLoggingLogRedaction",
        ":TestMockStackL2cap",
        ":TestMockStackMetrics",
        ":TestMockStackBtm",
        ":TestMockStackAcl",
        ":TestMockStackHcic",
        ":TestMockMainShim",
        ":TestMockStackSdp",
        "fuzzers/avrc_fuzzer.cc",
    ],
    static_libs: [
        "libgmock",
    ],
    target: {
        android: {
            static_libs: [
                "libcom.android.sysprop.bluetooth",
            ],
        },
    },
}

cc_fuzz {
    name: "l2cap-fuzzer",
    defaults: [
        "btstack_fuzzer_default",
        "fluoride_defaults_fuzzable",
    ],
    include_dirs: [
        "packages/modules/Bluetooth/system/stack/btm",
        "external/flatbuffers/include",
        "external/rust/crates/quiche/deps/boringssl/src/include",
    ],
    generated_headers: [
        "BluetoothGeneratedDumpsysDataSchema_h",
        "BluetoothGeneratedPackets_h",
    ],
    srcs: [
        "l2cap/*.cc",
        ":TestCommonLogMsg",
        ":TestCommonMockFunctions",
        ":TestCommonStackConfig",
        ":TestFakeOsi",
        ":TestMockBtif",
        ":TestMockDevice",
        ":TestMockGdOsLoggingLogRedaction",
        ":TestMockStackMetrics",
        ":TestMockStackBtm",
        ":TestMockStackAcl",
        ":TestMockStackHcic",
        ":TestMockMainShim",
        "fuzzers/l2cap_fuzzer.cc",
    ],
    static_libs: [
        "libgmock",
    ],
    target: {
        android: {
            shared_libs: [
                "libPlatformProperties",
            ],
        },
    },
}

// Bluetooth stack unit tests for target
cc_test {
    name: "net_test_stack",
+95 −0
Original line number Diff line number Diff line
# Bluetooth Stack Fuzzers

## Overview
Bluetooth stack implements very complex wireless communication protocols and
scenarios. It's been a hotspot for security researchers and attackers. Fuzzing
has been used as a popular approach to look for security vulnerabilities in
Bluetooth stack.

Due to the complex architecture of the Android Bluetooth stack, fuzzing the
entire stack with pure software is very difficult and impractical. Instead,
multiple fuzzers are created to target different areas of the BT stack. Fuzzers
in this directory focuses on the components under `system/stack`.

## Attack surface selection
For security purpose, remote attack surfaces usually take higher priority since
they can cause much severe damage comparing to local attacks. This makes the
incoming BT message handlers our focus. The goal is to be able to pipe randomly
generated data packets to those message handlers to explore the code path each
component contains. This helps flushing out any memory/logic issues in the
remote message handling routine.

Components requiring no authentication, or dealing with messages before
authentication have a higher fuzzing priority. This includes the SDP, GATT, SMP
and L2CAP components. A couple post authentication components such as BNEP,
AVRC, AVCT are also covered by different fuzzers.

## Bluetooth stack overview
According to Bluetooth spec and the source code, most of the components we care
here work above the L2CAP layer. In general they work with the following
sequences:
1. At initialization, a component registers itself to L2CAP with a set of
callback functions, which, usually contains at least one function handling the
incoming Bluetooth packets.
2. Each component also exposes certain APIs to upper layers, which can be higher
level Bluetooth framework, or even applications. Bluetooth framework or
applications use these APIs to configure the stack, and issue requests.
3. Upper layer also registers callbacks into each component. When a component
receives a response, it parses and validates the response, extracts the payload
data, and passes data to upper layer using those callbacks.
4. Many Bluetooth components work in both server mode and client mode with
different sets of APIs and processing logics.
5. It's common for a Bluetooth stack component to use state machines. The state
transition happens when APIs are called, or incoming packets are handled.

## Fuzzer design
The fuzzers are designed to simulate how a component is used in the real world,
but with a lot of simplifications. Here is how they work in general:
1. First a fuzzer should mock the L2CAP APIs to capture the registration call
from the target component.
2. At each fuzzing iteration, the fuzzer initializes the target component using
its initialization function. This will cause the component to register itself to
L2CAP. Because L2CAP APIs are mocked, the fuzzer will capture the registration
information, most importantly, the message handler callback function.
3. The fuzzer then calls necessary APIs and callbacks exposed to L2CAP to
further initialize the target component into either server mode or client mode.
4. Starting from here, the fuzzer splits the input data into multiple packets,
and feeds them to the target component using the previously captured message
handler callback.
5. It's common that a fuzzer also needs to call certain APIs to trigger state
transition of the target component. The fuzzer might use fixed data or data
derived from fuzzing input to make those API calls.
6. Once all the data is consumed, the target is cleaned up so next iteration can
start cleanly. It's important to cleanup all the data so there is no state
pollution between two iterations, otherwise it will be very difficult to
reproduce a crash.

## Mocking dependencies
For maximium fuzzing efficiency, the fuzzers are created to include the target
component and minimium number of other Bluetooth components. This means any
dependencies from other Bluetooth components need to be mocked. The mocks are
implemented with a balance of reaching maximium target code coverage and
minimium development effort. Some of the mocks are simply not implemented.

## Future improvement
These fuzzers are still far from perfect, with the following possible
improvements:
1. Code coverage

    It's very important to review the code coverage of each fuzzer. Any big
    coverage gaps should be analyzed and improved. This can be done by adding
    additional logic in the fuzzing loop, such as calling certain APIs,
    providing upper layer callbacks, or changing the mock behaviors.

2. Performance

    The fuzzers are designed to run as fast as possible. But there might still
    be some room to improve the performance. Profiling can be done to figure
    out the performance bottlenecks, which might be sleeps, tight for loops, or
    computational heavy operations, such as crypto functions.

3. Component coverage

    Currently only 3 fuzzers are created. More should be added so we can cover
    most of the stack components. With the mocks and design patterns it
    shouldn't be too difficult.
+220 −0
Original line number Diff line number Diff line
/*
 * Copyright 2022 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <fuzzer/FuzzedDataProvider.h>

#include <cstdint>
#include <functional>
#include <string>
#include <vector>

#include "osi/include/allocator.h"
#include "stack/include/avct_api.h"
#include "stack/include/avrc_api.h"
#include "test/fake/fake_osi.h"
#include "test/mock/mock_btif_config.h"
#include "test/mock/mock_stack_acl.h"
#include "test/mock/mock_stack_btm_dev.h"
#include "test/mock/mock_stack_l2cap_api.h"
#include "test/mock/mock_stack_l2cap_ble.h"
#include "types/bluetooth/uuid.h"

using bluetooth::Uuid;

// Verify the passed data is readable
static void ConsumeData(const uint8_t* data, size_t size) {
  volatile uint8_t checksum = 0;
  for (size_t i = 0; i < size; i++) {
    checksum ^= data[i];
  }
}

namespace {

constexpr uint16_t kDummyCid = 0x1234;
constexpr uint8_t kDummyId = 0x77;
constexpr uint8_t kDummyRemoteAddr[] = {0x77, 0x88, 0x99, 0xAA, 0xBB, 0xCC};

// Set up default callback structure
static tL2CAP_APPL_INFO avct_appl, avct_br_appl;

class FakeBtStack {
 public:
  FakeBtStack() {
    test::mock::stack_l2cap_api::L2CA_DataWrite.body = [](uint16_t cid,
                                                          BT_HDR* hdr) {
      CHECK(cid == kDummyCid);
      ConsumeData((const uint8_t*)hdr, hdr->offset + hdr->len);
      osi_free(hdr);
      return L2CAP_DW_SUCCESS;
    };
    test::mock::stack_l2cap_api::L2CA_DisconnectReq.body = [](uint16_t cid) {
      CHECK(cid == kDummyCid);
      return true;
    };
    test::mock::stack_l2cap_api::L2CA_ConnectReq2.body =
        [](uint16_t psm, const RawAddress& p_bd_addr, uint16_t sec_level) {
          CHECK(p_bd_addr == kDummyRemoteAddr);
          return kDummyCid;
        };
    test::mock::stack_l2cap_api::L2CA_Register2.body =
        [](uint16_t psm, const tL2CAP_APPL_INFO& p_cb_info, bool enable_snoop,
           tL2CAP_ERTM_INFO* p_ertm_info, uint16_t my_mtu,
           uint16_t required_remote_mtu, uint16_t sec_level) {
          CHECK(psm == AVCT_PSM || psm == AVCT_BR_PSM);
          if (psm == AVCT_PSM) {
            avct_appl = p_cb_info;
          } else if (psm == AVCT_BR_PSM) {
            avct_br_appl = p_cb_info;
          }
          return psm;
        };
    test::mock::stack_l2cap_api::L2CA_Deregister.body = [](uint16_t psm) {};
  }

  ~FakeBtStack() {
    test::mock::stack_l2cap_api::L2CA_DataWrite = {};
    test::mock::stack_l2cap_api::L2CA_ConnectReq2 = {};
    test::mock::stack_l2cap_api::L2CA_DisconnectReq = {};
    test::mock::stack_l2cap_api::L2CA_Register2 = {};
    test::mock::stack_l2cap_api::L2CA_Deregister = {};
  }
};

class Fakes {
 public:
  test::fake::FakeOsi fake_osi;
  FakeBtStack fake_stack;
};

}  // namespace

#ifdef OS_ANDROID
namespace android {
namespace sysprop {
namespace bluetooth {
namespace Avrcp {
std::optional<bool> absolute_volume() { return true; }
}  // namespace Avrcp

namespace Bta {
std::optional<std::int32_t> disable_delay() { return 200; }
}  // namespace Bta

namespace Pan {
std::optional<bool> nap() { return false; }
}  // namespace Pan
}  // namespace bluetooth
}  // namespace sysprop
}  // namespace android
#endif

static void ctrl_cb(uint8_t handle, uint8_t event, uint16_t result,
                    const RawAddress* peer_addr) {}

static void msg_cb(uint8_t handle, uint8_t label, uint8_t opcode,
                   tAVRC_MSG* p_msg) {
  uint8_t scratch_buf[512];
  tAVRC_STS status;

  if (p_msg->hdr.ctype == AVCT_CMD) {
    tAVRC_COMMAND cmd = {0};
    memset(scratch_buf, 0, sizeof(scratch_buf));
    status = AVRC_ParsCommand(p_msg, &cmd, scratch_buf, sizeof(scratch_buf));
    if (status == AVRC_STS_NO_ERROR) {
      BT_HDR* p_pkt = (BT_HDR*)nullptr;
      status = AVRC_BldCommand(&cmd, &p_pkt);
      if (status == AVRC_STS_NO_ERROR && p_pkt) {
        osi_free(p_pkt);
      }
    }
  } else if (p_msg->hdr.ctype == AVCT_RSP) {
    tAVRC_RESPONSE rsp = {0};
    memset(scratch_buf, 0, sizeof(scratch_buf));
    status = AVRC_ParsResponse(p_msg, &rsp, scratch_buf, sizeof(scratch_buf));
    if (status == AVRC_STS_NO_ERROR) {
      BT_HDR* p_pkt = (BT_HDR*)nullptr;
      status = AVRC_BldResponse(handle, &rsp, &p_pkt);
      if (status == AVRC_STS_NO_ERROR && p_pkt) {
        osi_free(p_pkt);
      }
    }

    uint16_t buf_len = sizeof(scratch_buf);
    memset(scratch_buf, 0, sizeof(scratch_buf));
    status = AVRC_Ctrl_ParsResponse(p_msg, &rsp, scratch_buf, &buf_len);
    if (status == AVRC_STS_NO_ERROR) {
      BT_HDR* p_pkt = (BT_HDR*)nullptr;
      status = AVRC_BldResponse(handle, &rsp, &p_pkt);
      if (status == AVRC_STS_NO_ERROR && p_pkt) {
        osi_free(p_pkt);
      }
    }
  }
}

static void Fuzz(const uint8_t* data, size_t size) {
  FuzzedDataProvider fdp(data, size);
  bool is_initiator = fdp.ConsumeBool();
  bool is_controller = fdp.ConsumeBool();
  bool is_br = fdp.ConsumeBool();

  AVCT_Register();
  AVRC_Init();

  tL2CAP_APPL_INFO* appl_info = is_br ? &avct_br_appl : &avct_appl;

  tAVRC_CONN_CB ccb = {
      .ctrl_cback = base::Bind(ctrl_cb),
      .msg_cback = base::Bind(msg_cb),
      .conn = (uint8_t)(is_initiator ? AVCT_INT : AVCT_ACP),
      .control = (uint8_t)(is_controller ? AVCT_CONTROL : AVCT_TARGET),
  };

  appl_info->pL2CA_ConnectInd_Cb(kDummyRemoteAddr, kDummyCid, 0, kDummyId);

  uint8_t handle;
  if (AVCT_SUCCESS != AVRC_Open(&handle, &ccb, kDummyRemoteAddr)) {
    return;
  }

  tL2CAP_CFG_INFO cfg;
  appl_info->pL2CA_ConfigCfm_Cb(kDummyCid, is_initiator, &cfg);

  // Feeding input packets
  constexpr uint16_t kMaxPacketSize = 1024;
  while (fdp.remaining_bytes() > 0) {
    auto size = fdp.ConsumeIntegralInRange<uint16_t>(0, kMaxPacketSize);
    auto bytes = fdp.ConsumeBytes<uint8_t>(size);
    BT_HDR* hdr = (BT_HDR*)osi_calloc(sizeof(BT_HDR) + bytes.size());
    hdr->len = bytes.size();
    std::copy(bytes.cbegin(), bytes.cend(), hdr->data);
    appl_info->pL2CA_DataInd_Cb(kDummyCid, hdr);
  }

  AVRC_Close(handle);

  // Simulating disconnecting event
  appl_info->pL2CA_DisconnectInd_Cb(kDummyCid, false);

  AVCT_Deregister();
}

extern "C" int LLVMFuzzerTestOneInput(const uint8_t* Data, size_t Size) {
  auto fakes = std::make_unique<Fakes>();
  Fuzz(Data, Size);
  return 0;
}
+173 −0

File added.

Preview size limit exceeded, changes collapsed.

+304 −0

File added.

Preview size limit exceeded, changes collapsed.

Loading