Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
9b0db90
Fix OOB read in mDNS response handling (CVE-2020-24340)
danielinux Sep 9, 2026
0008a7c
Bound DNS name decompression to packet length (CVE-2020-24338/24339)
danielinux Sep 9, 2026
b48544e
Fix Skoll scan findings in DNS, DHCP and SNTP modules
danielinux Sep 9, 2026
304667e
Bound IPv6 ND option copy to destination size and option area
danielinux Sep 9, 2026
e2d9845
Bound 6LoWPAN reassembly fragment copy to datagram size
danielinux Sep 9, 2026
b4dd932
Cap PPP CHAP challenge copy to the allocated buffer
danielinux Sep 9, 2026
bb33f7a
Bound IPv6 extension header walk to the datagram length
danielinux Sep 9, 2026
170713f
Cap TCP SACK option read to the remaining options length
danielinux Sep 9, 2026
15841ab
Reject IPHC reassembly when compressed length exceeds the frame
danielinux Sep 9, 2026
fe27cf6
Reject 802.15.4 frames shorter than their own header
danielinux Sep 9, 2026
6940e82
Cap 6LoWPAN fragment copy to the datagram size
danielinux Sep 9, 2026
ebf3766
Validate OLSR per-message size before parsing
danielinux Sep 9, 2026
1582ea6
Cap concurrent 6LoWPAN reassembly entries
danielinux Sep 9, 2026
83ffe86
Bound ICMPv6 PTB and echo-reply header reads
danielinux Sep 9, 2026
e807c0a
Bound IGMP checksum length to the received frame
danielinux Sep 9, 2026
9367584
Guard NAT outbound tuple against NULL
danielinux Sep 9, 2026
3b300af
Bound radiotest receive buffer and frame length
danielinux Sep 9, 2026
db5528d
Fix uncrustify continuation indent in mdns unit test
danielinux Sep 9, 2026
baf8ddf
Address Copilot review: bound parsing, scope DHCP teardown
danielinux Sep 9, 2026
4e1fbfd
Document CVE-2020-24338/24339/24340 as fixed in 2.2
danielinux Sep 9, 2026
5b5114e
Document Forescout FSCT-2026-0017/0018/0019/0020/0022 fixes in 2.2
danielinux Sep 9, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 25 additions & 8 deletions docs/security.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,7 @@ Please consider checking the list of known issues before using picoTCP in produc

Vulnerabilities that can be found in picoTCP-NG v.2.1:

* ❗ Improper bound checking against the parsing of domain names may result in remote code execution (CVE-2020-24338)
* Triaged. Awaiting assignment.

* ❗ Improper bound checking against the parsing of domain names may result in a denial of service (CVE-2020-24339)
* Triaged. Awaiting assignment.

* ❗ Improper checks in the process of DNS response handling which may lead to memory corruption (CVE-2020-24340)
* Triaged. Awaiting assignment.
* None currently known.

Vulnerabilities fixed in this version:

Expand All @@ -41,3 +34,27 @@ Vulnerabilities fixed in this version:
* Improper checks against the length of incoming TCP packets may enable an out-of-bound read and/or memory corruption (CVE-2020-24341)
* **Fixed in v2.1** :heavy_check_mark:

* Improper checks in the process of DNS response handling which may lead to memory corruption (CVE-2020-24340)
* **Fixed in 2.2** :heavy_check_mark:

* Improper bound checking against the parsing of domain names which may result in remote code execution (CVE-2020-24338)
* **Fixed in 2.2** :heavy_check_mark:

* Improper bound checking against the parsing of domain names which may result in a denial of service (CVE-2020-24339)
* **Fixed in 2.2** :heavy_check_mark:

* Heap buffer overflow in IP fragment reassembly due to a 16-bit length truncation (Forescout FSCT-2026-0017)
* **Fixed in 2.2** :heavy_check_mark:

* Address-buffer overflow in the pico_socket_accept() peer-address copy (Forescout FSCT-2026-0018)
* **Fixed in 2.2** :heavy_check_mark:

* Unsigned integer underflow in UDP receive leading to an out-of-bounds read (Forescout FSCT-2026-0019)
* **Fixed in 2.2** :heavy_check_mark:

* Out-of-bounds read in DNS client name lookup due to an unbounded name walk (Forescout FSCT-2026-0020)
* **Fixed in 2.2** :heavy_check_mark:

* Out-of-bounds read in IPv4 input processing due to an off-by-option-length bound check (Forescout FSCT-2026-0022)
* **Fixed in 2.2** :heavy_check_mark:

54 changes: 52 additions & 2 deletions modules/pico_6lowpan.c
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,9 @@
#define FRAG1_DISPATCH (0xC0)
#define FRAGN_DISPATCH (0xE0)
#define FRAG_TIMEOUT (5)
/* Cap on concurrent reassembly entries to bound memory use against a
* flood of distinct fragment tags. */
#define PICO_6LOWPAN_MAX_REASSEMBLY (16)
/*******************************************************************************
* Type definitions
******************************************************************************/
Expand Down Expand Up @@ -1069,6 +1072,10 @@ pico_iphc_reassemble(struct pico_frame *f, uint8_t **chunks, int32_t *chunks_len

/* Calculate buffer size including IPv6 payload */
payload_len = (int32_t)f->len - handled_len;
if (payload_len < 0) {
/* Compressed length exceeds the frame; malformed. */
return pico_iphc_bail_out(chunks, n);
}
processed_len += payload_len; /* Length of frame after processing */

/* Reallocate frame size if there isn't enough room available */
Expand Down Expand Up @@ -1276,6 +1283,13 @@ frag_update(struct pico_frame *f, struct frag_ctx *frag, uint8_t units, uint16_t
static void
frag_fill(uint8_t *frag, uint8_t dispatch, uint16_t dgram_size, uint16_t tag, uint8_t dgram_off, int32_t offset, uint16_t copy, uint16_t copied, uint8_t *buf)
{
/* Never copy past the end of the datagram. */
if ((uint32_t)copied + (uint32_t)copy > (uint32_t)dgram_size) {
if (copied >= dgram_size)
copy = 0;
else
copy = (uint16_t)(dgram_size - copied);
}
frag[0] = (uint8_t)(dispatch | ((uint8_t)short_be(dgram_size) & 0x07));
frag[1] = (uint8_t)(short_be(dgram_size) >> 8);
frag[2] = (uint8_t)(short_be(tag));
Expand Down Expand Up @@ -1525,8 +1539,40 @@ pico_6lowpan_decompress(struct pico_frame *f)
static int32_t
defrag_new(struct pico_frame *f, uint16_t dgram_size, uint16_t tag, uint16_t off)
{
struct pico_frame *r = pico_proto_6lowpan_ll.alloc(f->dev->stack, &pico_proto_6lowpan_ll, f->dev, dgram_size);
struct pico_frame *r;
struct pico_stack *S = f->dev->stack;
struct pico_tree_node *i = NULL, *next = NULL;
struct frag_ctx *key = NULL;
struct frag_ctx *oldest = NULL;
int32_t count = 0;

/* Cap concurrent reassembly entries to bound memory use. */
pico_tree_foreach_safe(i, &S->LPReassemblyTree, next) {
if ((key = i->keyValue)) {
count++;
if (!oldest || key->timestamp < oldest->timestamp) {
oldest = key;
}
}
}
if (count >= PICO_6LOWPAN_MAX_REASSEMBLY) {
/* Tree is full; evict the oldest reassembly to make room. */
if (oldest) {
pico_tree_delete(&S->LPReassemblyTree, oldest);
pico_frame_discard(oldest->f);
PICO_FREE(oldest);
} else {
return -1;
}
}

r = pico_proto_6lowpan_ll.alloc(S, &pico_proto_6lowpan_ll, f->dev, dgram_size);
if (r) {
if ((uint32_t)off + (uint32_t)f->len > (uint32_t)dgram_size) {
pico_frame_discard(f);
pico_frame_discard(r);
return -1;
}
r->start = r->buffer + (int32_t)(r->buffer_len - (uint32_t)dgram_size);
r->len = dgram_size;
r->net_hdr = r->start;
Expand All @@ -1535,7 +1581,7 @@ defrag_new(struct pico_frame *f, uint16_t dgram_size, uint16_t tag, uint16_t off
r->src = f->src;
r->dst = f->dst;
buf_move(r->net_hdr + off, f->start, f->len);
if (frag_store(r, dgram_size, tag, 0, (uint16_t)f->len, &f->dev->stack->LPReassemblyTree) < 0) {
if (frag_store(r, dgram_size, tag, 0, (uint16_t)f->len, &S->LPReassemblyTree) < 0) {
pico_frame_discard(f);
pico_frame_discard(r);
return -1;
Expand All @@ -1549,6 +1595,10 @@ static int32_t
defrag_update(struct frag_ctx *frag, uint16_t off, struct pico_frame *f)
{
struct pico_frame *r = frag->f;
if ((uint32_t)off + (uint32_t)f->len > (uint32_t)frag->dgram_size) {
pico_frame_discard(f);
return -1;
}
buf_move(r->start + (int32_t)off, f->start, f->len); /* Copy at start */
frag->copied = (uint16_t)(frag->copied + (uint16_t)f->len);
pico_frame_discard(f);
Expand Down
2 changes: 1 addition & 1 deletion modules/pico_6lowpan_ll.c
Original file line number Diff line number Diff line change
Expand Up @@ -349,7 +349,7 @@ pico_6lowpan_ll_process_in(struct pico_stack *S, struct pico_protocol *self, str
case FRAME_6LOWPAN_LL_DISCARD:
/* Something went wrong, discard the frame */
pico_frame_discard(f);
break;
return -1;
default:
/* Success, update link layer header length */
len = (uint32_t)(len + (uint32_t)ret);
Expand Down
20 changes: 16 additions & 4 deletions modules/pico_802154.c
Original file line number Diff line number Diff line change
Expand Up @@ -202,8 +202,22 @@ pico_802154_process_in(struct pico_frame *f)
{
#ifndef PICO_6LOWPAN_NOMAC
struct pico_802154_hdr *hdr = (struct pico_802154_hdr *)f->net_hdr;
uint16_t fcf = short_be(hdr->fcf);
uint8_t hlen = 0;
uint16_t fcf;
uint8_t hlen;

/* The frame must hold at least the minimum 802.15.4 header before any
* field is read from it. */
if (f->len < SIZE_802154_MHR_MIN) {
return FRAME_6LOWPAN_LL_DISCARD;
}

fcf = short_be(hdr->fcf);
hlen = frame_802154_hdr_len(hdr);
if (f->len < (uint32_t)hlen) {
/* Frame shorter than its own header; malformed. */
return FRAME_6LOWPAN_LL_DISCARD;
}

f->src.pan = frame_802154_src(hdr);
f->dst.pan = frame_802154_dst(hdr);

Expand All @@ -214,8 +228,6 @@ pico_802154_process_in(struct pico_frame *f)
f->flags |= PICO_FRAME_FLAG_LL_SEC;
}

hlen = frame_802154_hdr_len(hdr);

/* XXX: Generic procedure to move forward in incoming processing function
* is updating the net_hdr-pointer */
f->net_hdr = f->datalink_hdr + (int32_t)hlen;
Expand Down
11 changes: 8 additions & 3 deletions modules/pico_dev_ppp.c
Original file line number Diff line number Diff line change
Expand Up @@ -1826,7 +1826,7 @@ static void auth_rsp(struct pico_device_ppp *ppp)
uint8_t *md5resp = resp + PPP_HDR_SIZE + PPP_PROTO_SLOT_SIZE + sizeof(struct pico_chap_hdr) + 1;
uint8_t *md5resp_len = resp + PPP_HDR_SIZE + PPP_PROTO_SLOT_SIZE + sizeof(struct pico_chap_hdr);
uint8_t *challenge;
uint32_t i = 0, pwdlen;
uint32_t i = 0, pwdlen, recvd_len;
uint8_t *recvd_challenge_len = ppp->pkt + sizeof(struct pico_chap_hdr);
uint8_t *recvd_challenge = recvd_challenge_len + 1;
size_t challenge_size = CHALLENGE_SIZE(ppp, ch);
Expand All @@ -1841,8 +1841,13 @@ static void auth_rsp(struct pico_device_ppp *ppp)
challenge[i++] = ch->id;
memcpy(challenge + i, ppp->password, pwdlen);
i += pwdlen;
memcpy(challenge + i, recvd_challenge, *recvd_challenge_len);
i += *recvd_challenge_len;
/* The buffer is sized by ch->len; the on-wire challenge length is an
* independent field, so cap the copy to the remaining buffer space. */
recvd_len = *recvd_challenge_len;
if (recvd_len > challenge_size - i)
recvd_len = (uint32_t)(challenge_size - i);
memcpy(challenge + i, recvd_challenge, recvd_len);
i += recvd_len;
pico_md5sum(md5resp, challenge, i);
PICO_FREE(challenge);
rh->id = ch->id;
Expand Down
4 changes: 3 additions & 1 deletion modules/pico_dev_radiotest.c
Original file line number Diff line number Diff line change
Expand Up @@ -329,6 +329,8 @@ static int radiotest_poll(struct pico_device *dev, int loop_score)
if (p.revents & POLLIN) {
ret_len = (int)recv(connection, &phy, (size_t)1, 0);
if (ret_len != 1) return loop_score;
if (phy > sizeof(buf))
return loop_score;
ret_len = (int)recv(connection, buf, (size_t)phy, 0);
if (ret_len != (int)phy)
return loop_score;
Expand All @@ -338,7 +340,7 @@ static int radiotest_poll(struct pico_device *dev, int loop_score)
}
}

if (ret_len < 2) { /* Not valid */
if (ret_len < 4) { /* Not a valid frame (frame + CRC + address) */
return loop_score;
}

Expand Down
22 changes: 20 additions & 2 deletions modules/pico_dhcp_client.c
Original file line number Diff line number Diff line change
Expand Up @@ -496,31 +496,43 @@ static void pico_dhcp_client_recv_params(struct pico_dhcp_client_cookie *dhcpc,
break;

case PICO_DHCP_OPT_MSGTYPE:
if (opt->len < 1)
break;
dhcpc->event = opt->ext.msg_type.type;
dhcpc_dbg("DHCP client: message type %u\n", dhcpc->event);
break;

case PICO_DHCP_OPT_LEASETIME:
if (opt->len < 4)
break;
dhcpc->lease_time = long_be(opt->ext.lease_time.time);
dhcpc_dbg("DHCP client: lease time %u\n", dhcpc->lease_time);
break;

case PICO_DHCP_OPT_RENEWALTIME:
if (opt->len < 4)
break;
dhcpc->t1_time = long_be(opt->ext.renewal_time.time);
dhcpc_dbg("DHCP client: renewal time %u\n", dhcpc->t1_time);
break;

case PICO_DHCP_OPT_REBINDINGTIME:
if (opt->len < 4)
break;
dhcpc->t2_time = long_be(opt->ext.rebinding_time.time);
dhcpc_dbg("DHCP client: rebinding time %u\n", dhcpc->t2_time);
break;

case PICO_DHCP_OPT_ROUTER:
if (opt->len < 4)
break;
dhcpc->gateway = opt->ext.router.ip;
dhcpc_dbg("DHCP client: router %08X\n", dhcpc->gateway.addr);
break;

case PICO_DHCP_OPT_DNS:
if (opt->len < 4)
break;
dhcpc->nameserver[0] = opt->ext.dns1.ip;
dhcpc_dbg("DHCP client: dns1 %08X\n", dhcpc->nameserver[0].addr);
if (opt->len >= 8) {
Expand All @@ -531,11 +543,15 @@ static void pico_dhcp_client_recv_params(struct pico_dhcp_client_cookie *dhcpc,
break;

case PICO_DHCP_OPT_NETMASK:
if (opt->len < 4)
break;
dhcpc->netmask = opt->ext.netmask.ip;
dhcpc_dbg("DHCP client: netmask %08X\n", dhcpc->netmask.addr);
break;

case PICO_DHCP_OPT_SERVERID:
if (opt->len < 4)
break;
dhcpc->server_id = opt->ext.server_id.ip;
dhcpc_dbg("DHCP client: server ID %08X\n", dhcpc->server_id.addr);
break;
Expand All @@ -546,21 +562,23 @@ static void pico_dhcp_client_recv_params(struct pico_dhcp_client_cookie *dhcpc,

case PICO_DHCP_OPT_HOSTNAME:
{
uint32_t maxlen = PICO_DHCP_HOSTNAME_MAXLEN;
uint32_t maxlen = PICO_DHCP_HOSTNAME_MAXLEN - 1;
if (opt->len < maxlen)
maxlen = opt->len;

strncpy(dhcpc->stack->dhcpc_host_name, opt->ext.string.txt, maxlen);
dhcpc->stack->dhcpc_host_name[maxlen] = '\0';
}
break;

case PICO_DHCP_OPT_DOMAINNAME:
{
uint32_t maxlen = PICO_DHCP_HOSTNAME_MAXLEN;
uint32_t maxlen = PICO_DHCP_HOSTNAME_MAXLEN - 1;
if (opt->len < maxlen)
maxlen = opt->len;

strncpy(dhcpc->stack->dhcpc_domain_name, opt->ext.string.txt, maxlen);
dhcpc->stack->dhcpc_domain_name[maxlen] = '\0';
}
break;

Expand Down
28 changes: 28 additions & 0 deletions modules/pico_dhcp_server.c
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,10 @@
/* maximum size of a DHCP message */
#define DHCP_SERVER_MAXMSGSIZE (PICO_IP_MRU - sizeof(struct pico_ipv4_hdr) - sizeof(struct pico_udp_hdr))

/* Cap on concurrent in-flight negotiations: a flood of DISCOVERs with distinct
* xids must not grow the negotiation tree without bound. */
#define PICO_DHCP_SERVER_MAX_NEGOTIATIONS 16

enum dhcp_server_state {
PICO_DHCP_STATE_DISCOVER = 0,
PICO_DHCP_STATE_OFFER,
Expand Down Expand Up @@ -224,10 +228,23 @@ static struct pico_dhcp_server_negotiation *pico_dhcp_server_add_negotiation(str
struct pico_dhcp_server_setting test = {
0
};
struct pico_tree_node *node, *victim;
uint32_t count = 0;

if (pico_dhcp_server_find_negotiation(dev->stack, hdr->xid))
return NULL;

/* Cap concurrent negotiations; evict the lowest-xid entry when full
* (the tree is keyed by xid, so firstNode is the smallest xid). */
pico_tree_foreach(node, &dev->stack->DHCPNegotiations) {
count++;
}
if (count >= PICO_DHCP_SERVER_MAX_NEGOTIATIONS) {
victim = pico_tree_firstNode(dev->stack->DHCPNegotiations.root);
if (victim != &LEAF)
PICO_FREE(pico_tree_delete(&dev->stack->DHCPNegotiations, victim->keyValue));
}

dhcpn = PICO_ZALLOC(sizeof(struct pico_dhcp_server_negotiation));
if (!dhcpn) {
pico_err = PICO_ERR_ENOMEM;
Expand Down Expand Up @@ -431,6 +448,8 @@ int pico_dhcp_server_destroy(struct pico_device *dev)
struct pico_dhcp_server_setting *found, test = {
0
};
struct pico_tree_node *node, *next;
struct pico_dhcp_server_negotiation *n;
test.dev = dev;
found = pico_tree_findKey(&dev->stack->DHCPSettings, &test);
if (!found) {
Expand All @@ -440,6 +459,15 @@ int pico_dhcp_server_destroy(struct pico_device *dev)

pico_tree_delete(&dev->stack->DHCPSettings, found);
PICO_FREE(found);

/* Free in-flight negotiations belonging to this device only, so that
* destroying one server does not disrupt others on the same stack.
* pico_tree_delete frees each tree node. */
pico_tree_foreach_safe(node, &dev->stack->DHCPNegotiations, next) {
n = node->keyValue;
if (n->dhcps && n->dhcps->dev == dev)
PICO_FREE(pico_tree_delete(&dev->stack->DHCPNegotiations, node->keyValue));
}
return 0;
}

Expand Down
Loading
Loading