Merge branch 'for_3.8-rc1' into v4l_for_linus
[deliverable/linux.git] / net / ipv4 / inet_diag.c
index 535584c00f9118fe33a17e79b858e66935f424f9..e23e16dc501d5458632df5e884149f89b30f9e28 100644 (file)
@@ -44,6 +44,10 @@ struct inet_diag_entry {
        u16 dport;
        u16 family;
        u16 userlocks;
+#if IS_ENABLED(CONFIG_IPV6)
+       struct in6_addr saddr_storage;  /* for IPv4-mapped-IPv6 addresses */
+       struct in6_addr daddr_storage;  /* for IPv4-mapped-IPv6 addresses */
+#endif
 };
 
 static DEFINE_MUTEX(inet_diag_table_mutex);
@@ -428,25 +432,31 @@ static int inet_diag_bc_run(const struct nlattr *_bc,
                                break;
                        }
 
-                       if (cond->prefix_len == 0)
-                               break;
-
                        if (op->code == INET_DIAG_BC_S_COND)
                                addr = entry->saddr;
                        else
                                addr = entry->daddr;
 
+                       if (cond->family != AF_UNSPEC &&
+                           cond->family != entry->family) {
+                               if (entry->family == AF_INET6 &&
+                                   cond->family == AF_INET) {
+                                       if (addr[0] == 0 && addr[1] == 0 &&
+                                           addr[2] == htonl(0xffff) &&
+                                           bitstring_match(addr + 3,
+                                                           cond->addr,
+                                                           cond->prefix_len))
+                                               break;
+                               }
+                               yes = 0;
+                               break;
+                       }
+
+                       if (cond->prefix_len == 0)
+                               break;
                        if (bitstring_match(addr, cond->addr,
                                            cond->prefix_len))
                                break;
-                       if (entry->family == AF_INET6 &&
-                           cond->family == AF_INET) {
-                               if (addr[0] == 0 && addr[1] == 0 &&
-                                   addr[2] == htonl(0xffff) &&
-                                   bitstring_match(addr + 3, cond->addr,
-                                                   cond->prefix_len))
-                                       break;
-                       }
                        yes = 0;
                        break;
                }
@@ -509,6 +519,55 @@ static int valid_cc(const void *bc, int len, int cc)
        return 0;
 }
 
+/* Validate an inet_diag_hostcond. */
+static bool valid_hostcond(const struct inet_diag_bc_op *op, int len,
+                          int *min_len)
+{
+       int addr_len;
+       struct inet_diag_hostcond *cond;
+
+       /* Check hostcond space. */
+       *min_len += sizeof(struct inet_diag_hostcond);
+       if (len < *min_len)
+               return false;
+       cond = (struct inet_diag_hostcond *)(op + 1);
+
+       /* Check address family and address length. */
+       switch (cond->family) {
+       case AF_UNSPEC:
+               addr_len = 0;
+               break;
+       case AF_INET:
+               addr_len = sizeof(struct in_addr);
+               break;
+       case AF_INET6:
+               addr_len = sizeof(struct in6_addr);
+               break;
+       default:
+               return false;
+       }
+       *min_len += addr_len;
+       if (len < *min_len)
+               return false;
+
+       /* Check prefix length (in bits) vs address length (in bytes). */
+       if (cond->prefix_len > 8 * addr_len)
+               return false;
+
+       return true;
+}
+
+/* Validate a port comparison operator. */
+static inline bool valid_port_comparison(const struct inet_diag_bc_op *op,
+                                        int len, int *min_len)
+{
+       /* Port comparisons put the port in a follow-on inet_diag_bc_op. */
+       *min_len += sizeof(struct inet_diag_bc_op);
+       if (len < *min_len)
+               return false;
+       return true;
+}
+
 static int inet_diag_bc_audit(const void *bytecode, int bytecode_len)
 {
        const void *bc = bytecode;
@@ -516,29 +575,39 @@ static int inet_diag_bc_audit(const void *bytecode, int bytecode_len)
 
        while (len > 0) {
                const struct inet_diag_bc_op *op = bc;
+               int min_len = sizeof(struct inet_diag_bc_op);
 
 //printk("BC: %d %d %d {%d} / %d\n", op->code, op->yes, op->no, op[1].no, len);
                switch (op->code) {
-               case INET_DIAG_BC_AUTO:
                case INET_DIAG_BC_S_COND:
                case INET_DIAG_BC_D_COND:
+                       if (!valid_hostcond(bc, len, &min_len))
+                               return -EINVAL;
+                       break;
                case INET_DIAG_BC_S_GE:
                case INET_DIAG_BC_S_LE:
                case INET_DIAG_BC_D_GE:
                case INET_DIAG_BC_D_LE:
-               case INET_DIAG_BC_JMP:
-                       if (op->no < 4 || op->no > len + 4 || op->no & 3)
-                               return -EINVAL;
-                       if (op->no < len &&
-                           !valid_cc(bytecode, bytecode_len, len - op->no))
+                       if (!valid_port_comparison(bc, len, &min_len))
                                return -EINVAL;
                        break;
+               case INET_DIAG_BC_AUTO:
+               case INET_DIAG_BC_JMP:
                case INET_DIAG_BC_NOP:
                        break;
                default:
                        return -EINVAL;
                }
-               if (op->yes < 4 || op->yes > len + 4 || op->yes & 3)
+
+               if (op->code != INET_DIAG_BC_NOP) {
+                       if (op->no < min_len || op->no > len + 4 || op->no & 3)
+                               return -EINVAL;
+                       if (op->no < len &&
+                           !valid_cc(bytecode, bytecode_len, len - op->no))
+                               return -EINVAL;
+               }
+
+               if (op->yes < min_len || op->yes > len + 4 || op->yes & 3)
                        return -EINVAL;
                bc  += op->yes;
                len -= op->yes;
@@ -596,6 +665,36 @@ static int inet_twsk_diag_dump(struct inet_timewait_sock *tw,
                                   cb->nlh->nlmsg_seq, NLM_F_MULTI, cb->nlh);
 }
 
+/* Get the IPv4, IPv6, or IPv4-mapped-IPv6 local and remote addresses
+ * from a request_sock. For IPv4-mapped-IPv6 we must map IPv4 to IPv6.
+ */
+static inline void inet_diag_req_addrs(const struct sock *sk,
+                                      const struct request_sock *req,
+                                      struct inet_diag_entry *entry)
+{
+       struct inet_request_sock *ireq = inet_rsk(req);
+
+#if IS_ENABLED(CONFIG_IPV6)
+       if (sk->sk_family == AF_INET6) {
+               if (req->rsk_ops->family == AF_INET6) {
+                       entry->saddr = inet6_rsk(req)->loc_addr.s6_addr32;
+                       entry->daddr = inet6_rsk(req)->rmt_addr.s6_addr32;
+               } else if (req->rsk_ops->family == AF_INET) {
+                       ipv6_addr_set_v4mapped(ireq->loc_addr,
+                                              &entry->saddr_storage);
+                       ipv6_addr_set_v4mapped(ireq->rmt_addr,
+                                              &entry->daddr_storage);
+                       entry->saddr = entry->saddr_storage.s6_addr32;
+                       entry->daddr = entry->daddr_storage.s6_addr32;
+               }
+       } else
+#endif
+       {
+               entry->saddr = &ireq->loc_addr;
+               entry->daddr = &ireq->rmt_addr;
+       }
+}
+
 static int inet_diag_fill_req(struct sk_buff *skb, struct sock *sk,
                              struct request_sock *req,
                              struct user_namespace *user_ns,
@@ -637,8 +736,10 @@ static int inet_diag_fill_req(struct sk_buff *skb, struct sock *sk,
        r->idiag_inode = 0;
 #if IS_ENABLED(CONFIG_IPV6)
        if (r->idiag_family == AF_INET6) {
-               *(struct in6_addr *)r->id.idiag_src = inet6_rsk(req)->loc_addr;
-               *(struct in6_addr *)r->id.idiag_dst = inet6_rsk(req)->rmt_addr;
+               struct inet_diag_entry entry;
+               inet_diag_req_addrs(sk, req, &entry);
+               memcpy(r->id.idiag_src, entry.saddr, sizeof(struct in6_addr));
+               memcpy(r->id.idiag_dst, entry.daddr, sizeof(struct in6_addr));
        }
 #endif
 
@@ -691,18 +792,7 @@ static int inet_diag_dump_reqs(struct sk_buff *skb, struct sock *sk,
                                continue;
 
                        if (bc) {
-                               entry.saddr =
-#if IS_ENABLED(CONFIG_IPV6)
-                                       (entry.family == AF_INET6) ?
-                                       inet6_rsk(req)->loc_addr.s6_addr32 :
-#endif
-                                       &ireq->loc_addr;
-                               entry.daddr =
-#if IS_ENABLED(CONFIG_IPV6)
-                                       (entry.family == AF_INET6) ?
-                                       inet6_rsk(req)->rmt_addr.s6_addr32 :
-#endif
-                                       &ireq->rmt_addr;
+                               inet_diag_req_addrs(sk, req, &entry);
                                entry.dport = ntohs(ireq->rmt_port);
 
                                if (!inet_diag_bc_run(bc, &entry))
@@ -892,13 +982,16 @@ static int __inet_diag_dump(struct sk_buff *skb, struct netlink_callback *cb,
                struct inet_diag_req_v2 *r, struct nlattr *bc)
 {
        const struct inet_diag_handler *handler;
+       int err = 0;
 
        handler = inet_diag_lock_handler(r->sdiag_protocol);
        if (!IS_ERR(handler))
                handler->dump(skb, cb, r, bc);
+       else
+               err = PTR_ERR(handler);
        inet_diag_unlock_handler(handler);
 
-       return skb->len;
+       return err ? : skb->len;
 }
 
 static int inet_diag_dump(struct sk_buff *skb, struct netlink_callback *cb)
This page took 0.032563 seconds and 5 git commands to generate.