0
点赞
收藏
分享

微信扫一扫

中标麒麟v7服务器宕机问题分析

钎探穗 2022-05-06 阅读 179

一 问题描述

某行v7服务器出现异常重启现象,故障系统转储vmcore,以及宕机的内核日志。

二 日志分析

通过查看dmesg日志,报错日志可分为两类:

1 python脚本运行过程中出现的”unhandled level 3 translation fault”,属于用户层进程崩溃的错误,和宕机问题无关;

2 时间戳在61817741时,内核报错“IPv4: Attempt to release TCP socket in state 10 ffff805624a7b800”。同时出现“Unable to handle kernel paging request at virtual address 0001950b”的报错,说明内核运行过程中访问了无效的虚拟地址或者说野指针。

2-1 分析IPv4: Attempt to release TCP socket in state 10报错

针对“IPv4: Attempt to release TCP socket in state 10 ffff805624a7b800”报错,内核函数(基于4.14内核)为inet_sock_destruct具体代码如图一:

         图 一

该函数核心作用为当用户层close socket时,释放对应inet路由缓存。根据代码逻辑可知,当sk->sk_state不为TCP_CLOSE时,会打印错误信息,同时输出对应sk->sk_state。由内核日志可知出问题时状态为10,通过图二TCP状态值枚举结构可知,当时sk_state为TCP_LISTEN。

 图 二

所以此处存在一个sk_state异常的问题,此处可能存在某种TCP通信过程中应用established->close->建立socket->listen的过程,close状态还未更新,sock已经处于listen状态。

2-2 分析内核oops

报错之后的堆栈信息如图三:

图 三

堆栈表示mlx5网卡驱动mlx5e_poll_rx_cq接受到网卡缓冲区的数据,通过__netif_receive_skb将sk_buffer提交到协议层,ip_local_deliver_finish在ip层做路由选择后提交给TCP层,入口函数为tcp_v4_rcv。

图四为tcp_v4_rcv->__inet_lookup_listener流程:

 图 四

通过以上流程图可知,__inet_lookup_skb实际为__inet_lookup_established和__inet_lookup_listener的封装。__inet_lookup_established核心代码如图五:

 图 五

核心功能为通过sk_nulls_for_each_rcu遍历established sock的hash表,选出匹配到对应源目的IP和源目的端口的成员,并返回对应sk结构指针。当sk->sk_refcnf引用计数为0时则返回NULL。此时就会转而执行__inet_lookup_listener,函数如图六:

图 六

核心功能为通过sk_for_each_rcu遍历listener sock的hash表,通过resueport_select_sock选择合适的可复用端口,返回对应sk结构指针。

通过crash调试vmcore:

通过dis ffff00000873ce54(__inet_lookup_listener+0xac)获取函数符号地址的反汇编信息,同时通过sym获取__inet_lookup_listener的符号地址如图七:

 图 七

通过图七可知,内核崩溃的原因在于将x19寄存器48偏移位置的值传入到x3寄存器时,该传入值是一个无效地址。

通过dis -l __inet_lookup_listener(符号地址)获取函数的反汇编信息如图八:

 图 八

通过图八可知,出问题的代码在net/ipv4/inet_hashtables.c的178行,查看内核源码可知该代码在compute_score函数中,同时也可以看到后续178行cmp的指令,说明存在对应条件比较。

下面为对应源码文件171-179行内容:

 图 九

net_eq比较sock_net(sk)返回地址和net地址对应cmp指令,所以问题很可能出现在sock_net(sk)函数内部。sock_net函数如图十:

 图 十

函数主要获取sk->sk_net成员的地址(即x19寄存器偏移48后的地址)。现在只需要确认sk_net成员在sock结构体中的偏移是否为48。

图十一为sock结构布局:

 图 十一

136偏移之前都是在sock_common结构中,查看sock_common结构布局如图十二:

 图 十二

结合内核源码图十三:

 图 十三

到此可以确认代码调用至__inet_lookup_listener->compute_socre->sock_net时

获取sk->sk_net地址时访问了无效地址。

2-3 分析sk初始化的问题

在2-2图四中已知,tcp_v4_rcv函数属于tcp层的入口函数。会存在__inet_lookup_established和__inet_lookup_listener两处查询函数的原因在于:内核需要区别此次通信是各个进程的第一次握手,还是建立三次握手后的通信。但是在两个函数遍历查询sock的方式存在差异(4.14针对ipv4而言):

上图的核心差异在于listen hash表和established hash表插入链表的方式不一样,hlist_nulls_add_tail_rcu初始化链表过程会检查各node的地址(sk)是否为NULL,  hlist_add_head_rcu 则不会检查。

__inet_lookup_listener通过sk_for_each_rcu遍历listener 表时存在null的sock结构后,如果sk已经为空,则会出现访问非法地址的情况,为了避免出现此类问题,需要在 sock加入链表前进行有效性判断。

结合2-1的报错,当时close触发后并没有触发close状态更新,对应sock的状态直接变为listen,会被重新添加到对应listener hash表中,此时sock的指针可能已经是无效的地址。

针对该问题社区已经提供对应解决方案:

https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/commit/?h=v4.19.219&id=28f0d54dbed848d231c9af37737ddd00968caaac

 

举报

相关推荐

v7带来错误

0 条评论