1. 问题描述
在南京中心CICD测试服务器环境下,新动漫频道在播放过程中,会出现core,导致livecached进程产生段错误,从而发生系统崩溃问题。
Core文件内容:
Core时nginx日志信息:
2. Core原因及分析过程
2.1. 分析过程
从core文件的段错误位置,判断在执行ngx_h264_parse.c中: start_code_prefix(bitsctx) 时,访问bitsctx时发生段错误。
从相应代码段处可以看到,start_code_prefix(bitsctx)中所引用的bitsctx来源于
h264ctx->bitsctx
代码段如下:
因此,分析core文件中h264ctx堆栈信息:使用指令 p *h264ctx
记录堆栈信息如下:
可以看到h264ctx->bitsctx和h264ctx->log的地址信息被写为0x10101010100和0x101000001010101的0、1组合
此时,为了获取bitsctx(环形缓冲区索引结构指针)的真正地址,在代码中H264解析方法前后增加日志打印,最终打印结果如下:
可见,真实的bitsctx的指针地址应为0x00007FB106B94010,说明h264ctx->bitsctx应该被误修改过。
由于h264ctx结构体的生命周期和频道一致,相关成员变量的赋值均在ngx_mpegts_parse_H264()方法内完成,因此合理推测,是由于上一次H264解包时,由于某些字段的错误解析,导致赋值越界,并且,赋值越界只可能存在于带有指针属性的相关赋值中(因为确定类型的非指针属性的变量,其边界是确定的,不会发生赋值越界;对于有指针属性的,比如数组、指针变量,使用循环体或者memcpy等方式进行赋值时,编译器不会检查是否赋值越界)。
从core文件中h264ctx结构体的堆栈信息中,可见
nal_unit_type = 6
这代表着,上一次h264解析的是SEI类型的数据包体
相关解析方法为h264_nal_parse_sei()
从h264ctx结构体相关定义中,发现与SEI类型解析相关的结构体如下:
其中具有指针属性的成员变量为图中红框内容,包括:
int32_t initial_cpb_removal_delay[32];
int32_t nitial_cpb_removal_delay_offset[32];
uint8_t clock_timestamp_flag[3];
与initial_cpb_removal_delay、nitial_cpb_removal_delay_offset相关的代码如下所示:
从h264ctx堆栈信息中,可以看到for循环赋值所依赖的cpb_cnt_minus1 = 0,因此,这两个数组均没有发生越界赋值问题(越界需要cpb_cnt_minus1>=32,32为initial_cpb_removal_delay的数组长度)。
接下来分析clock_timestamp_flag[3]的赋值:
从相关代码段中可以分析到,如果clock_timestamp_flag[3]赋值越界,需要NumClockTS>=3。而NumClockTS依赖于sei_num_clock_ts_table[pt->pic_struct]。
从h264ctx堆栈信息中,得到pic_struct = 12
查找sei_num_clock_ts_table数组定义:如下
重点来了:sei_num_clock_ts_table数组长度只有9,且最大成员值为3,而在本次解析中:
NumClockTS = sei_num_clock_ts_table[pt->pic_struct]
NumClockTS获取的值,是sei_num_clock_ts_table尾后位置的值,此数值未必在0~8之间,如果超过9,那么NumClockTS的值将不可预知,在for循环赋值之中,就会循环向后赋值,导致clock_timestamp_flag成员之后的h264ctx结构体中相关成员被错误赋值(覆盖)。
在相关代码段增加打印信息,打印NumClockTS
可以看见,NumClockTS的值为110,远超过最大值3。
正是这个错误,导致了本次core问题。
2.2. Core原因
由于在H264解析中,解析SEI类型时,由于解析clock_timestamp_flag [3]所依赖的pic_struct字段的值大于sei_num_clock_ts_table[9]的边界,从而NumClockTS取值错误,发生赋值越界,导致 h264ctx->bitsctx被错误赋值。当下一次进入H264解析时,访问错误地址段,导致段错误。详细分析过程见上。
3. 解决办法
查阅H264相关手册,pic_struct相关内容如下,其有效值为0~8,与代码实现时一致。但是9~15为保留值,应予以相关处理,保证代码在pic_struct出现非0~8时,不会出现意外取值。
代码修改如下:
当pic_struct取值9~15之间(最大值15,因为其有效长度为4bit)时,NumClockTS = 0,不进行相关内容解析,避免越界赋值。