ARM: fix csum_tcpudp_magic() miscompilation
authorRussell King <rmk+kernel@arm.linux.org.uk>
Thu, 12 Dec 2013 22:49:14 +0000 (22:49 +0000)
committerRussell King <rmk+kernel@arm.linux.org.uk>
Sun, 29 Dec 2013 12:32:41 +0000 (12:32 +0000)
There is a miscompilation of csum_tcpudp_magic() due to the way we pass
the asm() operands in.  Fortunately, this doesn't affect the IP code,
but can affect anyone who passes ntohs(udp->len) as the length argument,
or protocols with more than 8 bits.

The problem stems from passing 16-bit operands into an asm() - GCC makes
no guarantees about what may be in the high 16-bits of such a register
passed into assembly which is in the "HI" machine mode.

Address this by changing the way we handle the 16-bit arguments - since
accumulating the protocol and length can never overflow, we can delegate
this to the compiler to perform, and then accumulate it into the
checksum inside the asm(), taking account of the endian-ness via an
appropriate 32-bit rotation.

While we are here, also realise that there's a chance to optimise this
a little: several callers from IP code pass a constant zero as the
initial sum.  This is wasteful - if we detect this condition, we can
optimise away one instruction.

Tested-by: Maxime Bizon <mbizon@freebox.fr>
Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk>
arch/arm/include/asm/checksum.h

index 6dcc164308688c8efdf7f688f239169537470da4..523315115478960a2e433a66276fea007b120cda 100644 (file)
@@ -87,19 +87,33 @@ static inline __wsum
 csum_tcpudp_nofold(__be32 saddr, __be32 daddr, unsigned short len,
                   unsigned short proto, __wsum sum)
 {
-       __asm__(
-       "adds   %0, %1, %2              @ csum_tcpudp_nofold    \n\
-       adcs    %0, %0, %3                                      \n"
+       u32 lenprot = len | proto << 16;
+       if (__builtin_constant_p(sum) && sum == 0) {
+               __asm__(
+               "adds   %0, %1, %2      @ csum_tcpudp_nofold0   \n\t"
 #ifdef __ARMEB__
-       "adcs   %0, %0, %4                                      \n"
+               "adcs   %0, %0, %3                              \n\t"
 #else
-       "adcs   %0, %0, %4, lsl #8                              \n"
+               "adcs   %0, %0, %3, ror #8                      \n\t"
 #endif
-       "adcs   %0, %0, %5                                      \n\
-       adc     %0, %0, #0"
-       : "=&r"(sum)
-       : "r" (sum), "r" (daddr), "r" (saddr), "r" (len), "Ir" (htons(proto))
-       : "cc");
+               "adc    %0, %0, #0"
+               : "=&r" (sum)
+               : "r" (daddr), "r" (saddr), "r" (lenprot)
+               : "cc");
+       } else {
+               __asm__(
+               "adds   %0, %1, %2      @ csum_tcpudp_nofold    \n\t"
+               "adcs   %0, %0, %3                              \n\t"
+#ifdef __ARMEB__
+               "adcs   %0, %0, %4                              \n\t"
+#else
+               "adcs   %0, %0, %4, ror #8                      \n\t"
+#endif
+               "adc    %0, %0, #0"
+               : "=&r"(sum)
+               : "r" (sum), "r" (daddr), "r" (saddr), "r" (lenprot)
+               : "cc");
+       }
        return sum;
 }      
 /*