|
@@ -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))
|