Linux X.25套接字栈越界读写漏洞详细分析

  • A+
所属分类:未分类

25简介

25协议简介

X.25接口协议于1976年首次提出,它是在加拿大DATAPAC公用分组网相关标准的基础上制定的,在1980年、1984年、1988年和1993年又进行了多次修改,是目前使用最广泛的分组交换协议。X.25协议是数据终端设备(DTE)和数据电路终接设备(DCE)之间的接口协议,该协议的制定实现了接口协议的标准化,使得各种DTE能够自由连接到各种分组交换网上。作为用户设备和网络之间的接口协议,X.25协议主要定义了数据传输通路的建立、保持和释放过程所需遵循的标准,数据传输过程中进行差错控制和流量控制的机制以及提供的基本业务和可选业务等。

X.25协议采用分层的体系结构,自下而上分为三层:物理层、数据链路层和分组层,分别对应于OSI参考模型的下三层。各层在功能上相互独立,每一层接受下一层提供的服务,同时也为上一层提供服务,相邻层之间通过原语进行通信。在接口的对等层之间通过对等层之间的通信协议进行信息交换的协商、控制和信息的传输。

Linux X.25套接字简介

1996/12/18,Linux 内核发布了 2.1.16版,第一次引入了对X.25协议的支持,定义了 AF_NFC 地址族:

#define AF_X25  9 /* Reserved for X.25 project  */

X25 sockets 为 X.25 数据包层协议(packet layer protocol)提供接口。 应用程序可以使用标准的 ITU X.25 建议 (X.25 DTE-DCE 模式)在公共 X.25 数据网中进行通讯。AF_X25 socket 地址族用 struct sockaddr_x25 代表 ITU-T X.121 规范中定义的网络地址。

struct x25_address {

char x25_addr[16];

};




struct sockaddr_x25 {

sa_family_t   sx25_family;  /* 必须是 AF_X25 */

x25_address   sx25_addr;    /* X.121 地址 */

};

sx25_addr 包含一个空零结尾的字符串 x25_addr[] 。 sx25_addr.x25_addr[] 由最多 15  个  ASCII字符(不包括结束的 0)构成 X.121 地址。 只能使用数字 `0' 到 `9' 。

X.25套接字仅支持 SOCK_SEQPACKET 类型,在建立X.25套接字时, socket()调用的参数如下

x25_socket = socket(PF_X25, SOCK_SEQPACKET, 0);

Linux内核中对应的 struct proto 和 struct proto_ops:

static struct proto x25_proto = {

.name    = "X25",

.owner   = THIS_MODULE,

.obj_size = sizeof(struct x25_sock),

};




static const struct proto_ops x25_proto_ops = {

.family =  AF_X25,

.owner =   THIS_MODULE,

.release = x25_release,

.bind =    x25_bind,

.connect = x25_connect,

.socketpair =  sock_no_socketpair,

.accept =  x25_accept,

.getname = x25_getname,

.poll =    datagram_poll,

.ioctl =   x25_ioctl,

#ifdef CONFIG_COMPAT

.compat_ioctl = compat_x25_ioctl,

#endif

.gettstamp =   sock_gettstamp,

.listen =  x25_listen,

.shutdown = sock_no_shutdown,

.setsockopt =  x25_setsockopt,

.getsockopt =  x25_getsockopt,

.sendmsg = x25_sendmsg,

.recvmsg = x25_recvmsg,

.mmap =    sock_no_mmap,

.sendpage = sock_no_sendpage,

};

Linux X.25套接字栈越界读漏洞

该漏洞位于x25_bind函数中,以Linux内核最新的稳定版本5.9.8为例,https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/tree/net/x25/af_x25.c?h=v5.9.8#n677

677 static int x25_bind(struct socket *sock, struct sockaddr *uaddr, int addr_len)

678 {

679 struct sock *sk = sock->sk;

680 struct sockaddr_x25 *addr = (struct sockaddr_x25 *)uaddr;

…………………………………………………………………………

692  len = strlen(addr->sx25_addr.x25_addr);

693  for (i = 0; i < len; i++) {

694   if (!isdigit(addr->sx25_addr.x25_addr[i])) {

695    rc = -EINVAL;

696    goto out;

697   }

698  }

…………………………………………………………………………

x25_bind有3个参数,第二个参数uaddr是应用层传递过来的套接字地址

在680行,uaddr转成了X.25套接字地址指针

在692行,调用strlen函数,获取addr->sx25_addr.x25_addr的长度

从693行开始的for循环,依次判断addr->sx25_addr.x25_addr字符串里面是不是全部是数字。按照ITU-T X.121 规范,套接字地址只能使用数字 `0' 到 `9' 数字表示,不能用其他字符。

再来看一下sockaddr_x25 结构体定义:

struct x25_address {

char x25_addr[16];

};




struct sockaddr_x25 {

sa_family_t   sx25_family;  /* 必须是 AF_X25 */

x25_address   sx25_addr;    /* X.121 地址 */

};

X.121 地址对应的结构体struct x25_address,其实是一个ascii字符串数组,大小是16。

X.121 地址最多15个ascii字符,而struct x25_address里面的x25_addr字符串数组大小是16,所以最后面是用来存储字符串末尾的空字符的。

x25_bind函数的漏洞在于:调用strlen函数之前,没有判断struct x25_address里面的x25_addr字符串是不是以空字符结尾的。如果末尾不是空字符,那么strlen函数获得的长度将会大于16,在接下来的for循环中,将会越界读取addr正常范围之后的数据。

Linux X.25套接字栈越界写漏洞

该漏洞由多个X.25套接字相关漏洞组合而成,仍然以Linux内核最新的稳定版本5.9.8为例,从x25_connect函数开始,源代码见:https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/tree/net/x25/af_x25.c?h=v5.9.8#n744

744 static int x25_connect(struct socket *sock, struct sockaddr *uaddr,

745         int addr_len, int flags)

746 {

747 struct sock *sk = sock->sk;

748 struct x25_sock *x25 = x25_sk(sk);

749 struct sockaddr_x25 *addr = (struct sockaddr_x25 *)uaddr;

…………………………………………………………………………

803 x25->dest_addr = addr->sx25_addr;

…………………………………………………………………………

811 x25_write_internal(sk, X25_CALL_REQUEST);

x25_connect有4个参数,第二个参数uaddr是应用层传递过来的套接字地址

在749行,uaddr转成了X.25套接字地址指针

在803行,addr->sx25_addr 赋给了x25套接字的dest_addr,用作连接的目标地址,

在811行,调用了x25_write_internal函数

x25_write_internal函数源代码见:https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/tree/net/x25/x25_subr.c?h=v5.9.8#n109

109 void x25_write_internal(struct sock *sk, int frametype)

110 {

111 struct x25_sock *x25 = x25_sk(sk);

…………………………………………………………………………

115 unsigned char  addresses[1 + X25_ADDR_LEN];

…………………………………………………………………………

179 switch (frametype) {

180

181  case X25_CALL_REQUEST:

182   dptr    = skb_put(skb, 1);

183   *dptr++ = X25_CALL_REQUEST;

184   len     = x25_addr_aton(addresses, &x25->dest_addr,

185      &x25->source_addr);

…………………………………………………………………………

111行,sk套接字转成x25套接字指针

115行,在栈上声明了一个字符串数组addresses,长度是1 + X25_ADDR_LEN,也就是17

传递过来的x25_write_internal函数第二个参数frametype是X25_CALL_REQUEST,由此在第181行开始处理

184行,调用x25_addr_aton函数,传递了在栈上声明的字符串数组addresses,x25套接字的dest_addr和source_addr,

dest_addr是在x25_connect函数中赋值的

source_addr是在x25_bind函数中赋值的

继续看x25_addr_aton源代码:https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/tree/net/x25/af_x25.c?h=v5.9.8#n154

154 int x25_addr_aton(unsigned char *p, struct x25_address *called_addr,

155    struct x25_address *calling_addr)

156 {

157 unsigned int called_len, calling_len;

158 char *called, *calling;

159 int i;

160

161 called  = called_addr->x25_addr;

162 calling = calling_addr->x25_addr;

163

164 called_len  = strlen(called);

165 calling_len = strlen(calling);

166

167 *p++ = (calling_len << 4) | (called_len << 0);

168

169 for (i = 0; i < (called_len + calling_len); i++) {

170  if (i < called_len) {

171   if (i % 2 != 0) {

172    *p |= (*called++ - '0') << 0;

173    p++;

174   } else {

175    *p = 0x00;

176    *p |= (*called++ - '0') << 4;

177   }

178  } else {

179   if (i % 2 != 0) {

180    *p |= (*calling++ - '0') << 0;

181    p++;

182   } else {

183    *p = 0x00;

184    *p |= (*calling++ - '0') << 4;

185   }

186  }

187 }

188

189 return 1 + (called_len + calling_len + 1) / 2;

190 }

x25_addr_aton函数有三个参数:

unsigned char *p:在栈上声明的字符串数组addresses

struct x25_address *called_addr:x25_connect函数中赋值的目标地址

struct x25_address *calling_addr:x25_bind函数中赋值的当前地址

164、165行,调用strlen获取目标地址、当前地址的长度,

169行开始的for循环,用得到的两个地址的长度,不断地往参数p指向的内存中写值

和x25_bind函数一样,x25_connect在赋值时,也没有判断应用层传递过来的套接字地址是不是以空字符结尾,在整个的传递过程中,x25_write_internal、x25_addr_aton两个函数也没有判断。

由此显而易见的是,如果x25_bind、x25_connect两个函数赋值的地址不是以空字符结尾的话,那么x25_addr_aton函数调用的两个strlen获取到的长度都将超过16,在169行for循环写入addresses时,将造成addresses的溢出,造成严重的栈溢出漏洞。

影响范围

最初的2.1.16版本的x25_bind函数(见https://elixir.bootlin.com/linux/2.1.16/source/net/x25/af_x25.c#L697),没有对addr做任何判断,直接赋给了sk套接字,所以上述的越界读漏洞在最初的版本中并不存在。当然,按照ITU-T X.121 规范,套接字地址只能使用数字 `0' 到 `9' 数字表示,不能用其他字符,所以这里的功能其实是不正常的。

从2.6.34版本开始(2010/05/16发布),x25_bind函数中开始加入strlen函数和for循环,用于判断addr的有效性,并持续至现在的最新版本5.9.8.所以上述越界读漏洞影响linux内核版本为:2.6.34~5.9.8

x25_connect、x25_write_internal、x25_addr_aton三个函数,自从引入以来,对dest_addr的没有变化,一直都没有判断是否以空字符结尾,所以上述越界写漏洞影响linux内核版本为:2.1.16~5.9.8,持续时间长达24年。

时间线

2020/11/09发现漏洞

2020/11/09邮件报告给 security@kernel.orglinux-distros@vs.openwall.org

2020/11/15漏洞细节公布在https://www.openwall.com/lists/oss-security/2020/11/15/2

参考资料

http://manpages.ubuntu.com/manpages/bionic/en/man7/x25.7.html

https://www.kernel.org/doc/Documentation/isdn/README.x25

本文作者:腾讯电脑管家, 转载请注明来自FreeBuf.COM

# linux安全 # 0Day漏洞 # 权限提升漏洞 # 腾讯安全 # X.25

发表评论

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: