NetKubeLab ไทย

Error detection — a checksum that is weak on purpose

The internet checksum cannot detect some kinds of corruption at all — not because of a bug, but because of a property a 1978 design document lists as an advantage

Earlier articles have mentioned checksums four times already — Ethernet has a check field at the end of the frame, IP has a checksum covering only its header, TCP and UDP have one covering the whole segment, and the layers article used TCP's checksum as proof that the layers leak.

None of them answered the question that should come first — what does it actually catch, and what does it miss?

The short answer is that the internet checksum is far weaker than people assume. There are kinds of corruption it cannot detect at all — not sometimes, but never, every single time — and that is not a bug. It follows from properties a 1978 design document lists as advantages.

This article proves it by running two hundred thousand trials.

If you have never looked, start here

The internet checksum can be computed in a few lines with no library at all.

$ python3 -c "
import struct
def ck(b):
    if len(b)%2: b += b'\0'
    s = sum(struct.unpack('!%dH' % (len(b)//2), b))
    while s >> 16: s = (s & 0xffff) + (s >> 16)
    return (~s) & 0xffff
print(hex(ck(b'HELLO WORLD!')))"

That gives 0x2e91 — take the bytes two at a time, add them all, fold the carry back in, invert every bit. Done.

That simplicity is both its strength and its weakness.

Corruption comes in several shapes, not one

When people say data got corrupted they usually picture a single flipped bit. On a real wire, damage comes in several shapes, and each one challenges a detection scheme differently.

  single-bit    one bit flips
  burst         several adjacent bits fail together, from brief noise
  reorder       data arrives in a different order
  duplicate     the same piece arrives twice
  truncate      data is cut short

A method that is good against one shape can be useless against another, which is the whole point of this article.

Parity — the simplest, and the easiest to fool

The oldest method counts the one-bits and adds one more bit to make the total even. The receiver counts again; if it is not even, something broke.

A single flipped bit turns the count of one-bits from even to odd so parity catches it, while two flipped bits return the count to even so parity misses it

Its limit follows straight from the definition — it only catches an odd number of flipped bits. Flip two and the total is even again, and parity reports everything is fine.

I ran two hundred thousand trials.

  flip 1 bit    caught every time
  flip 2 bits   never caught, not once
  flip 3 bits   caught every time
  flip 4 bits   never caught, not once

Not 99% or 1%, but 0% and 100% alternating with odd and even — perfectly predictable behaviour, and very bad if real damage tends to arrive in bursts.

The internet checksum

RFC 1071 of September 1988, Computing the Internet Checksum, describes the method and the reasoning behind it. It uses one's complement addition, which differs from ordinary addition in that the carry is folded back into the lowest bit.

The document explains why this was chosen over XOR.

"One's complement arithmetic is better than two's complement because it is equally sensitive to errors in all bit positions."

"It is just this property that makes some sort of addition preferable to a simple exclusive-OR which is frequently used but permits an even number of drops (pick ups) in any bit channel."

XOR has parity's problem: an even number of errors cancels itself out. Addition fixes that, at the cost of a new problem.

Four properties, of which the first is the weakness

The appendix of RFC 1071 is an older document, IEN 45, dated 5 June 1978 and written by William W. Plummer, listing four properties the addition operator must have. The first is:

"(P1) + is commutative. Thus, the order in which the 16-bit bytes are 'added' together is unimportant."

Read that again slowly. The order of the words does not matter — written as an advantage, because it lets an implementation walk memory in either direction and compute quickly on a variety of machines.

But the same sentence means something else too: if order does not matter to the computation, then reordering cannot be detected.

The others follow the same reasoning.

  P2   an identity element, so the sender zeroes the field then computes
  P3   an inverse, so the receiver can compute and expect zero
  P4   associativity, so the checksum field can sit anywhere

Together the four make the checksum fast and flexible — and blind in several places at once.

The experiment: what is caught, what is not

I took one sentence and damaged it four ways, then measured whether each method noticed. ok means caught, MISS means missed.

A table comparing parity, checksum and CRC-32 against four kinds of corruption, showing that only CRC catches a swap of two 16-bit words

  corruption                 parity  checksum  CRC-32
  flip 1 bit                 ok      ok        ok
  flip 2 bits in one byte    MISS    ok        ok
  swap two 16-bit words      MISS    MISS      ok
  swap word 1 and word 5     MISS    MISS      ok

The bottom two rows are P1 in action.

Think about what that means in practice. If the message is a payment instruction with the account number and the amount in different words, swapping those two words changes everything — and the checksum reports it as fine, every time.

The 3.1% that is not a coincidence

Two hundred thousand trials on random 64-byte messages, counting only the cases that went undetected.

  corruption       parity            checksum         CRC-32
  flip 1 bit             0 (  0.0%)       0 (  0.0%)        0
  flip 2 bits      200,000 (100.0%)   6,282 (  3.1%)        0
  flip 3 bits            0 (  0.0%)     590 (  0.3%)        0
  flip 4 bits      200,000 (100.0%)     651 (  0.3%)        0
  swap two words   199,995 (100.0%) 199,995 (100.0%)        0

The 3.1% on the two-bit row is the interesting one, because if damage were purely random a 16-bit checksum should miss about 1 in 65,536, or 0.0015%. What was measured is two thousand times worse than that.

I chased the mechanism, and it explains the number completely.

Two bits at the same position in two different words, one flipping from zero to one and adding value while the other flips from one to zero and subtracts the same amount, leaving the total unchanged

If the two flipped bits sit at the same position within their 16-bit words and flip in opposite directions, one adds 2^k and the other subtracts exactly 2^k. The total is unchanged, so the checksum is unchanged.

The odds work out directly.

  1 in 16    same bit position
  1 in 2     opposite directions
  1 in 32    together, which is 3.1%

And counting from the trial data: the number of cases meeting that condition was 6,117, and the number of checksum misses was 6,117 exactly. Not close — identical, case for case.

CRC — stronger, and living one layer down

Ethernet does not use an additive checksum. It uses CRC-32, computed as polynomial division in binary rather than addition, so it has no commutative property to exploit.

In the same trials, CRC-32 caught every case — one million runs without a single miss.

A diagram showing CRC-32 at the end of the Ethernet frame covering the whole frame, while the IP checksum covers only the header and TCP's covers header and data

That the layers chose different methods is not inconsistency; it is a different trade.

  Ethernet   CRC-32       whole frame   done in hardware, costs no CPU
  IP         16-bit sum   header only   routers recompute at every hop
  TCP        16-bit sum   header+data   done in software, must be fast

Why accept something weaker higher up

The answer lives in properties P2 and P4 above — because routers have to edit the header at every hop.

Every time a packet passes a router, TTL drops by one, which means the IP checksum must be recomputed. With CRC-32 the router would have to read the whole packet and start over each time; with this kind of addition it can adjust only the part that changed and never touch the other bytes.

That is a trade of detection strength against speed of editing in transit, decided back when a router was a PDP that did arithmetic slowly. IEN 45 even lists memory cycles per word in the document.

RFC 1071 states one more property that explains why it had to be this shape.

"(P6) Adding the checksum to a packet does not change the information bytes."

"This property allows intermediate computers such as gateway machines to act on fields (i.e., the Internet Destination Address) without having to first decode the packet."

A router can read the destination address immediately without decoding anything first — something certain error-correcting codes cannot offer.

Two zeros, and a bug that lived in an RFC for four years

One's complement arithmetic has an oddity: it has two zeros.

  +0   0x0000
  -0   0xFFFF

Both represent zero, and that difference produced a bug that sat in a standards document for four years.

RFC 1141 of 1990 offered a shortcut letting routers adjust the checksum without recomputing it entirely. The formula works in almost every case, except when the result is zero. RFC 1624 of May 1994 corrected it and explained why:

"RFC 1141 yields an updated header checksum of -0 when it should be +0. This is because it assumed that one's complement has a distributive property, which does not hold when the result is 0"

A formula that looks right is wrong only when the result is zero, and nobody noticed for four years because that case is rare. That is the signature of an arithmetic bug — it does not fail constantly. It waits.

When it lies

"The checksum passed, so the data is correct." No. It means the data was not damaged in a way this method can see, which is a different statement.

"A 16-bit checksum misses 1 in 65,536." True only if damage is purely random. Real damage has structure, and the measured figure above is 3.1% in one case.

"Ethernet already checked it, so the upper layers need not." Ethernet's CRC covers one cable's worth of travel. At the router the frame is unwrapped and rebuilt with a fresh CRC, so corruption that happens inside the router's memory gets a new, perfectly valid CRC for the now-wrong data. This is exactly why an end-to-end checksum has to exist as well.

"CRC is a form of encryption." Not remotely. Anyone can recompute a CRC. It detects accidents, not intent. To stop someone deliberately editing data you need signatures, as the TLS article describes.

"UDP's checksum can be turned off, it's fine." On IPv4 it can. On IPv6 it cannot, because IPv6 removed the header checksum entirely — if the transport layer does not check, nothing does.

Real cases from real work

Case 1 — data is corrupted while everything reports healthy

Situation Files copied over the network are occasionally corrupt, but no error counter anywhere shows anything.

How to read it Switch counters only count frames whose CRC failed. If the damage happens inside an intermediate device's memory rather than on the wire, the frame gets its CRC computed after it was already wrong, so the counters stay clean — and TCP's checksum has the miss rate shown above. The only way to prove it is to compare a hash of the file at both ends.

What this does not prove Different hashes say the data is wrong; they do not say where it went wrong. It could be disk, memory, or network. Each has to be tested separately.

Case 2 — choosing a method for your own work

Situation You are designing a protocol for an embedded device.

How to read it Ask two questions before choosing.

  1  what shape of damage do you expect
  2  does anything need to edit the data in transit

If nothing edits in transit, choose a CRC: much stronger, and most hardware computes it for free. If an intermediate device must edit some field, a 16-bit sum may be worth it for the same reason IP chose one.

What this does not prove The numbers here come from corruption I generated randomly. Real damage on a wire tends to arrive in adjacent bursts, which is exactly what CRCs are designed for — and this article has not tested that case.

Case 3 — proving the blindness to somebody

Situation You need to explain to a team why another hash layer is needed.

Command Let them run it. Ten seconds.

$ python3 -c "
import struct
def ck(b):
    s = sum(struct.unpack('!%dH' % (len(b)//2), b))
    while s >> 16: s = (s & 0xffff) + (s >> 16)
    return (~s) & 0xffff
a = b'AABB'
b = b'BBAA'
print(a, hex(ck(a)))
print(b, hex(ck(b)))"

Real output

  b'AABB' 0x7c7c
  b'BBAA' 0x7c7c

How to read it Different data, identical checksum, because P1 said the order does not matter. Seeing it with your own eyes ends the argument faster than explaining it.

What this does not prove The example shows it is possible; it says nothing about how often it happens on a real network. Something has to cause words to be swapped, and that is far rarer than a flipped bit.

When detection is not enough

Detection tells you only that something broke; it neither locates nor repairs it. There are two ways forward — ask for a retransmission, which is what TCP does, or add enough redundancy that the receiver can repair it, which is what satellite links and ECC memory do.

The first is much cheaper when retransmission is fast. The second wins when retransmission is expensive or too slow. And neither helps at all if the other end is deliberately editing the data.

References

Standards

  • RFC 1071, September 1988, Computing the Internet Checksum — the method, and an appendix reproducing IEN 45 of 1978 which lists properties P1 through P6 quoted here
  • RFC 1141, January 1990, the shortcut for adjusting a checksum, with the flaw
  • RFC 1624, May 1994, which fixed it, with the explanation about the two zeros
  • RFC 9293, the requirement that TCP's checksum can never be turned off

อ่านหน้านี้เป็นภาษาไทย

← Back to the basics