Closed Bug 2067654 Opened 8 days ago Closed 6 days ago

HE3 only ever sees first address of a multi-address DNS answer

Categories

(Core :: Networking: HTTP, defect, P2)

defect

Tracking

()

RESOLVED FIXED
157 Branch
Tracking Status
firefox157 --- fixed

People

(Reporter: leggert, Assigned: mail)

References

(Blocks 1 open bug)

Details

(Whiteboard: [necko-triaged])

Attachments

(1 file)

[Claude found this while I had it looking at some other things. Filing it here in case this is real.]

netwerk/protocol/http/happy_eyeballs_glue/src/lib.rs:750 declares the opaque handle for mozilla::net::NetAddr as a zero-sized type:

#[repr(C)]
pub union NetAddr {
    _private: [u8; 0],
}

size_of::<NetAddr>() == 0. This is fine for the *const NetAddr parameters it was modelled on (see neqo_glue/src/lib.rs:161, which uses it correctly), but this crate also uses it as a collection element type: ThinVec<NetAddr>, which cbindgen maps to const nsTArray<NetAddr>* on the C++ side, where NetAddr resolves to the real 112-byte union.

ThinVec::len() and the slice base pointer are both correct, so the loops run the right number of times over the right buffer. But a slice iterator over a zero-sized element type cannot advance a stride-0 pointer -- core::slice::Iter::next decrements a length counter and returns self.ptr unchanged for ZSTs. Every iteration therefore passes the address of element 0 to moz_netaddr_get_family / moz_netaddr_get_network_order_ip / moz_netaddr_get_ipv6.

An N-address answer is converted into N copies of addresses[0].

Affected loops:

  • process_dns_response_a -- lib.rs:299-308
  • process_dns_response_aaaa -- lib.rs:330-341
  • process_dns_response_https, ipv4_hints and ipv6_hints -- lib.rs:381-408

Impact

Happy Eyeballs receives a degenerate address list, so connection racing and fallback within an address family are defeated: it retries one IP instead of trying the others. A host whose first A record is unreachable will not fall back to its remaining A records via this path. SVCB/HTTPS-RR IP hints are affected the same way.

network.http.happy_eyeballs_enabled defaults to true on desktop on all channels (StaticPrefList.yaml:17058), so this is enabled by default in release.

The debug_assert!/debug_assert_eq! family checks in these loops do not fire, because element 0 has the expected family -- which is why debug and CI builds have not caught it.

Suggested fix

Do not encode sizeof(NetAddr) in Rust -- the local arm of the union is #if defined(XP_UNIX) || defined(XP_WIN), so the size is platform-dependent. Keep the type opaque and move the indexing to C++, e.g.:

extern "C" size_t moz_netaddr_array_length(const nsTArray<NetAddr>* aArr);
extern "C" const NetAddr* moz_netaddr_array_at(const nsTArray<NetAddr>* aArr, size_t aIndex);

replacing the ThinVec<NetAddr> parameters and the ipv4_hints/ipv6_hints members with an opaque array handle. Passing (*const NetAddr, usize) plus a stride obtained from C++ would also work.

Worth adding alongside the fix: a compile-time guard so an opaque FFI type can never again be used as a ThinVec/slice element, and a test that exercises a hostname with more than one A record.

Summary: He3 only ever sees first address of a multi-address DNS answer → HE3 only ever sees first address of a multi-address DNS answer
Assignee: nobody → mail
Status: NEW → ASSIGNED
Priority: -- → P2
Whiteboard: [necko-triaged]

Well spotted @Lars. The fact that I missed this thus far is puzzling to me. Thank you.

The glue declared NetAddr as an opaque zero-sized union and held it in a
ThinVec<NetAddr>. C++ passed full-size elements, so Rust walked the array with
a stride of 0: every slot read the first address. The engine then dropped the
duplicates as already-attempted endpoints, leaving one connection attempt and
no failover.

An opaque type is safe behind a pointer but not as a collection element, so
pass the IpAddr that cbindgen already generates for the reverse direction.

Severity: S3 → S2
Pushed by mail@max-inden.de: https://github.com/mozilla-firefox/firefox/commit/9235aa7dd1d6 https://hg.mozilla.org/integration/autoland/rev/bd7475f1c05c Fix Happy Eyeballs only trying the first DNS address. r=kershaw,necko-reviewers
Status: ASSIGNED → RESOLVED
Closed: 6 days ago
Resolution: --- → FIXED
Target Milestone: --- → 157 Branch
You need to log in before you can comment on or make changes to this bug.

Attachment

General

Creator:
Created:
Updated:
Size: