Contents
  1. 1. zlib 内部崩溃
  2. 2. zlib 介绍
  3. 3. 崩溃的分析
  4. 4. 结论

zlib 内部崩溃

崩溃在 free_tiny 上

0   libsystem_malloc.dylib          free_tiny + 56
1   libz.1.dylib                    deflateEnd + 108
2   libz.1.dylib                    deflateEnd + 108
3   libz.1.dylib                    compress2 + 136

或者最近一个栈帧是 ‘_bzero’ 或者别的填零操作。 bzero 是什么操作呢?

The bzero() function erases the data in the n bytes of the memory
starting at the location pointed to by s, by writing zeros (bytes
containing '\0') to that area.

bzero 就是把内存区域挨个儿填零,等于是把之前的内容像擦黑板一样擦除干净了。另外呢,wiki 上说 bzero 有安全风险

1.  The explicit_bzero() function does not guarantee that sensitive
    data is completely erased from memory.  (The same is true of
    bzero().)  For example, there may be copies of the sensitive data
    in a register and in "scratch" stack areas.  The explicit_bzero()
    function is not aware of these copies, and can't erase them.

2.  In some circumstances, explicit_bzero() can decrease security.  If
    the compiler determined that the variable containing the sensitive
    data could be optimized to be stored in a register (because it is
    small enough to fit in a register, and no operation other than the
    explicit_bzero() call would need to take the address of the
    variable), then the explicit_bzero() call will force the data to
    be copied from the register to a location in RAM that is then
    immediately erased (while the copy in the register remains
    unaffected).  The problem here is that data in RAM is more likely
    to be exposed by a bug than data in a register, and thus the
    explicit_bzero() call creates a brief time window where the
    sensitive data is more vulnerable than it would otherwise have
    been if no attempt had been made to erase the data.
  • 敏感的数据可能会在寄存器和 scratch 的栈区域内拷贝一份,填零的函数不知道这些存在,不会擦除他们。
  • 接下来是一个比较极端的情况,使用 explicit_bzero() 会强制把原本被编译器放到寄存器存储的东西拷贝到内存里然后立马擦除,但是这中间又有一个可以作案的时间窗口。

但是不知道他说的 scratch stack area 是啥。。。。。

总的来说就是在回收内存的时候一套组合拳,然后 zlib 就崩在这里了。

栈帧最后如果是 tiny_free 就崩在引用 access garbage pointer;如果是 bzero 就是对空指针解引用。

zlib 介绍

zlib 是一个压缩库,苹果把它开源了,可以看之前的一份介绍。文章里关于 dylib 版本的发现就是在研究这个 crash 时发现的。

苹果的 zlib 库

崩溃的分析

从系统的 compress() 接口出发,进入到 zlib.1.dylib 的 compress2(),然后到 deflateEnd() 里面。

这个 end 函数比较简单,用一个 TRY_FREE 来释放变量,TRY_FREE 是个宏,展开最后还是调用的 free() 函数。

int ZEXPORT deflateEnd (strm)
    z_streamp strm;
{
    int status;

    if (deflateStateCheck(strm)) return Z_STREAM_ERROR;

    status = strm->state->status;

    /* Deallocate in reverse order of allocations: */
    TRY_FREE(strm, strm->state->pending_buf);
    TRY_FREE(strm, strm->state->head);
    TRY_FREE(strm, strm->state->prev);
    TRY_FREE(strm, strm->state->window);

    ZFREE(strm, strm->state);
    strm->state = Z_NULL;

    return status == BUSY_STATE ? Z_DATA_ERROR : Z_OK;
}

有时候崩溃栈里还有 deflateInit2_ 的记录,然后到 end,再 free 再崩溃。主要是 deflateInit2_ 这段猜测是内存不足,申请了几个内存,然后检查了一遍,有空的就立马进 end 函数进行 free。然后返回 Z_MEM_ERROR 内存错误。只有可供分配的内存不足才有 malloc 失败,或者说大概率上是这样。

s->window = (Bytef *) ZALLOC(strm, s->w_size, 2*sizeof(Byte));
s->prev   = (Posf *)  ZALLOC(strm, s->w_size, sizeof(Pos));
s->head   = (Posf *)  ZALLOC(strm, s->hash_size, sizeof(Pos));
s->high_water = 0;      /* nothing written to s->window yet */

s->lit_bufsize = 1 << (memLevel + 6); /* 16K elements by default */

overlay = (ushf *) ZALLOC(strm, s->lit_bufsize, sizeof(ush)+2);
s->pending_buf = (uchf *) overlay;
s->pending_buf_size = (ulg)s->lit_bufsize * (sizeof(ush)+2L);

if (s->window == Z_NULL || s->prev == Z_NULL || s->head == Z_NULL ||
    s->pending_buf == Z_NULL) {
    s->status = FINISH_STATE;
    strm->msg = ERR_MSG(Z_MEM_ERROR);
    deflateEnd (strm);
    return Z_MEM_ERROR;
}

结合内存占用情况分析,发现崩溃的样本内存占用都偏高,另外几例直接收到了内存警告。连续警告了好几次,然后跪了。

结论

内存不足,或者是 OOM

Contents
  1. 1. zlib 内部崩溃
  2. 2. zlib 介绍
  3. 3. 崩溃的分析
  4. 4. 结论