Skip to content

Commit 7d92e8b

Browse files
committed
Gate request-side arp_store_neighbor in arp_recv on a matching arp_pending_match_and_clear so unsolicited ARP requests can no longer fill the neighbor cache and lock out legitimate replies, with test_arp_request_flood_does_not_lock_out_legit_reply as regression test.
Updated three pre-existing tests to model the now-required solicited-learn path.
1 parent b700877 commit 7d92e8b

4 files changed

Lines changed: 98 additions & 2 deletions

File tree

src/test/unit/unit.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -715,6 +715,7 @@ Suite *wolf_suite(void)
715715
tcase_add_test(tc_proto, test_arp_recv_null_stack);
716716
tcase_add_test(tc_proto, test_arp_recv_request_sends_reply);
717717
tcase_add_test(tc_proto, test_arp_recv_request_does_not_store_self_neighbor);
718+
tcase_add_test(tc_proto, test_arp_request_flood_does_not_lock_out_legit_reply);
718719
tcase_add_test(tc_proto, test_arp_recv_request_no_send_fn);
719720
tcase_add_test(tc_proto, test_wolfip_if_for_local_ip_paths);
720721
tcase_add_test(tc_proto, test_wolfip_if_for_local_ip_null_found);

src/test/unit/unit_tests_proto.c

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2673,6 +2673,11 @@ START_TEST(test_arp_request_handling) {
26732673
memcpy(arp_req.sma, req_mac, 6);
26742674
arp_req.tip = ee32(device_ip);
26752675

2676+
/* Model a solicited learn: stack has an outstanding ARP request for
2677+
* req_ip, so the request handler is allowed to populate the cache. */
2678+
s.last_tick = 1000;
2679+
arp_pending_record(&s, TEST_PRIMARY_IF, req_ip);
2680+
26762681
/* Call arp_recv with the ARP request */
26772682
arp_recv(&s, TEST_PRIMARY_IF, &arp_req, sizeof(arp_req));
26782683
wolfIP_poll(&s, 1000);

src/test/unit/unit_tests_tcp_ack.c

Lines changed: 86 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2235,6 +2235,9 @@ START_TEST(test_arp_recv_request_does_not_store_self_neighbor)
22352235
wolfIP_filter_set_callback(NULL, NULL);
22362236
wolfIP_filter_set_mask(0);
22372237

2238+
s.last_tick = 1000;
2239+
arp_pending_record(&s, TEST_PRIMARY_IF, sender_ip);
2240+
22382241
memset(&arp_req, 0, sizeof(arp_req));
22392242
arp_req.htype = ee16(1);
22402243
arp_req.ptype = ee16(0x0800);
@@ -2264,6 +2267,89 @@ START_TEST(test_arp_recv_request_does_not_store_self_neighbor)
22642267
}
22652268
END_TEST
22662269

2270+
/* Regression: a same-LAN attacker that floods the ARP cache by sending
2271+
* MAX_NEIGHBORS ARP requests from distinct sender IP/MAC pairs targeting our
2272+
* IP must not lock out legitimate ARP replies for outstanding requests.
2273+
* arp_store_neighbor's silent-drop-when-full behaviour, combined with
2274+
* unconditional sender caching from the request branch of arp_recv, lets a
2275+
* flood deny resolution of any new peer until ARP_AGING_TIMEOUT_MS elapses. */
2276+
START_TEST(test_arp_request_flood_does_not_lock_out_legit_reply)
2277+
{
2278+
struct wolfIP s;
2279+
struct arp_packet arp_pkt;
2280+
struct wolfIP_ll_dev *ll;
2281+
struct ipconf *conf;
2282+
const ip4 our_ip = 0x0A000001U;
2283+
const ip4 legit_ip = 0x0A0000FEU;
2284+
const uint8_t legit_mac[6] = {0x02, 0xCA, 0xFE, 0xBA, 0xBE, 0x01};
2285+
uint8_t mac_out[6];
2286+
int i;
2287+
2288+
wolfIP_init(&s);
2289+
mock_link_init(&s);
2290+
wolfIP_ipconfig_set(&s, our_ip, 0xFFFFFF00U, 0);
2291+
wolfIP_filter_set_callback(NULL, NULL);
2292+
wolfIP_filter_set_mask(0);
2293+
2294+
ll = wolfIP_getdev_ex(&s, TEST_PRIMARY_IF);
2295+
conf = wolfIP_ipconf_at(&s, TEST_PRIMARY_IF);
2296+
s.last_tick = 1000;
2297+
2298+
/* Attacker floods MAX_NEIGHBORS ARP requests from distinct sender
2299+
* IP/MAC pairs, all targeting our IP. Each one passes the
2300+
* broadcast/multicast/zero/own-IP filter and so reaches
2301+
* arp_store_neighbor unconditionally. */
2302+
for (i = 0; i < MAX_NEIGHBORS; i++) {
2303+
uint8_t fake_mac[6] = {0xDE, 0xAD, 0xBE, 0xEF, 0x00,
2304+
(uint8_t)(0x10 + i)};
2305+
ip4 fake_ip = (ip4)(0x0A000010U + (uint32_t)i);
2306+
2307+
memset(&arp_pkt, 0, sizeof(arp_pkt));
2308+
memcpy(arp_pkt.eth.dst, ll->mac, 6);
2309+
memcpy(arp_pkt.eth.src, fake_mac, 6);
2310+
arp_pkt.eth.type = ee16(ETH_TYPE_ARP);
2311+
arp_pkt.htype = ee16(1);
2312+
arp_pkt.ptype = ee16(0x0800);
2313+
arp_pkt.hlen = 6;
2314+
arp_pkt.plen = 4;
2315+
arp_pkt.opcode = ee16(ARP_REQUEST);
2316+
memcpy(arp_pkt.sma, fake_mac, 6);
2317+
arp_pkt.sip = ee32(fake_ip);
2318+
memset(arp_pkt.tma, 0, 6);
2319+
arp_pkt.tip = ee32(conf->ip);
2320+
2321+
s.last_tick += 1;
2322+
arp_recv(&s, TEST_PRIMARY_IF, &arp_pkt, sizeof(arp_pkt));
2323+
}
2324+
2325+
/* Stack issues a legitimate ARP request for legit_ip and then receives
2326+
* the matching reply. Caching this reply must succeed even with the
2327+
* neighbor table full of attacker-driven entries. */
2328+
s.last_tick += 1;
2329+
arp_pending_record(&s, TEST_PRIMARY_IF, legit_ip);
2330+
2331+
memset(&arp_pkt, 0, sizeof(arp_pkt));
2332+
memcpy(arp_pkt.eth.dst, ll->mac, 6);
2333+
memcpy(arp_pkt.eth.src, legit_mac, 6);
2334+
arp_pkt.eth.type = ee16(ETH_TYPE_ARP);
2335+
arp_pkt.htype = ee16(1);
2336+
arp_pkt.ptype = ee16(0x0800);
2337+
arp_pkt.hlen = 6;
2338+
arp_pkt.plen = 4;
2339+
arp_pkt.opcode = ee16(ARP_REPLY);
2340+
memcpy(arp_pkt.sma, legit_mac, 6);
2341+
arp_pkt.sip = ee32(legit_ip);
2342+
memcpy(arp_pkt.tma, ll->mac, 6);
2343+
arp_pkt.tip = ee32(conf->ip);
2344+
2345+
s.last_tick += 1;
2346+
arp_recv(&s, TEST_PRIMARY_IF, &arp_pkt, sizeof(arp_pkt));
2347+
2348+
ck_assert_int_eq(arp_lookup(&s, TEST_PRIMARY_IF, legit_ip, mac_out), 0);
2349+
ck_assert_mem_eq(mac_out, legit_mac, 6);
2350+
}
2351+
END_TEST
2352+
22672353
START_TEST(test_send_ttl_exceeded_filter_drop)
22682354
{
22692355
struct wolfIP s;
@@ -2690,7 +2776,6 @@ START_TEST(test_arp_recv_filter_drop)
26902776

26912777
arp_recv(&s, TEST_PRIMARY_IF, &arp_req, sizeof(arp_req));
26922778
ck_assert_uint_eq(last_frame_sent_size, 0);
2693-
ck_assert_int_ne(s.arp.neighbors[0].ip, IPADDR_ANY);
26942779

26952780
wolfIP_filter_set_callback(NULL, NULL);
26962781
wolfIP_filter_set_eth_mask(0);

src/wolfip.c

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8129,7 +8129,12 @@ static void arp_recv(struct wolfIP *s, unsigned int if_idx, void *buf, int len)
81298129
if (idx >= 0) {
81308130
if (memcmp(s->arp.neighbors[idx].mac, sender_mac, 6) == 0)
81318131
s->arp.neighbors[idx].ts = s->last_tick;
8132-
} else {
8132+
} else if (arp_pending_match_and_clear(s, if_idx, sip)) {
8133+
/* Only learn from an unsolicited request when we have an
8134+
* outstanding ARP request for this peer; otherwise a
8135+
* same-LAN attacker can flood requests from distinct
8136+
* sender IP/MAC pairs to exhaust the neighbor table and
8137+
* lock out legitimate replies until ARP_AGING_TIMEOUT_MS. */
81338138
arp_store_neighbor(s, if_idx, sip, sender_mac);
81348139
}
81358140
}

0 commit comments

Comments
 (0)