On 27/02/2014, at 11:04 AM, Theo de Raadt wrote:
> 
> There was a method of converting an in-bound checksum, due to NAT
> conversion, into a new out-bound checksum.  A process is required,
> it's how NAT works.
> 
> A new method of version is being used.  It is mathematically equivelant
> to the old method.

First, I agree with Theo that modifying a checksum is
mathematically equivalent to regenerating it; both give the same
result on ideal hardware.

Of course, we use checksums because our hardware isn't ideal, so
let's look at how the two approaches differ when a router 
fault occurs.

Take Stuart Henderson's example:

> Consider this scenario, which has happened in real life.
> 
> - NIC supports checksum offloading, verified checksum is OK.
> 
> - PCI transfers are broken (in my case it affected multiple
> machines of a certain type, so most likely a motherboard bug),
> causing some corruption in the payload, but the machine won't
> detect them because it doesn't look at checksums itself, just
> trusts the NIC's "rx csum good" flag.
> 
> In this situation, packets which have been NATted that are
> corrupt now get a new checksum that is valid; so the final
> endpoint can not detect the breakage.

That is, when the router offloads and regenerates, the router's
egress NIC will hide any card, stack, bus or memory fault a
verified packet suffered in passing through the router when it
regenerates a new checksum from the now corrupt data.

Looking at the code, the relevant functions are
pf.c:pf_check_proto_cksum(), which trusts the ingress NIC's
checksum good flag, and pf.c:pf_cksum(), which zeros the existing
checksum on that basis and flags it to be regenerated by the
egress NIC[1].

By contrast, checksum modification is far more reliable. In order
to hide payload corruption the update code[1] would have to
modify the checksum to exactly account for it. But that would
have to happen by accident --- by a fault that in effect computes
the necessary change --- as the update code never considers the
payload[0]. It's not impossible but, on the other hand,
checksum regeneration guarantees to hide faults in the
regenerating router. 

We conclude that in the typical offloading case, regenerated
checksums, unlike modified ones, cannot detect faults in the
regenerating routers. 

Whether this difference is significant is a matter 
of judgment and a separate issue.

I've some ideas about solutions but will leave those for 
another email.

best, 
Richard. 

PS. I find the following terminology helpful:

Checksums calculated from the origin data are 'original';
checksums calculated from a copy are 'regenerated'.

Checksums may also be 'modified' to account for altered data in
such a way as to preserve originality for any unaltered data[0].

A checksum is 'end-to-end' if it is delivered original with
respect to the payload. A modified checksum may be end-to-end but
never a regenerated checksum as it is not original.

[0] Strikingly, RFC1631 (1994) and RFC3022 (2001), the NAT RFCs, 
fail to say end-to-end preservation is a property of their checksum 
modification algorithm. I presume it just didn't seem worth 
mentioning as, lacking hardware offload back then, one wouldn't 
regenerate in software on performance grounds alone. It is only 
alluded to in RFC1071 (1988) "Computing the Internet Checksum", 
which states that a checksum remains end-to-end when modified 
'since it was not fully recomputed'. Although that's still true 
if NAT modifies it, NAT makes the meaning of 'end-to-end' 
more complex; I think my above terminology helps there. 

[1]
I'll quote OpenBSD code here for completeness, contrasting 
modification (OpenBSD 5.3) with regeneration (OpenBSD 5.4) 

OpenBSD 5.3 NAT modified the checksum as follows: 

--- pf.c 1.818 (OPENBSD_5_3) --- 

Assuming an AF_INET <-> AF_INET TCP connection. 

pf_test_rule()
3862: pf_translate()
  3881: pf_change_ap()  [ src addr/port ]
     1671: PF_ACPY  [ = pf_addrcpy() ] 
     1689: pf_cksum_fixup(...) 
           [ 
             psuedo code is: 
             sum = fixup(sum, addr16[1]) 
             sum = fixup(sum, addr16[0]) 
             sum = fixup(sum, port) 
           ] 
        1662: l = cksum + old - new <--- checksum modified 
              [ then presumably account for ones-complement carries ] 
  3887: pf_change_ap() etc [ dst addr/port ] 

On subsequent state matching:  

pf_test()
6788: pf_test_state_tcp() [ for TCP ] 
  4566: pf_change_ap() etc [ for src addr/port ] 
  4574: pf_change_ap() etc [ for dst addr/port ]  

---

OpenBSD 5.4 NAT regenerates checksums as follows: 

--- pf.c 1.863 (post OPENBSD_5_4) --- 

Assuming an AF_INET <-> AF_INET TCP connection.

On initial rule match: 
pf_test_rule()
3445: pf_translate()
  3707: pf_change_ap()
     1677: PF_ACPY [= pf_addrcpy()] 
3461: pf_cksum()
  6775: pd->hdr.tcp->th_sum = 0; <--- checksum zeroed
        m->m_pkthdr.csum_flags |= M_TCP_CSUM_OUT <--- flagged for recalculation
        (if orig checksum good) 

On subsequent state matching: 
pf_test_state() 
~4445: pf_change_ap() etc
4471: pf_cksum() etc

--- 

Reply via email to