Commit message: sonmp: fix heap overflow when reading SONMP packets

By sending short SONMP packets, an attacker can make the decoder crash
by reading too much data on the heap. SONMP packets are fixed in size,
just ensure we get the enough bytes to contain a SONMP packet.

CVE-2021-43612
CVE: CVE-2021-43612
Vulnerability description: In lldpd before 1.0.13, when decoding SONMP packets in the sonmp_decode function, it's possible to trigger an out-of-bounds heap read via short SONMP packets.

Project: lldpd
File: check_sonmp.c

main()
{
	int number_failed;
	Suite *s = sonmp_suite ();
	SRunner *sr = srunner_create (s);
	srunner_set_fork_status (sr, CK_NOFORK); /* Can't fork because
						    we need to write
						    files */
	srunner_run_all (sr, CK_ENV);
	number_failed = srunner_ntests_failed (sr);
	srunner_free (sr);
	return (number_failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE;
}

Project: lldpd
File: check_sonmp.c

START_TEST (test_recv_sonmp)
{
	char pkt1[] = {
		0x01, 0x00, 0x81, 0x00, 0x01, 0x00, 0x00, 0x1b,
		0x25, 0x08, 0x50, 0x47, 0x00, 0x13, 0xaa, 0xaa,
		0x03, 0x00, 0x00, 0x81, 0x01, 0xa2, 0xac, 0x10,
		0x65, 0xa8, 0x00, 0x02, 0x08, 0x38, 0x0c, 0x02,
		0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
		0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
		0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
		0x00, 0x00, 0x00, 0x00 };
	/* This is:
IEEE 802.3 Ethernet 
    Destination: Bay-Networks-(Synoptics)-autodiscovery (01:00:81:00:01:00)
    Source: Nortel_08:50:47 (00:1b:25:08:50:47)
    Length: 19
    Trailer: 000000000000000000000000000000000000000000000000...
Logical-Link Control
    DSAP: SNAP (0xaa)
    IG Bit: Individual
    SSAP: SNAP (0xaa)
    CR Bit: Command
    Control field: U, func=UI (0x03)
        000. 00.. = Command: Unnumbered Information (0x00)
        .... ..11 = Frame type: Unnumbered frame (0x03)
    Organization Code: Nortel Networks SONMP (0x000081)
    PID: SONMP segment hello (0x01a2)
Nortel Networks / SynOptics Network Management Protocol
    NMM IP address: 172.16.101.168 (172.16.101.168)
    Segment Identifier: 0x000208
    Chassis type: Accelar 8610 L3 switch (56)
    Backplane type: ethernet, fast ethernet and gigabit ethernet (12)
    NMM state: Heartbeat (2)
    Number of links: 1
	*/
	struct lldpd_chassis *nchassis = NULL;
	struct lldpd_port *nport = NULL;
	char cid[5];
	in_addr_t ip;

	fail_unless(sonmp_decode(NULL, pkt1, sizeof(pkt1), &hardware,
		&nchassis, &nport) != -1);
	if (!nchassis || !nport) {
		fail("unable to decode packet");
		return;
	}
	ck_assert_int_eq(nchassis->c_id_subtype,
	    LLDP_CHASSISID_SUBTYPE_ADDR);
	ck_assert_int_eq(nchassis->c_id_len, 5);
	cid[0] = 1;
	ip = inet_addr("172.16.101.168");
	memcpy(cid + 1, &ip, sizeof(in_addr_t));
	fail_unless(memcmp(nchassis->c_id, cid, 5) == 0);
	ck_assert_str_eq(nchassis->c_name, "172.16.101.168");
	ck_assert_str_eq(nchassis->c_descr, "Nortel Ethernet Routing 8610 L3 Switch");
	ck_assert_int_eq(TAILQ_FIRST(&nchassis->c_mgmt)->m_addr.inet.s_addr,
	    (u_int32_t)inet_addr("172.16.101.168"));
	ck_assert_int_eq(TAILQ_FIRST(&nchassis->c_mgmt)->m_iface, 0);
	ck_assert_str_eq(nport->p_descr, "port 2/8");
	ck_assert_int_eq(nport->p_id_subtype,
	    LLDP_PORTID_SUBTYPE_LOCAL);
	ck_assert_int_eq(nport->p_id_len, strlen("00-02-08"));
	fail_unless(memcmp(nport->p_id,
		"00-02-08", strlen("00-02-08")) == 0);
	ck_assert_int_eq(nchassis->c_cap_enabled, 0);
}

Project: lldpd
File: sonmp.c

sonmp_decode(struct lldpd *cfg, char *frame, int s,
    struct lldpd_hardware *hardware,
    struct lldpd_chassis **newchassis, struct lldpd_port **newport)
{
	const u_int8_t mcastaddr[] = SONMP_MULTICAST_ADDR;
	struct lldpd_chassis *chassis;
	struct lldpd_port *port;
	struct lldpd_mgmt *mgmt;
	int length, i;
	u_int8_t *pos;
	u_int8_t seg[3], rchassis;
	struct in_addr address;

	log_debug("sonmp", "decode SONMP PDU from %s",
	    hardware->h_ifname);

	if ((chassis = calloc(1, sizeof(struct lldpd_chassis))) == NULL) {
		log_warn("sonmp", "failed to allocate remote chassis");
		return -1;
	}
	TAILQ_INIT(&chassis->c_mgmt);
	if ((port = calloc(1, sizeof(struct lldpd_port))) == NULL) {
		log_warn("sonmp", "failed to allocate remote port");
		free(chassis);
		return -1;
	}
#ifdef ENABLE_DOT1
	TAILQ_INIT(&port->p_vlans);
#endif

	length = s;
	pos = (u_int8_t*)frame;
	if (length < SONMP_SIZE + 2*ETHER_ADDR_LEN + sizeof(u_int16_t)) {
		log_warnx("sonmp", "too short SONMP frame received on %s", hardware->h_ifname);
		goto malformed;
	}
	if (PEEK_CMP(mcastaddr, sizeof(mcastaddr)) != 0)
		/* There is two multicast address. We just handle only one of
		 * them. */
		goto malformed;
	/* We skip to LLC PID */
	PEEK_DISCARD(ETHER_ADDR_LEN); PEEK_DISCARD_UINT16;
	PEEK_DISCARD(6);
	if (PEEK_UINT16 != LLC_PID_SONMP_HELLO) {
		log_debug("sonmp", "incorrect LLC protocol ID received for SONMP on %s",
		    hardware->h_ifname);
		goto malformed;
	}

	chassis->c_id_subtype = LLDP_CHASSISID_SUBTYPE_ADDR;
	if ((chassis->c_id = calloc(1, sizeof(struct in_addr) + 1)) == NULL) {
		log_warn("sonmp", "unable to allocate memory for chassis id on %s",
			hardware->h_ifname);
		goto malformed;
	}
	chassis->c_id_len = sizeof(struct in_addr) + 1;
	chassis->c_id[0] = 1;
	PEEK_BYTES(&address, sizeof(struct in_addr));
	memcpy(chassis->c_id + 1, &address, sizeof(struct in_addr));
	if (asprintf(&chassis->c_name, "%s", inet_ntoa(address)) == -1) {
		log_warnx("sonmp", "unable to write chassis name for %s",
		    hardware->h_ifname);
		goto malformed;
	}
	PEEK_BYTES(seg, sizeof(seg));
	rchassis = PEEK_UINT8;
	for (i=0; sonmp_chassis_types[i].type != 0; i++) {
		if (sonmp_chassis_types[i].type == rchassis)
			break;
	}
	if (asprintf(&chassis->c_descr, "%s",
		sonmp_chassis_types[i].description) == -1) {
		log_warnx("sonmp", "unable to write chassis description for %s",
		    hardware->h_ifname);
		goto malformed;
	}
	mgmt = lldpd_alloc_mgmt(LLDPD_AF_IPV4, &address, sizeof(struct in_addr), 0);
	if (mgmt == NULL) {
		if (errno == ENOMEM)
			log_warn("sonmp", "unable to allocate memory for management address");
		else
			log_warn("sonmp", "too large management address received on %s",
			    hardware->h_ifname);
		goto malformed;
	}
	TAILQ_INSERT_TAIL(&chassis->c_mgmt, mgmt, m_entries);
	port->p_ttl = cfg?(cfg->g_config.c_tx_interval * cfg->g_config.c_tx_hold):
	    LLDPD_TTL;
	port->p_ttl = (port->p_ttl + 999) / 1000;

	port->p_id_subtype = LLDP_PORTID_SUBTYPE_LOCAL;
	if (asprintf(&port->p_id, "%02x-%02x-%02x",
		seg[0], seg[1], seg[2]) == -1) {
		log_warn("sonmp", "unable to allocate memory for port id on %s",
		    hardware->h_ifname);
		goto malformed;
	}
	port->p_id_len = strlen(port->p_id);

	/* Port description depend on the number of segments */
	if ((seg[0] == 0) && (seg[1] == 0)) {
		if (asprintf(&port->p_descr, "port %d",
			seg[2]) == -1) {
			log_warnx("sonmp", "unable to write port description for %s",
			    hardware->h_ifname);
			goto malformed;
		}
	} else if (seg[0] == 0) {
		if (asprintf(&port->p_descr, "port %d/%d",
			seg[1], seg[2]) == -1) {
			log_warnx("sonmp", "unable to write port description for %s",
			    hardware->h_ifname);
			goto malformed;
		}
	} else {
		if (asprintf(&port->p_descr, "port %x:%x:%x",
			seg[0], seg[1], seg[2]) == -1) {
			log_warnx("sonmp", "unable to write port description for %s",
			    hardware->h_ifname);
			goto malformed;
		}
	}
	*newchassis = chassis;
	*newport = port;
	return 1;

malformed:
	lldpd_chassis_cleanup(chassis, 1);
	lldpd_port_cleanup(port, 1);
	free(port);
	return -1;
}

Project: lldpd
File: check_sonmp.c

sonmp_suite(void)
{
	Suite *s = suite_create("SONMP");

#ifdef ENABLE_SONMP
	TCase *tc_send = tcase_create("Send SONMP packets");
	TCase *tc_receive = tcase_create("Receive SONMP packets");

	tcase_add_checked_fixture(tc_send, pcap_setup, pcap_teardown);
	tcase_add_test(tc_send, test_send_sonmp);
	suite_add_tcase(s, tc_send);

	tcase_add_test(tc_receive, test_recv_sonmp);
	suite_add_tcase(s, tc_receive);
#endif

	return s;
}

Project: lldpd
File: check_sonmp.c

START_TEST (test_send_sonmp)
{
	int n;
	/* Packet we should build:
IEEE 802.3 Ethernet 
    Destination: Bay-Networks-(Synoptics)-autodiscovery (01:00:81:00:01:00)
    Source: 5e:10:8e:e7:84:ad (5e:10:8e:e7:84:ad)
    Length: 19
Logical-Link Control
    DSAP: SNAP (0xaa)
    IG Bit: Individual
    SSAP: SNAP (0xaa)
    CR Bit: Command
    Control field: U, func=UI (0x03)
        000. 00.. = Command: Unnumbered Information (0x00)
        .... ..11 = Frame type: Unnumbered frame (0x03)
    Organization Code: Nortel Networks SONMP (0x000081)
    PID: SONMP segment hello (0x01a2)
Nortel Networks / SynOptics Network Management Protocol
    NMM IP address: 172.17.142.37 (172.17.142.37)
    Segment Identifier: 0x000004
    Chassis type: Unknown (1)
    Backplane type: ethernet, fast ethernet and gigabit ethernet (12)
    NMM state: New (3)
    Number of links: 1

IEEE 802.3 Ethernet 
    Destination: Bay-Networks-(Synoptics)-autodiscovery (01:00:81:00:01:01)
    Source: 5e:10:8e:e7:84:ad (5e:10:8e:e7:84:ad)
    Length: 19
Logical-Link Control
    DSAP: SNAP (0xaa)
    IG Bit: Individual
    SSAP: SNAP (0xaa)
    CR Bit: Command
    Control field: U, func=UI (0x03)
        000. 00.. = Command: Unnumbered Information (0x00)
        .... ..11 = Frame type: Unnumbered frame (0x03)
    Organization Code: Nortel Networks SONMP (0x000081)
    PID: SONMP flatnet hello (0x01a1)
Nortel Networks / SynOptics Network Management Protocol
    NMM IP address: 172.17.142.37 (172.17.142.37)
    Segment Identifier: 0x000004
    Chassis type: Unknown (1)
    Backplane type: ethernet, fast ethernet and gigabit ethernet (12)
    NMM state: New (3)
    Number of links: 1
	*/
	char pkt1[] = {
		0x01, 0x00, 0x81, 0x00, 0x01, 0x00, 0x5e, 0x10,
		0x8e, 0xe7, 0x84, 0xad, 0x00, 0x13, 0xaa, 0xaa,
		0x03, 0x00, 0x00, 0x81, 0x01, 0xa2, 0xac, 0x11,
		0x8e, 0x25, 0x00, 0x00, 0x04, 0x01, 0x0c, 0x03,
		0x01 };
	char pkt2[] = {
		0x01, 0x00, 0x81, 0x00, 0x01, 0x01, 0x5e, 0x10,
		0x8e, 0xe7, 0x84, 0xad, 0x00, 0x13, 0xaa, 0xaa,
		0x03, 0x00, 0x00, 0x81, 0x01, 0xa1, 0xac, 0x11,
		0x8e, 0x25, 0x00, 0x00, 0x04, 0x01, 0x0c, 0x03,
		0x01 };
	struct packet *pkt;
	in_addr_t addr;	
	struct lldpd_mgmt *mgmt;

	/* Populate port and chassis */
	hardware.h_lport.p_id_subtype = LLDP_PORTID_SUBTYPE_IFNAME;
	hardware.h_lport.p_id = "Not used";
	hardware.h_lport.p_id_len = strlen(hardware.h_lport.p_id);
	chassis.c_id_subtype = LLDP_CHASSISID_SUBTYPE_LLADDR;
	chassis.c_id = macaddress;
	chassis.c_id_len = ETHER_ADDR_LEN;
	TAILQ_INIT(&chassis.c_mgmt);
	addr = inet_addr("172.17.142.37");
	mgmt = lldpd_alloc_mgmt(LLDPD_AF_IPV4,
				&addr, sizeof(in_addr_t), 0);
	if (mgmt == NULL)
		ck_abort();
	TAILQ_INSERT_TAIL(&chassis.c_mgmt, mgmt, m_entries);

	/* Build packet */
	n = sonmp_send(NULL, &hardware);
	if (n != 0) {
		fail("unable to build packet");
		return;
	}
	if (TAILQ_EMPTY(&pkts)) {
		fail("no packets sent");
		return;
	}
	pkt = TAILQ_FIRST(&pkts);
	ck_assert_int_eq(pkt->size, sizeof(pkt1));
	fail_unless(memcmp(pkt->data, pkt1, sizeof(pkt1)) == 0);
	pkt = TAILQ_NEXT(pkt, next);
	if (!pkt) {
		fail("need one more packet");
		return;
	}
	ck_assert_int_eq(pkt->size, sizeof(pkt2));
	fail_unless(memcmp(pkt->data, pkt2, sizeof(pkt2)) == 0);
	fail_unless(TAILQ_NEXT(pkt, next) == NULL, "more than two packets sent");
}