ip_options_compile: properly handle unaligned pointer
Chris Metcalf [Sun, 29 May 2011 10:55:44 +0000 (10:55 +0000)]
The current code takes an unaligned pointer and does htonl() on it to
make it big-endian, then does a memcpy().  The problem is that the
compiler decides that since the pointer is to a __be32, it is legal
to optimize the copy into a processor word store.  However, on an
architecture that does not handled unaligned writes in kernel space,
this produces an unaligned exception fault.

The solution is to track the pointer as a "char *" (which removes a bunch
of unpleasant casts in any case), and then just use put_unaligned_be32()
to write the value to memory.

Signed-off-by: Chris Metcalf <cmetcalf@tilera.com>
Signed-off-by: David S. Miller <davem@zippy.davemloft.net>

net/ipv4/ip_options.c

index c3118e1..ec93335 100644 (file)
@@ -14,6 +14,7 @@
 #include <linux/slab.h>
 #include <linux/types.h>
 #include <asm/uaccess.h>
+#include <asm/unaligned.h>
 #include <linux/skbuff.h>
 #include <linux/ip.h>
 #include <linux/icmp.h>
@@ -350,7 +351,7 @@ int ip_options_compile(struct net *net,
                                goto error;
                        }
                        if (optptr[2] <= optlen) {
-                               __be32 *timeptr = NULL;
+                               unsigned char *timeptr = NULL;
                                if (optptr[2]+3 > optptr[1]) {
                                        pp_ptr = optptr + 2;
                                        goto error;
@@ -359,7 +360,7 @@ int ip_options_compile(struct net *net,
                                      case IPOPT_TS_TSONLY:
                                        opt->ts = optptr - iph;
                                        if (skb)
-                                               timeptr = (__be32*)&optptr[optptr[2]-1];
+                                               timeptr = &optptr[optptr[2]-1];
                                        opt->ts_needtime = 1;
                                        optptr[2] += 4;
                                        break;
@@ -371,7 +372,7 @@ int ip_options_compile(struct net *net,
                                        opt->ts = optptr - iph;
                                        if (rt)  {
                                                memcpy(&optptr[optptr[2]-1], &rt->rt_spec_dst, 4);
-                                               timeptr = (__be32*)&optptr[optptr[2]+3];
+                                               timeptr = &optptr[optptr[2]+3];
                                        }
                                        opt->ts_needaddr = 1;
                                        opt->ts_needtime = 1;
@@ -389,7 +390,7 @@ int ip_options_compile(struct net *net,
                                                if (inet_addr_type(net, addr) == RTN_UNICAST)
                                                        break;
                                                if (skb)
-                                                       timeptr = (__be32*)&optptr[optptr[2]+3];
+                                                       timeptr = &optptr[optptr[2]+3];
                                        }
                                        opt->ts_needtime = 1;
                                        optptr[2] += 8;
@@ -403,10 +404,10 @@ int ip_options_compile(struct net *net,
                                }
                                if (timeptr) {
                                        struct timespec tv;
-                                       __be32  midtime;
+                                       u32  midtime;
                                        getnstimeofday(&tv);
-                                       midtime = htonl((tv.tv_sec % 86400) * MSEC_PER_SEC + tv.tv_nsec / NSEC_PER_MSEC);
-                                       memcpy(timeptr, &midtime, sizeof(__be32));
+                                       midtime = (tv.tv_sec % 86400) * MSEC_PER_SEC + tv.tv_nsec / NSEC_PER_MSEC;
+                                       put_unaligned_be32(midtime, timeptr);
                                        opt->is_changed = 1;
                                }
                        } else {