I found a portability bug in haproxy's CRC32 implementation, while
adding adding CRC32 support to Postfix's haproxy2 code. The haproxy
code produces incorrect results on ix86. Which just happens to work
as long as everyone is using ix86.

The same bug appears to exist in other hash functions in
haproxy/src/hash.c.

My proposed solution: introduce a NEW type of TLV for checksums
that are generated with a more portable CRC32 implementation.

Details
=======

Haproxy CRC32 support adds a checksum to the haproxy protocol header.
This protects against random bitflips that happen before a proxy
header is written to a sender's TCP stack, and after a proxy header
is read from a receiver's TCP stack (during transport over TCP, the
proxy header is always protected by the TCP checksum).

Here's the code from haproxy/src/hash.c:

    unsigned int hash_crc32(const char *key, int len)
    {
        unsigned int hash;
        ...
        hash = ~0;
        while (len--) {
            hash ^= *key++;
            ...
        }
        ...
    }

The bug is that 'key' should be 'const unsigned char *'. Otherwise,
the result depends on the hardware and/or compiler.

Below is a small test program to illustrate these differences.

* On systems where 'char' is a signed type, *key is a signed
  8-bit value in the range -128..+127. In the code above, this is
  converted to int -128..+127, then converted to unsigned, where
  the negative integers become very large positive numbers.

  Example: the 8-bit value 0xff becomes the 32-bit value 0xffffffff.
  This breaks the CRC32 computation, due to non-portable programming.
  See test program below.

* On systems where 'char' is an unsigned type. *key is an unsigned
  8-bit value in the range 0..255. In the code above, this is
  converted to int 0..255, then converned to unsigned 0..255.

  Example: the 8-bit value 0xff becomes the 32-bit value 0xff. This
  does not break the CRC32 computation, but that is just the result
  of dumb luck. Code that is portable would always work this way.

As luck has it, with ix86 systems, 'char' is a signed type. Therefore
the CRC32 computation is screwed up (both i386 and x86_64 a.k.a. amd64).

I wrote a small test program (not included) that compares the result
from haproxy's hash_crc32() implementation and some other crc32
implementations that I found on the web, plus an implementation
that I cobbled together. All portable implementations agree on the
result. hash_crc32() only some of the time agrees with the portable
implementations.

This makes Postfix support for haproxy CRC checks pointless.  It
would have to compute two versions of CRC32: the correct one and
the one that is broken, otherwise CRC32 would only work between
similar systems.

        Wietse

#include <stdlib.h>
#include <stdio.h>

int     main(void)
{
    {
        char    c = 0xff;
        unsigned i = c;

        printf("default char 0xff -> unsigned int 0x%x\n", i);
    }
    {
        unsigned char c = 0xff;
        unsigned i = c;

        printf("unsigned char 0xff -> unsigned int 0x%x\n", i);
    }
    {
        signed char c = 0xff;
        unsigned i = c;

        printf("signed char 0xff -> unsigned int 0x%x\n", i);
    }
    exit (0);
}

Reply via email to