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

Commit 76d59dd1 authored by Ajay Panicker's avatar Ajay Panicker
Browse files

Add Filtering for snoop logs based on L2CAP and RFCOMM Channels (1/4)

* Change btsnoop logging into disabled, filtered, and full modes
* When ro.isdebuggable is 1, filtered logging is enabled by default
* Otherwise, disabled by default
* When legacy mode is enabled, translate it to full and reset legacy
  property to empty

Bug: 112970672
Bug: 67669544
Test: See that the snoop file always exists but is filtered when
snooplogs are disabled and unfiltered when enabled.
Change-Id: I894d36cfd976d03c788626b9197fea1182a6f3ba
parent 5ba54302
Loading
Loading
Loading
Loading
+22 −0
Original line number Diff line number Diff line
@@ -29,6 +29,28 @@ typedef struct btsnoop_t {
  // true, the packet is marked as incoming. Otherwise, the packet is marked
  // as outgoing.
  void (*capture)(const BT_HDR* packet, bool is_received);

  // Set a L2CAP channel as whitelisted, allowing packets with that L2CAP CID
  // to show up in the snoop logs.
  void (*whitelist_l2c_channel)(uint16_t conn_handle, uint16_t local_cid,
                                uint16_t remote_cid);

  // Set a RFCOMM dlci as whitelisted, allowing packets with that RFCOMM CID
  // to show up in the snoop logs. The local_cid is used to associate it with
  // its corrisponding ACL connection. The dlci is the channel with direction
  // so there is no chance of a collision if two services are using the same
  // channel but in different directions.
  void (*whitelist_rfc_dlci)(uint16_t local_cid, uint8_t dlci);

  // Indicate that the provided L2CAP channel is being used for RFCOMM.
  // If packets with the provided L2CAP CID are encountered, they will be
  // filtered on RFCOMM based on channels provided to |filter_rfc_channel|.
  void (*add_rfc_l2c_channel)(uint16_t conn_handle, uint16_t local_cid,
                              uint16_t remote_cid);

  // Clear an L2CAP channel from being filtered.
  void (*clear_l2cap_whitelist)(uint16_t conn_handle, uint16_t local_cid,
                                uint16_t remote_cid);
} btsnoop_t;

const btsnoop_t* btsnoop_get_interface(void);
+246 −46
Original line number Diff line number Diff line
@@ -21,6 +21,7 @@
#include <mutex>

#include <arpa/inet.h>
#include <base/logging.h>
#include <errno.h>
#include <fcntl.h>
#include <inttypes.h>
@@ -34,14 +35,21 @@
#include <sys/time.h>
#include <sys/uio.h>
#include <unistd.h>
#include <mutex>
#include <unordered_map>
#include <unordered_set>

#include "bt_types.h"
#include "common/time_util.h"
#include "hci/include/btsnoop.h"
#include "hci/include/btsnoop_mem.h"
#include "hci_layer.h"
#include "internal_include/bt_trace.h"
#include "osi/include/log.h"
#include "osi/include/properties.h"
#include "stack/include/hcimsgs.h"
#include "stack/include/rfcdefs.h"
#include "stack/l2cap/l2c_int.h"
#include "stack_config.h"

// The number of of packets per btsnoop file before we rotate to the next
@@ -50,7 +58,14 @@
// property
#define DEFAULT_BTSNOOP_SIZE 0xffff

#define BTSNOOP_ENABLE_PROPERTY "persist.bluetooth.btsnoopenable"
#define IS_DEBUGGABLE_PROPERTY "ro.debuggable"

#define BTSNOOP_LOG_MODE_PROPERTY "persist.bluetooth.btsnooplogmode"
#define BTSNOOP_DEFAULT_MODE_PROPERTY "persist.bluetooth.btsnoopdefaultmode"
#define BTSNOOP_MODE_DISABLED "disabled"
#define BTSNOOP_MODE_FILTERED "filtered"
#define BTSNOOP_MODE_FULL "full"

#define BTSNOOP_PATH_PROPERTY "persist.bluetooth.btsnooppath"
#define DEFAULT_BTSNOOP_PATH "/data/misc/bluetooth/logs/btsnoop_hci.log"
#define BTSNOOP_MAX_PACKETS_PROPERTY "persist.bluetooth.btsnoopsize"
@@ -65,33 +80,138 @@ typedef enum {
// Epoch in microseconds since 01/01/0000.
static const uint64_t BTSNOOP_EPOCH_DELTA = 0x00dcddb30f2f8000ULL;

// Number of bytes into a packet where you can find the value for a channel.
static const size_t ACL_CHANNEL_OFFSET = 0;
static const size_t L2C_CHANNEL_OFFSET = 6;
static const size_t RFC_CHANNEL_OFFSET = 8;
static const size_t RFC_EVENT_OFFSET = 9;

// The size of the L2CAP header. All information past this point is removed from
// a filtered packet.
static const uint32_t L2C_HEADER_SIZE = 9;

static int logfile_fd = INVALID_FD;
static std::mutex btsnoop_mutex;

static int32_t packets_per_file;
static int32_t packet_counter;

// Channel tracking variables for filtering.

// Keeps track of L2CAP channels that need to be filtered out of the snoop
// logs.
class FilterTracker {
 public:
  // NOTE: 1 is used as a static CID for L2CAP signaling
  std::unordered_set<uint16_t> l2c_local_cid = {1};
  std::unordered_set<uint16_t> l2c_remote_cid = {1};
  uint16_t rfc_local_cid = 0;
  uint16_t rfc_remote_cid = 0;
  std::unordered_set<uint16_t> rfc_channels = {0};

  // Adds L2C channel to whitelist.
  void addL2cCid(uint16_t local_cid, uint16_t remote_cid) {
    l2c_local_cid.insert(local_cid);
    l2c_remote_cid.insert(remote_cid);
  }

  // Sets L2CAP channel that RFCOMM uses.
  void setRfcCid(uint16_t local_cid, uint16_t remote_cid) {
    rfc_local_cid = local_cid;
    rfc_remote_cid = remote_cid;
  }

  // Remove L2C channel from whitelist.
  void removeL2cCid(uint16_t local_cid, uint16_t remote_cid) {
    if (rfc_local_cid == local_cid) {
      rfc_channels.clear();
      rfc_channels.insert(0);
      rfc_local_cid = 0;
      rfc_remote_cid = 0;
    }

    l2c_local_cid.erase(local_cid);
    l2c_remote_cid.erase(remote_cid);
  }

  void addRfcDlci(uint8_t channel) { rfc_channels.insert(channel); }

  bool isWhitelistedL2c(bool local, uint16_t cid) {
    const auto& set = local ? l2c_local_cid : l2c_remote_cid;
    return (set.find(cid) != set.end());
  }

  bool isRfcChannel(bool local, uint16_t cid) {
    const auto& channel = local ? rfc_local_cid : rfc_remote_cid;
    return cid == channel;
  }

  bool isWhitelistedDlci(uint8_t dlci) {
    return rfc_channels.find(dlci) != rfc_channels.end();
  }
};

std::mutex filter_list_mutex;
std::unordered_map<uint16_t, FilterTracker> filter_list;
std::unordered_map<uint16_t, uint16_t> local_cid_to_acl;

// Cached value for whether full snoop logs are enabled. So the property isn't
// checked for every packet.
static bool is_btsnoop_enabled;
static bool is_btsnoop_filtered;

// TODO(zachoverflow): merge btsnoop and btsnoop_net together
void btsnoop_net_open();
void btsnoop_net_close();
void btsnoop_net_write(const void* data, size_t length);

static void delete_btsnoop_files();
static bool is_btsnoop_enabled();
static char* get_btsnoop_log_path(char* log_path);
static char* get_btsnoop_last_log_path(char* last_log_path, char* log_path);
static void delete_btsnoop_files(bool filtered);
static std::string get_btsnoop_log_path(bool filtered);
static std::string get_btsnoop_last_log_path(std::string log_path);
static void open_next_snoop_file();
static void btsnoop_write_packet(packet_type_t type, uint8_t* packet,
                                 bool is_received, uint64_t timestamp_us);

// Module lifecycle functions

static future_t* start_up(void) {
static future_t* start_up() {
  std::array<char, PROPERTY_VALUE_MAX> property = {};
  std::lock_guard<std::mutex> lock(btsnoop_mutex);

  if (!is_btsnoop_enabled()) {
    delete_btsnoop_files();
  // Default mode is FILTERED on userdebug/eng build, DISABLED on user build.
  // It can also be overwritten by modifying the global setting.
  int is_debuggable = osi_property_get_int32(IS_DEBUGGABLE_PROPERTY, 0);
  std::string default_mode = BTSNOOP_MODE_DISABLED;
  if (is_debuggable) {
    int len = osi_property_get(BTSNOOP_DEFAULT_MODE_PROPERTY, property.data(),
                               BTSNOOP_MODE_FILTERED);
    default_mode = std::string(property.data(), len);
  }

  // Get the actual mode
  int len = osi_property_get(BTSNOOP_LOG_MODE_PROPERTY, property.data(),
                             default_mode.c_str());
  std::string btsnoop_mode(property.data(), len);

  if (btsnoop_mode == BTSNOOP_MODE_FILTERED) {
    LOG(INFO) << __func__ << ": Filtered Snoop Logs enabled";
    is_btsnoop_enabled = true;
    is_btsnoop_filtered = true;
    delete_btsnoop_files(false);
  } else if (btsnoop_mode == BTSNOOP_MODE_FULL) {
    LOG(INFO) << __func__ << ": Snoop Logs fully enabled";
    is_btsnoop_enabled = true;
    is_btsnoop_filtered = false;
    delete_btsnoop_files(true);
  } else {
    LOG(INFO) << __func__ << ": Snoop Logs disabled";
    is_btsnoop_enabled = false;
    is_btsnoop_filtered = false;
    delete_btsnoop_files(true);
    delete_btsnoop_files(false);
  }

  if (is_btsnoop_enabled) {
    open_next_snoop_file();
    packets_per_file = osi_property_get_int32(BTSNOOP_MAX_PACKETS_PROPERTY,
                                              DEFAULT_BTSNOOP_SIZE);
@@ -104,14 +224,21 @@ static future_t* start_up(void) {
static future_t* shut_down(void) {
  std::lock_guard<std::mutex> lock(btsnoop_mutex);

  if (!is_btsnoop_enabled()) {
    delete_btsnoop_files();
  if (is_btsnoop_enabled) {
    if (is_btsnoop_filtered) {
      delete_btsnoop_files(false);
    } else {
      delete_btsnoop_files(true);
    }
  } else {
    delete_btsnoop_files(true);
    delete_btsnoop_files(false);
  }

  if (logfile_fd != INVALID_FD) close(logfile_fd);
  logfile_fd = INVALID_FD;

  btsnoop_net_close();
  if (is_btsnoop_enabled) btsnoop_net_close();

  return NULL;
}
@@ -129,7 +256,12 @@ static void capture(const BT_HDR* buffer, bool is_received) {
  uint8_t* p = const_cast<uint8_t*>(buffer->data + buffer->offset);

  std::lock_guard<std::mutex> lock(btsnoop_mutex);
  uint64_t timestamp_us = bluetooth::common::time_gettimeofday_us();

  struct timespec ts_now = {};
  clock_gettime(CLOCK_REALTIME, &ts_now);
  uint64_t timestamp_us =
      ((uint64_t)ts_now.tv_sec * 1000000L) + ((uint64_t)ts_now.tv_nsec / 1000);

  btsnoop_mem_capture(buffer, timestamp_us);

  if (logfile_fd == INVALID_FD) return;
@@ -152,39 +284,75 @@ static void capture(const BT_HDR* buffer, bool is_received) {
  }
}

static const btsnoop_t interface = {capture};
static void whitelist_l2c_channel(uint16_t conn_handle, uint16_t local_cid,
                                  uint16_t remote_cid) {
  LOG(INFO) << __func__
            << ": Whitelisting l2cap channel. conn_handle=" << conn_handle
            << " cid=" << loghex(local_cid) << ":" << loghex(remote_cid);
  std::lock_guard lock(filter_list_mutex);

const btsnoop_t* btsnoop_get_interface() {
  return &interface;
  // This will create the entry if there is no associated filter with the
  // connection.
  filter_list[conn_handle].addL2cCid(local_cid, remote_cid);
}

// Internal functions
static void delete_btsnoop_files() {
  LOG_VERBOSE(LOG_TAG, "Deleting snoop log if it exists");
  char log_path[PROPERTY_VALUE_MAX];
  char last_log_path[PROPERTY_VALUE_MAX + sizeof(".last")];
  get_btsnoop_log_path(log_path);
  get_btsnoop_last_log_path(last_log_path, log_path);
  remove(log_path);
  remove(last_log_path);
static void whitelist_rfc_dlci(uint16_t local_cid, uint8_t dlci) {
  LOG(INFO) << __func__
            << ": Whitelisting rfcomm channel. L2CAP CID=" << loghex(local_cid)
            << " DLCI=" << loghex(dlci);
  std::lock_guard lock(filter_list_mutex);

  tL2C_CCB* p_ccb = l2cu_find_ccb_by_cid(nullptr, local_cid);
  filter_list[p_ccb->p_lcb->handle].addRfcDlci(dlci);
}

static bool is_btsnoop_enabled() {
  char btsnoop_enabled[PROPERTY_VALUE_MAX] = {0};
  osi_property_get(BTSNOOP_ENABLE_PROPERTY, btsnoop_enabled, "false");
  return strncmp(btsnoop_enabled, "true", 4) == 0;
static void add_rfc_l2c_channel(uint16_t conn_handle, uint16_t local_cid,
                                uint16_t remote_cid) {
  LOG(INFO) << __func__
            << ": rfcomm data going over l2cap channel. conn_handle="
            << conn_handle << " cid=" << loghex(local_cid) << ":"
            << loghex(remote_cid);
  std::lock_guard lock(filter_list_mutex);

  filter_list[conn_handle].setRfcCid(local_cid, remote_cid);
  local_cid_to_acl.insert({local_cid, conn_handle});
}

static char* get_btsnoop_log_path(char* btsnoop_path) {
static void clear_l2cap_whitelist(uint16_t conn_handle, uint16_t local_cid,
                                  uint16_t remote_cid) {
  LOG(INFO) << __func__
            << ": Clearing whitelist from l2cap channel. conn_handle="
            << conn_handle << " cid=" << local_cid << ":" << remote_cid;

  std::lock_guard lock(filter_list_mutex);
  filter_list[conn_handle].removeL2cCid(local_cid, remote_cid);
}

static const btsnoop_t interface = {capture, whitelist_l2c_channel,
                                    whitelist_rfc_dlci, add_rfc_l2c_channel,
                                    clear_l2cap_whitelist};

const btsnoop_t* btsnoop_get_interface() { return &interface; }

static void delete_btsnoop_files(bool filtered) {
  LOG(INFO) << __func__
            << ": Deleting snoop logs if they exist. filtered = " << filtered;
  auto log_path = get_btsnoop_log_path(filtered);
  remove(log_path.c_str());
  remove(get_btsnoop_last_log_path(log_path).c_str());
}

std::string get_btsnoop_log_path(bool filtered) {
  char btsnoop_path[PROPERTY_VALUE_MAX];
  osi_property_get(BTSNOOP_PATH_PROPERTY, btsnoop_path, DEFAULT_BTSNOOP_PATH);
  return btsnoop_path;
  std::string result(btsnoop_path);
  if (filtered) result = result.append(".filtered");

  return result;
}

static char* get_btsnoop_last_log_path(char* last_log_path,
                                       char* btsnoop_path) {
  snprintf(last_log_path, PROPERTY_VALUE_MAX + sizeof(".last"), "%s.last",
           btsnoop_path);
  return last_log_path;
std::string get_btsnoop_last_log_path(std::string btsnoop_path) {
  return btsnoop_path.append(".last");
}

static void open_next_snoop_file() {
@@ -195,22 +363,20 @@ static void open_next_snoop_file() {
    logfile_fd = INVALID_FD;
  }

  char log_path[PROPERTY_VALUE_MAX];
  char last_log_path[PROPERTY_VALUE_MAX + sizeof(".last")];
  get_btsnoop_log_path(log_path);
  get_btsnoop_last_log_path(last_log_path, log_path);
  auto log_path = get_btsnoop_log_path(is_btsnoop_filtered);
  auto last_log_path = get_btsnoop_last_log_path(log_path);

  if (rename(log_path, last_log_path) != 0 && errno != ENOENT)
    LOG_ERROR(LOG_TAG, "%s unable to rename '%s' to '%s': %s", __func__,
              log_path, last_log_path, strerror(errno));
  if (rename(log_path.c_str(), last_log_path.c_str()) != 0 && errno != ENOENT)
    LOG(ERROR) << __func__ << ": unable to rename '" << log_path << "' to '"
               << last_log_path << "' : " << strerror(errno);

  mode_t prevmask = umask(0);
  logfile_fd = open(log_path, O_WRONLY | O_CREAT | O_TRUNC,
  logfile_fd = open(log_path.c_str(), O_WRONLY | O_CREAT | O_TRUNC,
                    S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH);
  umask(prevmask);
  if (logfile_fd == INVALID_FD) {
    LOG_ERROR(LOG_TAG, "%s unable to open '%s': %s", __func__, log_path,
              strerror(errno));
    LOG(ERROR) << __func__ << ": unable to open '" << log_path
               << "' : " << strerror(errno);
    return;
  }

@@ -235,6 +401,32 @@ static uint64_t htonll(uint64_t ll) {
  return ll;
}

static bool should_filter_log(bool is_received, uint8_t* packet) {
  uint16_t acl_handle =
      HCID_GET_HANDLE((((uint16_t)packet[ACL_CHANNEL_OFFSET + 1]) << 8) +
                      packet[ACL_CHANNEL_OFFSET]);

  std::lock_guard lock(filter_list_mutex);
  auto& filters = filter_list[acl_handle];
  uint16_t l2c_channel =
      (packet[L2C_CHANNEL_OFFSET + 1] << 8) + packet[L2C_CHANNEL_OFFSET];
  if (filters.isRfcChannel(is_received, l2c_channel)) {
    uint8_t rfc_event = packet[RFC_EVENT_OFFSET] & 0b11101111;
    if (rfc_event == RFCOMM_SABME || rfc_event == RFCOMM_UA) {
      return false;
    }

    uint8_t rfc_dlci = packet[RFC_CHANNEL_OFFSET] >> 2;
    if (!filters.isWhitelistedDlci(rfc_dlci)) {
      return true;
    }
  } else if (!filters.isWhitelistedL2c(is_received, l2c_channel)) {
    return true;
  }

  return false;
}

static void btsnoop_write_packet(packet_type_t type, uint8_t* packet,
                                 bool is_received, uint64_t timestamp_us) {
  uint32_t length_he = 0;
@@ -261,7 +453,15 @@ static void btsnoop_write_packet(packet_type_t type, uint8_t* packet,

  btsnoop_header_t header;
  header.length_original = htonl(length_he);
  header.length_captured = header.length_original;

  bool blacklisted = false;
  if (is_btsnoop_filtered && type == kAclPacket) {
    blacklisted = should_filter_log(is_received, packet);
  }

  header.length_captured =
      blacklisted ? htonl(L2C_HEADER_SIZE) : header.length_original;
  if (blacklisted) length_he = L2C_HEADER_SIZE;
  header.flags = htonl(flags);
  header.dropped_packets = 0;
  header.timestamp = htonll(timestamp_us + BTSNOOP_EPOCH_DELTA);
+4 −2
Original line number Diff line number Diff line
@@ -56,7 +56,8 @@ void AVCT_Register(uint16_t mtu, UNUSED_ATTR uint16_t mtu_br,
  AVCT_TRACE_API("AVCT_Register");

  /* register PSM with L2CAP */
  L2CA_Register(AVCT_PSM, (tL2CAP_APPL_INFO*)&avct_l2c_appl);
  L2CA_Register(AVCT_PSM, (tL2CAP_APPL_INFO*)&avct_l2c_appl,
                true /* enable_snoop */);

  /* set security level */
  BTM_SetSecurityLevel(true, "", BTM_SEC_SERVICE_AVCTP, sec_mask, AVCT_PSM, 0,
@@ -68,7 +69,8 @@ void AVCT_Register(uint16_t mtu, UNUSED_ATTR uint16_t mtu_br,
  memset(&avct_cb, 0, sizeof(tAVCT_CB));

  /* Include the browsing channel which uses eFCR */
  L2CA_Register(AVCT_BR_PSM, (tL2CAP_APPL_INFO*)&avct_l2c_br_appl);
  L2CA_Register(AVCT_BR_PSM, (tL2CAP_APPL_INFO*)&avct_l2c_br_appl,
                true /*enable_snoop*/);

  /* AVCTP browsing channel uses the same security service as AVCTP control
   * channel */
+2 −1
Original line number Diff line number Diff line
@@ -90,7 +90,8 @@ void avdt_scb_transport_channel_timer_timeout(void* data) {
 ******************************************************************************/
void AVDT_Register(AvdtpRcb* p_reg, tAVDT_CTRL_CBACK* p_cback) {
  /* register PSM with L2CAP */
  L2CA_Register(AVDT_PSM, (tL2CAP_APPL_INFO*)&avdt_l2c_appl);
  L2CA_Register(AVDT_PSM, (tL2CAP_APPL_INFO*)&avdt_l2c_appl,
                true /* enable_snoop */);

  /* set security level */
  BTM_SetSecurityLevel(true, "", BTM_SEC_SERVICE_AVDTP, p_reg->sec_mask,
+2 −1
Original line number Diff line number Diff line
@@ -94,7 +94,8 @@ tBNEP_RESULT bnep_register_with_l2cap(void) {
  bnep_cb.reg_info.pL2CA_CongestionStatus_Cb = bnep_congestion_ind;

  /* Now, register with L2CAP */
  if (!L2CA_Register(BT_PSM_BNEP, &bnep_cb.reg_info)) {
  if (!L2CA_Register(BT_PSM_BNEP, &bnep_cb.reg_info,
                     false /* enable_snoop */)) {
    BNEP_TRACE_ERROR("BNEP - Registration failed");
    return BNEP_SECURITY_FAIL;
  }
Loading