Direct naar de inhoud
⚡ Kritieke cyberalerts voor jouw sector & systemen — direct in je inbox. Aanmelden →
cybernieuws.nl Cybersecurity en informatiebeveiling nieuws alerts live · 1 min 173 bronnen 2 kritiek 36 vandaag
CVE's Videos Dagbriefing

← Terug naar CVE-database

CVE-2026-79981

MEDIUM · 6.5 CVSS Gepubliceerd: CWE-330

Beschrijving NL

De DNS-stubresolver in subsys/net/lib/dns/resolve.c bindt één UDP-socket per geconfigureerde server wanneer de resolvercontext wordt geïnitialiseerd (dns_resolve_init_locked() -> register_dispatcher() -> dns_dispatcher_register()), waarbij de lokale poort op 0 blijft staan, zodat de stack precies één keer een tijdelijke poort kiest. dns_write() verstuurt vervolgens elke volgende query vanaf dezelfde socket zonder opnieuw te binden, waardoor de bronpoort constant blijft gedurende de levensduur van de resolvercontext, terwijl alleen de 16-bits transactie-identifier (ctx->queries[i].id, verkregen met sys_rand16_get()) per query varieert. RFC 5452 sectie 9.2 vereist dat beide variëren; met een vaste poort hoeft een aanvaller die buiten het pad opereert alleen de identifier te raden. De stabiele poort is gemakkelijk te achterhalen. Elke waargenomen query onthult het permanent, en het kan ook worden gevonden door het efemere UDP-bereik te onderzoeken: subsys/net/ip/connection.c beantwoordt niet-overeenkomende unicast UDP met een ICMP-poort-onbereikbaar, dus de socket van de resolver is de enige poort in dat bereik die stil blijft. De acceptatie van een antwoord wordt uitsluitend bepaald door get_slot_by_id() die de transactie-ID en een CRC-16-hash van de opgevraagde naam vergelijkt. Het bronadres van het antwoord wordt nooit gecontroleerd (dispatcher_cb() geeft ARG_UNUSED(addr)), dus vervalste antwoorden hoeven niet eens te worden gespoofd om te lijken alsof ze van de geconfigureerde server afkomstig zijn; ze hoeven alleen de poort van de resolver te bereiken. Een aanvaller met UDP-bereikbaarheid naar het apparaat (hetzelfde LAN-, Wi-Fi- of meshsegment, of een wereldwijd routerbaar apparaatadres) kan daarom tijdens een queryvenster antwoorden op een voorspelbare querynaam in de 16-bits identificatieruimte overspoelen en de legitieme server racen. Een overwinning zorgt ervoor dat de applicatie een door de aanvaller gekozen adres oplost, en met CONFIG_DNS_RESOLVER_CACHE ingeschakeld wordt het vervalste record behouden voor de door de aanvaller gekozen TTL. De aanval vereist nog steeds het winnen van die race, en applicaties die TLS-certificaten valideren voor het opgeloste eindpunt zijn grotendeels beschermd. De oplossing trekt vóór elke zoekopdracht een nieuwe lokale poort naar een server die niets uitstaande heeft: dns_randomize_source_port() verwijdert de dispatcher, sluit en heropent de socket en registreert deze opnieuw gebonden aan poort 0, zodat het systeem een nieuwe poort selecteert, waardoor de gokruimte per zoekopdracht wordt verhoogd van ongeveer 16 naar ongeveer 31 bits. Multicast DNS en LLMNR zijn uitgesloten omdat hun poorten protocolvast zijn. Het gedrag wordt gecontroleerd door CONFIG_DNS_RESOLVER_RANDOMIZE_SOURCE_PORT (standaard y).

Origineel (Engels) tonen

The DNS stub resolver in subsys/net/lib/dns/resolve.c binds one UDP socket per configured server when the resolver context is initialised (dns_resolve_init_locked() -> register_dispatcher() -> dns_dispatcher_register()), with the local port left at 0 so the stack picks an ephemeral port exactly once. dns_write() then sends every subsequent query from that same socket without re-binding, so the source port is constant for the lifetime of the resolver context while only the 16-bit transaction identifier (ctx->queries[i].id, drawn with sys_rand16_get()) varies per query. RFC 5452 section 9.2 requires both to vary; with a fixed port, an off-path attacker has to guess only the identifier.

The stable port is cheap to learn. Any single observed query reveals it permanently, and it can also be found by probing the ephemeral UDP range: subsys/net/ip/connection.c answers unmatched unicast UDP with an ICMP port-unreachable, so the resolver's socket is the one port in that range that stays silent. Acceptance of a reply is decided solely by get_slot_by_id() matching the transaction identifier and a CRC-16 of the queried name — the reply's source address is never checked (dispatcher_cb() does ARG_UNUSED(addr)), so forged answers need not even be spoofed to appear to come from the configured server; they only need to reach the resolver's port.

An attacker with UDP reachability to the device (same LAN, Wi-Fi or mesh segment, or a globally routable device address) can therefore flood answers for a predictable query name across the 16-bit identifier space during a query window and race the legitimate server. A win makes the application resolve an attacker-chosen address, and with CONFIG_DNS_RESOLVER_CACHE enabled the forged record is retained for its attacker-chosen TTL. The attack still requires winning that race, and applications that validate TLS certificates for the resolved endpoint are largely protected.

The fix draws a fresh local port before each query to a server that has nothing outstanding: dns_randomize_source_port() unregisters the dispatcher, closes and reopens the socket, and re-registers it bound to port 0 so the system selects a new port, raising the per-query guessing space from roughly 16 to roughly 31 bits. Multicast DNS and LLMNR are excluded because their ports are protocol-fixed. The behaviour is controlled by CONFIG_DNS_RESOLVER_RANDOMIZE_SOURCE_PORT (default y).

References