Android蓝牙代码学习——内存分配

本文探讨Android蓝牙代码中的内存分配技术,重点介绍内存溢出保护机制——随机Canary。通过在分配的内存块头尾填充随机数据并记录,一旦检测到数据变化即触发异常处理,防止内存攻击。同时,文章提到了分配和释放内存的接口,以及如何追踪内存分配与释放,防止内存泄漏。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

从棉花糖开始,Android的蓝牙native代码就开始发生变化,更多的用面向对象的C++来设计。到了奥利奥,代码中还使用了一些相对新一些的技术,同时提供了一些基本的工具。今天先从内存分配开始,学习其中用到的一些技术。

内存分配与释放的接口

在整个蓝牙的native代码中,动态的内存分配使用了osi_malloc、osi_calloc接口,释放则是osi_free。以osi_malloc为例,它的实现如下:

void* osi_malloc(size_t size) {
  size_t real_size = allocation_tracker_resize_for_canary(size);
  void* ptr = malloc(real_size);
  CHECK(ptr);
  return allocation_tracker_notify_alloc(alloc_allocator_id, ptr, size);
}

在函数内部,最终的内存分配还是用到了malloc函数,只是对内存的size和返回值作了额外处理。第一眼看到allocation_tracker_resize_for_canary这个函数的时候,你会发现它的名字取得不错,应该包含了对buffer的resize和track功能。那么canary这个词代表什么呢?

矿井中的金丝雀

在win10的translator中输入canary这个单词,你得到的答案就只有“金丝雀”,看起来和内存分配没半毛钱关系。不甘心,翻了墙问了谷歌,最终在维基百科的英文页面找到了答案1

金丝雀除了有一个好嗓子,它还对瓦斯等有毒气体特别敏感。少量的一点瓦斯气体即可让其产生反应,晕倒甚至是死亡。在科技还不发达的17世纪,这样的特性对于采矿业工人来说可谓救命稻草。在矿井中放置一只金丝雀,工人们便可以预判是否有瓦斯泄漏,保证生命安全。

而到了编程世界,金丝雀就是内存溢出保护的一种方法。

Buffer Overflow Protection

内存溢出本身是一种黑客的攻击手段,可以用来破坏内存数据。而如果是stack中的数据出现了内存溢出,主机的控制权可能就会被交出去了。不要以为内存溢出的攻击有多么复杂,简易的实现网上都有2。基于stack的内存溢出攻击,其基本原理就是,通过传递超过已知buffer长度的数据,覆盖stack中保存函数返回地址的那个内存栏位,使其跳转至指定的地址,执行恶意代码。

为了能正确的处理内存溢出问题,金丝雀、边界检查和打标签等方案被提出。金丝雀方案的基本思路是,在已分配内存和其他控制数据之间提供一段内容已知的“保护数据”。就像瓦斯泄漏时,最先受到影响的是金丝雀一样,一旦出现内存溢出,最先被“污染”的内存将是这段保护数据。当代码检测到这种情况时,便可以选择安全的方式进行异常处理。

Random Canaries

今天要分析的蓝牙代码,它使用的内存保护机制被称为“random canaries”。基本思路就是,在分配的内存块的头尾各分配一段空间,用随机的数据将其填充;同时,这段随机的数据会被记录下来。在释放内存块前,代码对内存块头尾的随机数据进行检查。如果发现这段数据和最初分配时设置的不一样,则判定为出现内存溢出,进而引入断言错误,终止程序的运行。分配内存的代码如下:

void* allocation_tracker_notify_alloc(uint8_t allocator_id, void* ptr,
                                      size_t requested_size) {
  char* return_ptr;
  {
    std::unique_lock<std::mutex> lock(tracker_lock);
    if (!enabled || !ptr) return ptr;

    // Keep statistics
    alloc_counter++;
    alloc_total_size += allocation_tracker_resize_for_canary(requested_size);

    return_ptr = ((char*)ptr) + canary_size;

    auto map_entry = allocations.find(return_ptr);
    allocation_t* allocation;
    if (map_entry != allocations.end()) {
      allocation = map_entry->second;
      CHECK(allocation->freed);  // Must have been freed before
    } else {
      allocation = (allocation_t*)calloc(1, sizeof(allocation_t));
      allocations[return_ptr] = allocation;
    }

    allocation->allocator_id = allocator_id;
    allocation->freed = false;
    allocation->size = requested_size;
    allocation->ptr = return_ptr;
  }

  // Add the canary on both sides
  memcpy(return_ptr - canary_size, canary, canary_size);
  memcpy(return_ptr + requested_size, canary, canary_size);

  return return_ptr;
}

经过上面的分配,实际使用的内存布局如下:

random canariesbuffer can be usedrandom canaries
8bytesuser requested buffer size8bytes

除了基本的random canaries,这里还使用了一个用于保存引用记录的结构体,定义如下:

typedef struct {
  uint8_t allocator_id;
  void* ptr;
  size_t size;
  bool freed;
} allocation_t;

与其他工具相同的是,这个结构体对外部也是隐藏的,因为它定义在源文件中,头文件中只有声明,外部只能使用指针的方式来使用它。每一个使用allocation_tracker_notify_alloc分配的内存,都会有一个对应的allocation_t结构体,保存用户实际可以使用的内存起始地址、可使用的内存块大小等信息。一方面,这样做可以追踪内存的分配与释放;另一方面,如果错误地将经过malloc、calloc直接分配的空间传递给allocation_tracker来释放,由于没有对应的allocation_t的记录,可以判定其为非法操作,避免内存泄漏。除此,代码中还有一个全局的unordered_map,保存实际可使用内存地址与allocation_t的键值对。

内存释放的代码如下,其实现思路就是前面提到的“random canaries”。注意,它返回的是实际分配的内存的起始地址,而不是用户可使用的内存首地址。

void* allocation_tracker_notify_free(UNUSED_ATTR uint8_t allocator_id,
                                     void* ptr) {
  std::unique_lock<std::mutex> lock(tracker_lock);

  if (!enabled || !ptr) return ptr;

  auto map_entry = allocations.find(ptr);
  CHECK(map_entry != allocations.end());
  allocation_t* allocation = map_entry->second;
  CHECK(allocation);          // Must have been tracked before
  CHECK(!allocation->freed);  // Must not be a double free
  CHECK(allocation->allocator_id ==
        allocator_id);  // Must be from the same allocator

  // Keep statistics
  free_counter++;
  free_total_size += allocation_tracker_resize_for_canary(allocation->size);

  allocation->freed = true;

  UNUSED_ATTR const char* beginning_canary = ((char*)ptr) - canary_size;
  UNUSED_ATTR const char* end_canary = ((char*)ptr) + allocation->size;

  for (size_t i = 0; i < canary_size; i++) {
    CHECK(beginning_canary[i] == canary[i]);
    CHECK(end_canary[i] == canary[i]);
  }

  // Free the hash map entry to avoid unlimited memory usage growth.
  // Double-free of memory is detected with "assert(allocation)" above
  // as the allocation entry will not be present.
  allocations.erase(ptr);
  free(allocation);

  return ((char*)ptr) - canary_size;
}

随机数的生成

最后是“random canaries”的来源——随机数生成器。它用到了系统的/dev/urandom节点,每次从其中read出一个int值,取1byte作为canaries的数组的一个成员。关于/dev/urandomh和/dev/random,网上有很多资料34,看起来其实现还是挺复杂的,这里不做展开。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值