From: Yu Watanabe Date: Mon, 16 Mar 2026 15:11:25 +0000 (+0900) Subject: ip-util: introduce udp_packet_verify() X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=11def23206d68d4a37b4ddc6b5a1b67c49554b30;p=thirdparty%2Fsystemd.git ip-util: introduce udp_packet_verify() This is mostly equivalent to dhcp_packet_verify_headers(), but - it optionally returns the UDP payload as iovec, and - supports IP header with options, - check packet length more strictly. --- diff --git a/src/libsystemd-network/dhcp-packet.c b/src/libsystemd-network/dhcp-packet.c index bc8f948d2ca..27b09a25bbb 100644 --- a/src/libsystemd-network/dhcp-packet.c +++ b/src/libsystemd-network/dhcp-packet.c @@ -10,7 +10,6 @@ #include "iovec-util.h" #include "iovec-wrapper.h" #include "ip-util.h" -#include "log.h" #include "memory-util.h" #define DHCP_CLIENT_MIN_OPTIONS_SIZE 312 @@ -118,67 +117,5 @@ int dhcp_packet_append_ip_headers( } int dhcp_packet_verify_headers(DHCPPacket *packet, size_t len, bool checksum, uint16_t port) { - size_t hdrlen; - - assert(packet); - - if (len < sizeof(DHCPPacket)) - return 0; - - /* IP */ - - if (packet->ip.version != IPVERSION) - return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), - "ignoring packet: not IPv4"); - - if (packet->ip.ihl < 5) - return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), - "ignoring packet: IPv4 IHL (%i words) invalid", - packet->ip.ihl); - - hdrlen = packet->ip.ihl * 4; - if (hdrlen < 20) - return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), - "ignoring packet: IPv4 IHL (%zu bytes) smaller than minimum (20 bytes)", - hdrlen); - - if (len < hdrlen) - return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), - "ignoring packet: packet (%zu bytes) smaller than expected (%zu) by IP header", - len, hdrlen); - - /* UDP */ - - if (packet->ip.protocol != IPPROTO_UDP) - return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), - "ignoring packet: not UDP"); - - if (len < hdrlen + be16toh(packet->udp.len)) - return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), - "ignoring packet: packet (%zu bytes) smaller than expected (%zu) by UDP header", - len, hdrlen + be16toh(packet->udp.len)); - - if (be16toh(packet->udp.dest) != port) - return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), - "ignoring packet: to port %u, which is not the DHCP client port (%u)", - be16toh(packet->udp.dest), port); - - /* checksums - computing these is relatively expensive, so only do it - if all the other checks have passed - */ - - if (ip_checksum(&packet->ip, hdrlen)) - return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), - "ignoring packet: invalid IP checksum"); - - if (checksum && packet->udp.check) { - packet->ip.check = packet->udp.len; - packet->ip.ttl = 0; - - if (ip_checksum(&packet->ip.ttl, be16toh(packet->udp.len) + 12)) - return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), - "ignoring packet: invalid UDP checksum"); - } - - return 0; + return udp_packet_verify(&IOVEC_MAKE(packet, len), port, checksum, /* ret_payload= */ NULL); } diff --git a/src/libsystemd-network/ip-util.c b/src/libsystemd-network/ip-util.c index fcc22e15e35..3f062a7ef00 100644 --- a/src/libsystemd-network/ip-util.c +++ b/src/libsystemd-network/ip-util.c @@ -5,6 +5,7 @@ #include "iovec-util.h" #include "iovec-wrapper.h" #include "ip-util.h" +#include "log.h" union iphdr_union { struct iphdr ip; @@ -155,3 +156,96 @@ int udp_packet_build( *ret_udphdr = udp; return 0; } + +int udp_packet_verify( + const struct iovec *packet, + uint16_t port, + bool checksum, + struct iovec *ret_payload) { + + assert(packet); + + /* This verifies IP and UDP packet headers and optionally returns the UDP payload. */ + + /* IP */ + if (packet->iov_len < sizeof(struct iphdr)) + return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), + "IPv4: packet (%zu bytes) smaller than minimum IP header (%zu bytes), ignoring packet.", + packet->iov_len, sizeof(struct iphdr)); + + const union iphdr_union *ip = (const union iphdr_union*) packet->iov_base; + if (ip->ip.version != IPVERSION) + return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), + "IPv4: packet is not IPv4, ignoring packet."); + + size_t iphdrlen = ip->ip.ihl * 4; + if (iphdrlen < sizeof(struct iphdr)) + return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), + "IPv4: IP header size (%zu bytes) smaller than minimum (%zu bytes), ignoring packet.", + iphdrlen, sizeof(struct iphdr)); + + if (packet->iov_len < iphdrlen) + return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), + "IPv4: packet (%zu bytes) smaller than IP header size (%zu bytes), ignoring packet.", + packet->iov_len, iphdrlen); + + size_t totlen = be16toh(ip->ip.tot_len); + if (totlen < iphdrlen) + return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), + "IPv4: packet size (%zu bytes) by IP header is smaller than the IP header size (%zu), ignoring packet.", + totlen, iphdrlen); + if (packet->iov_len < totlen) + return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), + "IPv4: packet (%zu bytes) smaller than expected (%zu) by IP header, ignoring packet.", + packet->iov_len, totlen); + + if (ip->ip.protocol != IPPROTO_UDP) + return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), + "IPv4: not UDP, ignoring packet."); + + if (iphdr_checksum(ip) != 0) + return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), + "IPv4: invalid IP checksum, ignoring packet."); + + /* UDP */ + if (totlen < iphdrlen + sizeof(struct udphdr)) + return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), + "UDP: packet (%zu bytes) smaller than IP header + UDP header, ignoring packet.", + totlen); + + const struct udphdr *udp = (const struct udphdr*) ((const uint8_t*) packet->iov_base + iphdrlen); + size_t udplen = be16toh(udp->len); + if (udplen < sizeof(struct udphdr)) + return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), + "UDP: UDP datagram (%zu bytes) smaller than UDP header (%zu bytes), ignoring packet.", + udplen, sizeof(struct udphdr)); + + if (totlen != iphdrlen + udplen) + return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), + "UDP: packet length by IP header (%zu bytes) does not match with the one by UDP header " + "(IP header %zu bytes + UDP %zu bytes = %zu bytes), ignoring packet.", + totlen, iphdrlen, udplen, iphdrlen + udplen); + + if (be16toh(udp->dest) != port) + return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), + "UDP: to port %u, which is not the expected port (%u), ignoring packet.", + be16toh(udp->dest), port); + + /* Calculate the UDP payload length from the UDP header (udplen), rather than the input packet length + * (len). The packet may contain garbage at the end. */ + struct iovec payload = IOVEC_MAKE( + (const uint8_t*) packet->iov_base + iphdrlen + sizeof(struct udphdr), + udplen - sizeof(struct udphdr)); + if (checksum && udp->check != 0 && + udphdr_checksum(ip->ip.saddr, ip->ip.daddr, udp, + &(struct iovec_wrapper) { + .iovec = &payload, + .count = 1, + }) != 0) + return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), + "UDP: invalid UDP checksum, ignoring packet."); + + if (ret_payload) + *ret_payload = payload; + return 0; +} diff --git a/src/libsystemd-network/ip-util.h b/src/libsystemd-network/ip-util.h index b42ff38af23..fbc602ace59 100644 --- a/src/libsystemd-network/ip-util.h +++ b/src/libsystemd-network/ip-util.h @@ -23,3 +23,9 @@ int udp_packet_build( const struct iovec_wrapper *payload, struct iphdr *ret_iphdr, struct udphdr *ret_udphdr); + +int udp_packet_verify( + const struct iovec *packet, + uint16_t port, + bool checksum, + struct iovec *ret_payload); diff --git a/src/libsystemd-network/test-ip-util.c b/src/libsystemd-network/test-ip-util.c index 58adb8f48ef..cf233ca91c5 100644 --- a/src/libsystemd-network/test-ip-util.c +++ b/src/libsystemd-network/test-ip-util.c @@ -1,6 +1,9 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ +#include "iovec-util.h" +#include "iovec-wrapper.h" #include "ip-util.h" +#include "random-util.h" #include "tests.h" TEST(ip_checksum) { @@ -13,4 +16,158 @@ TEST(ip_checksum) { ASSERT_EQ(ip_checksum(buf, 20), be16toh(0x78ae)); } +static void create_packet(struct iphdr *ip, struct udphdr *udp, struct iovec_wrapper *payload, struct iovec *ret) { + _cleanup_(iovw_done) struct iovec_wrapper iovw = {}; + ASSERT_OK(iovw_put(&iovw, ip, sizeof(struct iphdr))); + ASSERT_OK(iovw_put(&iovw, udp, sizeof(struct udphdr))); + ASSERT_OK(iovw_put_iovw(&iovw, payload)); + ASSERT_OK(iovw_concat(&iovw, ret)); +} + +TEST(udp_packet_build_and_verify) { + size_t n = random_u64_range(20) + 20; + + _cleanup_(iovw_done_free) struct iovec_wrapper payload = {}; + size_t i; + FOREACH_ARGUMENT(i, 1, 0, 1, 1, 3, 1, 2, 1, n, n, n + 1, n + 1, n + 2, n + 3, n + 4, n + 5, n + 6) { + struct iovec tmp = {}; + ASSERT_OK(random_bytes_allocate_iovec(i, &tmp)); + ASSERT_OK(iovw_consume_iov(&payload, &tmp)); + } + + struct iphdr ip; + struct udphdr udp; + ASSERT_OK(udp_packet_build( + /* source_addr= */ htobe32(0xC0020001), + /* source_port= */ 42, + /* destination_addr= */ htobe32(0xC0020002), + /* destination_port= */ 43, + /* ip_service_type= */ 7, + &payload, + &ip, + &udp)); + + _cleanup_(iovec_done) struct iovec joined = {}; + ASSERT_OK(iovw_concat(&payload, &joined)); + + struct iphdr ip2; + struct udphdr udp2; + ASSERT_OK(udp_packet_build( + /* source_addr= */ htobe32(0xC0020001), + /* source_port= */ 42, + /* destination_addr= */ htobe32(0xC0020002), + /* destination_port= */ 43, + /* ip_service_type= */ 7, + &(struct iovec_wrapper) { + .iovec = &joined, + .count = 1, + }, + &ip2, + &udp2)); + + ASSERT_EQ(memcmp(&ip, &ip2, sizeof(struct iphdr)), 0); + ASSERT_EQ(memcmp(&udp, &udp2, sizeof(struct udphdr)), 0); + + _cleanup_(iovec_done) struct iovec packet = {}; + create_packet(&ip, &udp, &payload, &packet); + + struct iovec iov; + ASSERT_OK(udp_packet_verify(&packet, /* port= */ 43, /* checksum= */ false, &iov)); + ASSERT_TRUE(iovec_equal(&iov, &joined)); + ASSERT_OK(udp_packet_verify(&packet, /* port= */ 43, /* checksum= */ true, &iov)); + ASSERT_TRUE(iovec_equal(&iov, &joined)); + + /* UDP port mismatch */ + ASSERT_ERROR(udp_packet_verify(&packet, /* port= */ 42, /* checksum= */ false, /* ret_payload= */ NULL), EBADMSG); + + /* truncated packet */ + ASSERT_ERROR(udp_packet_verify(&IOVEC_MAKE(packet.iov_base, packet.iov_len - 1), + /* port= */ 43, /* checksum= */ false, /* ret_payload= */ NULL), EBADMSG); + + /* bad IP version */ + struct iphdr badip = ip; + badip.version = 6; + iovec_done(&packet); + create_packet(&badip, &udp, &payload, &packet); + ASSERT_ERROR(udp_packet_verify(&packet, /* port= */ 43, /* checksum= */ false, /* ret_payload= */ NULL), EBADMSG); + + /* bad IP header size */ + badip = ip; + badip.ihl = 1; + iovec_done(&packet); + create_packet(&badip, &udp, &payload, &packet); + ASSERT_ERROR(udp_packet_verify(&packet, /* port= */ 43, /* checksum= */ false, /* ret_payload= */ NULL), EBADMSG); + + /* packet size in IP header is smaller than IP header size */ + badip = ip; + badip.tot_len = htobe16(1); + iovec_done(&packet); + create_packet(&badip, &udp, &payload, &packet); + ASSERT_ERROR(udp_packet_verify(&packet, /* port= */ 43, /* checksum= */ false, /* ret_payload= */ NULL), EBADMSG); + + /* packet size in IP header is larger than the packet size */ + badip = ip; + badip.tot_len = htobe16(be16toh(ip.tot_len) + 1); + iovec_done(&packet); + create_packet(&badip, &udp, &payload, &packet); + ASSERT_ERROR(udp_packet_verify(&packet, /* port= */ 43, /* checksum= */ false, /* ret_payload= */ NULL), EBADMSG); + + /* IP protocol mismatch */ + badip = ip; + badip.protocol = IPPROTO_TCP; + iovec_done(&packet); + create_packet(&badip, &udp, &payload, &packet); + ASSERT_ERROR(udp_packet_verify(&packet, /* port= */ 43, /* checksum= */ false, /* ret_payload= */ NULL), EBADMSG); + + /* bad IP header checksum */ + badip = ip; + badip.check = ~ip.check; + iovec_done(&packet); + create_packet(&badip, &udp, &payload, &packet); + ASSERT_ERROR(udp_packet_verify(&packet, /* port= */ 43, /* checksum= */ false, /* ret_payload= */ NULL), EBADMSG); + + /* UDP length is smaller than the UDP header size */ + struct udphdr badudp = udp; + badudp.len = htobe16(1); + iovec_done(&packet); + create_packet(&ip, &badudp, &payload, &packet); + ASSERT_ERROR(udp_packet_verify(&packet, /* port= */ 43, /* checksum= */ false, /* ret_payload= */ NULL), EBADMSG); + + /* UDP length is smaller than the packet size */ + badudp = udp; + badudp.len = htobe16(be16toh(udp.len) - 1); + iovec_done(&packet); + create_packet(&ip, &badudp, &payload, &packet); + ASSERT_ERROR(udp_packet_verify(&packet, /* port= */ 43, /* checksum= */ false, /* ret_payload= */ NULL), EBADMSG); + + /* UDP length is larger than the packet size */ + badudp = udp; + badudp.len = htobe16(be16toh(udp.len) + 1); + iovec_done(&packet); + create_packet(&ip, &badudp, &payload, &packet); + ASSERT_ERROR(udp_packet_verify(&packet, /* port= */ 43, /* checksum= */ false, /* ret_payload= */ NULL), EBADMSG); + + /* bad UDP checksum */ + badudp = udp; + if (udp.check != UINT16_MAX) + badudp.check = ~udp.check; + else + badudp.check = 0xdeadu; + iovec_done(&packet); + create_packet(&ip, &badudp, &payload, &packet); + ASSERT_OK(udp_packet_verify(&packet, /* port= */ 43, /* checksum= */ false, &iov)); + ASSERT_TRUE(iovec_equal(&iov, &joined)); + ASSERT_ERROR(udp_packet_verify(&packet, /* port= */ 43, /* checksum= */ true, /* ret_payload= */ NULL), EBADMSG); + + /* missing UDP checksum */ + badudp = udp; + badudp.check = 0; + iovec_done(&packet); + create_packet(&ip, &badudp, &payload, &packet); + ASSERT_OK(udp_packet_verify(&packet, /* port= */ 43, /* checksum= */ false, &iov)); + ASSERT_TRUE(iovec_equal(&iov, &joined)); + ASSERT_OK(udp_packet_verify(&packet, /* port= */ 43, /* checksum= */ true, &iov)); + ASSERT_TRUE(iovec_equal(&iov, &joined)); +} + DEFINE_TEST_MAIN(LOG_DEBUG);