CVE-2020-15173 (published 2020-09-09T23:15Z)

In ACCEL-PPP (an implementation of PPTP/PPPoE/L2TP/SSTP), there is a buffer overflow when receiving an l2tp control packet ith an AVP which type is a string and no hidden flags, length set to less than 6. If your application is used in open networks or there are untrusted nodes in the network it is highly recommended to apply the patch. The problem was patched with commit 2324bcd5ba12cf28f47357a8f03cd41b7c04c52b As a workaround changes of commit 2324bcd5ba12cf28f47357a8f03cd41b7c04c52b can be applied to older versions.

Severity: HIGH
CVSS v2 base score: 7.5 (AV:N/AC:L/Au:N/C:P/I:P/A:P)
CVSS v3 base score: 9.8, CRITICAL (CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H)

Weakness information:
cwe_id: CWE-120
cwe_name: Buffer Copy without Checking Size of Input ('Classic Buffer Overflow')
description: The product copies an input buffer to an output buffer without verifying that the size of the input buffer is less than the size of the output buffer, leading to a buffer overflow.
extended_description: A buffer overflow condition exists when a product attempts to put more data in a buffer than it can hold, or when it attempts to put data in a memory area outside of the boundaries of a buffer. The simplest type of error, and the most common cause of buffer overflows, is the "classic" case in which the product copies the buffer without restricting how much is copied. Other variants exist, but the existence of a classic overflow strongly suggests that the programmer is not considering even the most basic of security protections.
         

Fix information:
repo_url: https://github.com/accel-ppp/accel-ppp
commit_details: repo_url: https://github.com/accel-ppp/accel-ppp
author: Vladislav Grishenko
author_date: 2020-09-06 02:38:35+05:00
msg: l2tp: fix RCE through buffer overflow & fix LE/BE compatibility

Unsufficent checks of valid l2tp header & avp length cause possible
RCE through buffer overflow, reported by https://github.com/WinMin
swings & leommxj, Chaitin Security Research Lab. Add missed header
length and avp length validation to fix the issue.

Order of struct bitfields is implementation-defined so current code
doesn't play well with big-endian arch. switch to explicit flag bit
checking/gathering to fix the issue.

RFC 2661 and 3931 requires that length, seqeuence flags must be set
and offset flag must not be set, so avp-premissive can't help in
this cases.
repository: repo_url: https://github.com/accel-ppp/accel-ppp
repo_name: accel-ppp/accel-ppp
description: High performance PPTP/L2TP/SSTP/PPPoE/IPoE server for Linux
repo_language: C
owner: accel-ppp
file_changes: filename: l2tp_prot.h
change_type: ModificationType.MODIFY
diff: @@ -5,17 +5,16 @@
 
 #define L2TP_PORT 1701
 
+#define L2TP_FLAG_T   0x8000
+#define L2TP_FLAG_L   0x4000
+#define L2TP_FLAG_S   0x0800
+#define L2TP_FLAG_O   0x0200
+#define L2TP_FLAG_P   0x0100
+#define L2TP_VER_MASK 0x000f
+
 struct l2tp_hdr_t
 {
-	uint8_t P:1;
-	uint8_t O:1;
-	uint8_t reserved2:1;
-	uint8_t S:1;
-	uint8_t reserved1:2;
-	uint8_t L:1;
-	uint8_t T:1;
-	uint8_t ver:4;
-	uint8_t reserved3:4;
+	uint16_t flags;
 	uint16_t length;
 	union {
 		struct {
@@ -28,18 +27,13 @@ struct l2tp_hdr_t
 	uint16_t Nr;
 } __attribute__((packed));
 
-/*#define L2TP_T(hdr) (hdr->flags >> 15)
-#define L2TP_L(hdr) ((hdr->flags >> 14) & 1)
-#define L2TP_S(hdr) ((hdr->flags >> 10) & 1)
-#define L2TP_O(hdr) ((hdr->flags >> 8) & 1)
-#define L2TP_VER(hdr) (hdr->flags & 0xf)*/
+#define L2TP_AVP_FLAG_M   0x8000
+#define L2TP_AVP_FLAG_H   0x4000
+#define L2TP_AVP_LEN_MASK 0x03ff
 
 struct l2tp_avp_t
 {
-	uint16_t length:10;
-	uint16_t reserved:4;
-	uint16_t H:1;
-	uint16_t M:1;
+	uint16_t flags;
 	uint16_t vendor;
 	uint16_t type;
 	uint8_t val[0];
@@ -53,4 +47,3 @@ struct l2tp_avp_result_code
 } __attribute__((packed));
 
 #endif
-

programming_language: C
filename: packet.c
change_type: ModificationType.MODIFY
diff: @@ -28,12 +28,18 @@ void l2tp_packet_print(const struct l2tp_packet_t *pack,
 	const struct l2tp_attr_t *attr;
 	const struct l2tp_dict_value_t *val;
 
-	if (pack->hdr.ver == 2) {
+	switch (pack->hdr.flags & L2TP_VER_MASK) {
+	case 2:
 		print("[L2TP tid=%u sid=%u", ntohs(pack->hdr.tid), ntohs(pack->hdr.sid));
 		log_ppp_debug(" Ns=%u Nr=%u", ntohs(pack->hdr.Ns), ntohs(pack->hdr.Nr));
-	} else {
+		break;
+	case 3:
 		print("[L2TP cid=%u", pack->hdr.cid);
 		log_ppp_debug(" Ns=%u Nr=%u", ntohs(pack->hdr.Ns), ntohs(pack->hdr.Nr));
+		break;
+	default:
+		print("[L2TP unknown version]\n");
+		return;
 	}
 
 	list_for_each_entry(attr, &pack->attrs, entry) {
@@ -72,10 +78,7 @@ struct l2tp_packet_t *l2tp_packet_alloc(int ver, int msg_type,
 
 	memset(pack, 0, sizeof(*pack));
 	INIT_LIST_HEAD(&pack->attrs);
-	pack->hdr.ver = ver;
-	pack->hdr.T = 1;
-	pack->hdr.L = 1;
-	pack->hdr.S = 1;
+	pack->hdr.flags = L2TP_FLAG_T | L2TP_FLAG_L | L2TP_FLAG_S | (ver & L2TP_VER_MASK);
 	memcpy(&pack->addr, addr, sizeof(*addr));
 	pack->hide_avps = H;
 	pack->secret = secret;
@@ -150,21 +153,23 @@ static int decode_avp(struct l2tp_avp_t *avp, const struct l2tp_attr_t *RV,
 	uint8_t md5[MD5_DIGEST_LENGTH];
 	uint8_t p1[MD5_DIGEST_LENGTH];
 	uint8_t *prev_block = NULL;
+	uint16_t avp_len;
 	uint16_t attr_len;
 	uint16_t orig_attr_len;
 	uint16_t bytes_left;
 	uint16_t blocks_left;
 	uint16_t last_block_len;
 
-	if (avp->length < sizeof(struct l2tp_avp_t) + 2) {
+	avp_len = avp->flags & L2TP_AVP_LEN_MASK;
+	if (avp_len < sizeof(struct l2tp_avp_t) + 2) {
 		/* Hidden AVPs must contain at least two bytes
 		   for storing original attribute length */
 		log_warn("l2tp: incorrect hidden avp received (type %hu):"
 			 " length too small (%hu bytes)\n",
-			 ntohs(avp->type), avp->length);
+			 ntohs(avp->type), avp_len);
 		return -1;
 	}
-	attr_len = avp->length - sizeof(struct l2tp_avp_t);
+	attr_len = avp_len - sizeof(struct l2tp_avp_t);
 
 	/* Decode first block */
 	MD5_Init(&md5_ctx);
@@ -228,31 +233,29 @@ static int decode_avp(struct l2tp_avp_t *avp, const struct l2tp_attr_t *RV,
 int l2tp_recv(int fd, struct l2tp_packet_t **p, struct in_pktinfo *pkt_info,
 	      const char *secret, size_t secret_len)
 {
-	int n, length;
-	uint8_t *buf;
+	struct l2tp_packet_t *pack;
 	struct l2tp_hdr_t *hdr;
 	struct l2tp_avp_t *avp;
-	struct l2tp_dict_attr_t *da;
-	struct l2tp_attr_t *attr, *RV = NULL;
-	uint8_t *ptr;
-	struct l2tp_packet_t *pack;
+	struct l2tp_attr_t *RV = NULL;
 	struct sockaddr_in addr;
-	socklen_t len = sizeof(addr);
-	struct msghdr msg;
-	char msg_control[128];
-	struct cmsghdr *cmsg;
+	socklen_t addr_len;
 	uint16_t orig_avp_len;
 	void *orig_avp_val;
+	uint8_t *buf, *ptr;
+	int n, length;
 
-  *p = NULL;
+	*p = NULL;
 
 	if (pkt_info) {
+		struct msghdr msg;
+		struct cmsghdr *cmsg;
+		char msg_control[128];
+
 		memset(&msg, 0, sizeof(msg));
 		msg.msg_control = msg_control;
-		msg.msg_controllen = 128;
+		msg.msg_controllen = sizeof(msg_control);
 
 		n = recvmsg(fd, &msg, MSG_PEEK);
-
 		if (n < 0) {
 			if (errno == EAGAIN)
 				return -1;
@@ -276,8 +279,8 @@ int l2tp_recv(int fd, struct l2tp_packet_t **p, struct in_pktinfo *pkt_info,
 	hdr = (struct l2tp_hdr_t *)buf;
 	ptr = (uint8_t *)(hdr + 1);
 
-	n = recvfrom(fd, buf, L2TP_MAX_PACKET_SIZE, 0, &addr, &len);
-
+	addr_len = sizeof(addr);
+	n = recvfrom(fd, buf, L2TP_MAX_PACKET_SIZE, 0, &addr, &addr_len);
 	if (n < 0) {
 		mempool_free(buf);
 		if (errno == EAGAIN) {
@@ -289,45 +292,50 @@ int l2tp_recv(int fd, struct l2tp_packet_t **p, struct in_pktinfo *pkt_info,
 		return 0;
 	}
 
-	if (n < 6) {
+	if (n < sizeof(*hdr)) {
 		if (conf_verbose)
 			log_warn("l2tp: short packet received (%i/%zu)\n", n, sizeof(*hdr));
 		goto out_err_hdr;
 	}
 
-	if (hdr->T == 0)
+	hdr->flags = ntohs(hdr->flags);
+	if (!(hdr->flags & L2TP_FLAG_T))
 		goto out_err_hdr;
-
-	if (n < ntohs(hdr->length)) {
+	if (!(hdr->flags & L2TP_FLAG_L)) {
 		if (conf_verbose)
-			log_warn("l2tp: short packet received (%i/%i)\n", n, ntohs(hdr->length));
+			log_warn("l2tp: incorrect control message received (L=0)\n");
 		goto out_err_hdr;
 	}
-
-	if (hdr->ver == 2) {
-		if (hdr->L == 0) {
-			if (conf_verbose)
-				log_warn("l2tp: incorrect message received (L=0)\n");
-			if (!conf_avp_permissive)
-			    goto out_err_hdr;
-		}
-
-		if (hdr->S == 0) {
+	if (!(hdr->flags & L2TP_FLAG_S)) {
+		if (conf_verbose)
+			log_warn("l2tp: incorrect control message received (S=0)\n");
+		goto out_err_hdr;
+	}
+	switch (hdr->flags & L2TP_VER_MASK) {
+	case 2:
+		if (hdr->flags & L2TP_FLAG_O) {
 			if (conf_verbose)
-				log_warn("l2tp: incorrect message received (S=0)\n");
-			if (!conf_avp_permissive)
-			    goto out_err_hdr;
+				log_warn("l2tp: incorrect control message received (O=1)\n");
+			goto out_err_hdr;
 		}
+		break;
+	case 3:
+		break;
+	default:
+		if (conf_verbose)
+			log_warn("l2tp: protocol version %i is not supported\n",
+				 hdr->flags & L2TP_VER_MASK);
+		goto out_err_hdr;
+	}
 
-		if (hdr->O == 1) {
-			if (conf_verbose)
-				log_warn("l2tp: incorrect message received (O=1)\n");
-			if (!conf_avp_permissive)
-			    goto out_err_hdr;
-		}
-	} else if (hdr->ver != 3) {
+	length = ntohs(hdr->length);
+	if (length < sizeof(*hdr)) {
 		if (conf_verbose)
-			log_warn("l2tp: protocol version %i is not supported\n", hdr->ver);
+			log_warn("l2tp: short packet received (%i/%zu)\n", length, sizeof(*hdr));
+		goto out_err_hdr;
+	} else if (n < length) {
+		if (conf_verbose)
+			log_warn("l2tp: short packet received (%i/%i)\n", n, length);
 		goto out_err_hdr;
 	}
 
@@ -342,13 +350,27 @@ int l2tp_recv(int fd, struct l2tp_packet_t **p, struct in_pktinfo *pkt_info,
 
 	memcpy(&pack->addr, &addr, sizeof(addr));
 	memcpy(&pack->hdr, hdr, sizeof(*hdr));
-	length = ntohs(hdr->length) - sizeof(*hdr);
+	length -= sizeof(*hdr);
 
-	while (length) {
-		*(uint16_t *)ptr = ntohs(*(uint16_t *)ptr);
-		avp = (struct l2tp_avp_t *)ptr;
+	while (length > 0) {
+		struct l2tp_dict_attr_t *da;
+		struct l2tp_attr_t *attr;
+		uint16_t avp_len;
 
-		if (avp->length > length) {
+		if (length < sizeof(*avp)) {
+			if (conf_verbose)
+				log_warn("l2tp: short avp received\n");
+			goto out_err;
+		}
+
+		avp = (struct l2tp_avp_t *)ptr;
+		avp->flags = ntohs(avp->flags);
+		avp_len = avp->flags & L2TP_AVP_LEN_MASK;
+		if (avp_len < sizeof(*avp)) {
+			if (conf_verbose)
+				log_warn("l2tp: short avp received\n");
+			goto out_err;
+		} else if (length < avp_len) {
 			if (conf_verbose)
 				log_warn("l2tp: incorrect avp received (exceeds message length)\n");
 			goto out_err;
@@ -359,26 +381,32 @@ int l2tp_recv(int fd, struct l2tp_packet_t **p, struct in_pktinfo *pkt_info,
 
 		da = l2tp_dict_find_attr_by_id(ntohs(avp->type));
 		if (!da) {
-			if (conf_verbose)
-				log_warn("l2tp: unknown avp received (type=%i, M=%u)\n", ntohs(avp->type), avp->M);
-			if (avp->M && !conf_avp_permissive)
+			if (conf_verbose) {
+				log_warn("l2tp: unknown avp received (type=%i, M=%u)\n",
+					 ntohs(avp->type), !!(avp->flags & L2TP_AVP_FLAG_M));
+			}
+			if ((avp->flags & L2TP_AVP_FLAG_M) && !conf_avp_permissive)
 				goto out_err;
 		} else {
-			if (da->M != -1 && da->M != avp->M) {
-				if (conf_verbose)
-					log_warn("l2tp: incorrect avp received (type=%i, M=%i, must be %i)\n", ntohs(avp->type), avp->M, da->M);
+			if (da->M != -1 && !da->M != !(avp->flags & L2TP_AVP_FLAG_M)) {
+				if (conf_verbose) {
+					log_warn("l2tp: incorrect avp received (type=%i, M=%i, must be %i)\n",
+						 ntohs(avp->type), !!(avp->flags & L2TP_AVP_FLAG_M), da->M);
+				}
 				if (!conf_avp_permissive)
 				    goto out_err;
 			}
 
-			if (da->H != -1 && da->H != avp->H) {
-				if (conf_verbose)
-					log_warn("l2tp: incorrect avp received (type=%i, H=%i, must be %i)\n", ntohs(avp->type), avp->H, da->H);
+			if (da->H != -1 && !da->H != !(avp->flags & L2TP_AVP_FLAG_H)) {
+				if (conf_verbose) {
+					log_warn("l2tp: incorrect avp received (type=%i, H=%i, must be %i)\n",
+						 ntohs(avp->type), !!(avp->flags & L2TP_AVP_FLAG_H), da->H);
+				}
 				if (!conf_avp_permissive)
 				    goto out_err;
 			}
 
-			if (avp->H) {
+			if (avp->flags & L2TP_AVP_FLAG_H) {
 				if (!RV) {
 					if (conf_verbose)
 						log_warn("l2tp: incorrect avp received (type=%i, H=1, but Random-Vector is not received)\n", ntohs(avp->type));
@@ -393,24 +421,21 @@ int l2tp_recv(int fd, struct l2tp_packet_t **p, struct in_pktinfo *pkt_info,
 				}
 				if (decode_avp(avp, RV, secret, secret_len) < 0)
 					goto out_err;
-			}
 
-			attr = mempool_alloc(attr_pool);
-			memset(attr, 0, sizeof(*attr));
-			list_add_tail(&attr->entry, &pack->attrs);
-
-			if (avp->H) {
 				orig_avp_len = ntohs(*(uint16_t *)avp->val) + sizeof(*avp);
 				orig_avp_val = avp->val + sizeof(uint16_t);
 			} else {
-				orig_avp_len = avp->length;
+				orig_avp_len = avp_len;
 				orig_avp_val = avp->val;
 			}
 
+			attr = mempool_alloc(attr_pool);
+			memset(attr, 0, sizeof(*attr));
 			attr->attr = da;
-			attr->M = avp->M;
+			attr->M = !!(avp->flags & L2TP_AVP_FLAG_M);
 			attr->H = 0;
 			attr->length = orig_avp_len - sizeof(*avp);
+			list_add_tail(&attr->entry, &pack->attrs);
 
 			if (attr->attr->id == Random_Vector)
 				RV = attr;
@@ -447,8 +472,8 @@ int l2tp_recv(int fd, struct l2tp_packet_t **p, struct in_pktinfo *pkt_info,
 			}
 		}
 skip:
-		ptr += avp->length;
-		length -= avp->length;
+		ptr += avp_len;
+		length -= avp_len;
 	}
 
 	*p = pack;
@@ -473,21 +498,22 @@ out_err_mem:
 
 int l2tp_packet_send(int sock, struct l2tp_packet_t *pack)
 {
-	uint8_t *buf = mempool_alloc(buf_pool);
+	struct l2tp_hdr_t *hdr;
 	struct l2tp_avp_t *avp;
 	struct l2tp_attr_t *attr;
-	uint8_t *ptr;
-	int n;
-	int len = sizeof(pack->hdr);
+	uint8_t *buf, *ptr;
+	int n, len;
 
+	buf = mempool_alloc(buf_pool);
 	if (!buf) {
 		log_emerg("l2tp: out of memory\n");
 		return -1;
 	}
 
 	memset(buf, 0, L2TP_MAX_PACKET_SIZE);
-
-	ptr = buf + sizeof(pack->hdr);
+	hdr = (struct l2tp_hdr_t *)buf;
+	ptr = (uint8_t *)(hdr + 1);
+	len = sizeof(pack->hdr);
 
 	list_for_each_entry(attr, &pack->attrs, entry) {
 		if (len + sizeof(*avp) + attr->length >= L2TP_MAX_PACKET_SIZE) {
@@ -497,10 +523,9 @@ int l2tp_packet_send(int sock, struct l2tp_packet_t *pack)
 		}
 		avp = (struct l2tp_avp_t *)ptr;
 		avp->type = htons(attr->attr->id);
-		avp->M = attr->M;
-		avp->H = attr->H;
-		avp->length = sizeof(*avp) + attr->length;
-		*(uint16_t *)ptr = htons(*(uint16_t *)ptr);
+		avp->flags = htons((attr->M ? L2TP_AVP_FLAG_M : 0) |
+				   (attr->H ? L2TP_AVP_FLAG_H : 0) |
+				   ((sizeof(*avp) + attr->length) & L2TP_AVP_LEN_MASK));
 		if (attr->H)
 			memcpy(avp->val, attr->val.octets, attr->length);
 		else
@@ -525,9 +550,9 @@ int l2tp_packet_send(int sock, struct l2tp_packet_t *pack)
 
 	pack->hdr.length = htons(len);
 	memcpy(buf, &pack->hdr, sizeof(pack->hdr));
+	hdr->flags = htons(pack->hdr.flags);
 
-	n = sendto(sock, buf, ntohs(pack->hdr.length), 0,
-		   &pack->addr, sizeof(pack->addr));
+	n = sendto(sock, buf, len, 0, &pack->addr, sizeof(pack->addr));
 	mempool_free(buf);
 
 	if (n < 0) {
@@ -541,9 +566,9 @@ int l2tp_packet_send(int sock, struct l2tp_packet_t *pack)
 		}
 	}
 
-	if (n != ntohs(pack->hdr.length)) {
+	if (n != len) {
 		if (conf_verbose)
-			log_warn("l2tp: short write (%i/%i)\n", n, ntohs(pack->hdr.length));
+			log_warn("l2tp: short write (%i/%i)\n", n, len);
 	}
 
 	return 0;

programming_language: C